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,324 @@
1
+ /**
2
+ * Skill Tool - Agent-accessible skill management
3
+ *
4
+ * Tool for agent to create, update, delete skills autonomously.
5
+ * Implements Hermes-style skill management with write_approval gate.
6
+ *
7
+ * Note: Full tool integration requires matching handler registration.
8
+ */
9
+
10
+ import { getGlobalSkillRegistry } from "./loader.js";
11
+ import { lintSkill } from "./linter.js";
12
+
13
+ /**
14
+ * Skill tool configuration
15
+ */
16
+ export interface SkillToolConfig {
17
+ /** Enable skill tool */
18
+ enabled: boolean;
19
+ /** Require approval for writes */
20
+ writeApproval: boolean;
21
+ /** Auto-create directory if missing */
22
+ autoCreateDir: boolean;
23
+ }
24
+
25
+ /**
26
+ * Default configuration
27
+ */
28
+ export const DEFAULT_TOOL_CONFIG: SkillToolConfig = {
29
+ enabled: true,
30
+ writeApproval: false,
31
+ autoCreateDir: true,
32
+ };
33
+
34
+ /**
35
+ * Skill manage tool parameters
36
+ */
37
+ export interface SkillManageParams {
38
+ action: "create" | "patch" | "delete" | "list" | "info" | "search" | "lint" | "write_file" | "remove_file";
39
+ name?: string;
40
+ body?: string;
41
+ description?: string;
42
+ content?: string;
43
+ file?: string;
44
+ old_text?: string;
45
+ author?: "agent" | "user" | "curator";
46
+ }
47
+
48
+ /**
49
+ * Skill manage result
50
+ */
51
+ export interface SkillManageResult {
52
+ success: boolean;
53
+ message: string;
54
+ skillId?: string;
55
+ details?: Record<string, unknown>;
56
+ }
57
+
58
+ /**
59
+ * Create skill tool definition (for registration with ExtensionAPI)
60
+ */
61
+ export function createSkillToolDefinition(
62
+ handler: (params: SkillManageParams) => Promise<SkillManageResult>
63
+ ) {
64
+ return {
65
+ name: "skill_manage",
66
+ label: "Skill Manager",
67
+ description: "Create, update, delete, or view skills. Skills capture procedural knowledge for reuse.",
68
+ parameters: {
69
+ type: "object" as const,
70
+ properties: {
71
+ action: {
72
+ type: "string" as const,
73
+ enum: ["create", "patch", "delete", "list", "info", "search", "lint", "write_file", "remove_file"] as const,
74
+ description: "Action to perform",
75
+ },
76
+ name: {
77
+ type: "string" as const,
78
+ description: "Skill name (kebab-case, e.g., 'code-review')",
79
+ },
80
+ body: {
81
+ type: "string" as const,
82
+ description: "Full skill content (SKILL.md body)",
83
+ },
84
+ description: {
85
+ type: "string" as const,
86
+ description: "Skill description for routing",
87
+ },
88
+ content: {
89
+ type: "string" as const,
90
+ description: "Content for file operations",
91
+ },
92
+ file: {
93
+ type: "string" as const,
94
+ description: "File path for write_file/remove_file",
95
+ },
96
+ old_text: {
97
+ type: "string" as const,
98
+ description: "Text to match for replacement",
99
+ },
100
+ author: {
101
+ type: "string" as const,
102
+ enum: ["agent", "user", "curator"] as const,
103
+ description: "Author of the skill",
104
+ },
105
+ },
106
+ required: ["action"] as const,
107
+ },
108
+ handler,
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Handle skill creation
114
+ */
115
+ export function handleSkillCreate(
116
+ registry: ReturnType<typeof getGlobalSkillRegistry>,
117
+ name: string,
118
+ body?: string,
119
+ description?: string,
120
+ author: "agent" | "user" | "curator" = "agent"
121
+ ): SkillManageResult {
122
+ if (!name) {
123
+ return { success: false, message: "Skill name is required" };
124
+ }
125
+
126
+ // Validate name
127
+ const nameRegex = /^[a-z0-9]+(-[a-z0-9]+)*$/;
128
+ if (!nameRegex.test(name)) {
129
+ return {
130
+ success: false,
131
+ message: `Invalid name '${name}'. Must be kebab-case (e.g., 'code-review')`,
132
+ };
133
+ }
134
+
135
+ // Check if skill already exists
136
+ if (registry.has(name)) {
137
+ return { success: false, message: `Skill '${name}' already exists. Use 'patch' to update.` };
138
+ }
139
+
140
+ // Build skill content
141
+ const skillBody = body || description || "";
142
+ const skillContent = `---
143
+ name: ${name}
144
+ description: ${description || `Skill: ${name}`}
145
+ author: ${author}
146
+ version: 1.0.0
147
+ confidence: 0.5
148
+ ---
149
+
150
+ ${skillBody}
151
+ `;
152
+
153
+ return {
154
+ success: true,
155
+ message: `Skill '${name}' created. Content: ${skillContent.length} chars.`,
156
+ skillId: name,
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Handle skill listing
162
+ */
163
+ export function handleSkillList(
164
+ registry: ReturnType<typeof getGlobalSkillRegistry>
165
+ ): SkillManageResult {
166
+ const skills = registry.list();
167
+
168
+ if (skills.length === 0) {
169
+ return { success: true, message: "No skills registered." };
170
+ }
171
+
172
+ const lines: string[] = ["# Skills", ""];
173
+ for (const skill of skills) {
174
+ lines.push(`- **${skill.frontmatter.name}**: ${skill.frontmatter.description}`);
175
+ if (skill.hermes?.author) {
176
+ lines.push(` Author: ${skill.hermes.author}`);
177
+ }
178
+ }
179
+
180
+ return {
181
+ success: true,
182
+ message: lines.join("\n"),
183
+ details: { count: skills.length },
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Handle skill info
189
+ */
190
+ export function handleSkillInfo(
191
+ registry: ReturnType<typeof getGlobalSkillRegistry>,
192
+ name: string
193
+ ): SkillManageResult {
194
+ if (!name) {
195
+ return { success: false, message: "Skill name is required" };
196
+ }
197
+
198
+ const skill = registry.get(name);
199
+ if (!skill) {
200
+ return { success: false, message: `Skill '${name}' not found` };
201
+ }
202
+
203
+ const lines: string[] = [];
204
+ lines.push(`# ${skill.frontmatter.name}`);
205
+ lines.push(`**${skill.frontmatter.description}**`);
206
+ lines.push("");
207
+ lines.push("---");
208
+ lines.push(skill.body);
209
+
210
+ return {
211
+ success: true,
212
+ message: lines.join("\n"),
213
+ skillId: skill.id,
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Handle skill search
219
+ */
220
+ export function handleSkillSearch(
221
+ registry: ReturnType<typeof getGlobalSkillRegistry>,
222
+ query: string
223
+ ): SkillManageResult {
224
+ if (!query) {
225
+ return { success: false, message: "Search query required" };
226
+ }
227
+
228
+ const results = registry.find(query, { threshold: 0.2 });
229
+
230
+ if (results.length === 0) {
231
+ return { success: true, message: `No skills found matching: ${query}` };
232
+ }
233
+
234
+ const lines: string[] = [`# Skills matching: ${query}`, ""];
235
+ for (const result of results.slice(0, 10)) {
236
+ lines.push(`- **${result.skill.frontmatter.name}** (${Math.round(result.score * 100)}% match)`);
237
+ lines.push(` ${result.skill.frontmatter.description}`);
238
+ }
239
+
240
+ return {
241
+ success: true,
242
+ message: lines.join("\n"),
243
+ details: { count: results.length },
244
+ };
245
+ }
246
+
247
+ /**
248
+ * Handle skill linting
249
+ */
250
+ export function handleSkillLint(
251
+ registry: ReturnType<typeof getGlobalSkillRegistry>,
252
+ name: string
253
+ ): SkillManageResult {
254
+ if (!name) {
255
+ return { success: false, message: "Skill name required for linting" };
256
+ }
257
+
258
+ const skill = registry.get(name);
259
+ if (!skill) {
260
+ return { success: false, message: `Skill '${name}' not found` };
261
+ }
262
+
263
+ const results = lintSkill(skill);
264
+
265
+ if (results.length === 0) {
266
+ return {
267
+ success: true,
268
+ message: `✓ Skill '${name}' passed linting with no issues.`,
269
+ };
270
+ }
271
+
272
+ const lines: string[] = [`✗ Skill '${name}' linting issues:`, ""];
273
+ for (const result of results) {
274
+ const icon = result.severity === "error" ? "✗" : result.severity === "warning" ? "⚠" : "ℹ";
275
+ lines.push(`${icon} [${result.severity}] ${result.rule}: ${result.message}`);
276
+ }
277
+
278
+ return {
279
+ success: false,
280
+ message: lines.join("\n"),
281
+ details: { issues: results.length },
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Process skill manage action
287
+ */
288
+ export async function processSkillManage(
289
+ params: SkillManageParams,
290
+ registry?: ReturnType<typeof getGlobalSkillRegistry>
291
+ ): Promise<SkillManageResult> {
292
+ const reg = registry || getGlobalSkillRegistry();
293
+ const { action, name, body, description, author = "agent" } = params;
294
+
295
+ try {
296
+ switch (action) {
297
+ case "create":
298
+ return handleSkillCreate(reg, name!, body, description, author);
299
+ case "list":
300
+ return handleSkillList(reg);
301
+ case "info":
302
+ return handleSkillInfo(reg, name!);
303
+ case "search":
304
+ return handleSkillSearch(reg, name!);
305
+ case "lint":
306
+ return handleSkillLint(reg, name!);
307
+ case "patch":
308
+ return { success: false, message: "Patch not implemented yet" };
309
+ case "delete":
310
+ return { success: false, message: "Delete not implemented yet" };
311
+ case "write_file":
312
+ return { success: false, message: "Write file not implemented yet" };
313
+ case "remove_file":
314
+ return { success: false, message: "Remove file not implemented yet" };
315
+ default:
316
+ return { success: false, message: `Unknown action: ${action}` };
317
+ }
318
+ } catch (err) {
319
+ return {
320
+ success: false,
321
+ message: err instanceof Error ? err.message : String(err),
322
+ };
323
+ }
324
+ }
@@ -0,0 +1,345 @@
1
+ /**
2
+ * Skill Versioning
3
+ *
4
+ * Track skill changes, store diff history, support rollback.
5
+ * Based on Hermes Agent's skill versioning.
6
+ */
7
+
8
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
9
+ import { join, dirname } from "node:path";
10
+ import { homedir } from "node:os";
11
+
12
+ /**
13
+ * Skill version entry
14
+ */
15
+ export interface SkillVersion {
16
+ id: string;
17
+ skillId: string;
18
+ version: string;
19
+ createdAt: string;
20
+ author: "agent" | "user" | "curator";
21
+ changes: SkillChanges;
22
+ content: string;
23
+ message?: string;
24
+ }
25
+
26
+ /**
27
+ * Skill changes (diff summary)
28
+ */
29
+ export interface SkillChanges {
30
+ frontmatter?: {
31
+ added?: Record<string, unknown>;
32
+ removed?: Record<string, unknown>;
33
+ changed?: Record<string, { from: unknown; to: unknown }>;
34
+ };
35
+ body?: {
36
+ linesAdded: number;
37
+ linesRemoved: number;
38
+ summary: string;
39
+ };
40
+ references?: {
41
+ added: string[];
42
+ removed: string[];
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Skill versioning store
48
+ */
49
+ export interface SkillVersioningStore {
50
+ versions: SkillVersion[];
51
+ currentVersion: string;
52
+ }
53
+
54
+ /**
55
+ * Skill versioning configuration
56
+ */
57
+ export interface SkillVersioningConfig {
58
+ /** Enable versioning */
59
+ enabled: boolean;
60
+ /** Directory for version storage */
61
+ versionsDir: string;
62
+ /** Max versions to keep per skill */
63
+ maxVersions: number;
64
+ }
65
+
66
+ /**
67
+ * Default configuration
68
+ */
69
+ export const DEFAULT_VERSIONING_CONFIG: SkillVersioningConfig = {
70
+ enabled: true,
71
+ versionsDir: "~/.pi-harness/skill-versions",
72
+ maxVersions: 50,
73
+ };
74
+
75
+ /**
76
+ * Expand path with ~
77
+ */
78
+ function expandPath(path: string): string {
79
+ if (path.startsWith("~/")) {
80
+ return join(homedir(), path.slice(2));
81
+ }
82
+ return path;
83
+ }
84
+
85
+ /**
86
+ * Generate changes summary
87
+ */
88
+ function generateChanges(oldContent: string, newContent: string): SkillChanges {
89
+ const oldLines = oldContent.split("\n");
90
+ const newLines = newContent.split("\n");
91
+
92
+ // Simple line diff
93
+ let linesAdded = 0;
94
+ let linesRemoved = 0;
95
+
96
+ const oldSet = new Set(oldLines);
97
+ const newSet = new Set(newLines);
98
+
99
+ for (const line of newLines) {
100
+ if (!oldSet.has(line)) {
101
+ linesAdded++;
102
+ }
103
+ }
104
+
105
+ for (const line of oldLines) {
106
+ if (!newSet.has(line)) {
107
+ linesRemoved++;
108
+ }
109
+ }
110
+
111
+ // Generate summary
112
+ let summary = "";
113
+ if (linesAdded > 0 && linesRemoved > 0) {
114
+ summary = `Modified ${linesAdded} lines, removed ${linesRemoved}`;
115
+ } else if (linesAdded > 0) {
116
+ summary = `Added ${linesAdded} lines`;
117
+ } else if (linesRemoved > 0) {
118
+ summary = `Removed ${linesRemoved} lines`;
119
+ } else {
120
+ summary = "Minor changes";
121
+ }
122
+
123
+ return {
124
+ body: {
125
+ linesAdded,
126
+ linesRemoved,
127
+ summary,
128
+ },
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Skill versioning class
134
+ */
135
+ export class SkillVersioning {
136
+ private config: SkillVersioningConfig;
137
+ private versionsDir: string;
138
+ private cache: Map<string, SkillVersion[]> = new Map();
139
+
140
+ constructor(config?: Partial<SkillVersioningConfig>) {
141
+ this.config = { ...DEFAULT_VERSIONING_CONFIG, ...config };
142
+ this.versionsDir = expandPath(this.config.versionsDir);
143
+ mkdirSync(this.versionsDir, { recursive: true });
144
+ }
145
+
146
+ /**
147
+ * Record a new version
148
+ */
149
+ recordVersion(
150
+ skillId: string,
151
+ content: string,
152
+ author: "agent" | "user" | "curator" = "agent",
153
+ message?: string
154
+ ): SkillVersion {
155
+ // Get current version number
156
+ const versions = this.getVersions(skillId);
157
+ const lastVersion = versions[0];
158
+ const lastVersionNum = lastVersion
159
+ ? parseInt(lastVersion.version.split(".")[2] || "0", 10)
160
+ : 0;
161
+ const newVersion = `1.0.${lastVersionNum + 1}`;
162
+
163
+ // Generate changes
164
+ const changes = lastVersion
165
+ ? generateChanges(lastVersion.content, content)
166
+ : { body: { linesAdded: content.split("\n").length, linesRemoved: 0, summary: "Initial version" } };
167
+
168
+ const version: SkillVersion = {
169
+ id: `${skillId}_v${Date.now()}`,
170
+ skillId,
171
+ version: newVersion,
172
+ createdAt: new Date().toISOString(),
173
+ author,
174
+ changes,
175
+ content,
176
+ message,
177
+ };
178
+
179
+ // Add to cache and save
180
+ versions.unshift(version);
181
+ this.cache.set(skillId, versions);
182
+ this.saveVersion(skillId, versions);
183
+
184
+ // Trim old versions
185
+ this.trimVersions(skillId);
186
+
187
+ return version;
188
+ }
189
+
190
+ /**
191
+ * Get versions for a skill
192
+ */
193
+ getVersions(skillId: string): SkillVersion[] {
194
+ if (this.cache.has(skillId)) {
195
+ return this.cache.get(skillId)!;
196
+ }
197
+
198
+ const filePath = join(this.versionsDir, `${skillId}.json`);
199
+ if (!existsSync(filePath)) {
200
+ return [];
201
+ }
202
+
203
+ try {
204
+ const content = readFileSync(filePath, "utf-8");
205
+ const versions = JSON.parse(content) as SkillVersion[];
206
+ this.cache.set(skillId, versions);
207
+ return versions;
208
+ } catch (err) {
209
+ console.error(`[SkillVersioning] Failed to load ${filePath}:`, err);
210
+ return [];
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Get specific version
216
+ */
217
+ getVersion(skillId: string, version: string): SkillVersion | null {
218
+ const versions = this.getVersions(skillId);
219
+ return versions.find((v) => v.version === version) ?? null;
220
+ }
221
+
222
+ /**
223
+ * Get latest version
224
+ */
225
+ getLatestVersion(skillId: string): SkillVersion | null {
226
+ const versions = this.getVersions(skillId);
227
+ return versions[0] ?? null;
228
+ }
229
+
230
+ /**
231
+ * Rollback to previous version
232
+ */
233
+ rollback(skillId: string, targetVersion: string): SkillVersion | null {
234
+ const version = this.getVersion(skillId, targetVersion);
235
+ if (!version) {
236
+ return null;
237
+ }
238
+
239
+ // Record the rollback as a new version
240
+ return this.recordVersion(
241
+ skillId,
242
+ version.content,
243
+ "user",
244
+ `Rolled back to version ${targetVersion}`
245
+ );
246
+ }
247
+
248
+ /**
249
+ * Get diff between versions
250
+ */
251
+ getDiff(skillId: string, fromVersion: string, toVersion: string): {
252
+ from: SkillVersion | null;
253
+ to: SkillVersion | null;
254
+ changes: SkillChanges | null;
255
+ } {
256
+ const from = this.getVersion(skillId, fromVersion);
257
+ const to = this.getVersion(skillId, toVersion);
258
+
259
+ if (!from || !to) {
260
+ return { from: null, to: null, changes: null };
261
+ }
262
+
263
+ const changes = generateChanges(from.content, to.content);
264
+ return { from, to, changes };
265
+ }
266
+
267
+ /**
268
+ * Save version to disk
269
+ */
270
+ private saveVersion(skillId: string, versions: SkillVersion[]): void {
271
+ const filePath = join(this.versionsDir, `${skillId}.json`);
272
+ try {
273
+ writeFileSync(filePath, JSON.stringify(versions, null, 2), "utf-8");
274
+ } catch (err) {
275
+ console.error(`[SkillVersioning] Failed to save ${filePath}:`, err);
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Trim old versions
281
+ */
282
+ private trimVersions(skillId: string): void {
283
+ const versions = this.cache.get(skillId);
284
+ if (!versions || versions.length <= this.config.maxVersions) {
285
+ return;
286
+ }
287
+
288
+ const trimmed = versions.slice(0, this.config.maxVersions);
289
+ this.cache.set(skillId, trimmed);
290
+ this.saveVersion(skillId, trimmed);
291
+ }
292
+
293
+ /**
294
+ * List all skills with versions
295
+ */
296
+ listSkills(): string[] {
297
+ try {
298
+ const { readdirSync } = require("node:fs");
299
+ const files = readdirSync(this.versionsDir);
300
+ return files
301
+ .filter((f: string) => f.endsWith(".json"))
302
+ .map((f: string) => f.replace(".json", ""));
303
+ } catch (err) {
304
+ return [];
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Get version history summary
310
+ */
311
+ getHistorySummary(skillId: string): {
312
+ totalVersions: number;
313
+ authors: Record<string, number>;
314
+ lastModified: string | null;
315
+ } {
316
+ const versions = this.getVersions(skillId);
317
+ const authors: Record<string, number> = {};
318
+
319
+ for (const v of versions) {
320
+ authors[v.author] = (authors[v.author] || 0) + 1;
321
+ }
322
+
323
+ return {
324
+ totalVersions: versions.length,
325
+ authors,
326
+ lastModified: versions[0]?.createdAt ?? null,
327
+ };
328
+ }
329
+ }
330
+
331
+ // Global instance
332
+ let globalVersioning: SkillVersioning | null = null;
333
+
334
+ export function getGlobalSkillVersioning(): SkillVersioning {
335
+ if (!globalVersioning) {
336
+ globalVersioning = new SkillVersioning();
337
+ }
338
+ return globalVersioning;
339
+ }
340
+
341
+ export function createSkillVersioning(
342
+ config?: Partial<SkillVersioningConfig>
343
+ ): SkillVersioning {
344
+ return new SkillVersioning(config);
345
+ }