agent-skillboard 0.1.1 → 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.
Files changed (58) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +154 -630
  3. package/docs/adapters.md +96 -96
  4. package/docs/ai-skill-routing-goal.md +112 -0
  5. package/docs/capabilities.md +6 -0
  6. package/docs/install.md +155 -105
  7. package/docs/plans/skillboard-variant-lifecycle-handoff.md +56 -0
  8. package/docs/policy-model.md +266 -214
  9. package/docs/positioning.md +94 -94
  10. package/docs/reference.md +349 -0
  11. package/docs/routing.md +85 -0
  12. package/docs/user-flow.md +75 -16
  13. package/docs/value-proof.md +190 -0
  14. package/docs/variant-lifecycle.md +86 -0
  15. package/docs/versioning.md +157 -138
  16. package/examples/multi-source-skills/anthropic/docx/SKILL.md +8 -8
  17. package/examples/multi-source-skills/anthropic/skill-creator/SKILL.md +8 -8
  18. package/examples/multi-source-skills/matt/grill-me/SKILL.md +8 -8
  19. package/examples/multi-source-skills/matt/tdd/SKILL.md +8 -8
  20. package/examples/multi-source-skills/omo/review-work/SKILL.md +8 -8
  21. package/examples/multi-source-skills/omo/ulw-plan/SKILL.md +8 -8
  22. package/examples/multi-source-skills/private/tdd-work-continuity/SKILL.md +8 -8
  23. package/examples/multi-source-skills/private/workflow-router/SKILL.md +8 -8
  24. package/examples/multi-source-skills/wshobson/python-testing/SKILL.md +8 -8
  25. package/examples/multi-source-skills/wshobson/security-review/SKILL.md +8 -8
  26. package/examples/skills/grill-me/SKILL.md +9 -9
  27. package/examples/skills/grill-with-docs/SKILL.md +9 -9
  28. package/examples/skills/requirement-intake/SKILL.md +9 -9
  29. package/examples/skills/tdd/SKILL.md +8 -8
  30. package/package.json +25 -20
  31. package/src/advisor/guidance.mjs +232 -0
  32. package/src/advisor/schema.mjs +2 -0
  33. package/src/advisor/skills.mjs +2 -0
  34. package/src/advisor.mjs +36 -7
  35. package/src/brief-cli.mjs +6 -5
  36. package/src/brief-renderer.mjs +225 -8
  37. package/src/cli.mjs +589 -34
  38. package/src/config-helpers.mjs +34 -18
  39. package/src/conflicts.mjs +70 -0
  40. package/src/control/can-use-guard.mjs +8 -3
  41. package/src/control/skill-crud.mjs +142 -0
  42. package/src/control/skill-variants.mjs +221 -0
  43. package/src/control/source-trust.mjs +1 -0
  44. package/src/control/variant-files.mjs +265 -0
  45. package/src/control/variant-lifecycle-config.mjs +156 -0
  46. package/src/control/variant-reset.mjs +171 -0
  47. package/src/control/variant-status.mjs +75 -0
  48. package/src/control.mjs +13 -1
  49. package/src/domain/rules/skills.mjs +60 -0
  50. package/src/domain/rules/workflows.mjs +13 -0
  51. package/src/impact.mjs +21 -12
  52. package/src/index.mjs +13 -1
  53. package/src/lifecycle-cli.mjs +18 -7
  54. package/src/lifecycle-content.mjs +29 -22
  55. package/src/route.mjs +537 -0
  56. package/src/source-verification.mjs +7 -3
  57. package/src/workspace.mjs +141 -43
  58. 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 { join, relative } from "node:path";
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
- 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
- }
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
@@ -11,5 +11,5 @@
11
11
  "strict": false,
12
12
  "noImplicitAny": false
13
13
  },
14
- "include": ["bin/**/*.mjs", "src/**/*.mjs", "test/**/*.mjs"]
14
+ "include": ["bin/**/*.mjs", "src/**/*.mjs", "test/**/*.mjs", ".github/scripts/**/*.mjs"]
15
15
  }