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.
- package/CHANGELOG.md +33 -0
- package/harness/loop-runtime.ts +3 -0
- package/index.ts +130 -29
- package/package.json +2 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/dist/client.d.ts +51 -0
- package/packages/qdrant-skills/dist/client.d.ts.map +1 -0
- package/packages/qdrant-skills/dist/client.js +105 -0
- package/packages/qdrant-skills/dist/client.js.map +1 -0
- package/packages/qdrant-skills/dist/embedder.d.ts +39 -0
- package/packages/qdrant-skills/dist/embedder.d.ts.map +1 -0
- package/packages/qdrant-skills/dist/embedder.js +101 -0
- package/packages/qdrant-skills/dist/embedder.js.map +1 -0
- package/packages/qdrant-skills/dist/index.d.ts +25 -0
- package/packages/qdrant-skills/dist/index.d.ts.map +1 -0
- package/packages/qdrant-skills/dist/index.js +25 -0
- package/packages/qdrant-skills/dist/index.js.map +1 -0
- package/packages/qdrant-skills/package.json +27 -0
- package/packages/qdrant-skills/src/client.ts +178 -0
- package/packages/qdrant-skills/src/embedder.ts +137 -0
- package/packages/qdrant-skills/src/index.ts +42 -0
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/dist/index.d.ts +40 -0
- package/packages/skills/dist/index.d.ts.map +1 -0
- package/packages/skills/dist/index.js +84 -0
- package/packages/skills/dist/index.js.map +1 -0
- package/packages/skills/dist/linter.d.ts +38 -0
- package/packages/skills/dist/linter.d.ts.map +1 -0
- package/packages/skills/dist/linter.js +323 -0
- package/packages/skills/dist/linter.js.map +1 -0
- package/packages/skills/dist/loader.d.ts +74 -0
- package/packages/skills/dist/loader.d.ts.map +1 -0
- package/packages/skills/dist/loader.js +280 -0
- package/packages/skills/dist/loader.js.map +1 -0
- package/packages/skills/dist/parser.d.ts +32 -0
- package/packages/skills/dist/parser.d.ts.map +1 -0
- package/packages/skills/dist/parser.js +459 -0
- package/packages/skills/dist/parser.js.map +1 -0
- package/packages/skills/dist/scanner.d.ts +33 -0
- package/packages/skills/dist/scanner.d.ts.map +1 -0
- package/packages/skills/dist/scanner.js +257 -0
- package/packages/skills/dist/scanner.js.map +1 -0
- package/packages/skills/dist/skill-commands.d.ts +33 -0
- package/packages/skills/dist/skill-commands.d.ts.map +1 -0
- package/packages/skills/dist/skill-commands.js +239 -0
- package/packages/skills/dist/skill-commands.js.map +1 -0
- package/packages/skills/dist/skill-tool.d.ts +120 -0
- package/packages/skills/dist/skill-tool.d.ts.map +1 -0
- package/packages/skills/dist/skill-tool.js +239 -0
- package/packages/skills/dist/skill-tool.js.map +1 -0
- package/packages/skills/dist/skill-versioning.d.ts +123 -0
- package/packages/skills/dist/skill-versioning.d.ts.map +1 -0
- package/packages/skills/dist/skill-versioning.js +240 -0
- package/packages/skills/dist/skill-versioning.js.map +1 -0
- package/packages/skills/dist/types.d.ts +226 -0
- package/packages/skills/dist/types.d.ts.map +1 -0
- package/packages/skills/dist/types.js +21 -0
- package/packages/skills/dist/types.js.map +1 -0
- package/packages/skills/package.json +46 -0
- package/packages/skills/src/index.ts +95 -0
- package/packages/skills/src/linter.ts +371 -0
- package/packages/skills/src/loader.ts +335 -0
- package/packages/skills/src/parser.ts +548 -0
- package/packages/skills/src/scanner.ts +322 -0
- package/packages/skills/src/skill-commands.ts +318 -0
- package/packages/skills/src/skill-tool.ts +324 -0
- package/packages/skills/src/skill-versioning.ts +345 -0
- package/packages/skills/src/types.ts +259 -0
- package/packages/skills/test/init-skills.test.ts +118 -0
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/dist/src/curator.d.ts +189 -0
- package/packages/trajectory/dist/src/curator.d.ts.map +1 -0
- package/packages/trajectory/dist/src/curator.js +340 -0
- package/packages/trajectory/dist/src/curator.js.map +1 -0
- package/packages/trajectory/dist/src/journey-renderer.d.ts +53 -0
- package/packages/trajectory/dist/src/journey-renderer.d.ts.map +1 -0
- package/packages/trajectory/dist/src/journey-renderer.js +324 -0
- package/packages/trajectory/dist/src/journey-renderer.js.map +1 -0
- package/packages/trajectory/dist/src/learning-graph.d.ts +159 -0
- package/packages/trajectory/dist/src/learning-graph.d.ts.map +1 -0
- package/packages/trajectory/dist/src/learning-graph.js +258 -0
- package/packages/trajectory/dist/src/learning-graph.js.map +1 -0
- package/packages/trajectory/dist/src/memory-store.d.ts +146 -0
- package/packages/trajectory/dist/src/memory-store.d.ts.map +1 -0
- package/packages/trajectory/dist/src/memory-store.js +472 -0
- package/packages/trajectory/dist/src/memory-store.js.map +1 -0
- package/packages/trajectory/dist/src/review-fork.d.ts +164 -0
- package/packages/trajectory/dist/src/review-fork.d.ts.map +1 -0
- package/packages/trajectory/dist/src/review-fork.js +332 -0
- package/packages/trajectory/dist/src/review-fork.js.map +1 -0
- package/packages/trajectory/dist/src/write-approval.d.ts +136 -0
- package/packages/trajectory/dist/src/write-approval.d.ts.map +1 -0
- package/packages/trajectory/dist/src/write-approval.js +281 -0
- package/packages/trajectory/dist/src/write-approval.js.map +1 -0
- package/packages/trajectory/package.json +1 -1
- package/packages/trajectory/src/curator.ts +485 -0
- package/packages/trajectory/src/journey-renderer.ts +399 -0
- package/packages/trajectory/src/learning-graph.ts +363 -0
- package/packages/trajectory/src/memory-store.ts +578 -0
- package/packages/trajectory/src/review-fork.ts +450 -0
- package/packages/trajectory/src/write-approval.ts +372 -0
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/dist/blackboard.d.ts +98 -0
- package/packages/write-review/dist/blackboard.d.ts.map +1 -0
- package/packages/write-review/dist/blackboard.js +289 -0
- package/packages/write-review/dist/blackboard.js.map +1 -0
- package/packages/write-review/dist/event-bus.d.ts +106 -0
- package/packages/write-review/dist/event-bus.d.ts.map +1 -0
- package/packages/write-review/dist/event-bus.js +294 -0
- package/packages/write-review/dist/event-bus.js.map +1 -0
- package/packages/write-review/dist/gate.d.ts +37 -0
- package/packages/write-review/dist/gate.d.ts.map +1 -0
- package/packages/write-review/dist/gate.js +138 -0
- package/packages/write-review/dist/gate.js.map +1 -0
- package/packages/write-review/dist/index.d.ts +22 -0
- package/packages/write-review/dist/index.d.ts.map +1 -0
- package/packages/write-review/dist/index.js +60 -0
- package/packages/write-review/dist/index.js.map +1 -0
- package/packages/write-review/dist/injection.d.ts +40 -0
- package/packages/write-review/dist/injection.d.ts.map +1 -0
- package/packages/write-review/dist/injection.js +179 -0
- package/packages/write-review/dist/injection.js.map +1 -0
- package/packages/write-review/dist/review.d.ts +54 -0
- package/packages/write-review/dist/review.d.ts.map +1 -0
- package/packages/write-review/dist/review.js +154 -0
- package/packages/write-review/dist/review.js.map +1 -0
- package/packages/write-review/dist/trigger.d.ts +39 -0
- package/packages/write-review/dist/trigger.d.ts.map +1 -0
- package/packages/write-review/dist/trigger.js +107 -0
- package/packages/write-review/dist/trigger.js.map +1 -0
- package/packages/write-review/dist/types.d.ts +78 -0
- package/packages/write-review/dist/types.d.ts.map +1 -0
- package/packages/write-review/dist/types.js +26 -0
- package/packages/write-review/dist/types.js.map +1 -0
- package/packages/write-review/package.json +1 -1
- package/packages/write-review/src/event-bus.ts +351 -0
- package/packages/write-review/src/index.ts +1 -0
- package/packages/write-review/src/injection.ts +25 -3
- package/proactive-compact.ts +51 -7
- package/scripts/sync-skills.ts +275 -0
- 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
|
+
}
|