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,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills Sync Script
|
|
3
|
+
*
|
|
4
|
+
* Sync skills from a source directory to ~/.pi-harness-runtime/skills/
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* bun scripts/sync-skills.ts --from ~/my-skills --to ~/.pi-harness-runtime/skills
|
|
8
|
+
* bun scripts/sync-skills.ts --from ~/frappe-bench/.claude-plugins/moocoding-skills/skills
|
|
9
|
+
* bun scripts/sync-skills.ts --list # List skills in target dir
|
|
10
|
+
* bun scripts/sync-skills.ts --check-only # Check without copying
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, mkdirSync, readdirSync, statSync, copyFileSync, rmSync } from "node:fs";
|
|
14
|
+
import { join, basename, dirname } from "node:path";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
|
|
17
|
+
// Default paths
|
|
18
|
+
const DEFAULT_TARGET = join(homedir(), ".pi-harness-runtime", "skills");
|
|
19
|
+
|
|
20
|
+
interface SyncOptions {
|
|
21
|
+
from: string;
|
|
22
|
+
to: string;
|
|
23
|
+
checkOnly: boolean;
|
|
24
|
+
verbose: boolean;
|
|
25
|
+
clean: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse command line arguments
|
|
30
|
+
*/
|
|
31
|
+
function parseArgs(): SyncOptions {
|
|
32
|
+
const args = process.argv.slice(2);
|
|
33
|
+
let from: string | undefined;
|
|
34
|
+
let to = DEFAULT_TARGET;
|
|
35
|
+
let checkOnly = false;
|
|
36
|
+
let verbose = false;
|
|
37
|
+
let clean = false;
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < args.length; i++) {
|
|
40
|
+
const arg = args[i];
|
|
41
|
+
if (arg === "--from" || arg === "-f") {
|
|
42
|
+
from = args[++i];
|
|
43
|
+
} else if (arg === "--to" || arg === "-t") {
|
|
44
|
+
to = args[++i];
|
|
45
|
+
} else if (arg === "--check-only" || arg === "-c") {
|
|
46
|
+
checkOnly = true;
|
|
47
|
+
} else if (arg === "--verbose" || arg === "-v") {
|
|
48
|
+
verbose = true;
|
|
49
|
+
} else if (arg === "--clean") {
|
|
50
|
+
clean = true;
|
|
51
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
52
|
+
printHelp();
|
|
53
|
+
process.exit(0);
|
|
54
|
+
} else if (arg === "--list") {
|
|
55
|
+
listSkills(to);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!from) {
|
|
61
|
+
console.error("Error: --from <source> is required");
|
|
62
|
+
printHelp();
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { from, to, checkOnly, verbose, clean };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Print help message
|
|
71
|
+
*/
|
|
72
|
+
function printHelp(): void {
|
|
73
|
+
console.log(`
|
|
74
|
+
Skills Sync Script
|
|
75
|
+
─────────────────
|
|
76
|
+
Sync skills from a source directory to ~/.pi-harness-runtime/skills/
|
|
77
|
+
|
|
78
|
+
Usage:
|
|
79
|
+
bun scripts/sync-skills.ts --from <source> [options]
|
|
80
|
+
|
|
81
|
+
Options:
|
|
82
|
+
--from, -f <path> Source directory containing skills (required)
|
|
83
|
+
--to, -t <path> Target directory (default: ~/.pi-harness-runtime/skills)
|
|
84
|
+
--list List skills in target directory
|
|
85
|
+
--check-only, -c Check what would be copied without copying
|
|
86
|
+
--verbose, -v Show detailed output
|
|
87
|
+
--clean Remove skills not in source
|
|
88
|
+
--help, -h Show this help message
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
# Sync from a local directory
|
|
92
|
+
bun scripts/sync-skills.ts --from ~/my-skills
|
|
93
|
+
|
|
94
|
+
# Sync from frappe-bench moocoding skills
|
|
95
|
+
bun scripts/sync-skills.ts --from ~/frappe-bench/.claude-plugins/moocoding-skills/skills
|
|
96
|
+
|
|
97
|
+
# Check what would be synced
|
|
98
|
+
bun scripts/sync-skills.ts --from ~/my-skills --check-only
|
|
99
|
+
|
|
100
|
+
# List current skills
|
|
101
|
+
bun scripts/sync-skills.ts --list
|
|
102
|
+
`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* List skills in a directory
|
|
107
|
+
*/
|
|
108
|
+
function listSkills(dir: string): void {
|
|
109
|
+
if (!existsSync(dir)) {
|
|
110
|
+
console.log(`Directory does not exist: ${dir}`);
|
|
111
|
+
console.log(`Run without --list to create it.`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const skills = getSkillDirs(dir);
|
|
116
|
+
if (skills.length === 0) {
|
|
117
|
+
console.log(`No skills found in ${dir}`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log(`Skills in ${dir}:`);
|
|
122
|
+
console.log("");
|
|
123
|
+
for (const skill of skills) {
|
|
124
|
+
console.log(` - ${basename(skill)}`);
|
|
125
|
+
}
|
|
126
|
+
console.log("");
|
|
127
|
+
console.log(`Total: ${skills.length} skills`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get skill directories (directories containing SKILL.md)
|
|
132
|
+
*/
|
|
133
|
+
function getSkillDirs(dir: string): string[] {
|
|
134
|
+
if (!existsSync(dir)) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const skills: string[] = [];
|
|
139
|
+
const entries = readdirSync(dir);
|
|
140
|
+
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
const fullPath = join(dir, entry);
|
|
143
|
+
const stat = statSync(fullPath);
|
|
144
|
+
|
|
145
|
+
if (stat.isDirectory()) {
|
|
146
|
+
// Check if it contains SKILL.md
|
|
147
|
+
const skillMdPath = join(fullPath, "SKILL.md");
|
|
148
|
+
if (existsSync(skillMdPath)) {
|
|
149
|
+
skills.push(fullPath);
|
|
150
|
+
} else {
|
|
151
|
+
// Recursively search subdirectories
|
|
152
|
+
skills.push(...getSkillDirs(fullPath));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return skills;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Copy a directory recursively
|
|
162
|
+
*/
|
|
163
|
+
function copyDirRecursive(src: string, dest: string, verbose: boolean): void {
|
|
164
|
+
if (!existsSync(dest)) {
|
|
165
|
+
mkdirSync(dest, { recursive: true });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const entries = readdirSync(src);
|
|
169
|
+
|
|
170
|
+
for (const entry of entries) {
|
|
171
|
+
const srcPath = join(src, entry);
|
|
172
|
+
const destPath = join(dest, entry);
|
|
173
|
+
const stat = statSync(srcPath);
|
|
174
|
+
|
|
175
|
+
if (stat.isDirectory()) {
|
|
176
|
+
copyDirRecursive(srcPath, destPath, verbose);
|
|
177
|
+
} else {
|
|
178
|
+
copyFileSync(srcPath, destPath);
|
|
179
|
+
if (verbose) {
|
|
180
|
+
console.log(` Copied: ${entry}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Sync skills from source to target
|
|
188
|
+
*/
|
|
189
|
+
function syncSkills(options: SyncOptions): void {
|
|
190
|
+
const { from, to, checkOnly, verbose, clean } = options;
|
|
191
|
+
|
|
192
|
+
// Validate source
|
|
193
|
+
if (!existsSync(from)) {
|
|
194
|
+
console.error(`Error: Source directory does not exist: ${from}`);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Create target if needed
|
|
199
|
+
if (!checkOnly && !existsSync(to)) {
|
|
200
|
+
console.log(`Creating target directory: ${to}`);
|
|
201
|
+
mkdirSync(to, { recursive: true });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Find skills in source
|
|
205
|
+
console.log(`Scanning source: ${from}`);
|
|
206
|
+
const sourceSkills = getSkillDirs(from);
|
|
207
|
+
console.log(`Found ${sourceSkills.length} skill(s) in source`);
|
|
208
|
+
|
|
209
|
+
if (sourceSkills.length === 0) {
|
|
210
|
+
console.log("No skills found. Make sure source contains directories with SKILL.md files.");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (checkOnly) {
|
|
215
|
+
console.log("\n[CHECK ONLY] Would sync the following skills:");
|
|
216
|
+
} else {
|
|
217
|
+
console.log("\nSyncing skills:");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const targetSkills: string[] = [];
|
|
221
|
+
const toClean: string[] = [];
|
|
222
|
+
|
|
223
|
+
for (const skillPath of sourceSkills) {
|
|
224
|
+
const skillName = basename(skillPath);
|
|
225
|
+
const targetPath = join(to, skillName);
|
|
226
|
+
|
|
227
|
+
console.log(` ${checkOnly ? "[ ]" : "[x]"} ${skillName}`);
|
|
228
|
+
targetSkills.push(skillName);
|
|
229
|
+
|
|
230
|
+
if (!checkOnly) {
|
|
231
|
+
copyDirRecursive(skillPath, targetPath, verbose);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Clean: remove skills in target not in source
|
|
236
|
+
if (clean) {
|
|
237
|
+
console.log("\nChecking for skills to remove...");
|
|
238
|
+
const existingTargetSkills = getSkillDirs(to);
|
|
239
|
+
const sourceSkillNames = new Set(sourceSkills.map(s => basename(s)));
|
|
240
|
+
|
|
241
|
+
for (const existingPath of existingTargetSkills) {
|
|
242
|
+
const existingName = basename(existingPath);
|
|
243
|
+
if (!sourceSkillNames.has(existingName)) {
|
|
244
|
+
toClean.push(existingPath);
|
|
245
|
+
console.log(` [-] ${existingName} (not in source)`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!checkOnly && toClean.length > 0) {
|
|
250
|
+
console.log("\nRemoving orphaned skills...");
|
|
251
|
+
for (const path of toClean) {
|
|
252
|
+
rmSync(path, { recursive: true, force: true });
|
|
253
|
+
console.log(` Removed: ${basename(path)}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
console.log("");
|
|
259
|
+
if (checkOnly) {
|
|
260
|
+
console.log(`Check complete. Run without --check-only to sync.`);
|
|
261
|
+
} else {
|
|
262
|
+
console.log(`Sync complete! ${sourceSkills.length} skill(s) synced to ${to}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Show next steps
|
|
266
|
+
console.log("");
|
|
267
|
+
console.log("Next steps:");
|
|
268
|
+
console.log(` 1. Restart pi to load new skills`);
|
|
269
|
+
console.log(` 2. Or run /reload in pi`);
|
|
270
|
+
console.log(` 3. List skills: /skill list`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Main
|
|
274
|
+
const options = parseArgs();
|
|
275
|
+
syncSkills(options);
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Sync Skills to Qdrant
|
|
4
|
+
*
|
|
5
|
+
* Uploads local skills to Qdrant vector database for team sharing.
|
|
6
|
+
* SANITIZES client-specific data by replacing with placeholders.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* bun scripts/sync-to-qdrant.ts --from ~/.pi-harness-runtime/skills
|
|
10
|
+
* bun scripts/sync-to-qdrant.ts --list # List skills in Qdrant
|
|
11
|
+
* bun scripts/sync-to-qdrant.ts --search "..." # Search skills in Qdrant
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
15
|
+
import { join, basename } from "node:path";
|
|
16
|
+
import { QdrantClient } from "@qdrant/js-client-rest";
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// SANITIZATION: Replace client-specific names with placeholders
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
interface SanitizationRule {
|
|
23
|
+
pattern: RegExp;
|
|
24
|
+
replacement: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const SANITIZATION_RULES: SanitizationRule[] = [
|
|
28
|
+
// Client company names (case-insensitive)
|
|
29
|
+
{ pattern: /\binpac\b/gi, replacement: "[Client A]" },
|
|
30
|
+
{ pattern: /\bm-capital\b/gi, replacement: "[Client A]" },
|
|
31
|
+
{ pattern: /\bmcapital\b/gi, replacement: "[Client A]" },
|
|
32
|
+
{ pattern: /\bdigisoft\b/gi, replacement: "[Client B]" },
|
|
33
|
+
|
|
34
|
+
// Project/app specific names
|
|
35
|
+
{ pattern: /\bgse-insurance\b/gi, replacement: "[Insurance Project]" },
|
|
36
|
+
{ pattern: /\btbs-import\b/gi, replacement: "[Import Project]" },
|
|
37
|
+
{ pattern: /\bdigisoft_erp\b/gi, replacement: "[Client B Erp]" },
|
|
38
|
+
{ pattern: /\bm_capital\b/gi, replacement: "[Client A]" },
|
|
39
|
+
{ pattern: /\binpac_\b/gi, replacement: "[Client A]_" },
|
|
40
|
+
{ pattern: /\bpaperclip_\b/gi, replacement: "[Client D]_" },
|
|
41
|
+
{ pattern: /\bopenclaw_\b/gi, replacement: "[Client F]_" },
|
|
42
|
+
|
|
43
|
+
// Generic replacements
|
|
44
|
+
{ pattern: /Company Name/gi, replacement: "[Client Name]" },
|
|
45
|
+
{ pattern: /your company/gi, replacement: "[Client]" },
|
|
46
|
+
{ pattern: /our client/gi, replacement: "[Client]" },
|
|
47
|
+
{ pattern: /client-specific/gi, replacement: "[Project-specific]" },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
// Skill name sanitization (for skill directory name -> sanitized display name)
|
|
51
|
+
const SKILL_NAME_MAP: Record<string, string> = {
|
|
52
|
+
'gse-insurance-step5-slot-filter': 'insurance-project-step-filter',
|
|
53
|
+
'inpac-pe-approval-workflow': 'client-a-pe-approval-workflow',
|
|
54
|
+
'inpac-po-approval-workflow': 'client-a-po-approval-workflow',
|
|
55
|
+
'inpac-pr-approval-workflow': 'client-a-pr-approval-workflow',
|
|
56
|
+
'tbs-import-clearance-lcv': 'import-project-clearance',
|
|
57
|
+
'whispertool': 'whisper-tool',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Sanitize text by replacing client-specific patterns
|
|
62
|
+
*/
|
|
63
|
+
function sanitize(text: string): string {
|
|
64
|
+
let result = text;
|
|
65
|
+
for (const rule of SANITIZATION_RULES) {
|
|
66
|
+
result = result.replace(rule.pattern, rule.replacement);
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// =============================================================================
|
|
72
|
+
// Qdrant Config
|
|
73
|
+
// =============================================================================
|
|
74
|
+
|
|
75
|
+
interface QdrantConfig {
|
|
76
|
+
url: string;
|
|
77
|
+
apiKey: string;
|
|
78
|
+
collection: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getQdrantConfig(): QdrantConfig | null {
|
|
82
|
+
let apiKey = process.env.QDRANT_API_KEY;
|
|
83
|
+
const url = process.env.QDRANT_CLUSTER_ENDPOINT || "https://api.qdrant.tech";
|
|
84
|
+
const collection = process.env.QDRANT_COLLECTION || "pi-harness-skills";
|
|
85
|
+
|
|
86
|
+
if (!apiKey) {
|
|
87
|
+
const keyPath = `${process.env.HOME}/.pi-harness-runtime/keys/qdrant-api-key.txt`;
|
|
88
|
+
if (existsSync(keyPath)) {
|
|
89
|
+
apiKey = readFileSync(keyPath, "utf-8").trim();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!apiKey) return null;
|
|
94
|
+
|
|
95
|
+
return { url, apiKey, collection };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseHostPort(url: string): { host: string; port: number } {
|
|
99
|
+
const match = url.match(/https?:\/\/([^:]+)(?::(\d+))?/);
|
|
100
|
+
return {
|
|
101
|
+
host: match?.[1] || url,
|
|
102
|
+
port: parseInt(match?.[2] || "6333"),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// =============================================================================
|
|
107
|
+
// Skill Reading
|
|
108
|
+
// =============================================================================
|
|
109
|
+
|
|
110
|
+
interface SkillDoc {
|
|
111
|
+
id: number;
|
|
112
|
+
name: string;
|
|
113
|
+
description: string;
|
|
114
|
+
body: string;
|
|
115
|
+
text: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function parseFrontmatter(content: string): Record<string, string> | null {
|
|
119
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
120
|
+
if (!match) return null;
|
|
121
|
+
|
|
122
|
+
const fm: Record<string, string> = {};
|
|
123
|
+
for (const line of match[1].split("\n")) {
|
|
124
|
+
const idx = line.indexOf(":");
|
|
125
|
+
if (idx === -1) continue;
|
|
126
|
+
fm[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
127
|
+
}
|
|
128
|
+
return fm;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function readSkill(dirPath: string): SkillDoc | null {
|
|
132
|
+
const skillPath = join(dirPath, "SKILL.md");
|
|
133
|
+
if (!existsSync(skillPath)) return null;
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const rawContent = readFileSync(skillPath, "utf-8");
|
|
137
|
+
const fm = parseFrontmatter(rawContent);
|
|
138
|
+
if (!fm) return null;
|
|
139
|
+
|
|
140
|
+
// Get original skill name from directory
|
|
141
|
+
const originalName = fm.name || basename(dirPath);
|
|
142
|
+
|
|
143
|
+
// Sanitize content
|
|
144
|
+
const sanitizedContent = sanitize(rawContent);
|
|
145
|
+
|
|
146
|
+
// Use name map for known sensitive skill names, otherwise sanitize
|
|
147
|
+
const name = SKILL_NAME_MAP[originalName] || sanitize(originalName);
|
|
148
|
+
|
|
149
|
+
const description = sanitize(fm.description || "");
|
|
150
|
+
const body = sanitizedContent.replace(/^---[\s\S]*?---\n/, "").trim();
|
|
151
|
+
|
|
152
|
+
// Text for embedding (sanitized)
|
|
153
|
+
const text = `${name}. ${description}. ${body.slice(0, 1000)}`;
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
id: 0,
|
|
157
|
+
name,
|
|
158
|
+
description,
|
|
159
|
+
body: body.slice(0, 2000),
|
|
160
|
+
text,
|
|
161
|
+
};
|
|
162
|
+
} catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function findSkillDirs(dir: string): string[] {
|
|
168
|
+
if (!existsSync(dir)) return [];
|
|
169
|
+
|
|
170
|
+
return readdirSync(dir)
|
|
171
|
+
.map(entry => join(dir, entry))
|
|
172
|
+
.filter(fullPath => {
|
|
173
|
+
const stat = statSync(fullPath);
|
|
174
|
+
return stat.isDirectory() && existsSync(join(fullPath, "SKILL.md"));
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// =============================================================================
|
|
179
|
+
// Qdrant Operations
|
|
180
|
+
// =============================================================================
|
|
181
|
+
|
|
182
|
+
async function ensureCollection(client: QdrantClient, name: string): Promise<void> {
|
|
183
|
+
try {
|
|
184
|
+
await client.createCollection(name, {
|
|
185
|
+
vectors: { size: 384, distance: "Cosine" }
|
|
186
|
+
});
|
|
187
|
+
} catch {
|
|
188
|
+
// Already exists - OK
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function clearCollection(client: QdrantClient, name: string): Promise<void> {
|
|
193
|
+
try {
|
|
194
|
+
await client.deleteCollection(name);
|
|
195
|
+
await ensureCollection(client, name);
|
|
196
|
+
} catch {
|
|
197
|
+
// Ignore
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function upsertSkills(
|
|
202
|
+
client: QdrantClient,
|
|
203
|
+
collectionName: string,
|
|
204
|
+
skills: SkillDoc[]
|
|
205
|
+
): Promise<void> {
|
|
206
|
+
const points = skills.map((skill, idx) => ({
|
|
207
|
+
id: idx + 1,
|
|
208
|
+
payload: {
|
|
209
|
+
name: skill.name,
|
|
210
|
+
description: skill.description,
|
|
211
|
+
body: skill.body,
|
|
212
|
+
},
|
|
213
|
+
vector: {
|
|
214
|
+
text: skill.text,
|
|
215
|
+
model: "sentence-transformers/all-MiniLM-L6-v2"
|
|
216
|
+
}
|
|
217
|
+
}));
|
|
218
|
+
|
|
219
|
+
await client.upsert(collectionName, { wait: true, points });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function searchSkills(
|
|
223
|
+
client: QdrantClient,
|
|
224
|
+
collectionName: string,
|
|
225
|
+
query: string
|
|
226
|
+
): Promise<void> {
|
|
227
|
+
const results = await client.query(collectionName, {
|
|
228
|
+
query: {
|
|
229
|
+
text: query,
|
|
230
|
+
model: "sentence-transformers/all-MiniLM-L6-v2"
|
|
231
|
+
},
|
|
232
|
+
limit: 10,
|
|
233
|
+
with_payload: true,
|
|
234
|
+
} as Parameters<typeof client.query>[1]);
|
|
235
|
+
|
|
236
|
+
const points = (results as { points?: unknown[] })?.points || [];
|
|
237
|
+
|
|
238
|
+
console.error(`\nFound ${points.length} skills:\n`);
|
|
239
|
+
for (const r of points as Array<{ score: number; payload?: Record<string, string> }>) {
|
|
240
|
+
console.error(`[${(r.score * 100).toFixed(1)}%] ${r.payload?.name || "unknown"}`);
|
|
241
|
+
console.error(` ${(r.payload?.description || "").slice(0, 80)}...`);
|
|
242
|
+
console.error("");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function getCollectionInfo(client: QdrantClient, name: string): Promise<number> {
|
|
247
|
+
try {
|
|
248
|
+
const info = await client.getCollection(name);
|
|
249
|
+
return (info as { result?: { points_count?: number } })?.result?.points_count || 0;
|
|
250
|
+
} catch {
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// =============================================================================
|
|
256
|
+
// CLI Commands
|
|
257
|
+
// =============================================================================
|
|
258
|
+
|
|
259
|
+
async function syncToQdrant(sourceDir: string, clear: boolean = false): Promise<void> {
|
|
260
|
+
const config = getQdrantConfig();
|
|
261
|
+
if (!config) {
|
|
262
|
+
console.error("Error: Qdrant API key not found.");
|
|
263
|
+
console.error("Set QDRANT_API_KEY or create:");
|
|
264
|
+
console.error(" ~/.pi-harness-runtime/keys/qdrant-api-key.txt");
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const { host, port } = parseHostPort(config.url);
|
|
269
|
+
const client = new QdrantClient({ host, port, apiKey: config.apiKey });
|
|
270
|
+
|
|
271
|
+
console.error(`[Sync] Connecting to Qdrant: ${config.url}`);
|
|
272
|
+
console.error(`[Sync] Collection: ${config.collection}`);
|
|
273
|
+
|
|
274
|
+
if (clear) {
|
|
275
|
+
console.error("[Sync] Clearing collection...");
|
|
276
|
+
await clearCollection(client, config.collection);
|
|
277
|
+
} else {
|
|
278
|
+
await ensureCollection(client, config.collection);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
console.error(`[Sync] Scanning: ${sourceDir}`);
|
|
282
|
+
const skillDirs = findSkillDirs(sourceDir);
|
|
283
|
+
|
|
284
|
+
// Read and sanitize ALL skills
|
|
285
|
+
const skills: SkillDoc[] = [];
|
|
286
|
+
|
|
287
|
+
for (const dir of skillDirs) {
|
|
288
|
+
const skill = readSkill(dir);
|
|
289
|
+
if (skill) {
|
|
290
|
+
skills.push(skill);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
console.error(`[Sync] Found ${skillDirs.length} skills`);
|
|
295
|
+
console.error(`[Sync] All skills sanitized and ready to upload`);
|
|
296
|
+
|
|
297
|
+
if (skills.length === 0) {
|
|
298
|
+
console.error("[Sync] No skills to upload.");
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
console.error(`[Sync] Uploading ${skills.length} sanitized skills to Qdrant...`);
|
|
303
|
+
await upsertSkills(client, config.collection, skills);
|
|
304
|
+
console.error(`[Sync] Complete! ${skills.length} sanitized skills uploaded.`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function listSkills(): Promise<void> {
|
|
308
|
+
const config = getQdrantConfig();
|
|
309
|
+
if (!config) {
|
|
310
|
+
console.error("Error: Qdrant API key not found.");
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const { host, port } = parseHostPort(config.url);
|
|
315
|
+
const client = new QdrantClient({ host, port, apiKey: config.apiKey });
|
|
316
|
+
|
|
317
|
+
const count = await getCollectionInfo(client, config.collection);
|
|
318
|
+
console.error(`Collection: ${config.collection}`);
|
|
319
|
+
console.error(`Skills: ${count}`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function search(query: string): Promise<void> {
|
|
323
|
+
const config = getQdrantConfig();
|
|
324
|
+
if (!config) {
|
|
325
|
+
console.error("Error: Qdrant API key not found.");
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const { host, port } = parseHostPort(config.url);
|
|
330
|
+
const client = new QdrantClient({ host, port, apiKey: config.apiKey });
|
|
331
|
+
|
|
332
|
+
console.error(`[Search] Query: "${query}"`);
|
|
333
|
+
await searchSkills(client, config.collection, query);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// =============================================================================
|
|
337
|
+
// Main
|
|
338
|
+
// =============================================================================
|
|
339
|
+
|
|
340
|
+
const args = process.argv.slice(2);
|
|
341
|
+
|
|
342
|
+
if (args.includes("--help")) {
|
|
343
|
+
console.log(`
|
|
344
|
+
Sync Skills to Qdrant (Sanitized)
|
|
345
|
+
──────────────────────────────────
|
|
346
|
+
Upload skills to Qdrant for team sharing.
|
|
347
|
+
Client-specific names are REPLACED with placeholders.
|
|
348
|
+
|
|
349
|
+
Sanitization Rules:
|
|
350
|
+
inpac → [Client A]
|
|
351
|
+
m-capital → [Client A]
|
|
352
|
+
digisoft → [Client B]
|
|
353
|
+
cloudshot → [Client C]
|
|
354
|
+
paperclip → [Client D]
|
|
355
|
+
teamw → [Client E]
|
|
356
|
+
openclaw → [Client F]
|
|
357
|
+
autoresearch → [Internal Tool]
|
|
358
|
+
|
|
359
|
+
Usage:
|
|
360
|
+
bun scripts/sync-to-qdrant.ts --from <dir> Sync skills from directory
|
|
361
|
+
bun scripts/sync-to-qdrant.ts --from <dir> --clear Clear and re-upload
|
|
362
|
+
bun scripts/sync-to-qdrant.ts --list List skills in Qdrant
|
|
363
|
+
bun scripts/sync-to-qdrant.ts --search <query> Search skills in Qdrant
|
|
364
|
+
bun scripts/sync-to-qdrant.ts --help Show this help
|
|
365
|
+
|
|
366
|
+
Environment:
|
|
367
|
+
QDRANT_API_KEY Your Qdrant Cloud API key
|
|
368
|
+
QDRANT_CLUSTER_ENDPOINT Qdrant URL
|
|
369
|
+
QDRANT_COLLECTION Collection name (default: pi-harness-skills)
|
|
370
|
+
|
|
371
|
+
Or create file:
|
|
372
|
+
~/.pi-harness-runtime/keys/qdrant-api-key.txt
|
|
373
|
+
`);
|
|
374
|
+
process.exit(0);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (args.includes("--list")) {
|
|
378
|
+
await listSkills();
|
|
379
|
+
process.exit(0);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const searchIdx = args.indexOf("--search");
|
|
383
|
+
if (searchIdx !== -1 && args[searchIdx + 1]) {
|
|
384
|
+
await search(args[searchIdx + 1]);
|
|
385
|
+
process.exit(0);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const fromIdx = args.indexOf("--from");
|
|
389
|
+
if (fromIdx === -1 || !args[fromIdx + 1]) {
|
|
390
|
+
console.error("Error: --from <directory> is required");
|
|
391
|
+
console.error("Use --help for usage information.");
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const sourceDir = args[fromIdx + 1];
|
|
396
|
+
const clear = args.includes("--clear");
|
|
397
|
+
|
|
398
|
+
await syncToQdrant(sourceDir, clear);
|