@serviceme/devtools-core 0.1.8 → 0.2.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 (47) hide show
  1. package/dist/auth.d.mts +590 -0
  2. package/dist/auth.d.ts +590 -0
  3. package/dist/auth.js +842 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/auth.mjs +804 -0
  6. package/dist/auth.mjs.map +1 -0
  7. package/dist/device.d.mts +456 -0
  8. package/dist/device.d.ts +456 -0
  9. package/dist/device.js +696 -0
  10. package/dist/device.js.map +1 -0
  11. package/dist/device.mjs +647 -0
  12. package/dist/device.mjs.map +1 -0
  13. package/dist/index-CrNC-aao.d.ts +277 -0
  14. package/dist/index-Dmyy4urr.d.mts +277 -0
  15. package/dist/index.d.mts +1372 -27
  16. package/dist/index.d.ts +1372 -27
  17. package/dist/index.js +5125 -888
  18. package/dist/index.js.map +1 -0
  19. package/dist/index.mjs +5022 -906
  20. package/dist/index.mjs.map +1 -0
  21. package/dist/skill-linker.d.mts +188 -0
  22. package/dist/skill-linker.d.ts +188 -0
  23. package/dist/skill-linker.js +374 -0
  24. package/dist/skill-linker.js.map +1 -0
  25. package/dist/skill-linker.mjs +330 -0
  26. package/dist/skill-linker.mjs.map +1 -0
  27. package/dist/skill-store.d.mts +35 -0
  28. package/dist/skill-store.d.ts +35 -0
  29. package/dist/skill-store.js +291 -0
  30. package/dist/skill-store.js.map +1 -0
  31. package/dist/skill-store.mjs +254 -0
  32. package/dist/skill-store.mjs.map +1 -0
  33. package/dist/submit.d.mts +3 -0
  34. package/dist/submit.d.ts +3 -0
  35. package/dist/submit.js +166 -0
  36. package/dist/submit.js.map +1 -0
  37. package/dist/submit.mjs +130 -0
  38. package/dist/submit.mjs.map +1 -0
  39. package/dist/toolbox.d.mts +240 -0
  40. package/dist/toolbox.d.ts +240 -0
  41. package/dist/toolbox.js +530 -0
  42. package/dist/toolbox.js.map +1 -0
  43. package/dist/toolbox.mjs +482 -0
  44. package/dist/toolbox.mjs.map +1 -0
  45. package/dist/types-B9gk3dXH.d.mts +62 -0
  46. package/dist/types-B9gk3dXH.d.ts +62 -0
  47. package/package.json +50 -5
@@ -0,0 +1,291 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/skill-store/index.ts
31
+ var skill_store_exports = {};
32
+ __export(skill_store_exports, {
33
+ SkillNotFoundError: () => SkillNotFoundError,
34
+ SkillStore: () => SkillStore,
35
+ extractFrontmatter: () => extractFrontmatter
36
+ });
37
+ module.exports = __toCommonJS(skill_store_exports);
38
+ var fs2 = __toESM(require("fs/promises"));
39
+ var path2 = __toESM(require("path"));
40
+
41
+ // src/repo-layout/index.ts
42
+ var fs = __toESM(require("fs/promises"));
43
+ var path = __toESM(require("path"));
44
+ var MARKER_FILENAME = ".serviceme-repo.json";
45
+ var SUPPORTED_SCHEMA = 1;
46
+ var DEFAULT_REPO_LAYOUT = {
47
+ schema: SUPPORTED_SCHEMA,
48
+ include: [],
49
+ exclude: [],
50
+ treatFilesAsSkills: false
51
+ };
52
+ async function loadRepoLayout(repoRoot) {
53
+ const markerPath = path.join(repoRoot, MARKER_FILENAME);
54
+ let raw;
55
+ try {
56
+ raw = await fs.readFile(markerPath, "utf8");
57
+ } catch {
58
+ return DEFAULT_REPO_LAYOUT;
59
+ }
60
+ let parsed;
61
+ try {
62
+ parsed = JSON.parse(raw);
63
+ } catch {
64
+ return null;
65
+ }
66
+ if (!parsed || typeof parsed !== "object") return null;
67
+ const obj = parsed;
68
+ if (obj.schema !== SUPPORTED_SCHEMA) return null;
69
+ if (!Array.isArray(obj.include) || obj.include.some((s) => typeof s !== "string")) {
70
+ return null;
71
+ }
72
+ if (!Array.isArray(obj.exclude) || obj.exclude.some((s) => typeof s !== "string")) {
73
+ return null;
74
+ }
75
+ if (typeof obj.treatFilesAsSkills !== "boolean") return null;
76
+ return {
77
+ schema: SUPPORTED_SCHEMA,
78
+ include: obj.include,
79
+ exclude: obj.exclude,
80
+ treatFilesAsSkills: obj.treatFilesAsSkills
81
+ };
82
+ }
83
+
84
+ // src/skill-store/types.ts
85
+ var SkillNotFoundError = class extends Error {
86
+ constructor(repoId, skillName) {
87
+ super(`skill/agent not found: ${repoId}/${skillName}`);
88
+ this.repoId = repoId;
89
+ this.skillName = skillName;
90
+ this.name = "SkillNotFoundError";
91
+ }
92
+ };
93
+
94
+ // src/skill-store/index.ts
95
+ function extractFrontmatter(raw) {
96
+ if (typeof raw !== "string") return null;
97
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
98
+ if (!match) return null;
99
+ const yamlBody = match[1] ?? "";
100
+ const body = match[2] ?? "";
101
+ const data = {};
102
+ for (const line of yamlBody.split(/\r?\n/)) {
103
+ if (line.trim().length === 0) continue;
104
+ if (line.trim().startsWith("#")) continue;
105
+ const kv = line.match(/^([a-zA-Z_][\w-]*)\s*:\s*(.*)$/);
106
+ if (!kv || !kv[1]) continue;
107
+ let value = (kv[2] ?? "").trim();
108
+ if (typeof value === "string") {
109
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
110
+ value = value.slice(1, -1);
111
+ }
112
+ }
113
+ data[kv[1]] = value;
114
+ }
115
+ return { data, body };
116
+ }
117
+ var SkillStore = class {
118
+ constructor(opts) {
119
+ this.repoRoots = /* @__PURE__ */ new Map();
120
+ for (const r of opts.repos) this.repoRoots.set(r.id, r.rootPath);
121
+ }
122
+ /** All skills + agents across all registered repos. */
123
+ async listAll() {
124
+ const all = [];
125
+ for (const repoId of this.repoRoots.keys()) {
126
+ all.push(...await this.listByRepo(repoId));
127
+ }
128
+ return all;
129
+ }
130
+ /** All skills + agents under a single repo. */
131
+ async listByRepo(repoId) {
132
+ const root = this.repoRoots.get(repoId);
133
+ if (!root) return [];
134
+ const layout = await loadRepoLayout(root) ?? DEFAULT_REPO_LAYOUT;
135
+ const entries = [];
136
+ await walkForEntries(root, repoId, entries, layout);
137
+ return entries;
138
+ }
139
+ /** Single entry detail (manifest + all files inside its dir). */
140
+ async get(repoId, name) {
141
+ const entries = await this.listByRepo(repoId);
142
+ const found = entries.find((e) => e.name === name);
143
+ if (!found) throw new SkillNotFoundError(repoId, name);
144
+ const files = await this.getFiles(repoId, name);
145
+ return { ...found, files };
146
+ }
147
+ /** All files inside a single entry's directory (manifest + extras). */
148
+ async getFiles(repoId, name) {
149
+ const entries = await this.listByRepo(repoId);
150
+ const found = entries.find((e) => e.name === name);
151
+ if (!found) throw new SkillNotFoundError(repoId, name);
152
+ const out = [];
153
+ if (found.dir === found.manifestPath) {
154
+ const content = await fs2.readFile(found.manifestPath, "utf8");
155
+ return [{ path: path2.basename(found.manifestPath), content }];
156
+ }
157
+ await collectFilesRecursive(found.dir, found.dir, out);
158
+ out.sort((a, b) => {
159
+ const aIsManifest = a.path === "SKILL.md" || a.path === "AGENT.md";
160
+ const bIsManifest = b.path === "SKILL.md" || b.path === "AGENT.md";
161
+ if (aIsManifest && !bIsManifest) return -1;
162
+ if (bIsManifest && !aIsManifest) return 1;
163
+ return a.path.localeCompare(b.path);
164
+ });
165
+ return out;
166
+ }
167
+ };
168
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
169
+ ".git",
170
+ "node_modules",
171
+ ".vscode",
172
+ "dist",
173
+ "build",
174
+ "out"
175
+ ]);
176
+ var SKIP_TOP_LEVEL_SCAN_DIRS = /* @__PURE__ */ new Set([...SKIP_DIRS, "plugins"]);
177
+ var FLAT_AGENT_FILE_SUFFIX = ".agent.md";
178
+ async function walkForEntries(rootDir, repoId, out, layout = DEFAULT_REPO_LAYOUT) {
179
+ let dirents;
180
+ try {
181
+ dirents = await fs2.readdir(rootDir, { withFileTypes: true });
182
+ } catch {
183
+ return;
184
+ }
185
+ for (const d of dirents) {
186
+ if (d.isFile() && d.name.endsWith(FLAT_AGENT_FILE_SUFFIX)) {
187
+ const filePath = path2.join(rootDir, d.name);
188
+ const stat2 = await fs2.stat(filePath);
189
+ const content = await fs2.readFile(filePath, "utf8");
190
+ const parsed = extractFrontmatter(content);
191
+ out.push({
192
+ repoId,
193
+ name: d.name.slice(0, -FLAT_AGENT_FILE_SUFFIX.length),
194
+ kind: "agent",
195
+ manifestPath: filePath,
196
+ // Sentinel: `dir === manifestPath` marks a flat single-file
197
+ // entry (no directory of its own — see getFiles()).
198
+ dir: filePath,
199
+ frontmatter: parsed?.data ?? {},
200
+ modifiedAt: stat2.mtime.toISOString()
201
+ });
202
+ continue;
203
+ }
204
+ if (!d.isDirectory()) continue;
205
+ if (SKIP_TOP_LEVEL_SCAN_DIRS.has(d.name)) continue;
206
+ if (layout.exclude.includes(d.name)) continue;
207
+ const childDir = path2.join(rootDir, d.name);
208
+ const kind = await detectKind(childDir);
209
+ if (kind) {
210
+ const manifestFilename = kind === "skill" ? "SKILL.md" : "AGENT.md";
211
+ const manifestPath = path2.join(childDir, manifestFilename);
212
+ const stat2 = await fs2.stat(manifestPath);
213
+ const content = await fs2.readFile(manifestPath, "utf8");
214
+ const parsed = extractFrontmatter(content);
215
+ out.push({
216
+ repoId,
217
+ name: d.name,
218
+ kind,
219
+ manifestPath,
220
+ dir: childDir,
221
+ frontmatter: parsed?.data ?? {},
222
+ modifiedAt: stat2.mtime.toISOString()
223
+ });
224
+ continue;
225
+ }
226
+ await walkForEntries(childDir, repoId, out, layout);
227
+ if (layout.treatFilesAsSkills && layout.include.includes(d.name)) {
228
+ await surfaceFilesAsSkills(childDir, d.name, repoId, out);
229
+ }
230
+ }
231
+ }
232
+ async function surfaceFilesAsSkills(absDir, relName, repoId, out) {
233
+ let dirents;
234
+ try {
235
+ dirents = await fs2.readdir(absDir, { withFileTypes: true });
236
+ } catch {
237
+ return;
238
+ }
239
+ for (const d of dirents) {
240
+ if (!d.isFile()) continue;
241
+ if (!d.name.endsWith(".md")) continue;
242
+ const filePath = path2.join(absDir, d.name);
243
+ const stat2 = await fs2.stat(filePath);
244
+ const content = await fs2.readFile(filePath, "utf8");
245
+ const parsed = extractFrontmatter(content);
246
+ const entryName = `${relName}/${d.name.replace(/\.md$/, "")}`;
247
+ out.push({
248
+ repoId,
249
+ name: entryName,
250
+ kind: "skill",
251
+ manifestPath: filePath,
252
+ dir: absDir,
253
+ frontmatter: parsed?.data ?? {},
254
+ modifiedAt: stat2.mtime.toISOString()
255
+ });
256
+ }
257
+ }
258
+ async function detectKind(dir) {
259
+ const [hasSkill, hasAgent] = await Promise.all([
260
+ fs2.access(path2.join(dir, "SKILL.md")).then(() => true).catch(() => false),
261
+ fs2.access(path2.join(dir, "AGENT.md")).then(() => true).catch(() => false)
262
+ ]);
263
+ if (hasSkill) return "skill";
264
+ if (hasAgent) return "agent";
265
+ return null;
266
+ }
267
+ async function collectFilesRecursive(absDir, entryRoot, out) {
268
+ let dirents;
269
+ try {
270
+ dirents = await fs2.readdir(absDir, { withFileTypes: true });
271
+ } catch {
272
+ return;
273
+ }
274
+ for (const d of dirents) {
275
+ if (SKIP_DIRS.has(d.name)) continue;
276
+ const full = path2.join(absDir, d.name);
277
+ if (d.isDirectory()) {
278
+ await collectFilesRecursive(full, entryRoot, out);
279
+ } else if (d.isFile()) {
280
+ const content = await fs2.readFile(full, "utf8");
281
+ out.push({ path: path2.relative(entryRoot, full), content });
282
+ }
283
+ }
284
+ }
285
+ // Annotate the CommonJS export names for ESM import in node:
286
+ 0 && (module.exports = {
287
+ SkillNotFoundError,
288
+ SkillStore,
289
+ extractFrontmatter
290
+ });
291
+ //# sourceMappingURL=skill-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/skill-store/index.ts","../src/repo-layout/index.ts","../src/skill-store/types.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport {\n\tDEFAULT_REPO_LAYOUT,\n\tloadRepoLayout,\n\ttype RepoLayoutDescriptor,\n} from \"../repo-layout\";\nimport type { SkillDetail, SkillEntry, SkillFile, SkillKind } from \"./types\";\nimport { SkillNotFoundError } from \"./types\";\n\n/**\n * Minimal frontmatter reader — same shape as the extension's\n * `WorkspaceSkillsInitializationService.extractFrontmatter`. We avoid\n * pulling a YAML dep into core; SKILL.md / AGENT.md in the wild use\n * a strict subset (top-level `key: value` pairs delimited by `---`).\n * Anything more exotic (nested mappings, multi-line scalars) is\n * passed through as the raw string in the corresponding value.\n */\nexport function extractFrontmatter(\n\traw: string,\n): { data: Record<string, unknown>; body: string } | null {\n\tif (typeof raw !== \"string\") return null;\n\tconst match = raw.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n\tif (!match) return null;\n\tconst yamlBody = match[1] ?? \"\";\n\tconst body = match[2] ?? \"\";\n\tconst data: Record<string, unknown> = {};\n\tfor (const line of yamlBody.split(/\\r?\\n/)) {\n\t\tif (line.trim().length === 0) continue;\n\t\tif (line.trim().startsWith(\"#\")) continue;\n\t\tconst kv = line.match(/^([a-zA-Z_][\\w-]*)\\s*:\\s*(.*)$/);\n\t\tif (!kv || !kv[1]) continue;\n\t\tlet value: unknown = (kv[2] ?? \"\").trim();\n\t\tif (typeof value === \"string\") {\n\t\t\tif (\n\t\t\t\t(value.startsWith('\"') && value.endsWith('\"')) ||\n\t\t\t\t(value.startsWith(\"'\") && value.endsWith(\"'\"))\n\t\t\t) {\n\t\t\t\tvalue = value.slice(1, -1);\n\t\t\t}\n\t\t}\n\t\tdata[kv[1]] = value;\n\t}\n\treturn { data, body };\n}\n\nexport type { SkillDetail, SkillEntry, SkillFile, SkillKind } from \"./types\";\n/**\n * Skill & Agent v2 — SkillStore (M4)\n *\n * Scans the local `~/.serviceme/repos/<id>/` tree to produce a unified\n * list of skills + agents. Local-only — no git, no network. Spec §5.5.\n *\n * Layout normalization (spec §12):\n * 1. **Standard layout** — `skills/<name>/SKILL.md` or\n * `agents/<name>/AGENT.md` under the repo root.\n * 2. **Anthropic flat layout** — `<name>/SKILL.md` directly under\n * the repo root (the `skills/` prefix is omitted).\n * 3. **awesome-copilot style** — root contains README + subdirs\n * that are skills/agents/instructions; we ignore README and\n * any dir without a manifest file.\n * 4. **Flat agent files** — a directory (commonly `agents/` or\n * `agents/official/`) full of `<name>.agent.md` files, one per\n * agent, with NO per-agent subdirectory. This is the prevailing\n * real-world convention (VS Code custom chat agents, GitHub's\n * awesome-copilot, and our own official ms-skills repo all ship\n * agents this way) — unlike skills, agents in the wild are\n * essentially never a `<name>/AGENT.md` directory pair. Detected\n * unconditionally, no `.serviceme-repo.json` opt-in required.\n *\n * Implementation: walk every directory under the repo root, check for\n * SKILL.md or AGENT.md, register if present. We skip the repo root\n * itself (no manifest lives at the top), `.git/`, and `node_modules/`.\n * Flat `*.agent.md` files are checked alongside directories at every\n * level of the walk.\n */\n// Re-export the error class + shared types so callers can\n// `import { SkillNotFoundError, SkillFile } from \"...\"`.\nexport { SkillNotFoundError } from \"./types\";\n\nexport class SkillStore {\n\tprivate readonly repoRoots: Map<string, string>;\n\n\tconstructor(opts: {\n\t\trepos: ReadonlyArray<{ id: string; rootPath: string }>;\n\t}) {\n\t\tthis.repoRoots = new Map();\n\t\tfor (const r of opts.repos) this.repoRoots.set(r.id, r.rootPath);\n\t}\n\n\t/** All skills + agents across all registered repos. */\n\tasync listAll(): Promise<SkillEntry[]> {\n\t\tconst all: SkillEntry[] = [];\n\t\tfor (const repoId of this.repoRoots.keys()) {\n\t\t\tall.push(...(await this.listByRepo(repoId)));\n\t\t}\n\t\treturn all;\n\t}\n\n\t/** All skills + agents under a single repo. */\n\tasync listByRepo(repoId: string): Promise<SkillEntry[]> {\n\t\tconst root = this.repoRoots.get(repoId);\n\t\tif (!root) return [];\n\t\tconst layout = (await loadRepoLayout(root)) ?? DEFAULT_REPO_LAYOUT;\n\t\tconst entries: SkillEntry[] = [];\n\t\tawait walkForEntries(root, repoId, entries, layout);\n\t\treturn entries;\n\t}\n\n\t/** Single entry detail (manifest + all files inside its dir). */\n\tasync get(repoId: string, name: string): Promise<SkillDetail> {\n\t\tconst entries = await this.listByRepo(repoId);\n\t\tconst found = entries.find((e) => e.name === name);\n\t\tif (!found) throw new SkillNotFoundError(repoId, name);\n\t\tconst files = await this.getFiles(repoId, name);\n\t\treturn { ...found, files };\n\t}\n\n\t/** All files inside a single entry's directory (manifest + extras). */\n\tasync getFiles(repoId: string, name: string): Promise<SkillFile[]> {\n\t\tconst entries = await this.listByRepo(repoId);\n\t\tconst found = entries.find((e) => e.name === name);\n\t\tif (!found) throw new SkillNotFoundError(repoId, name);\n\n\t\tconst out: SkillFile[] = [];\n\t\t// Flat single-file entries (e.g. `agents/foo.agent.md`) have `dir`\n\t\t// pointing at the manifest file itself, not a directory — there's\n\t\t// no sibling files to collect, just the one manifest.\n\t\tif (found.dir === found.manifestPath) {\n\t\t\tconst content = await fs.readFile(found.manifestPath, \"utf8\");\n\t\t\treturn [{ path: path.basename(found.manifestPath), content }];\n\t\t}\n\t\tawait collectFilesRecursive(found.dir, found.dir, out);\n\t\t// Sort for determinism (manifest first, then alphabetical)\n\t\tout.sort((a, b) => {\n\t\t\tconst aIsManifest = a.path === \"SKILL.md\" || a.path === \"AGENT.md\";\n\t\t\tconst bIsManifest = b.path === \"SKILL.md\" || b.path === \"AGENT.md\";\n\t\t\tif (aIsManifest && !bIsManifest) return -1;\n\t\t\tif (bIsManifest && !aIsManifest) return 1;\n\t\t\treturn a.path.localeCompare(b.path);\n\t\t});\n\t\treturn out;\n\t}\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Internals\n// ─────────────────────────────────────────────────────────────────────\n\nconst SKIP_DIRS = new Set([\n\t\".git\",\n\t\"node_modules\",\n\t\".vscode\",\n\t\"dist\",\n\t\"build\",\n\t\"out\",\n]);\n\n/**\n * Directories that are never treated as candidate skill/agent parents\n * during the top-level catalog walk. `plugins/` is a distinct concept\n * from the skill/agent catalog itself — a plugin bundle\n * (`plugins/<scope>/<plugin-name>/{skills,agents}/...`) re-packages\n * ALREADY-cataloged official skills/agents (see the plugin's own\n * `catalog.json`, which lists them by name) purely for discovery\n * grouping. Walking into it produces duplicate `SkillEntry` values\n * with the SAME `repoId`+`name` as their real, top-level counterpart\n * (e.g. `skills/official/acreadiness-assess` AND\n * `plugins/official/acreadiness-cockpit/skills/acreadiness-assess`),\n * which breaks anything keyed on `repoId/name` (React list rendering,\n * the \"installed\" lookup, `SkillStore.get()`'s `.find()`).\n *\n * Scoped separately from `SKIP_DIRS` (used by both this walk AND\n * `collectFilesRecursive`) so an actual skill that happens to ship its\n * own `plugins/` asset folder still has that folder listed in its own\n * file detail view.\n */\nconst SKIP_TOP_LEVEL_SCAN_DIRS = new Set([...SKIP_DIRS, \"plugins\"]);\n\n/** Suffix that marks a standalone file as a flat agent manifest (no wrapping dir). */\nconst FLAT_AGENT_FILE_SUFFIX = \".agent.md\";\n\n/**\n * Recursive walk that yields SkillEntry values for any directory\n * containing a SKILL.md or AGENT.md file, PLUS any standalone\n * `<name>.agent.md` file (see class doc, layout style 4). The dir\n * itself is the \"entry root\" for directory-based entries; the\n * manifest file itself is the \"entry root\" for flat agent files.\n *\n * The optional `layout` descriptor (from `.serviceme-repo.json`)\n * widens the scan: included subdirs are walked even without a\n * manifest at the top level, and `treatFilesAsSkills` surfaces\n * `<name>.md` files inside them as skills (frontmatter parsed\n * from the file body).\n */\nasync function walkForEntries(\n\trootDir: string,\n\trepoId: string,\n\tout: SkillEntry[],\n\tlayout: RepoLayoutDescriptor = DEFAULT_REPO_LAYOUT,\n): Promise<void> {\n\tlet dirents: import(\"node:fs\").Dirent[];\n\ttry {\n\t\tdirents = await fs.readdir(rootDir, { withFileTypes: true });\n\t} catch {\n\t\treturn;\n\t}\n\n\tfor (const d of dirents) {\n\t\tif (d.isFile() && d.name.endsWith(FLAT_AGENT_FILE_SUFFIX)) {\n\t\t\tconst filePath = path.join(rootDir, d.name);\n\t\t\tconst stat = await fs.stat(filePath);\n\t\t\tconst content = await fs.readFile(filePath, \"utf8\");\n\t\t\tconst parsed = extractFrontmatter(content);\n\t\t\tout.push({\n\t\t\t\trepoId,\n\t\t\t\tname: d.name.slice(0, -FLAT_AGENT_FILE_SUFFIX.length),\n\t\t\t\tkind: \"agent\",\n\t\t\t\tmanifestPath: filePath,\n\t\t\t\t// Sentinel: `dir === manifestPath` marks a flat single-file\n\t\t\t\t// entry (no directory of its own — see getFiles()).\n\t\t\t\tdir: filePath,\n\t\t\t\tfrontmatter: parsed?.data ?? {},\n\t\t\t\tmodifiedAt: stat.mtime.toISOString(),\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tif (!d.isDirectory()) continue;\n\t\tif (SKIP_TOP_LEVEL_SCAN_DIRS.has(d.name)) continue;\n\t\tif (layout.exclude.includes(d.name)) continue;\n\t\tconst childDir = path.join(rootDir, d.name);\n\t\tconst kind = await detectKind(childDir);\n\t\tif (kind) {\n\t\t\tconst manifestFilename = kind === \"skill\" ? \"SKILL.md\" : \"AGENT.md\";\n\t\t\tconst manifestPath = path.join(childDir, manifestFilename);\n\t\t\tconst stat = await fs.stat(manifestPath);\n\t\t\tconst content = await fs.readFile(manifestPath, \"utf8\");\n\t\t\tconst parsed = extractFrontmatter(content);\n\t\t\tout.push({\n\t\t\t\trepoId,\n\t\t\t\tname: d.name,\n\t\t\t\tkind,\n\t\t\t\tmanifestPath,\n\t\t\t\tdir: childDir,\n\t\t\t\tfrontmatter: parsed?.data ?? {},\n\t\t\t\tmodifiedAt: stat.mtime.toISOString(),\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\t// No manifest at the top — recurse, but ALSO check whether\n\t\t// the layout descriptor wants this subdir widened.\n\t\tawait walkForEntries(childDir, repoId, out, layout);\n\t\tif (layout.treatFilesAsSkills && layout.include.includes(d.name)) {\n\t\t\tawait surfaceFilesAsSkills(childDir, d.name, repoId, out);\n\t\t}\n\t}\n}\n\n/**\n * Walk `<includedSubdir>/<file>.md` and register each as a skill.\n * The `.md` file itself is the manifest — frontmatter (if any) is\n * parsed and surfaced. This is the awesome-copilot style: a\n * `prompts/` dir full of `<name>.md` files, no SKILL.md anywhere.\n */\nasync function surfaceFilesAsSkills(\n\tabsDir: string,\n\trelName: string,\n\trepoId: string,\n\tout: SkillEntry[],\n): Promise<void> {\n\tlet dirents: import(\"node:fs\").Dirent[];\n\ttry {\n\t\tdirents = await fs.readdir(absDir, { withFileTypes: true });\n\t} catch {\n\t\treturn;\n\t}\n\tfor (const d of dirents) {\n\t\tif (!d.isFile()) continue;\n\t\tif (!d.name.endsWith(\".md\")) continue;\n\t\tconst filePath = path.join(absDir, d.name);\n\t\tconst stat = await fs.stat(filePath);\n\t\tconst content = await fs.readFile(filePath, \"utf8\");\n\t\tconst parsed = extractFrontmatter(content);\n\t\t// Entry name = \"<subdir>/<file>\" (with the .md stripped) so it\n\t\t// stays unique even when two included subdirs share filenames.\n\t\tconst entryName = `${relName}/${d.name.replace(/\\.md$/, \"\")}`;\n\t\tout.push({\n\t\t\trepoId,\n\t\t\tname: entryName,\n\t\t\tkind: \"skill\",\n\t\t\tmanifestPath: filePath,\n\t\t\tdir: absDir,\n\t\t\tfrontmatter: parsed?.data ?? {},\n\t\t\tmodifiedAt: stat.mtime.toISOString(),\n\t\t});\n\t}\n}\n\n/**\n * Returns the SkillKind of a directory if it contains a manifest file,\n * or `null` if it doesn't. Manifest precedence: SKILL.md wins if both\n * are present (defensive — the spec separates skills and agents into\n * different subdir trees, so this collision shouldn't happen in\n * well-formed repos).\n */\nasync function detectKind(dir: string): Promise<SkillKind | null> {\n\tconst [hasSkill, hasAgent] = await Promise.all([\n\t\tfs\n\t\t\t.access(path.join(dir, \"SKILL.md\"))\n\t\t\t.then(() => true)\n\t\t\t.catch(() => false),\n\t\tfs\n\t\t\t.access(path.join(dir, \"AGENT.md\"))\n\t\t\t.then(() => true)\n\t\t\t.catch(() => false),\n\t]);\n\tif (hasSkill) return \"skill\";\n\tif (hasAgent) return \"agent\";\n\treturn null;\n}\n\n/**\n * Walk a skill/agent directory recursively, pushing every file into\n * `out` with its path RELATIVE to the entry root. Skips `.git/`,\n * `node_modules/`, and the SKIP_DIRS set.\n */\nasync function collectFilesRecursive(\n\tabsDir: string,\n\tentryRoot: string,\n\tout: SkillFile[],\n): Promise<void> {\n\tlet dirents: import(\"node:fs\").Dirent[];\n\ttry {\n\t\tdirents = await fs.readdir(absDir, { withFileTypes: true });\n\t} catch {\n\t\treturn;\n\t}\n\tfor (const d of dirents) {\n\t\tif (SKIP_DIRS.has(d.name)) continue;\n\t\tconst full = path.join(absDir, d.name);\n\t\tif (d.isDirectory()) {\n\t\t\tawait collectFilesRecursive(full, entryRoot, out);\n\t\t} else if (d.isFile()) {\n\t\t\tconst content = await fs.readFile(full, \"utf8\");\n\t\t\tout.push({ path: path.relative(entryRoot, full), content });\n\t\t}\n\t}\n}\n","/**\n * Spec §12 — Repo layout descriptor.\n *\n * Some third-party repos (awesome-copilot, composio) use a\n * non-default layout that mixes prompts / instructions / agents /\n * hooks / plugins at the root. The default SkillStore scan\n * (see ../skill-store/index.ts) only picks up directories that\n * contain a `SKILL.md` or `AGENT.md` manifest, so prompts stored\n * as `<subdir>/<name>.md` (no manifest) get missed.\n *\n * This module is the loader for the optional `.serviceme-repo.json`\n * marker file a repo author can drop in at the repo root to opt\n * into a wider scan. The schema:\n *\n * {\n * \"schema\": 1,\n * \"include\": [\"prompts\", \"instructions\"], // subdirs to treat as \"skill dirs\"\n * \"exclude\": [\"hooks\", \"plugins\"], // subdirs to skip\n * \"treatFilesAsSkills\": true // inside an included subdir, each .md file is a skill\n * }\n *\n * All four fields are optional. The defaults match the v0.2\n * walkForEntries behaviour (manifest-based, no marker, no\n * extensions), so adding the file is purely additive.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §12\n */\n\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nconst MARKER_FILENAME = \".serviceme-repo.json\";\nconst SUPPORTED_SCHEMA = 1;\n\nexport interface RepoLayoutDescriptor {\n\t/** Schema version. Currently always 1. */\n\tschema: number;\n\t/** Subdirs (relative to repo root) to walk for entries. */\n\tinclude: string[];\n\t/** Subdirs (relative to repo root) to skip, even if a parent is included. */\n\texclude: string[];\n\t/**\n\t * When true, an included subdir's `<name>.md` files are\n\t * surfaced as skills (frontmatter is parsed from each file as\n\t * a stand-in for SKILL.md). When false (default), the\n\t * include list only widens the recursion — the manifest rule\n\t * still applies.\n\t */\n\ttreatFilesAsSkills: boolean;\n}\n\n/**\n * Default descriptor used when no `.serviceme-repo.json` is\n * present. The shape is the same as what an empty marker would\n * produce, so callers don't have to special-case the \"no marker\"\n * branch.\n */\nexport const DEFAULT_REPO_LAYOUT: RepoLayoutDescriptor = {\n\tschema: SUPPORTED_SCHEMA,\n\tinclude: [],\n\texclude: [],\n\ttreatFilesAsSkills: false,\n};\n\n/**\n * Try to load a `.serviceme-repo.json` from the given repo root.\n * Returns `DEFAULT_REPO_LAYOUT` (not undefined) when the file is\n * absent — callers branch on `include.length` / `treatFilesAsSkills`\n * to decide whether to widen the scan.\n *\n * Malformed markers (bad JSON, wrong schema) are surfaced as\n * `null` so callers can warn the user instead of silently\n * treating them as the default.\n */\nexport async function loadRepoLayout(repoRoot: string): Promise<RepoLayoutDescriptor | null> {\n\tconst markerPath = path.join(repoRoot, MARKER_FILENAME);\n\tlet raw: string;\n\ttry {\n\t\traw = await fs.readFile(markerPath, \"utf8\");\n\t} catch {\n\t\treturn DEFAULT_REPO_LAYOUT;\n\t}\n\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\treturn null;\n\t}\n\tif (!parsed || typeof parsed !== \"object\") return null;\n\tconst obj = parsed as Record<string, unknown>;\n\n\tif (obj.schema !== SUPPORTED_SCHEMA) return null;\n\tif (!Array.isArray(obj.include) || obj.include.some((s) => typeof s !== \"string\")) {\n\t\treturn null;\n\t}\n\tif (!Array.isArray(obj.exclude) || obj.exclude.some((s) => typeof s !== \"string\")) {\n\t\treturn null;\n\t}\n\tif (typeof obj.treatFilesAsSkills !== \"boolean\") return null;\n\n\treturn {\n\t\tschema: SUPPORTED_SCHEMA,\n\t\tinclude: obj.include as string[],\n\t\texclude: obj.exclude as string[],\n\t\ttreatFilesAsSkills: obj.treatFilesAsSkills,\n\t};\n}\n\n/** Subdir name → entry kind override (rarely needed; default = manifest-driven). */\nexport type KindOverride = Map<string, \"skill\" | \"agent\">;\n","/**\n * Skill & Agent v2 — SkillStore Types\n *\n * SkillStore scans the local `~/.serviceme/repos/<id>/` tree to produce\n * a unified list of skills + agents. The store is the single source of\n * truth for \"what's available right now\" — the UI reads it; SubmitClient\n * reads it; install flows read it.\n *\n * Three repo layouts are supported in v1 (per spec §12):\n * 1. **Default** (`medalsoftchina-ms-skills`): `skills/<name>/SKILL.md`\n * + `agents/<name>/AGENT.md`.\n * 2. **Anthropic-style flat** (`anthropics/skills`): each subdir of\n * the repo root IS a skill — `<name>/SKILL.md` (no `skills/`\n * intermediate).\n * 3. **awesome-copilot style** (`github/awesome-copilot`): mixed\n * prompts/instructions/agents at the root.\n *\n * Normalization rule (spec §12): for every directory under the repo\n * root, peek for a `SKILL.md` or `AGENT.md` file. If present, register\n * it as a skill or agent respectively. This is more permissive than\n * guessing from the directory name and works for all three styles.\n *\n * The store is local-only and pure — no git, no network. Tests\n * construct a fixture repo tree and pass the root in directly.\n */\n\n/** What's available at this entry: a skill or an agent. */\nexport type SkillKind = \"skill\" | \"agent\";\n\n/** Top-level summary of a discovered skill/agent entry. */\nexport interface SkillEntry {\n\t/** Repository the entry was found in. */\n\trepoId: string;\n\t/** Skill or agent name (the directory name under the repo). */\n\tname: string;\n\tkind: SkillKind;\n\t/** Absolute path to the SKILL.md / AGENT.md file. */\n\tmanifestPath: string;\n\t/** Absolute path to the directory containing the entry's files. */\n\tdir: string;\n\t/** Parsed frontmatter (best-effort; raw key/value map). */\n\tfrontmatter: Record<string, unknown>;\n\t/** ISO timestamp of the manifest file's mtime. */\n\tmodifiedAt: string;\n}\n\n/** Full detail of an entry: summary + all the files inside the dir. */\nexport interface SkillDetail extends SkillEntry {\n\tfiles: SkillFile[];\n}\n\n/** A single file inside a skill / agent directory. */\nexport interface SkillFile {\n\t/** Path relative to the entry's directory. */\n\tpath: string;\n\t/** UTF-8 content. */\n\tcontent: string;\n}\n\n/** Sentinel error for store lookups that miss. */\nexport class SkillNotFoundError extends Error {\n\tconstructor(\n\t\tpublic readonly repoId: string,\n\t\tpublic readonly skillName: string\n\t) {\n\t\tsuper(`skill/agent not found: ${repoId}/${skillName}`);\n\t\tthis.name = \"SkillNotFoundError\";\n\t}\n}\n"],"mappings":"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","names":["fs","path","stat"]}
@@ -0,0 +1,254 @@
1
+ // src/skill-store/index.ts
2
+ import * as fs2 from "fs/promises";
3
+ import * as path2 from "path";
4
+
5
+ // src/repo-layout/index.ts
6
+ import * as fs from "fs/promises";
7
+ import * as path from "path";
8
+ var MARKER_FILENAME = ".serviceme-repo.json";
9
+ var SUPPORTED_SCHEMA = 1;
10
+ var DEFAULT_REPO_LAYOUT = {
11
+ schema: SUPPORTED_SCHEMA,
12
+ include: [],
13
+ exclude: [],
14
+ treatFilesAsSkills: false
15
+ };
16
+ async function loadRepoLayout(repoRoot) {
17
+ const markerPath = path.join(repoRoot, MARKER_FILENAME);
18
+ let raw;
19
+ try {
20
+ raw = await fs.readFile(markerPath, "utf8");
21
+ } catch {
22
+ return DEFAULT_REPO_LAYOUT;
23
+ }
24
+ let parsed;
25
+ try {
26
+ parsed = JSON.parse(raw);
27
+ } catch {
28
+ return null;
29
+ }
30
+ if (!parsed || typeof parsed !== "object") return null;
31
+ const obj = parsed;
32
+ if (obj.schema !== SUPPORTED_SCHEMA) return null;
33
+ if (!Array.isArray(obj.include) || obj.include.some((s) => typeof s !== "string")) {
34
+ return null;
35
+ }
36
+ if (!Array.isArray(obj.exclude) || obj.exclude.some((s) => typeof s !== "string")) {
37
+ return null;
38
+ }
39
+ if (typeof obj.treatFilesAsSkills !== "boolean") return null;
40
+ return {
41
+ schema: SUPPORTED_SCHEMA,
42
+ include: obj.include,
43
+ exclude: obj.exclude,
44
+ treatFilesAsSkills: obj.treatFilesAsSkills
45
+ };
46
+ }
47
+
48
+ // src/skill-store/types.ts
49
+ var SkillNotFoundError = class extends Error {
50
+ constructor(repoId, skillName) {
51
+ super(`skill/agent not found: ${repoId}/${skillName}`);
52
+ this.repoId = repoId;
53
+ this.skillName = skillName;
54
+ this.name = "SkillNotFoundError";
55
+ }
56
+ };
57
+
58
+ // src/skill-store/index.ts
59
+ function extractFrontmatter(raw) {
60
+ if (typeof raw !== "string") return null;
61
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
62
+ if (!match) return null;
63
+ const yamlBody = match[1] ?? "";
64
+ const body = match[2] ?? "";
65
+ const data = {};
66
+ for (const line of yamlBody.split(/\r?\n/)) {
67
+ if (line.trim().length === 0) continue;
68
+ if (line.trim().startsWith("#")) continue;
69
+ const kv = line.match(/^([a-zA-Z_][\w-]*)\s*:\s*(.*)$/);
70
+ if (!kv || !kv[1]) continue;
71
+ let value = (kv[2] ?? "").trim();
72
+ if (typeof value === "string") {
73
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
74
+ value = value.slice(1, -1);
75
+ }
76
+ }
77
+ data[kv[1]] = value;
78
+ }
79
+ return { data, body };
80
+ }
81
+ var SkillStore = class {
82
+ constructor(opts) {
83
+ this.repoRoots = /* @__PURE__ */ new Map();
84
+ for (const r of opts.repos) this.repoRoots.set(r.id, r.rootPath);
85
+ }
86
+ /** All skills + agents across all registered repos. */
87
+ async listAll() {
88
+ const all = [];
89
+ for (const repoId of this.repoRoots.keys()) {
90
+ all.push(...await this.listByRepo(repoId));
91
+ }
92
+ return all;
93
+ }
94
+ /** All skills + agents under a single repo. */
95
+ async listByRepo(repoId) {
96
+ const root = this.repoRoots.get(repoId);
97
+ if (!root) return [];
98
+ const layout = await loadRepoLayout(root) ?? DEFAULT_REPO_LAYOUT;
99
+ const entries = [];
100
+ await walkForEntries(root, repoId, entries, layout);
101
+ return entries;
102
+ }
103
+ /** Single entry detail (manifest + all files inside its dir). */
104
+ async get(repoId, name) {
105
+ const entries = await this.listByRepo(repoId);
106
+ const found = entries.find((e) => e.name === name);
107
+ if (!found) throw new SkillNotFoundError(repoId, name);
108
+ const files = await this.getFiles(repoId, name);
109
+ return { ...found, files };
110
+ }
111
+ /** All files inside a single entry's directory (manifest + extras). */
112
+ async getFiles(repoId, name) {
113
+ const entries = await this.listByRepo(repoId);
114
+ const found = entries.find((e) => e.name === name);
115
+ if (!found) throw new SkillNotFoundError(repoId, name);
116
+ const out = [];
117
+ if (found.dir === found.manifestPath) {
118
+ const content = await fs2.readFile(found.manifestPath, "utf8");
119
+ return [{ path: path2.basename(found.manifestPath), content }];
120
+ }
121
+ await collectFilesRecursive(found.dir, found.dir, out);
122
+ out.sort((a, b) => {
123
+ const aIsManifest = a.path === "SKILL.md" || a.path === "AGENT.md";
124
+ const bIsManifest = b.path === "SKILL.md" || b.path === "AGENT.md";
125
+ if (aIsManifest && !bIsManifest) return -1;
126
+ if (bIsManifest && !aIsManifest) return 1;
127
+ return a.path.localeCompare(b.path);
128
+ });
129
+ return out;
130
+ }
131
+ };
132
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
133
+ ".git",
134
+ "node_modules",
135
+ ".vscode",
136
+ "dist",
137
+ "build",
138
+ "out"
139
+ ]);
140
+ var SKIP_TOP_LEVEL_SCAN_DIRS = /* @__PURE__ */ new Set([...SKIP_DIRS, "plugins"]);
141
+ var FLAT_AGENT_FILE_SUFFIX = ".agent.md";
142
+ async function walkForEntries(rootDir, repoId, out, layout = DEFAULT_REPO_LAYOUT) {
143
+ let dirents;
144
+ try {
145
+ dirents = await fs2.readdir(rootDir, { withFileTypes: true });
146
+ } catch {
147
+ return;
148
+ }
149
+ for (const d of dirents) {
150
+ if (d.isFile() && d.name.endsWith(FLAT_AGENT_FILE_SUFFIX)) {
151
+ const filePath = path2.join(rootDir, d.name);
152
+ const stat2 = await fs2.stat(filePath);
153
+ const content = await fs2.readFile(filePath, "utf8");
154
+ const parsed = extractFrontmatter(content);
155
+ out.push({
156
+ repoId,
157
+ name: d.name.slice(0, -FLAT_AGENT_FILE_SUFFIX.length),
158
+ kind: "agent",
159
+ manifestPath: filePath,
160
+ // Sentinel: `dir === manifestPath` marks a flat single-file
161
+ // entry (no directory of its own — see getFiles()).
162
+ dir: filePath,
163
+ frontmatter: parsed?.data ?? {},
164
+ modifiedAt: stat2.mtime.toISOString()
165
+ });
166
+ continue;
167
+ }
168
+ if (!d.isDirectory()) continue;
169
+ if (SKIP_TOP_LEVEL_SCAN_DIRS.has(d.name)) continue;
170
+ if (layout.exclude.includes(d.name)) continue;
171
+ const childDir = path2.join(rootDir, d.name);
172
+ const kind = await detectKind(childDir);
173
+ if (kind) {
174
+ const manifestFilename = kind === "skill" ? "SKILL.md" : "AGENT.md";
175
+ const manifestPath = path2.join(childDir, manifestFilename);
176
+ const stat2 = await fs2.stat(manifestPath);
177
+ const content = await fs2.readFile(manifestPath, "utf8");
178
+ const parsed = extractFrontmatter(content);
179
+ out.push({
180
+ repoId,
181
+ name: d.name,
182
+ kind,
183
+ manifestPath,
184
+ dir: childDir,
185
+ frontmatter: parsed?.data ?? {},
186
+ modifiedAt: stat2.mtime.toISOString()
187
+ });
188
+ continue;
189
+ }
190
+ await walkForEntries(childDir, repoId, out, layout);
191
+ if (layout.treatFilesAsSkills && layout.include.includes(d.name)) {
192
+ await surfaceFilesAsSkills(childDir, d.name, repoId, out);
193
+ }
194
+ }
195
+ }
196
+ async function surfaceFilesAsSkills(absDir, relName, repoId, out) {
197
+ let dirents;
198
+ try {
199
+ dirents = await fs2.readdir(absDir, { withFileTypes: true });
200
+ } catch {
201
+ return;
202
+ }
203
+ for (const d of dirents) {
204
+ if (!d.isFile()) continue;
205
+ if (!d.name.endsWith(".md")) continue;
206
+ const filePath = path2.join(absDir, d.name);
207
+ const stat2 = await fs2.stat(filePath);
208
+ const content = await fs2.readFile(filePath, "utf8");
209
+ const parsed = extractFrontmatter(content);
210
+ const entryName = `${relName}/${d.name.replace(/\.md$/, "")}`;
211
+ out.push({
212
+ repoId,
213
+ name: entryName,
214
+ kind: "skill",
215
+ manifestPath: filePath,
216
+ dir: absDir,
217
+ frontmatter: parsed?.data ?? {},
218
+ modifiedAt: stat2.mtime.toISOString()
219
+ });
220
+ }
221
+ }
222
+ async function detectKind(dir) {
223
+ const [hasSkill, hasAgent] = await Promise.all([
224
+ fs2.access(path2.join(dir, "SKILL.md")).then(() => true).catch(() => false),
225
+ fs2.access(path2.join(dir, "AGENT.md")).then(() => true).catch(() => false)
226
+ ]);
227
+ if (hasSkill) return "skill";
228
+ if (hasAgent) return "agent";
229
+ return null;
230
+ }
231
+ async function collectFilesRecursive(absDir, entryRoot, out) {
232
+ let dirents;
233
+ try {
234
+ dirents = await fs2.readdir(absDir, { withFileTypes: true });
235
+ } catch {
236
+ return;
237
+ }
238
+ for (const d of dirents) {
239
+ if (SKIP_DIRS.has(d.name)) continue;
240
+ const full = path2.join(absDir, d.name);
241
+ if (d.isDirectory()) {
242
+ await collectFilesRecursive(full, entryRoot, out);
243
+ } else if (d.isFile()) {
244
+ const content = await fs2.readFile(full, "utf8");
245
+ out.push({ path: path2.relative(entryRoot, full), content });
246
+ }
247
+ }
248
+ }
249
+ export {
250
+ SkillNotFoundError,
251
+ SkillStore,
252
+ extractFrontmatter
253
+ };
254
+ //# sourceMappingURL=skill-store.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/skill-store/index.ts","../src/repo-layout/index.ts","../src/skill-store/types.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport {\n\tDEFAULT_REPO_LAYOUT,\n\tloadRepoLayout,\n\ttype RepoLayoutDescriptor,\n} from \"../repo-layout\";\nimport type { SkillDetail, SkillEntry, SkillFile, SkillKind } from \"./types\";\nimport { SkillNotFoundError } from \"./types\";\n\n/**\n * Minimal frontmatter reader — same shape as the extension's\n * `WorkspaceSkillsInitializationService.extractFrontmatter`. We avoid\n * pulling a YAML dep into core; SKILL.md / AGENT.md in the wild use\n * a strict subset (top-level `key: value` pairs delimited by `---`).\n * Anything more exotic (nested mappings, multi-line scalars) is\n * passed through as the raw string in the corresponding value.\n */\nexport function extractFrontmatter(\n\traw: string,\n): { data: Record<string, unknown>; body: string } | null {\n\tif (typeof raw !== \"string\") return null;\n\tconst match = raw.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n\tif (!match) return null;\n\tconst yamlBody = match[1] ?? \"\";\n\tconst body = match[2] ?? \"\";\n\tconst data: Record<string, unknown> = {};\n\tfor (const line of yamlBody.split(/\\r?\\n/)) {\n\t\tif (line.trim().length === 0) continue;\n\t\tif (line.trim().startsWith(\"#\")) continue;\n\t\tconst kv = line.match(/^([a-zA-Z_][\\w-]*)\\s*:\\s*(.*)$/);\n\t\tif (!kv || !kv[1]) continue;\n\t\tlet value: unknown = (kv[2] ?? \"\").trim();\n\t\tif (typeof value === \"string\") {\n\t\t\tif (\n\t\t\t\t(value.startsWith('\"') && value.endsWith('\"')) ||\n\t\t\t\t(value.startsWith(\"'\") && value.endsWith(\"'\"))\n\t\t\t) {\n\t\t\t\tvalue = value.slice(1, -1);\n\t\t\t}\n\t\t}\n\t\tdata[kv[1]] = value;\n\t}\n\treturn { data, body };\n}\n\nexport type { SkillDetail, SkillEntry, SkillFile, SkillKind } from \"./types\";\n/**\n * Skill & Agent v2 — SkillStore (M4)\n *\n * Scans the local `~/.serviceme/repos/<id>/` tree to produce a unified\n * list of skills + agents. Local-only — no git, no network. Spec §5.5.\n *\n * Layout normalization (spec §12):\n * 1. **Standard layout** — `skills/<name>/SKILL.md` or\n * `agents/<name>/AGENT.md` under the repo root.\n * 2. **Anthropic flat layout** — `<name>/SKILL.md` directly under\n * the repo root (the `skills/` prefix is omitted).\n * 3. **awesome-copilot style** — root contains README + subdirs\n * that are skills/agents/instructions; we ignore README and\n * any dir without a manifest file.\n * 4. **Flat agent files** — a directory (commonly `agents/` or\n * `agents/official/`) full of `<name>.agent.md` files, one per\n * agent, with NO per-agent subdirectory. This is the prevailing\n * real-world convention (VS Code custom chat agents, GitHub's\n * awesome-copilot, and our own official ms-skills repo all ship\n * agents this way) — unlike skills, agents in the wild are\n * essentially never a `<name>/AGENT.md` directory pair. Detected\n * unconditionally, no `.serviceme-repo.json` opt-in required.\n *\n * Implementation: walk every directory under the repo root, check for\n * SKILL.md or AGENT.md, register if present. We skip the repo root\n * itself (no manifest lives at the top), `.git/`, and `node_modules/`.\n * Flat `*.agent.md` files are checked alongside directories at every\n * level of the walk.\n */\n// Re-export the error class + shared types so callers can\n// `import { SkillNotFoundError, SkillFile } from \"...\"`.\nexport { SkillNotFoundError } from \"./types\";\n\nexport class SkillStore {\n\tprivate readonly repoRoots: Map<string, string>;\n\n\tconstructor(opts: {\n\t\trepos: ReadonlyArray<{ id: string; rootPath: string }>;\n\t}) {\n\t\tthis.repoRoots = new Map();\n\t\tfor (const r of opts.repos) this.repoRoots.set(r.id, r.rootPath);\n\t}\n\n\t/** All skills + agents across all registered repos. */\n\tasync listAll(): Promise<SkillEntry[]> {\n\t\tconst all: SkillEntry[] = [];\n\t\tfor (const repoId of this.repoRoots.keys()) {\n\t\t\tall.push(...(await this.listByRepo(repoId)));\n\t\t}\n\t\treturn all;\n\t}\n\n\t/** All skills + agents under a single repo. */\n\tasync listByRepo(repoId: string): Promise<SkillEntry[]> {\n\t\tconst root = this.repoRoots.get(repoId);\n\t\tif (!root) return [];\n\t\tconst layout = (await loadRepoLayout(root)) ?? DEFAULT_REPO_LAYOUT;\n\t\tconst entries: SkillEntry[] = [];\n\t\tawait walkForEntries(root, repoId, entries, layout);\n\t\treturn entries;\n\t}\n\n\t/** Single entry detail (manifest + all files inside its dir). */\n\tasync get(repoId: string, name: string): Promise<SkillDetail> {\n\t\tconst entries = await this.listByRepo(repoId);\n\t\tconst found = entries.find((e) => e.name === name);\n\t\tif (!found) throw new SkillNotFoundError(repoId, name);\n\t\tconst files = await this.getFiles(repoId, name);\n\t\treturn { ...found, files };\n\t}\n\n\t/** All files inside a single entry's directory (manifest + extras). */\n\tasync getFiles(repoId: string, name: string): Promise<SkillFile[]> {\n\t\tconst entries = await this.listByRepo(repoId);\n\t\tconst found = entries.find((e) => e.name === name);\n\t\tif (!found) throw new SkillNotFoundError(repoId, name);\n\n\t\tconst out: SkillFile[] = [];\n\t\t// Flat single-file entries (e.g. `agents/foo.agent.md`) have `dir`\n\t\t// pointing at the manifest file itself, not a directory — there's\n\t\t// no sibling files to collect, just the one manifest.\n\t\tif (found.dir === found.manifestPath) {\n\t\t\tconst content = await fs.readFile(found.manifestPath, \"utf8\");\n\t\t\treturn [{ path: path.basename(found.manifestPath), content }];\n\t\t}\n\t\tawait collectFilesRecursive(found.dir, found.dir, out);\n\t\t// Sort for determinism (manifest first, then alphabetical)\n\t\tout.sort((a, b) => {\n\t\t\tconst aIsManifest = a.path === \"SKILL.md\" || a.path === \"AGENT.md\";\n\t\t\tconst bIsManifest = b.path === \"SKILL.md\" || b.path === \"AGENT.md\";\n\t\t\tif (aIsManifest && !bIsManifest) return -1;\n\t\t\tif (bIsManifest && !aIsManifest) return 1;\n\t\t\treturn a.path.localeCompare(b.path);\n\t\t});\n\t\treturn out;\n\t}\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Internals\n// ─────────────────────────────────────────────────────────────────────\n\nconst SKIP_DIRS = new Set([\n\t\".git\",\n\t\"node_modules\",\n\t\".vscode\",\n\t\"dist\",\n\t\"build\",\n\t\"out\",\n]);\n\n/**\n * Directories that are never treated as candidate skill/agent parents\n * during the top-level catalog walk. `plugins/` is a distinct concept\n * from the skill/agent catalog itself — a plugin bundle\n * (`plugins/<scope>/<plugin-name>/{skills,agents}/...`) re-packages\n * ALREADY-cataloged official skills/agents (see the plugin's own\n * `catalog.json`, which lists them by name) purely for discovery\n * grouping. Walking into it produces duplicate `SkillEntry` values\n * with the SAME `repoId`+`name` as their real, top-level counterpart\n * (e.g. `skills/official/acreadiness-assess` AND\n * `plugins/official/acreadiness-cockpit/skills/acreadiness-assess`),\n * which breaks anything keyed on `repoId/name` (React list rendering,\n * the \"installed\" lookup, `SkillStore.get()`'s `.find()`).\n *\n * Scoped separately from `SKIP_DIRS` (used by both this walk AND\n * `collectFilesRecursive`) so an actual skill that happens to ship its\n * own `plugins/` asset folder still has that folder listed in its own\n * file detail view.\n */\nconst SKIP_TOP_LEVEL_SCAN_DIRS = new Set([...SKIP_DIRS, \"plugins\"]);\n\n/** Suffix that marks a standalone file as a flat agent manifest (no wrapping dir). */\nconst FLAT_AGENT_FILE_SUFFIX = \".agent.md\";\n\n/**\n * Recursive walk that yields SkillEntry values for any directory\n * containing a SKILL.md or AGENT.md file, PLUS any standalone\n * `<name>.agent.md` file (see class doc, layout style 4). The dir\n * itself is the \"entry root\" for directory-based entries; the\n * manifest file itself is the \"entry root\" for flat agent files.\n *\n * The optional `layout` descriptor (from `.serviceme-repo.json`)\n * widens the scan: included subdirs are walked even without a\n * manifest at the top level, and `treatFilesAsSkills` surfaces\n * `<name>.md` files inside them as skills (frontmatter parsed\n * from the file body).\n */\nasync function walkForEntries(\n\trootDir: string,\n\trepoId: string,\n\tout: SkillEntry[],\n\tlayout: RepoLayoutDescriptor = DEFAULT_REPO_LAYOUT,\n): Promise<void> {\n\tlet dirents: import(\"node:fs\").Dirent[];\n\ttry {\n\t\tdirents = await fs.readdir(rootDir, { withFileTypes: true });\n\t} catch {\n\t\treturn;\n\t}\n\n\tfor (const d of dirents) {\n\t\tif (d.isFile() && d.name.endsWith(FLAT_AGENT_FILE_SUFFIX)) {\n\t\t\tconst filePath = path.join(rootDir, d.name);\n\t\t\tconst stat = await fs.stat(filePath);\n\t\t\tconst content = await fs.readFile(filePath, \"utf8\");\n\t\t\tconst parsed = extractFrontmatter(content);\n\t\t\tout.push({\n\t\t\t\trepoId,\n\t\t\t\tname: d.name.slice(0, -FLAT_AGENT_FILE_SUFFIX.length),\n\t\t\t\tkind: \"agent\",\n\t\t\t\tmanifestPath: filePath,\n\t\t\t\t// Sentinel: `dir === manifestPath` marks a flat single-file\n\t\t\t\t// entry (no directory of its own — see getFiles()).\n\t\t\t\tdir: filePath,\n\t\t\t\tfrontmatter: parsed?.data ?? {},\n\t\t\t\tmodifiedAt: stat.mtime.toISOString(),\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tif (!d.isDirectory()) continue;\n\t\tif (SKIP_TOP_LEVEL_SCAN_DIRS.has(d.name)) continue;\n\t\tif (layout.exclude.includes(d.name)) continue;\n\t\tconst childDir = path.join(rootDir, d.name);\n\t\tconst kind = await detectKind(childDir);\n\t\tif (kind) {\n\t\t\tconst manifestFilename = kind === \"skill\" ? \"SKILL.md\" : \"AGENT.md\";\n\t\t\tconst manifestPath = path.join(childDir, manifestFilename);\n\t\t\tconst stat = await fs.stat(manifestPath);\n\t\t\tconst content = await fs.readFile(manifestPath, \"utf8\");\n\t\t\tconst parsed = extractFrontmatter(content);\n\t\t\tout.push({\n\t\t\t\trepoId,\n\t\t\t\tname: d.name,\n\t\t\t\tkind,\n\t\t\t\tmanifestPath,\n\t\t\t\tdir: childDir,\n\t\t\t\tfrontmatter: parsed?.data ?? {},\n\t\t\t\tmodifiedAt: stat.mtime.toISOString(),\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\t// No manifest at the top — recurse, but ALSO check whether\n\t\t// the layout descriptor wants this subdir widened.\n\t\tawait walkForEntries(childDir, repoId, out, layout);\n\t\tif (layout.treatFilesAsSkills && layout.include.includes(d.name)) {\n\t\t\tawait surfaceFilesAsSkills(childDir, d.name, repoId, out);\n\t\t}\n\t}\n}\n\n/**\n * Walk `<includedSubdir>/<file>.md` and register each as a skill.\n * The `.md` file itself is the manifest — frontmatter (if any) is\n * parsed and surfaced. This is the awesome-copilot style: a\n * `prompts/` dir full of `<name>.md` files, no SKILL.md anywhere.\n */\nasync function surfaceFilesAsSkills(\n\tabsDir: string,\n\trelName: string,\n\trepoId: string,\n\tout: SkillEntry[],\n): Promise<void> {\n\tlet dirents: import(\"node:fs\").Dirent[];\n\ttry {\n\t\tdirents = await fs.readdir(absDir, { withFileTypes: true });\n\t} catch {\n\t\treturn;\n\t}\n\tfor (const d of dirents) {\n\t\tif (!d.isFile()) continue;\n\t\tif (!d.name.endsWith(\".md\")) continue;\n\t\tconst filePath = path.join(absDir, d.name);\n\t\tconst stat = await fs.stat(filePath);\n\t\tconst content = await fs.readFile(filePath, \"utf8\");\n\t\tconst parsed = extractFrontmatter(content);\n\t\t// Entry name = \"<subdir>/<file>\" (with the .md stripped) so it\n\t\t// stays unique even when two included subdirs share filenames.\n\t\tconst entryName = `${relName}/${d.name.replace(/\\.md$/, \"\")}`;\n\t\tout.push({\n\t\t\trepoId,\n\t\t\tname: entryName,\n\t\t\tkind: \"skill\",\n\t\t\tmanifestPath: filePath,\n\t\t\tdir: absDir,\n\t\t\tfrontmatter: parsed?.data ?? {},\n\t\t\tmodifiedAt: stat.mtime.toISOString(),\n\t\t});\n\t}\n}\n\n/**\n * Returns the SkillKind of a directory if it contains a manifest file,\n * or `null` if it doesn't. Manifest precedence: SKILL.md wins if both\n * are present (defensive — the spec separates skills and agents into\n * different subdir trees, so this collision shouldn't happen in\n * well-formed repos).\n */\nasync function detectKind(dir: string): Promise<SkillKind | null> {\n\tconst [hasSkill, hasAgent] = await Promise.all([\n\t\tfs\n\t\t\t.access(path.join(dir, \"SKILL.md\"))\n\t\t\t.then(() => true)\n\t\t\t.catch(() => false),\n\t\tfs\n\t\t\t.access(path.join(dir, \"AGENT.md\"))\n\t\t\t.then(() => true)\n\t\t\t.catch(() => false),\n\t]);\n\tif (hasSkill) return \"skill\";\n\tif (hasAgent) return \"agent\";\n\treturn null;\n}\n\n/**\n * Walk a skill/agent directory recursively, pushing every file into\n * `out` with its path RELATIVE to the entry root. Skips `.git/`,\n * `node_modules/`, and the SKIP_DIRS set.\n */\nasync function collectFilesRecursive(\n\tabsDir: string,\n\tentryRoot: string,\n\tout: SkillFile[],\n): Promise<void> {\n\tlet dirents: import(\"node:fs\").Dirent[];\n\ttry {\n\t\tdirents = await fs.readdir(absDir, { withFileTypes: true });\n\t} catch {\n\t\treturn;\n\t}\n\tfor (const d of dirents) {\n\t\tif (SKIP_DIRS.has(d.name)) continue;\n\t\tconst full = path.join(absDir, d.name);\n\t\tif (d.isDirectory()) {\n\t\t\tawait collectFilesRecursive(full, entryRoot, out);\n\t\t} else if (d.isFile()) {\n\t\t\tconst content = await fs.readFile(full, \"utf8\");\n\t\t\tout.push({ path: path.relative(entryRoot, full), content });\n\t\t}\n\t}\n}\n","/**\n * Spec §12 — Repo layout descriptor.\n *\n * Some third-party repos (awesome-copilot, composio) use a\n * non-default layout that mixes prompts / instructions / agents /\n * hooks / plugins at the root. The default SkillStore scan\n * (see ../skill-store/index.ts) only picks up directories that\n * contain a `SKILL.md` or `AGENT.md` manifest, so prompts stored\n * as `<subdir>/<name>.md` (no manifest) get missed.\n *\n * This module is the loader for the optional `.serviceme-repo.json`\n * marker file a repo author can drop in at the repo root to opt\n * into a wider scan. The schema:\n *\n * {\n * \"schema\": 1,\n * \"include\": [\"prompts\", \"instructions\"], // subdirs to treat as \"skill dirs\"\n * \"exclude\": [\"hooks\", \"plugins\"], // subdirs to skip\n * \"treatFilesAsSkills\": true // inside an included subdir, each .md file is a skill\n * }\n *\n * All four fields are optional. The defaults match the v0.2\n * walkForEntries behaviour (manifest-based, no marker, no\n * extensions), so adding the file is purely additive.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §12\n */\n\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nconst MARKER_FILENAME = \".serviceme-repo.json\";\nconst SUPPORTED_SCHEMA = 1;\n\nexport interface RepoLayoutDescriptor {\n\t/** Schema version. Currently always 1. */\n\tschema: number;\n\t/** Subdirs (relative to repo root) to walk for entries. */\n\tinclude: string[];\n\t/** Subdirs (relative to repo root) to skip, even if a parent is included. */\n\texclude: string[];\n\t/**\n\t * When true, an included subdir's `<name>.md` files are\n\t * surfaced as skills (frontmatter is parsed from each file as\n\t * a stand-in for SKILL.md). When false (default), the\n\t * include list only widens the recursion — the manifest rule\n\t * still applies.\n\t */\n\ttreatFilesAsSkills: boolean;\n}\n\n/**\n * Default descriptor used when no `.serviceme-repo.json` is\n * present. The shape is the same as what an empty marker would\n * produce, so callers don't have to special-case the \"no marker\"\n * branch.\n */\nexport const DEFAULT_REPO_LAYOUT: RepoLayoutDescriptor = {\n\tschema: SUPPORTED_SCHEMA,\n\tinclude: [],\n\texclude: [],\n\ttreatFilesAsSkills: false,\n};\n\n/**\n * Try to load a `.serviceme-repo.json` from the given repo root.\n * Returns `DEFAULT_REPO_LAYOUT` (not undefined) when the file is\n * absent — callers branch on `include.length` / `treatFilesAsSkills`\n * to decide whether to widen the scan.\n *\n * Malformed markers (bad JSON, wrong schema) are surfaced as\n * `null` so callers can warn the user instead of silently\n * treating them as the default.\n */\nexport async function loadRepoLayout(repoRoot: string): Promise<RepoLayoutDescriptor | null> {\n\tconst markerPath = path.join(repoRoot, MARKER_FILENAME);\n\tlet raw: string;\n\ttry {\n\t\traw = await fs.readFile(markerPath, \"utf8\");\n\t} catch {\n\t\treturn DEFAULT_REPO_LAYOUT;\n\t}\n\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\treturn null;\n\t}\n\tif (!parsed || typeof parsed !== \"object\") return null;\n\tconst obj = parsed as Record<string, unknown>;\n\n\tif (obj.schema !== SUPPORTED_SCHEMA) return null;\n\tif (!Array.isArray(obj.include) || obj.include.some((s) => typeof s !== \"string\")) {\n\t\treturn null;\n\t}\n\tif (!Array.isArray(obj.exclude) || obj.exclude.some((s) => typeof s !== \"string\")) {\n\t\treturn null;\n\t}\n\tif (typeof obj.treatFilesAsSkills !== \"boolean\") return null;\n\n\treturn {\n\t\tschema: SUPPORTED_SCHEMA,\n\t\tinclude: obj.include as string[],\n\t\texclude: obj.exclude as string[],\n\t\ttreatFilesAsSkills: obj.treatFilesAsSkills,\n\t};\n}\n\n/** Subdir name → entry kind override (rarely needed; default = manifest-driven). */\nexport type KindOverride = Map<string, \"skill\" | \"agent\">;\n","/**\n * Skill & Agent v2 — SkillStore Types\n *\n * SkillStore scans the local `~/.serviceme/repos/<id>/` tree to produce\n * a unified list of skills + agents. The store is the single source of\n * truth for \"what's available right now\" — the UI reads it; SubmitClient\n * reads it; install flows read it.\n *\n * Three repo layouts are supported in v1 (per spec §12):\n * 1. **Default** (`medalsoftchina-ms-skills`): `skills/<name>/SKILL.md`\n * + `agents/<name>/AGENT.md`.\n * 2. **Anthropic-style flat** (`anthropics/skills`): each subdir of\n * the repo root IS a skill — `<name>/SKILL.md` (no `skills/`\n * intermediate).\n * 3. **awesome-copilot style** (`github/awesome-copilot`): mixed\n * prompts/instructions/agents at the root.\n *\n * Normalization rule (spec §12): for every directory under the repo\n * root, peek for a `SKILL.md` or `AGENT.md` file. If present, register\n * it as a skill or agent respectively. This is more permissive than\n * guessing from the directory name and works for all three styles.\n *\n * The store is local-only and pure — no git, no network. Tests\n * construct a fixture repo tree and pass the root in directly.\n */\n\n/** What's available at this entry: a skill or an agent. */\nexport type SkillKind = \"skill\" | \"agent\";\n\n/** Top-level summary of a discovered skill/agent entry. */\nexport interface SkillEntry {\n\t/** Repository the entry was found in. */\n\trepoId: string;\n\t/** Skill or agent name (the directory name under the repo). */\n\tname: string;\n\tkind: SkillKind;\n\t/** Absolute path to the SKILL.md / AGENT.md file. */\n\tmanifestPath: string;\n\t/** Absolute path to the directory containing the entry's files. */\n\tdir: string;\n\t/** Parsed frontmatter (best-effort; raw key/value map). */\n\tfrontmatter: Record<string, unknown>;\n\t/** ISO timestamp of the manifest file's mtime. */\n\tmodifiedAt: string;\n}\n\n/** Full detail of an entry: summary + all the files inside the dir. */\nexport interface SkillDetail extends SkillEntry {\n\tfiles: SkillFile[];\n}\n\n/** A single file inside a skill / agent directory. */\nexport interface SkillFile {\n\t/** Path relative to the entry's directory. */\n\tpath: string;\n\t/** UTF-8 content. */\n\tcontent: string;\n}\n\n/** Sentinel error for store lookups that miss. */\nexport class SkillNotFoundError extends Error {\n\tconstructor(\n\t\tpublic readonly repoId: string,\n\t\tpublic readonly skillName: string\n\t) {\n\t\tsuper(`skill/agent not found: ${repoId}/${skillName}`);\n\t\tthis.name = \"SkillNotFoundError\";\n\t}\n}\n"],"mappings":"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","names":["fs","path","stat"]}