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.
Files changed (94) hide show
  1. package/CONTRIBUTING.md +83 -0
  2. package/LICENSE +21 -0
  3. package/README.md +645 -0
  4. package/bin/skillboard.mjs +4 -0
  5. package/docs/adapters.md +127 -0
  6. package/docs/capabilities.md +107 -0
  7. package/docs/install.md +346 -0
  8. package/docs/plans/20260625-080025-skillboard-mvp-review.md +189 -0
  9. package/docs/plans/README.md +20 -0
  10. package/docs/policy-model.md +251 -0
  11. package/docs/positioning.md +94 -0
  12. package/docs/profiles.md +166 -0
  13. package/docs/rollout-runbook.md +60 -0
  14. package/docs/user-flow.md +231 -0
  15. package/docs/versioning.md +201 -0
  16. package/examples/multi-source-skills/anthropic/docx/SKILL.md +8 -0
  17. package/examples/multi-source-skills/anthropic/skill-creator/SKILL.md +8 -0
  18. package/examples/multi-source-skills/matt/grill-me/SKILL.md +8 -0
  19. package/examples/multi-source-skills/matt/tdd/SKILL.md +8 -0
  20. package/examples/multi-source-skills/omo/review-work/SKILL.md +8 -0
  21. package/examples/multi-source-skills/omo/ulw-plan/SKILL.md +8 -0
  22. package/examples/multi-source-skills/private/tdd-work-continuity/SKILL.md +8 -0
  23. package/examples/multi-source-skills/private/workflow-router/SKILL.md +8 -0
  24. package/examples/multi-source-skills/wshobson/python-testing/SKILL.md +8 -0
  25. package/examples/multi-source-skills/wshobson/security-review/SKILL.md +8 -0
  26. package/examples/multi-source.config.yaml +271 -0
  27. package/examples/skillboard.config.yaml +194 -0
  28. package/examples/skills/grill-me/SKILL.md +9 -0
  29. package/examples/skills/grill-with-docs/SKILL.md +9 -0
  30. package/examples/skills/requirement-intake/SKILL.md +9 -0
  31. package/examples/skills/tdd/SKILL.md +8 -0
  32. package/package.json +58 -0
  33. package/profiles/anthropics-skills.yaml +17 -0
  34. package/profiles/mattpocock-skills.yaml +26 -0
  35. package/profiles/oh-my-openagent.yaml +32 -0
  36. package/profiles/voltagent-awesome-agent-skills.yaml +18 -0
  37. package/profiles/wshobson-agents.yaml +19 -0
  38. package/src/advisor/action-core.mjs +74 -0
  39. package/src/advisor/actions.mjs +256 -0
  40. package/src/advisor/application-commands.mjs +59 -0
  41. package/src/advisor/apply-action.mjs +183 -0
  42. package/src/advisor/schema.mjs +112 -0
  43. package/src/advisor/setup-actions.mjs +42 -0
  44. package/src/advisor/skills.mjs +191 -0
  45. package/src/advisor/sort.mjs +15 -0
  46. package/src/advisor/sources.mjs +160 -0
  47. package/src/advisor/trust-policy.mjs +65 -0
  48. package/src/advisor/workflow.mjs +41 -0
  49. package/src/advisor.mjs +134 -0
  50. package/src/agent-inventory-platforms.mjs +100 -0
  51. package/src/agent-inventory.mjs +804 -0
  52. package/src/brief-cli.mjs +40 -0
  53. package/src/brief-renderer.mjs +362 -0
  54. package/src/change-plan.mjs +169 -0
  55. package/src/cli.mjs +1171 -0
  56. package/src/config-helpers.mjs +72 -0
  57. package/src/control/can-use-guard.mjs +91 -0
  58. package/src/control/config-write.mjs +163 -0
  59. package/src/control/skill-crud.mjs +394 -0
  60. package/src/control/source-trust.mjs +161 -0
  61. package/src/control/workflow-crud.mjs +104 -0
  62. package/src/control.mjs +197 -0
  63. package/src/doctor.mjs +299 -0
  64. package/src/domain/constants.mjs +47 -0
  65. package/src/domain/indexes.mjs +29 -0
  66. package/src/domain/rules/capabilities.mjs +33 -0
  67. package/src/domain/rules/harnesses.mjs +16 -0
  68. package/src/domain/rules/install-units.mjs +95 -0
  69. package/src/domain/rules/skills.mjs +105 -0
  70. package/src/domain/rules/workflows.mjs +105 -0
  71. package/src/domain/skill-state-matrix.mjs +79 -0
  72. package/src/domain/source-classes.mjs +99 -0
  73. package/src/hook-plan.mjs +152 -0
  74. package/src/impact.mjs +41 -0
  75. package/src/index.mjs +82 -0
  76. package/src/init.mjs +136 -0
  77. package/src/install-output-detector.mjs +337 -0
  78. package/src/install-units.mjs +62 -0
  79. package/src/inventory-refresh.mjs +45 -0
  80. package/src/lifecycle-cli.mjs +166 -0
  81. package/src/lifecycle-content.mjs +87 -0
  82. package/src/policy.mjs +42 -0
  83. package/src/reconcile.mjs +111 -0
  84. package/src/report.mjs +151 -0
  85. package/src/review.mjs +88 -0
  86. package/src/rollout.mjs +453 -0
  87. package/src/skill-paths.mjs +17 -0
  88. package/src/source-cache.mjs +178 -0
  89. package/src/source-profile-loader.mjs +160 -0
  90. package/src/source-profiles.mjs +299 -0
  91. package/src/source-verification.mjs +252 -0
  92. package/src/uninstall.mjs +258 -0
  93. package/src/workspace.mjs +219 -0
  94. package/tsconfig.lsp.json +15 -0
@@ -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
+ }