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
package/src/advisor.mjs
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
auditSources,
|
|
3
|
+
listWorkflows
|
|
4
|
+
} from "./control.mjs";
|
|
5
|
+
import { doctorProject } from "./doctor.mjs";
|
|
6
|
+
import { buildActionCards, buildInitActions } from "./advisor/actions.mjs";
|
|
7
|
+
import {
|
|
8
|
+
buildBrief,
|
|
9
|
+
buildCleanup,
|
|
10
|
+
emptySkillGroups,
|
|
11
|
+
emptyWorkflowState,
|
|
12
|
+
healthFromDoctor,
|
|
13
|
+
resolveProjectPaths
|
|
14
|
+
} from "./advisor/schema.mjs";
|
|
15
|
+
import {
|
|
16
|
+
buildReviewQueue,
|
|
17
|
+
emptySources,
|
|
18
|
+
sourcesFromDoctor,
|
|
19
|
+
summarizeSources
|
|
20
|
+
} from "./advisor/sources.mjs";
|
|
21
|
+
import { skillsForWorkflow, skillsWithoutWorkflow } from "./advisor/skills.mjs";
|
|
22
|
+
import { resolveWorkflow } from "./advisor/workflow.mjs";
|
|
23
|
+
import { loadWorkspace } from "./workspace.mjs";
|
|
24
|
+
|
|
25
|
+
export async function buildSkillBrief(options = {}) {
|
|
26
|
+
const paths = resolveProjectPaths(options);
|
|
27
|
+
const cleanup = await buildCleanup(paths.root);
|
|
28
|
+
const configDoctor = await doctorProject({
|
|
29
|
+
root: paths.root,
|
|
30
|
+
configPath: paths.configPath,
|
|
31
|
+
skillsRoot: paths.skillsRoot
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!configDoctor.config.exists || !configDoctor.initialized) {
|
|
35
|
+
return buildBrief({
|
|
36
|
+
ok: false,
|
|
37
|
+
error: {
|
|
38
|
+
code: "not-initialized",
|
|
39
|
+
message: configDoctor.config.error ?? "skillboard.config.yaml was not found"
|
|
40
|
+
},
|
|
41
|
+
health: healthFromDoctor(configDoctor, paths),
|
|
42
|
+
workflow: emptyWorkflowState(),
|
|
43
|
+
skills: emptySkillGroups(),
|
|
44
|
+
sources: emptySources(),
|
|
45
|
+
reviewQueue: [],
|
|
46
|
+
cleanup,
|
|
47
|
+
actions: requestedActions(options) ? buildInitActions(paths) : undefined
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!configDoctor.config.valid) {
|
|
52
|
+
return buildExpectedConfigError(configDoctor, paths, cleanup, {
|
|
53
|
+
code: "invalid-config",
|
|
54
|
+
message: configDoctor.config.error ?? "skillboard.config.yaml is invalid"
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let workspace;
|
|
59
|
+
try {
|
|
60
|
+
workspace = await loadWorkspace({ configPath: paths.configPath, skillsRoot: paths.skillsRoot });
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return buildExpectedConfigError(configDoctor, paths, cleanup, {
|
|
63
|
+
code: "invalid-config",
|
|
64
|
+
message: error instanceof Error ? error.message : String(error)
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const doctor = await doctorProject({
|
|
69
|
+
root: paths.root,
|
|
70
|
+
configPath: paths.configPath,
|
|
71
|
+
skillsRoot: paths.skillsRoot,
|
|
72
|
+
workspace,
|
|
73
|
+
verifySources: options.verifySources
|
|
74
|
+
});
|
|
75
|
+
const sourceAudit = auditSources(workspace);
|
|
76
|
+
const workflow = resolveWorkflow(listWorkflows(workspace), options.workflow);
|
|
77
|
+
const reviewQueue = buildReviewQueue(workspace, sourceAudit);
|
|
78
|
+
|
|
79
|
+
if (workflow.unknown) {
|
|
80
|
+
const skills = skillsWithoutWorkflow(workspace);
|
|
81
|
+
const actionData = actionsForBrief({ options, paths, workflow, skills, reviewQueue, cleanup, workspace });
|
|
82
|
+
return buildBrief({
|
|
83
|
+
ok: false,
|
|
84
|
+
error: {
|
|
85
|
+
code: "unknown-workflow",
|
|
86
|
+
message: `Unknown workflow: ${workflow.selected}`
|
|
87
|
+
},
|
|
88
|
+
health: healthFromDoctor(doctor, paths),
|
|
89
|
+
workflow,
|
|
90
|
+
skills,
|
|
91
|
+
sources: summarizeSources(sourceAudit),
|
|
92
|
+
reviewQueue: actionData.reviewQueue,
|
|
93
|
+
cleanup,
|
|
94
|
+
actions: actionData.actions
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const skills = skillsForWorkflow(workspace, workflow.selected, sourceAudit);
|
|
99
|
+
const actionData = actionsForBrief({ options, paths, workflow, skills, reviewQueue, cleanup, workspace });
|
|
100
|
+
return buildBrief({
|
|
101
|
+
ok: doctor.ok,
|
|
102
|
+
health: healthFromDoctor(doctor, paths),
|
|
103
|
+
workflow,
|
|
104
|
+
skills,
|
|
105
|
+
sources: summarizeSources(sourceAudit),
|
|
106
|
+
reviewQueue: actionData.reviewQueue,
|
|
107
|
+
cleanup,
|
|
108
|
+
actions: actionData.actions
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function buildExpectedConfigError(doctor, paths, cleanup, error) {
|
|
113
|
+
return buildBrief({
|
|
114
|
+
ok: false,
|
|
115
|
+
error,
|
|
116
|
+
health: healthFromDoctor(doctor, paths),
|
|
117
|
+
workflow: emptyWorkflowState(),
|
|
118
|
+
skills: emptySkillGroups(),
|
|
119
|
+
sources: sourcesFromDoctor(doctor),
|
|
120
|
+
reviewQueue: [],
|
|
121
|
+
cleanup
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function actionsForBrief(context) {
|
|
126
|
+
if (!requestedActions(context.options)) {
|
|
127
|
+
return { reviewQueue: context.reviewQueue, actions: undefined };
|
|
128
|
+
}
|
|
129
|
+
return buildActionCards(context);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function requestedActions(options) {
|
|
133
|
+
return options.includeActions === true;
|
|
134
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
export async function defaultScanRoots(home, env) {
|
|
5
|
+
const codexHome = env.CODEX_HOME ?? join(home, ".codex");
|
|
6
|
+
const hermesHome = env.HERMES_HOME ?? join(home, ".hermes");
|
|
7
|
+
return [
|
|
8
|
+
join(codexHome, "skills", ".system"),
|
|
9
|
+
join(codexHome, "skills"),
|
|
10
|
+
join(codexHome, "plugins", "cache"),
|
|
11
|
+
join(home, ".claude", "skills"),
|
|
12
|
+
join(hermesHome, "skills"),
|
|
13
|
+
...(await hermesProfileSkillRoots(hermesHome))
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function systemCodexUnit(path, home) {
|
|
18
|
+
return {
|
|
19
|
+
id: "codex.system-skills",
|
|
20
|
+
kind: "agent",
|
|
21
|
+
sourceClass: "runtime-extension",
|
|
22
|
+
priority: 55,
|
|
23
|
+
trustLevel: "reviewed",
|
|
24
|
+
source: displayPath(path, home),
|
|
25
|
+
scope: "user-global",
|
|
26
|
+
manifestPath: "",
|
|
27
|
+
cachePath: displayPath(path, home),
|
|
28
|
+
category: "agent-runtime"
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function userCodexUnit(path, home) {
|
|
33
|
+
return userSkillUnit("codex.user-skills", path, home);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function userClaudeUnit(path, home) {
|
|
37
|
+
return userSkillUnit("claude.user-skills", path, home);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function userHermesUnit(path, home) {
|
|
41
|
+
return userSkillUnit("hermes.user-skills", path, home);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function hermesProfileUnit(path, home) {
|
|
45
|
+
const profile = safeSegment(basename(dirname(path))).replaceAll(".", "-");
|
|
46
|
+
return userSkillUnit(`hermes.profile.${profile}.skills`, path, home);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function customUserUnit(path, home) {
|
|
50
|
+
return {
|
|
51
|
+
...userSkillUnit(`custom.${safeSegment(basename(path))}.skills`, path, home),
|
|
52
|
+
scope: "local"
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isHermesProfileSkillsPath(path) {
|
|
57
|
+
const normalized = path.replaceAll("\\", "/");
|
|
58
|
+
return /\/\.hermes\/profiles\/[^/]+\/skills$/u.test(normalized);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function safeSegment(value) {
|
|
62
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9._-]+/gu, "-").replace(/^-+|-+$/gu, "");
|
|
63
|
+
return normalized.length === 0 ? "skill" : normalized;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function displayPath(path, home) {
|
|
67
|
+
const resolvedHome = resolve(home);
|
|
68
|
+
const resolvedPath = resolve(path);
|
|
69
|
+
const rel = relative(resolvedHome, resolvedPath);
|
|
70
|
+
if (rel === "") {
|
|
71
|
+
return "~";
|
|
72
|
+
}
|
|
73
|
+
if (!rel.startsWith("..") && !isAbsolute(rel)) {
|
|
74
|
+
return `~/${rel.replaceAll("\\", "/")}`;
|
|
75
|
+
}
|
|
76
|
+
return resolvedPath;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function hermesProfileSkillRoots(hermesHome) {
|
|
80
|
+
const profilesRoot = join(hermesHome, "profiles");
|
|
81
|
+
const entries = await readdir(profilesRoot, { withFileTypes: true }).catch(() => []);
|
|
82
|
+
return entries
|
|
83
|
+
.filter((entry) => entry.isDirectory())
|
|
84
|
+
.map((entry) => join(profilesRoot, entry.name, "skills"));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function userSkillUnit(id, path, home) {
|
|
88
|
+
return {
|
|
89
|
+
id,
|
|
90
|
+
kind: "skill",
|
|
91
|
+
sourceClass: undefined,
|
|
92
|
+
priority: 100,
|
|
93
|
+
trustLevel: "trusted",
|
|
94
|
+
source: displayPath(path, home),
|
|
95
|
+
scope: "user-global",
|
|
96
|
+
manifestPath: "",
|
|
97
|
+
cachePath: displayPath(path, home),
|
|
98
|
+
category: "user"
|
|
99
|
+
};
|
|
100
|
+
}
|