pi-harness-runtime 1.1.95 → 1.1.97

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 (226) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/harness/loop-runtime.ts +3 -0
  3. package/index.ts +130 -29
  4. package/package.json +2 -1
  5. package/packages/a2a-adapter/package.json +1 -1
  6. package/packages/architecture-generator/package.json +1 -1
  7. package/packages/auth/package.json +1 -1
  8. package/packages/autonomous-refactor/package.json +1 -1
  9. package/packages/autonomous-runtime/package.json +1 -1
  10. package/packages/cache-strategy/package.json +1 -1
  11. package/packages/capability-registry/package.json +1 -1
  12. package/packages/checkpoint/package.json +1 -1
  13. package/packages/cli-plugin-sdk/package.json +1 -1
  14. package/packages/clipboard/package.json +1 -1
  15. package/packages/clipboard-plugin/package.json +1 -1
  16. package/packages/code-generation/package.json +1 -1
  17. package/packages/code-review/package.json +1 -1
  18. package/packages/codex-adapter/package.json +1 -1
  19. package/packages/config-capture/package.json +1 -1
  20. package/packages/context-compiler/package.json +1 -1
  21. package/packages/context-discovery/package.json +1 -1
  22. package/packages/context-manager/package.json +1 -1
  23. package/packages/cookie-sanitizer/package.json +1 -1
  24. package/packages/cost-optimizer/package.json +1 -1
  25. package/packages/dependency-analyzer/package.json +1 -1
  26. package/packages/django-plugin/package.json +1 -1
  27. package/packages/doc-generator/package.json +1 -1
  28. package/packages/evaluation-engine/package.json +1 -1
  29. package/packages/evaluation-runner/package.json +1 -1
  30. package/packages/event-bus/package.json +1 -1
  31. package/packages/event-store/package.json +1 -1
  32. package/packages/experience-replay/package.json +1 -1
  33. package/packages/feedback-collector/package.json +1 -1
  34. package/packages/file-copy-helper/package.json +1 -1
  35. package/packages/framework-detector/package.json +1 -1
  36. package/packages/framework-plugin-sdk/package.json +1 -1
  37. package/packages/frappe-plugin/package.json +1 -1
  38. package/packages/generic-web-plugin/package.json +1 -1
  39. package/packages/health-monitor/package.json +1 -1
  40. package/packages/intent-analyzer/package.json +1 -1
  41. package/packages/jev-judge/package.json +1 -1
  42. package/packages/knowledge-graph/package.json +1 -1
  43. package/packages/knowledge-retrieval/package.json +1 -1
  44. package/packages/laravel-plugin/package.json +1 -1
  45. package/packages/learning-engine/package.json +1 -1
  46. package/packages/mcp-adapter/package.json +1 -1
  47. package/packages/memory-engine/package.json +1 -1
  48. package/packages/milestone-manager/package.json +1 -1
  49. package/packages/model-registry/package.json +1 -1
  50. package/packages/nextjs-plugin/package.json +1 -1
  51. package/packages/notification/package.json +1 -1
  52. package/packages/observability/package.json +1 -1
  53. package/packages/okf-indexer/package.json +1 -1
  54. package/packages/performance-optimizer/package.json +1 -1
  55. package/packages/privilege-broker/package.json +1 -1
  56. package/packages/project-analyzer/package.json +1 -1
  57. package/packages/project-bootstrap/package.json +1 -1
  58. package/packages/projection-engine/package.json +1 -1
  59. package/packages/prompt-compiler/package.json +1 -1
  60. package/packages/prompt-versioning/package.json +1 -1
  61. package/packages/provider-adapter-sdk/package.json +1 -1
  62. package/packages/provider-router/package.json +1 -1
  63. package/packages/provider-selector/package.json +1 -1
  64. package/packages/providers/package.json +1 -1
  65. package/packages/qdrant-skills/dist/client.d.ts +51 -0
  66. package/packages/qdrant-skills/dist/client.d.ts.map +1 -0
  67. package/packages/qdrant-skills/dist/client.js +105 -0
  68. package/packages/qdrant-skills/dist/client.js.map +1 -0
  69. package/packages/qdrant-skills/dist/embedder.d.ts +39 -0
  70. package/packages/qdrant-skills/dist/embedder.d.ts.map +1 -0
  71. package/packages/qdrant-skills/dist/embedder.js +101 -0
  72. package/packages/qdrant-skills/dist/embedder.js.map +1 -0
  73. package/packages/qdrant-skills/dist/index.d.ts +25 -0
  74. package/packages/qdrant-skills/dist/index.d.ts.map +1 -0
  75. package/packages/qdrant-skills/dist/index.js +25 -0
  76. package/packages/qdrant-skills/dist/index.js.map +1 -0
  77. package/packages/qdrant-skills/package.json +27 -0
  78. package/packages/qdrant-skills/src/client.ts +178 -0
  79. package/packages/qdrant-skills/src/embedder.ts +137 -0
  80. package/packages/qdrant-skills/src/index.ts +42 -0
  81. package/packages/quota-manager/package.json +1 -1
  82. package/packages/rate-limiter/package.json +1 -1
  83. package/packages/react-vite-plugin/package.json +1 -1
  84. package/packages/release-manager/package.json +1 -1
  85. package/packages/requirement-compiler/package.json +1 -1
  86. package/packages/runtime/package.json +1 -1
  87. package/packages/scheduler/package.json +1 -1
  88. package/packages/scheduler-adapter/package.json +1 -1
  89. package/packages/session/package.json +1 -1
  90. package/packages/session-api/package.json +1 -1
  91. package/packages/session-export/package.json +1 -1
  92. package/packages/shared-context/package.json +1 -1
  93. package/packages/skill-mcp-client/package.json +1 -1
  94. package/packages/skill-registry/package.json +1 -1
  95. package/packages/skills/dist/index.d.ts +40 -0
  96. package/packages/skills/dist/index.d.ts.map +1 -0
  97. package/packages/skills/dist/index.js +84 -0
  98. package/packages/skills/dist/index.js.map +1 -0
  99. package/packages/skills/dist/linter.d.ts +38 -0
  100. package/packages/skills/dist/linter.d.ts.map +1 -0
  101. package/packages/skills/dist/linter.js +323 -0
  102. package/packages/skills/dist/linter.js.map +1 -0
  103. package/packages/skills/dist/loader.d.ts +74 -0
  104. package/packages/skills/dist/loader.d.ts.map +1 -0
  105. package/packages/skills/dist/loader.js +280 -0
  106. package/packages/skills/dist/loader.js.map +1 -0
  107. package/packages/skills/dist/parser.d.ts +32 -0
  108. package/packages/skills/dist/parser.d.ts.map +1 -0
  109. package/packages/skills/dist/parser.js +459 -0
  110. package/packages/skills/dist/parser.js.map +1 -0
  111. package/packages/skills/dist/scanner.d.ts +33 -0
  112. package/packages/skills/dist/scanner.d.ts.map +1 -0
  113. package/packages/skills/dist/scanner.js +257 -0
  114. package/packages/skills/dist/scanner.js.map +1 -0
  115. package/packages/skills/dist/skill-commands.d.ts +33 -0
  116. package/packages/skills/dist/skill-commands.d.ts.map +1 -0
  117. package/packages/skills/dist/skill-commands.js +239 -0
  118. package/packages/skills/dist/skill-commands.js.map +1 -0
  119. package/packages/skills/dist/skill-tool.d.ts +120 -0
  120. package/packages/skills/dist/skill-tool.d.ts.map +1 -0
  121. package/packages/skills/dist/skill-tool.js +239 -0
  122. package/packages/skills/dist/skill-tool.js.map +1 -0
  123. package/packages/skills/dist/skill-versioning.d.ts +123 -0
  124. package/packages/skills/dist/skill-versioning.d.ts.map +1 -0
  125. package/packages/skills/dist/skill-versioning.js +240 -0
  126. package/packages/skills/dist/skill-versioning.js.map +1 -0
  127. package/packages/skills/dist/types.d.ts +226 -0
  128. package/packages/skills/dist/types.d.ts.map +1 -0
  129. package/packages/skills/dist/types.js +21 -0
  130. package/packages/skills/dist/types.js.map +1 -0
  131. package/packages/skills/package.json +46 -0
  132. package/packages/skills/src/index.ts +95 -0
  133. package/packages/skills/src/linter.ts +371 -0
  134. package/packages/skills/src/loader.ts +335 -0
  135. package/packages/skills/src/parser.ts +548 -0
  136. package/packages/skills/src/scanner.ts +322 -0
  137. package/packages/skills/src/skill-commands.ts +318 -0
  138. package/packages/skills/src/skill-tool.ts +324 -0
  139. package/packages/skills/src/skill-versioning.ts +345 -0
  140. package/packages/skills/src/types.ts +259 -0
  141. package/packages/skills/test/init-skills.test.ts +118 -0
  142. package/packages/sprint-planner/package.json +1 -1
  143. package/packages/subscription-engine/package.json +1 -1
  144. package/packages/task-compiler/package.json +1 -1
  145. package/packages/tencentdb-memory/package.json +1 -1
  146. package/packages/tencentdb-sync/package.json +1 -1
  147. package/packages/test-data-generator/package.json +1 -1
  148. package/packages/test-generator/package.json +1 -1
  149. package/packages/todo-bd-sync/package.json +1 -1
  150. package/packages/token-estimation/package.json +1 -1
  151. package/packages/token-optimizer/package.json +1 -1
  152. package/packages/trajectory/dist/src/curator.d.ts +189 -0
  153. package/packages/trajectory/dist/src/curator.d.ts.map +1 -0
  154. package/packages/trajectory/dist/src/curator.js +340 -0
  155. package/packages/trajectory/dist/src/curator.js.map +1 -0
  156. package/packages/trajectory/dist/src/journey-renderer.d.ts +53 -0
  157. package/packages/trajectory/dist/src/journey-renderer.d.ts.map +1 -0
  158. package/packages/trajectory/dist/src/journey-renderer.js +324 -0
  159. package/packages/trajectory/dist/src/journey-renderer.js.map +1 -0
  160. package/packages/trajectory/dist/src/learning-graph.d.ts +159 -0
  161. package/packages/trajectory/dist/src/learning-graph.d.ts.map +1 -0
  162. package/packages/trajectory/dist/src/learning-graph.js +258 -0
  163. package/packages/trajectory/dist/src/learning-graph.js.map +1 -0
  164. package/packages/trajectory/dist/src/memory-store.d.ts +146 -0
  165. package/packages/trajectory/dist/src/memory-store.d.ts.map +1 -0
  166. package/packages/trajectory/dist/src/memory-store.js +472 -0
  167. package/packages/trajectory/dist/src/memory-store.js.map +1 -0
  168. package/packages/trajectory/dist/src/review-fork.d.ts +164 -0
  169. package/packages/trajectory/dist/src/review-fork.d.ts.map +1 -0
  170. package/packages/trajectory/dist/src/review-fork.js +332 -0
  171. package/packages/trajectory/dist/src/review-fork.js.map +1 -0
  172. package/packages/trajectory/dist/src/write-approval.d.ts +136 -0
  173. package/packages/trajectory/dist/src/write-approval.d.ts.map +1 -0
  174. package/packages/trajectory/dist/src/write-approval.js +281 -0
  175. package/packages/trajectory/dist/src/write-approval.js.map +1 -0
  176. package/packages/trajectory/package.json +1 -1
  177. package/packages/trajectory/src/curator.ts +485 -0
  178. package/packages/trajectory/src/journey-renderer.ts +399 -0
  179. package/packages/trajectory/src/learning-graph.ts +363 -0
  180. package/packages/trajectory/src/memory-store.ts +578 -0
  181. package/packages/trajectory/src/review-fork.ts +450 -0
  182. package/packages/trajectory/src/write-approval.ts +372 -0
  183. package/packages/tui/package.json +1 -1
  184. package/packages/types/package.json +1 -1
  185. package/packages/workflow-events/package.json +1 -1
  186. package/packages/workspace-scanner/package.json +1 -1
  187. package/packages/worktree/package.json +1 -1
  188. package/packages/write-review/dist/blackboard.d.ts +98 -0
  189. package/packages/write-review/dist/blackboard.d.ts.map +1 -0
  190. package/packages/write-review/dist/blackboard.js +289 -0
  191. package/packages/write-review/dist/blackboard.js.map +1 -0
  192. package/packages/write-review/dist/event-bus.d.ts +106 -0
  193. package/packages/write-review/dist/event-bus.d.ts.map +1 -0
  194. package/packages/write-review/dist/event-bus.js +294 -0
  195. package/packages/write-review/dist/event-bus.js.map +1 -0
  196. package/packages/write-review/dist/gate.d.ts +37 -0
  197. package/packages/write-review/dist/gate.d.ts.map +1 -0
  198. package/packages/write-review/dist/gate.js +138 -0
  199. package/packages/write-review/dist/gate.js.map +1 -0
  200. package/packages/write-review/dist/index.d.ts +22 -0
  201. package/packages/write-review/dist/index.d.ts.map +1 -0
  202. package/packages/write-review/dist/index.js +60 -0
  203. package/packages/write-review/dist/index.js.map +1 -0
  204. package/packages/write-review/dist/injection.d.ts +40 -0
  205. package/packages/write-review/dist/injection.d.ts.map +1 -0
  206. package/packages/write-review/dist/injection.js +179 -0
  207. package/packages/write-review/dist/injection.js.map +1 -0
  208. package/packages/write-review/dist/review.d.ts +54 -0
  209. package/packages/write-review/dist/review.d.ts.map +1 -0
  210. package/packages/write-review/dist/review.js +154 -0
  211. package/packages/write-review/dist/review.js.map +1 -0
  212. package/packages/write-review/dist/trigger.d.ts +39 -0
  213. package/packages/write-review/dist/trigger.d.ts.map +1 -0
  214. package/packages/write-review/dist/trigger.js +107 -0
  215. package/packages/write-review/dist/trigger.js.map +1 -0
  216. package/packages/write-review/dist/types.d.ts +78 -0
  217. package/packages/write-review/dist/types.d.ts.map +1 -0
  218. package/packages/write-review/dist/types.js +26 -0
  219. package/packages/write-review/dist/types.js.map +1 -0
  220. package/packages/write-review/package.json +1 -1
  221. package/packages/write-review/src/event-bus.ts +351 -0
  222. package/packages/write-review/src/index.ts +1 -0
  223. package/packages/write-review/src/injection.ts +25 -3
  224. package/proactive-compact.ts +51 -7
  225. package/scripts/sync-skills.ts +275 -0
  226. package/scripts/sync-to-qdrant.ts +398 -0
@@ -0,0 +1,548 @@
1
+ /**
2
+ * SKILL.md Parser
3
+ *
4
+ * Parses skill files with YAML frontmatter and markdown body.
5
+ * Based on pi.dev Agent Skills specification.
6
+ */
7
+
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import type {
10
+ Skill,
11
+ SkillFrontmatter,
12
+ HermesExtensions,
13
+ SkillReference,
14
+ SkillExample,
15
+ SkillMetadata,
16
+ SkillPitfall,
17
+ } from "./types.js";
18
+
19
+ /**
20
+ * Simple YAML-like frontmatter parser (subset of YAML for skill frontmatter)
21
+ * Handles: strings, numbers, booleans, arrays, nested objects
22
+ */
23
+ function parseFrontmatter(yaml: string): Record<string, unknown> {
24
+ const result: Record<string, unknown> = {};
25
+ const lines = yaml.split("\n");
26
+ let i = 0;
27
+
28
+ function parseValue(line: string, _indent: number): string | number | boolean | string[] | undefined {
29
+ line = line.trimEnd();
30
+
31
+ // Empty line
32
+ if (!line) return undefined;
33
+
34
+ // Boolean
35
+ if (line === "true") return true;
36
+ if (line === "false") return false;
37
+
38
+ // Number
39
+ if (/^-?\d+(\.\d+)?$/.test(line)) return Number(line);
40
+
41
+ // Quoted string
42
+ if (line.startsWith('"') && line.endsWith('"')) {
43
+ return line.slice(1, -1);
44
+ }
45
+ if (line.startsWith("'") && line.endsWith("'")) {
46
+ return line.slice(1, -1);
47
+ }
48
+
49
+ // Array
50
+ if (line.match(/^-\s/)) {
51
+ const arr: unknown[] = [];
52
+ const itemMatch = line.match(/^-\s+(.*)$/);
53
+ if (itemMatch) {
54
+ arr.push(parseValue(itemMatch[1], 0));
55
+ }
56
+ return arr as string[];
57
+ }
58
+
59
+ // Plain value
60
+ return line;
61
+ }
62
+
63
+ function parseBlock(_key: string, lines: string[], start: number): { value: unknown[]; end: number } {
64
+ const arr: unknown[] = [];
65
+ let j = start;
66
+ while (j < lines.length) {
67
+ const line = lines[j];
68
+ if (!line.trim()) {
69
+ j++;
70
+ continue;
71
+ }
72
+ const match = line.match(/^-\s+(.*)$/);
73
+ if (match) {
74
+ arr.push(parseValue(match[1], 0));
75
+ j++;
76
+ } else {
77
+ break;
78
+ }
79
+ }
80
+ return { value: arr, end: j };
81
+ }
82
+
83
+ while (i < lines.length) {
84
+ const line = lines[i];
85
+
86
+ // Skip empty lines and comments
87
+ if (!line.trim() || line.trim().startsWith("#")) {
88
+ i++;
89
+ continue;
90
+ }
91
+
92
+ // Check for key: value pattern
93
+ const kvMatch = line.match(/^([\w_]+):\s*(.*)$/);
94
+ if (kvMatch) {
95
+ const key = kvMatch[1];
96
+ const value = kvMatch[2].trim();
97
+
98
+ // Check if it's an array/block
99
+ if (!value) {
100
+ // Look ahead for array items
101
+ const blockStart = i + 1;
102
+ if (blockStart < lines.length && lines[blockStart].trim().startsWith("- ")) {
103
+ const { value: blockValue, end } = parseBlock(key, lines, blockStart);
104
+ result[key] = blockValue;
105
+ i = end;
106
+ continue;
107
+ }
108
+ // Empty value
109
+ result[key] = "";
110
+ i++;
111
+ continue;
112
+ }
113
+
114
+ result[key] = parseValue(value, 0);
115
+ i++;
116
+ continue;
117
+ }
118
+
119
+ i++;
120
+ }
121
+
122
+ return result;
123
+ }
124
+
125
+ // Regex patterns for frontmatter
126
+ const FRONTMATTER_REGEX = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/;
127
+ const NAME_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
128
+
129
+ /**
130
+ * Parse a SKILL.md file
131
+ */
132
+ export function parseSkillFile(filePath: string): Skill {
133
+ if (!existsSync(filePath)) {
134
+ throw new Error(`Skill file not found: ${filePath}`);
135
+ }
136
+
137
+ const content = readFileSync(filePath, "utf-8");
138
+ return parseSkillContent(content, filePath);
139
+ }
140
+
141
+ /**
142
+ * Parse SKILL.md content string
143
+ */
144
+ export function parseSkillContent(content: string, filePath?: string): Skill {
145
+ const match = content.match(FRONTMATTER_REGEX);
146
+
147
+ if (!match) {
148
+ // No frontmatter - treat entire content as body
149
+ return createSkillWithoutFrontmatter(content, filePath);
150
+ }
151
+
152
+ const [, frontmatterStr, body] = match;
153
+
154
+ let frontmatter: Record<string, unknown>;
155
+ try {
156
+ frontmatter = parseFrontmatter(frontmatterStr);
157
+ } catch (err) {
158
+ throw new Error(`Invalid YAML frontmatter in ${filePath}: ${err}`);
159
+ }
160
+
161
+ // Extract pi.dev standard fields
162
+ const fm = extractFrontmatter(frontmatter);
163
+
164
+ // Extract Hermes extensions
165
+ const hermes = extractHermesExtensions(frontmatter);
166
+
167
+ // Validate required fields
168
+ if (!fm.name) {
169
+ throw new Error(`Missing required field 'name' in ${filePath}`);
170
+ }
171
+ if (!fm.description) {
172
+ throw new Error(`Missing required field 'description' in ${filePath}`);
173
+ }
174
+
175
+ // Validate name format (kebab-case)
176
+ if (!NAME_REGEX.test(fm.name)) {
177
+ throw new Error(
178
+ `Invalid skill name '${fm.name}' in ${filePath}. Must be kebab-case (e.g., 'code-review')`
179
+ );
180
+ }
181
+
182
+ // Create skill object
183
+ const skill: Skill = {
184
+ id: fm.name,
185
+ path: filePath ? getSkillDirectory(filePath) : "",
186
+ frontmatter: fm,
187
+ hermes,
188
+ body: body.trim(),
189
+ references: [],
190
+ examples: [],
191
+ metadata: mergeMetadata(fm, hermes),
192
+ loaded: false,
193
+ };
194
+
195
+ return skill;
196
+ }
197
+
198
+ /**
199
+ * Extract pi.dev standard frontmatter fields
200
+ */
201
+ function extractFrontmatter(raw: Record<string, unknown>): SkillFrontmatter {
202
+ return {
203
+ name: String(raw.name || ""),
204
+ description: String(raw.description || ""),
205
+ version: raw.version ? String(raw.version) : undefined,
206
+ license: raw.license ? String(raw.license) : undefined,
207
+ compatibility: raw.compatibility ? String(raw.compatibility) : undefined,
208
+ metadata: raw.metadata as Record<string, unknown> | undefined,
209
+ allowed_tools: Array.isArray(raw.allowed_tools)
210
+ ? raw.allowed_tools.map(String)
211
+ : undefined,
212
+ disable_model_invocation: Boolean(raw.disable_model_invocation),
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Extract Hermes-specific extensions from frontmatter
218
+ */
219
+ function extractHermesExtensions(raw: Record<string, unknown>): HermesExtensions {
220
+ return {
221
+ author: normalizeAuthor(raw.author),
222
+ confidence:
223
+ typeof raw.confidence === "number"
224
+ ? Math.max(0, Math.min(1, raw.confidence))
225
+ : undefined,
226
+ usage_count:
227
+ typeof raw.usage_count === "number" ? Math.floor(raw.usage_count) : undefined,
228
+ created_at: raw.created_at ? String(raw.created_at) : undefined,
229
+ updated_at: raw.updated_at ? String(raw.updated_at) : undefined,
230
+ triggers: Array.isArray(raw.triggers)
231
+ ? raw.triggers.map(String)
232
+ : undefined,
233
+ pitfalls: Array.isArray(raw.pitfalls)
234
+ ? raw.pitfalls.map((p) => normalizePitfall(p))
235
+ : undefined,
236
+ anti_patterns: Array.isArray(raw.anti_patterns)
237
+ ? raw.anti_patterns.map(String)
238
+ : undefined,
239
+ auto_update: Boolean(raw.auto_update),
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Normalize author field
245
+ */
246
+ function normalizeAuthor(author: unknown): "agent" | "user" | "curator" | undefined {
247
+ if (typeof author === "string") {
248
+ const normalized = author.toLowerCase();
249
+ if (
250
+ normalized === "agent" ||
251
+ normalized === "user" ||
252
+ normalized === "curator"
253
+ ) {
254
+ return normalized;
255
+ }
256
+ }
257
+ return undefined;
258
+ }
259
+
260
+ /**
261
+ * Normalize pitfall entry
262
+ */
263
+ function normalizePitfall(p: unknown): SkillPitfall {
264
+ if (typeof p === "string") {
265
+ return { name: p, why: "" };
266
+ }
267
+ if (typeof p === "object" && p !== null) {
268
+ const obj = p as Record<string, unknown>;
269
+ return {
270
+ name: String(obj.name || "unnamed"),
271
+ why: String(obj.why || obj.description || ""),
272
+ affects: obj.affects ? String(obj.affects) : undefined,
273
+ };
274
+ }
275
+ return { name: "unknown", why: "" };
276
+ }
277
+
278
+ /**
279
+ * Create skill without frontmatter (backward compatibility)
280
+ */
281
+ function createSkillWithoutFrontmatter(
282
+ content: string,
283
+ filePath?: string
284
+ ): Skill {
285
+ const name = filePath ? getSkillDirectory(filePath).split("/").pop() || "unnamed" : "unnamed";
286
+
287
+ return {
288
+ id: name,
289
+ path: filePath ? getSkillDirectory(filePath) : "",
290
+ frontmatter: {
291
+ name,
292
+ description: `Auto-generated skill from ${name}`,
293
+ },
294
+ hermes: {},
295
+ body: content.trim(),
296
+ references: [],
297
+ examples: [],
298
+ metadata: {
299
+ name,
300
+ description: `Auto-generated skill from ${name}`,
301
+ },
302
+ loaded: false,
303
+ };
304
+ }
305
+
306
+ /**
307
+ * Get skill directory from SKILL.md path
308
+ */
309
+ function getSkillDirectory(skillFilePath: string): string {
310
+ // Remove /SKILL.md from path
311
+ const match = skillFilePath.match(/^(.*)\/SKILL\.md$/);
312
+ return match ? match[1] : skillFilePath;
313
+ }
314
+
315
+ /**
316
+ * Merge frontmatter and hermes into metadata
317
+ */
318
+ function mergeMetadata(
319
+ fm: SkillFrontmatter,
320
+ hermes: HermesExtensions
321
+ ): SkillMetadata {
322
+ return {
323
+ ...fm,
324
+ ...hermes,
325
+ };
326
+ }
327
+
328
+ /**
329
+ * Scan skill directory for references and examples
330
+ */
331
+ export function scanSkillDirectory(skillDir: string): {
332
+ references: SkillReference[];
333
+ examples: SkillExample[];
334
+ } {
335
+ const references: SkillReference[] = [];
336
+ const examples: SkillExample[] = [];
337
+
338
+ // Import dynamic dependencies at runtime
339
+ try {
340
+ const { readdirSync, statSync } = require("node:fs");
341
+ const { join, relative } = require("node:path");
342
+
343
+ function scanDir(dir: string, baseDir: string): void {
344
+ if (!existsSync(dir)) return;
345
+
346
+ const entries = readdirSync(dir);
347
+ for (const entry of entries) {
348
+ if (entry.startsWith(".")) continue;
349
+
350
+ const fullPath = join(dir, entry);
351
+ const stat = statSync(fullPath);
352
+
353
+ if (stat.isDirectory()) {
354
+ if (entry === "references") {
355
+ scanReferences(fullPath, baseDir, references);
356
+ } else if (entry === "examples") {
357
+ scanExamples(fullPath, baseDir, examples);
358
+ }
359
+ }
360
+ }
361
+ }
362
+
363
+ function scanReferences(
364
+ dir: string,
365
+ baseDir: string,
366
+ refs: SkillReference[]
367
+ ): void {
368
+ if (!existsSync(dir)) return;
369
+
370
+ const { readdirSync, statSync } = require("node:fs");
371
+ const { join } = require("node:path");
372
+
373
+ const entries = readdirSync(dir);
374
+ for (const entry of entries) {
375
+ if (entry.startsWith(".")) continue;
376
+
377
+ const fullPath = join(dir, entry);
378
+ const stat = statSync(fullPath);
379
+
380
+ if (stat.isFile() && /\.(md|txt|json|yaml|yml)$/i.test(entry)) {
381
+ refs.push({
382
+ name: entry.replace(/\.(md|txt|json|yaml|yml)$/i, ""),
383
+ path: fullPath,
384
+ relativePath: relative(baseDir, fullPath),
385
+ });
386
+ }
387
+ }
388
+ }
389
+
390
+ function scanExamples(
391
+ dir: string,
392
+ baseDir: string,
393
+ exs: SkillExample[]
394
+ ): void {
395
+ if (!existsSync(dir)) return;
396
+
397
+ const { readdirSync, statSync } = require("node:fs");
398
+ const { join } = require("node:path");
399
+
400
+ const entries = readdirSync(dir);
401
+ for (const entry of entries) {
402
+ if (entry.startsWith(".")) continue;
403
+
404
+ const fullPath = join(dir, entry);
405
+ const stat = statSync(fullPath);
406
+
407
+ if (stat.isFile() && /\.(md|json|txt)$/i.test(entry)) {
408
+ exs.push({
409
+ name: entry.replace(/\.(md|json|txt)$/i, ""),
410
+ path: fullPath,
411
+ relativePath: relative(baseDir, fullPath),
412
+ });
413
+ }
414
+ }
415
+ }
416
+
417
+ scanDir(skillDir, skillDir);
418
+ } catch {
419
+ // Silently ignore fs errors during directory scan
420
+ }
421
+
422
+ return { references, examples };
423
+ }
424
+
425
+ /**
426
+ * Serialize skill back to SKILL.md format
427
+ */
428
+ export function serializeSkill(skill: Skill): string {
429
+ const lines: string[] = ["---"];
430
+
431
+ // Add frontmatter fields
432
+ if (skill.frontmatter.name) {
433
+ lines.push(`name: ${skill.frontmatter.name}`);
434
+ }
435
+ if (skill.frontmatter.description) {
436
+ lines.push(`description: ${skill.frontmatter.description}`);
437
+ }
438
+ if (skill.frontmatter.version) {
439
+ lines.push(`version: ${skill.frontmatter.version}`);
440
+ }
441
+ if (skill.frontmatter.license) {
442
+ lines.push(`license: ${skill.frontmatter.license}`);
443
+ }
444
+ if (skill.frontmatter.compatibility) {
445
+ lines.push(`compatibility: ${skill.frontmatter.compatibility}`);
446
+ }
447
+ if (skill.frontmatter.disable_model_invocation) {
448
+ lines.push(`disable_model_invocation: true`);
449
+ }
450
+ if (skill.frontmatter.allowed_tools?.length) {
451
+ lines.push(`allowed_tools:`);
452
+ for (const tool of skill.frontmatter.allowed_tools) {
453
+ lines.push(` - ${tool}`);
454
+ }
455
+ }
456
+
457
+ // Add Hermes extensions
458
+ if (skill.hermes?.author) {
459
+ lines.push(`author: ${skill.hermes.author}`);
460
+ }
461
+ if (skill.hermes?.confidence !== undefined) {
462
+ lines.push(`confidence: ${skill.hermes.confidence}`);
463
+ }
464
+ if (skill.hermes?.usage_count !== undefined) {
465
+ lines.push(`usage_count: ${skill.hermes.usage_count}`);
466
+ }
467
+ if (skill.hermes?.triggers?.length) {
468
+ lines.push(`triggers:`);
469
+ for (const trigger of skill.hermes.triggers) {
470
+ lines.push(` - "${trigger}"`);
471
+ }
472
+ }
473
+ if (skill.hermes?.pitfalls?.length) {
474
+ lines.push(`pitfalls:`);
475
+ for (const pitfall of skill.hermes.pitfalls) {
476
+ lines.push(` - name: ${pitfall.name}`);
477
+ if (pitfall.why) {
478
+ lines.push(` why: ${pitfall.why}`);
479
+ }
480
+ if (pitfall.affects) {
481
+ lines.push(` affects: ${pitfall.affects}`);
482
+ }
483
+ }
484
+ }
485
+ if (skill.hermes?.anti_patterns?.length) {
486
+ lines.push(`anti_patterns:`);
487
+ for (const anti of skill.hermes.anti_patterns) {
488
+ lines.push(` - "${anti}"`);
489
+ }
490
+ }
491
+ if (skill.hermes?.auto_update !== undefined) {
492
+ lines.push(`auto_update: ${skill.hermes.auto_update}`);
493
+ }
494
+
495
+ // Add timestamps
496
+ const now = new Date().toISOString();
497
+ if (!skill.hermes?.created_at) {
498
+ lines.push(`created_at: ${now}`);
499
+ }
500
+ lines.push(`updated_at: ${now}`);
501
+
502
+ // Add metadata if present
503
+ if (skill.frontmatter.metadata && Object.keys(skill.frontmatter.metadata).length > 0) {
504
+ lines.push(`metadata:`);
505
+ for (const [key, value] of Object.entries(skill.frontmatter.metadata)) {
506
+ lines.push(` ${key}: ${JSON.stringify(value)}`);
507
+ }
508
+ }
509
+
510
+ lines.push("---");
511
+ lines.push("");
512
+ lines.push(skill.body);
513
+
514
+ return lines.join("\n");
515
+ }
516
+
517
+ /**
518
+ * Generate SKILL.md from trajectory or partial skill
519
+ */
520
+ export function generateSkillFromTemplate(
521
+ template: Partial<Skill>,
522
+ overrides?: Partial<SkillFrontmatter>
523
+ ): string {
524
+ const skill: Skill = {
525
+ id: template.id || "new-skill",
526
+ path: template.path || "",
527
+ frontmatter: {
528
+ name: overrides?.name || template.frontmatter?.name || "new-skill",
529
+ description:
530
+ overrides?.description ||
531
+ template.frontmatter?.description ||
532
+ "Auto-generated skill",
533
+ version: overrides?.version || template.frontmatter?.version || "1.0.0",
534
+ ...overrides,
535
+ },
536
+ hermes: template.hermes || {},
537
+ body: template.body || "",
538
+ references: [],
539
+ examples: [],
540
+ metadata: {},
541
+ loaded: false,
542
+ };
543
+
544
+ return serializeSkill(skill);
545
+ }
546
+
547
+ // Re-export types for convenience
548
+ export type { SkillPitfall } from "./types.js";