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,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";
|