pi-para 0.5.2
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 +214 -0
- package/README.md +305 -0
- package/SETUP.md +171 -0
- package/dist/capture.d.ts +34 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +461 -0
- package/dist/capture.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +405 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.d.ts +15 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +786 -0
- package/dist/commands.js.map +1 -0
- package/dist/context.d.ts +61 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +289 -0
- package/dist/context.js.map +1 -0
- package/dist/daemon.d.ts +49 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +226 -0
- package/dist/daemon.js.map +1 -0
- package/dist/frontmatter.d.ts +59 -0
- package/dist/frontmatter.d.ts.map +1 -0
- package/dist/frontmatter.js +170 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/gepa/dataset.d.ts +36 -0
- package/dist/gepa/dataset.d.ts.map +1 -0
- package/dist/gepa/dataset.js +148 -0
- package/dist/gepa/dataset.js.map +1 -0
- package/dist/gepa/evaluator.d.ts +51 -0
- package/dist/gepa/evaluator.d.ts.map +1 -0
- package/dist/gepa/evaluator.js +317 -0
- package/dist/gepa/evaluator.js.map +1 -0
- package/dist/gepa/index.d.ts +66 -0
- package/dist/gepa/index.d.ts.map +1 -0
- package/dist/gepa/index.js +240 -0
- package/dist/gepa/index.js.map +1 -0
- package/dist/gepa/llm.d.ts +35 -0
- package/dist/gepa/llm.d.ts.map +1 -0
- package/dist/gepa/llm.js +268 -0
- package/dist/gepa/llm.js.map +1 -0
- package/dist/gepa/optimized/capture-prompt.txt +65 -0
- package/dist/gepa/optimizer.d.ts +29 -0
- package/dist/gepa/optimizer.d.ts.map +1 -0
- package/dist/gepa/optimizer.js +249 -0
- package/dist/gepa/optimizer.js.map +1 -0
- package/dist/gepa/targets.d.ts +39 -0
- package/dist/gepa/targets.d.ts.map +1 -0
- package/dist/gepa/targets.js +326 -0
- package/dist/gepa/targets.js.map +1 -0
- package/dist/gepa/types.d.ts +168 -0
- package/dist/gepa/types.d.ts.map +1 -0
- package/dist/gepa/types.js +124 -0
- package/dist/gepa/types.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +294 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest.d.ts +88 -0
- package/dist/ingest.d.ts.map +1 -0
- package/dist/ingest.js +105 -0
- package/dist/ingest.js.map +1 -0
- package/dist/link-discovery.d.ts +40 -0
- package/dist/link-discovery.d.ts.map +1 -0
- package/dist/link-discovery.js +122 -0
- package/dist/link-discovery.js.map +1 -0
- package/dist/link-utils.d.ts +30 -0
- package/dist/link-utils.d.ts.map +1 -0
- package/dist/link-utils.js +145 -0
- package/dist/link-utils.js.map +1 -0
- package/dist/lint.d.ts +39 -0
- package/dist/lint.d.ts.map +1 -0
- package/dist/lint.js +827 -0
- package/dist/lint.js.map +1 -0
- package/dist/maintainer.d.ts +24 -0
- package/dist/maintainer.d.ts.map +1 -0
- package/dist/maintainer.js +358 -0
- package/dist/maintainer.js.map +1 -0
- package/dist/processor.d.ts +14 -0
- package/dist/processor.d.ts.map +1 -0
- package/dist/processor.js +361 -0
- package/dist/processor.js.map +1 -0
- package/dist/query.d.ts +69 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +107 -0
- package/dist/query.js.map +1 -0
- package/dist/raw.d.ts +57 -0
- package/dist/raw.d.ts.map +1 -0
- package/dist/raw.js +219 -0
- package/dist/raw.js.map +1 -0
- package/dist/redact.d.ts +21 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +143 -0
- package/dist/redact.js.map +1 -0
- package/dist/scope.d.ts +54 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +161 -0
- package/dist/scope.js.map +1 -0
- package/dist/scripts/bulk-link-fix.d.ts +9 -0
- package/dist/scripts/bulk-link-fix.d.ts.map +1 -0
- package/dist/scripts/bulk-link-fix.js +106 -0
- package/dist/scripts/bulk-link-fix.js.map +1 -0
- package/dist/session-tools.d.ts +33 -0
- package/dist/session-tools.d.ts.map +1 -0
- package/dist/session-tools.js +211 -0
- package/dist/session-tools.js.map +1 -0
- package/dist/state.d.ts +54 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +176 -0
- package/dist/state.js.map +1 -0
- package/dist/store.d.ts +56 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +227 -0
- package/dist/store.js.map +1 -0
- package/dist/summarize.d.ts +56 -0
- package/dist/summarize.d.ts.map +1 -0
- package/dist/summarize.js +231 -0
- package/dist/summarize.js.map +1 -0
- package/dist/tag-registry.d.ts +56 -0
- package/dist/tag-registry.d.ts.map +1 -0
- package/dist/tag-registry.js +146 -0
- package/dist/tag-registry.js.map +1 -0
- package/dist/templates/prompts.d.ts +63 -0
- package/dist/templates/prompts.d.ts.map +1 -0
- package/dist/templates/prompts.js +260 -0
- package/dist/templates/prompts.js.map +1 -0
- package/dist/tools.d.ts +26 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +1033 -0
- package/dist/tools.js.map +1 -0
- package/dist/watcher.d.ts +25 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +84 -0
- package/dist/watcher.js.map +1 -0
- package/dist/webui/client/dist/index.html +1885 -0
- package/dist/webui/server.d.ts +31 -0
- package/dist/webui/server.d.ts.map +1 -0
- package/dist/webui/server.js +566 -0
- package/dist/webui/server.js.map +1 -0
- package/dist/wiki.d.ts +103 -0
- package/dist/wiki.d.ts.map +1 -0
- package/dist/wiki.js +428 -0
- package/dist/wiki.js.map +1 -0
- package/package.json +142 -0
- package/scripts/gepa/__pycache__/dataset.cpython-313.pyc +0 -0
- package/scripts/gepa/__pycache__/lm_providers.cpython-313.pyc +0 -0
- package/scripts/gepa/__pycache__/metric.cpython-313.pyc +0 -0
- package/scripts/gepa/__pycache__/program.cpython-313.pyc +0 -0
- package/scripts/gepa/__pycache__/wiki_reader.cpython-313.pyc +0 -0
- package/scripts/gepa/dataset.py +192 -0
- package/scripts/gepa/lm_providers.py +472 -0
- package/scripts/gepa/metric.py +279 -0
- package/scripts/gepa/optimize.py +367 -0
- package/scripts/gepa/program.py +99 -0
- package/scripts/gepa/pyproject.toml +19 -0
- package/scripts/gepa/run-all.sh +53 -0
- package/scripts/gepa/uv.lock +2241 -0
- package/scripts/gepa/wiki_reader.py +113 -0
- package/skills/para/SKILL.md +77 -0
- package/skills/setup/SKILL.md +20 -0
- package/src/capture.ts +563 -0
- package/src/cli.ts +416 -0
- package/src/commands.ts +878 -0
- package/src/context.ts +390 -0
- package/src/daemon.ts +288 -0
- package/src/frontmatter.ts +219 -0
- package/src/gepa/index.ts +287 -0
- package/src/gepa/optimized/capture-prompt.txt +65 -0
- package/src/index.ts +384 -0
- package/src/ingest.ts +177 -0
- package/src/link-discovery.ts +170 -0
- package/src/link-utils.ts +169 -0
- package/src/lint.ts +1018 -0
- package/src/maintainer.ts +404 -0
- package/src/processor.ts +415 -0
- package/src/python/__pycache__/minimax_lm.cpython-312.pyc +0 -0
- package/src/python/minimax_lm.py +198 -0
- package/src/python/rlm_capture.py +215 -0
- package/src/query.ts +148 -0
- package/src/raw.ts +276 -0
- package/src/redact.ts +159 -0
- package/src/scope.ts +214 -0
- package/src/scripts/bulk-link-fix.ts +126 -0
- package/src/session-tools.ts +260 -0
- package/src/state.ts +271 -0
- package/src/store.ts +288 -0
- package/src/summarize.ts +273 -0
- package/src/tag-registry.ts +157 -0
- package/src/templates/index.md +17 -0
- package/src/templates/prompts.ts +274 -0
- package/src/templates/schema.md +116 -0
- package/src/tools.ts +1293 -0
- package/src/watcher.ts +101 -0
- package/src/webui/client/dist/index.html +1885 -0
- package/src/webui/server.ts +765 -0
- package/src/wiki.ts +550 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAML frontmatter parsing and serialization for wiki pages.
|
|
3
|
+
*
|
|
4
|
+
* Handles the `---\n...\n---` frontmatter block in markdown files.
|
|
5
|
+
* Uses the `yaml` package for robust parsing. Preserves unknown fields.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
9
|
+
import type { PageFrontmatter, ParaCategory } from "./wiki.js";
|
|
10
|
+
|
|
11
|
+
// -- Schema versioning -------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
/** Current schema version. Bump when frontmatter/body format changes. */
|
|
14
|
+
export const CURRENT_SCHEMA_VERSION = 1;
|
|
15
|
+
|
|
16
|
+
/** A migration transforms pages from one schema version to the next. */
|
|
17
|
+
export interface Migration {
|
|
18
|
+
from: number;
|
|
19
|
+
to: number;
|
|
20
|
+
migrate: (fm: Record<string, unknown>, body: string) => { fm: Record<string, unknown>; body: string };
|
|
21
|
+
description: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Registry of all schema migrations, ordered by `from` version.
|
|
26
|
+
* Empty while CURRENT_SCHEMA_VERSION = 1 — add entries when bumping.
|
|
27
|
+
*/
|
|
28
|
+
export const MIGRATIONS: Migration[] = [];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Apply all applicable migrations to bring a page up to CURRENT_SCHEMA_VERSION.
|
|
32
|
+
*
|
|
33
|
+
* Chains migrations sequentially: 1→2, 2→3, etc.
|
|
34
|
+
* If `schemaVersion` is already >= CURRENT_SCHEMA_VERSION, returns as-is (no downgrade).
|
|
35
|
+
* If `schemaVersion` is missing, defaults to 1.
|
|
36
|
+
*/
|
|
37
|
+
export function migrateToLatest(
|
|
38
|
+
fm: Record<string, unknown>,
|
|
39
|
+
body: string,
|
|
40
|
+
): { fm: Record<string, unknown>; body: string } {
|
|
41
|
+
let version = typeof fm.schemaVersion === "number" ? fm.schemaVersion : 1;
|
|
42
|
+
|
|
43
|
+
// Don't downgrade pages ahead of current version
|
|
44
|
+
if (version >= CURRENT_SCHEMA_VERSION) {
|
|
45
|
+
return { fm: { ...fm, schemaVersion: version }, body };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let currentFm = { ...fm };
|
|
49
|
+
let currentBody = body;
|
|
50
|
+
|
|
51
|
+
while (version < CURRENT_SCHEMA_VERSION) {
|
|
52
|
+
const migration = MIGRATIONS.find((m) => m.from === version);
|
|
53
|
+
if (!migration) {
|
|
54
|
+
// No migration path — stop at current version
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
const result = migration.migrate(currentFm, currentBody);
|
|
58
|
+
currentFm = result.fm;
|
|
59
|
+
currentBody = result.body;
|
|
60
|
+
version = migration.to;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
currentFm.schemaVersion = version;
|
|
64
|
+
return { fm: currentFm, body: currentBody };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const VALID_PARA_CATEGORIES = new Set<string>([
|
|
68
|
+
"projects",
|
|
69
|
+
"areas",
|
|
70
|
+
"resources",
|
|
71
|
+
"archives",
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
const FRONTMATTER_REGEX = /^---\n([\s\S]*?)\n?---\n?([\s\S]*)$/;
|
|
75
|
+
|
|
76
|
+
// -- Functions ---------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Parse YAML frontmatter from markdown content.
|
|
80
|
+
*
|
|
81
|
+
* Expects content starting with `---\n...\n---`. If no frontmatter block
|
|
82
|
+
* is found, returns default frontmatter and the entire content as body.
|
|
83
|
+
* If the YAML is malformed, throws with a descriptive error.
|
|
84
|
+
*/
|
|
85
|
+
export function parseFrontmatter(content: string): {
|
|
86
|
+
frontmatter: PageFrontmatter;
|
|
87
|
+
body: string;
|
|
88
|
+
} {
|
|
89
|
+
const match = content.match(FRONTMATTER_REGEX);
|
|
90
|
+
if (!match) {
|
|
91
|
+
return {
|
|
92
|
+
frontmatter: validateFrontmatter({}),
|
|
93
|
+
body: content,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const rawYaml = match[1];
|
|
98
|
+
const body = match[2];
|
|
99
|
+
|
|
100
|
+
let parsed: unknown;
|
|
101
|
+
try {
|
|
102
|
+
parsed = parseYaml(rawYaml);
|
|
103
|
+
} catch (err: unknown) {
|
|
104
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
105
|
+
throw new Error(`Malformed YAML frontmatter: ${msg}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (parsed == null || typeof parsed !== "object") {
|
|
109
|
+
return {
|
|
110
|
+
frontmatter: validateFrontmatter({}),
|
|
111
|
+
body,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
frontmatter: validateFrontmatter(parsed as Record<string, unknown>),
|
|
117
|
+
body,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Serialize frontmatter and body back to a markdown string with YAML
|
|
123
|
+
* frontmatter block.
|
|
124
|
+
*/
|
|
125
|
+
export function serializeFrontmatter(
|
|
126
|
+
frontmatter: PageFrontmatter,
|
|
127
|
+
body: string,
|
|
128
|
+
): string {
|
|
129
|
+
// Always write the current schema version on serialize
|
|
130
|
+
const fmWithVersion = { ...frontmatter, schemaVersion: CURRENT_SCHEMA_VERSION };
|
|
131
|
+
const yaml = stringifyYaml(fmWithVersion, { lineWidth: 0 }).trimEnd();
|
|
132
|
+
return `---\n${yaml}\n---\n${body}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Validate and normalize a raw frontmatter object into a PageFrontmatter.
|
|
137
|
+
*
|
|
138
|
+
* Required field: `title` (defaults to "Untitled" if missing).
|
|
139
|
+
* All other fields get sensible defaults. Unknown fields are preserved.
|
|
140
|
+
*/
|
|
141
|
+
export function validateFrontmatter(
|
|
142
|
+
fm: Record<string, unknown>,
|
|
143
|
+
): PageFrontmatter {
|
|
144
|
+
const now = new Date().toISOString();
|
|
145
|
+
|
|
146
|
+
const title =
|
|
147
|
+
typeof fm.title === "string" && fm.title.length > 0
|
|
148
|
+
? fm.title
|
|
149
|
+
: "Untitled";
|
|
150
|
+
|
|
151
|
+
const para = validateParaCategory(fm.para);
|
|
152
|
+
const scope = toStringArray(fm.scope);
|
|
153
|
+
const tags = toStringArray(fm.tags);
|
|
154
|
+
const sources = toStringArray(fm.sources);
|
|
155
|
+
const links = toStringArray(fm.links);
|
|
156
|
+
const created =
|
|
157
|
+
typeof fm.created === "string" && fm.created.length > 0
|
|
158
|
+
? fm.created
|
|
159
|
+
: now;
|
|
160
|
+
const updated =
|
|
161
|
+
typeof fm.updated === "string" && fm.updated.length > 0
|
|
162
|
+
? fm.updated
|
|
163
|
+
: now;
|
|
164
|
+
|
|
165
|
+
const schemaVersion =
|
|
166
|
+
typeof fm.schemaVersion === "number" ? fm.schemaVersion : 1;
|
|
167
|
+
|
|
168
|
+
// Build result with known fields first, then spread unknown fields underneath.
|
|
169
|
+
// Known fields override any unknown fields with the same name.
|
|
170
|
+
const knownKeys = new Set([
|
|
171
|
+
"title",
|
|
172
|
+
"para",
|
|
173
|
+
"scope",
|
|
174
|
+
"tags",
|
|
175
|
+
"sources",
|
|
176
|
+
"created",
|
|
177
|
+
"updated",
|
|
178
|
+
"links",
|
|
179
|
+
"schemaVersion",
|
|
180
|
+
]);
|
|
181
|
+
const unknownFields: Record<string, unknown> = {};
|
|
182
|
+
for (const [key, value] of Object.entries(fm)) {
|
|
183
|
+
if (!knownKeys.has(key)) {
|
|
184
|
+
unknownFields[key] = value;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
...unknownFields,
|
|
190
|
+
title,
|
|
191
|
+
para,
|
|
192
|
+
scope,
|
|
193
|
+
tags,
|
|
194
|
+
sources,
|
|
195
|
+
created,
|
|
196
|
+
updated,
|
|
197
|
+
links,
|
|
198
|
+
schemaVersion,
|
|
199
|
+
} as PageFrontmatter;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// -- Helpers -----------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
function validateParaCategory(value: unknown): ParaCategory {
|
|
205
|
+
if (typeof value === "string" && VALID_PARA_CATEGORIES.has(value)) {
|
|
206
|
+
return value as ParaCategory;
|
|
207
|
+
}
|
|
208
|
+
return "resources";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function toStringArray(value: unknown): string[] {
|
|
212
|
+
if (Array.isArray(value)) {
|
|
213
|
+
return value.filter((v): v is string => typeof v === "string");
|
|
214
|
+
}
|
|
215
|
+
if (typeof value === "string") {
|
|
216
|
+
return [value];
|
|
217
|
+
}
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GEPA optimizer orchestrator — TypeScript side.
|
|
3
|
+
*
|
|
4
|
+
* 1. Extracts all prompt/tool/skill instruction texts from source
|
|
5
|
+
* 2. Writes targets.json for the Python DSPy GEPA script
|
|
6
|
+
* 3. Reads auth.json → passes API keys as env vars to uv subprocess
|
|
7
|
+
* 4. Calls `uv run scripts/gepa/optimize.py`
|
|
8
|
+
* 5. Reads results.json → saves optimized prompts to disk
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { dirname, join, resolve } from "node:path";
|
|
15
|
+
|
|
16
|
+
// -- Types -------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
interface GEPATarget {
|
|
19
|
+
name: string;
|
|
20
|
+
type: "prompt" | "tool" | "skill";
|
|
21
|
+
content: string;
|
|
22
|
+
sourceFile: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface GEPAResult {
|
|
26
|
+
target: string;
|
|
27
|
+
status: "success" | "error";
|
|
28
|
+
baseline_score?: number;
|
|
29
|
+
optimized_score?: number;
|
|
30
|
+
improvement_pct?: number;
|
|
31
|
+
optimized_instruction?: string;
|
|
32
|
+
original_instruction?: string;
|
|
33
|
+
model?: string;
|
|
34
|
+
auto?: string;
|
|
35
|
+
error?: string;
|
|
36
|
+
timestamp?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface GEPAOutput {
|
|
40
|
+
results: GEPAResult[];
|
|
41
|
+
model: string;
|
|
42
|
+
auto: string;
|
|
43
|
+
wiki_dir: string;
|
|
44
|
+
timestamp: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface GEPAOptions {
|
|
48
|
+
target?: string;
|
|
49
|
+
/** Student model — runs the proxy (fast, cheap). */
|
|
50
|
+
studentModel?: string;
|
|
51
|
+
/** Teacher/reflection model — proposes mutations (smart, creative). */
|
|
52
|
+
teacherModel?: string;
|
|
53
|
+
/** Judge model — scores outputs (fast, cheap). Default: same as student. */
|
|
54
|
+
judgeModel?: string;
|
|
55
|
+
/** Shorthand: sets studentModel (backward compat with --model flag) */
|
|
56
|
+
model?: string;
|
|
57
|
+
/** Shorthand: sets teacherModel (backward compat with --reflection-model flag) */
|
|
58
|
+
reflectionModel?: string;
|
|
59
|
+
auto?: "light" | "medium" | "heavy";
|
|
60
|
+
maxMetricCalls?: number;
|
|
61
|
+
threads?: number;
|
|
62
|
+
seed?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// -- Paths -------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
const WIKI_DIR = join(homedir(), ".pi", "wiki");
|
|
68
|
+
const GEPA_DIR = join(WIKI_DIR, "gepa");
|
|
69
|
+
const INPUT_DIR = join(GEPA_DIR, "input");
|
|
70
|
+
const OUTPUT_DIR = join(GEPA_DIR, "output");
|
|
71
|
+
const OPTIMIZED_DIR = join(GEPA_DIR, "optimized");
|
|
72
|
+
const HISTORY_DIR = join(GEPA_DIR, "history");
|
|
73
|
+
|
|
74
|
+
// -- Package root ------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
function findPackageRoot(): string {
|
|
77
|
+
let dir = dirname(new URL(import.meta.url).pathname);
|
|
78
|
+
for (let i = 0; i < 5; i++) {
|
|
79
|
+
const pkgPath = join(dir, "package.json");
|
|
80
|
+
if (existsSync(pkgPath)) {
|
|
81
|
+
try {
|
|
82
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
83
|
+
if (pkg.name === "@picassio/pi-para") return dir;
|
|
84
|
+
} catch {}
|
|
85
|
+
}
|
|
86
|
+
dir = dirname(dir);
|
|
87
|
+
}
|
|
88
|
+
return resolve(dirname(new URL(import.meta.url).pathname), "../..");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// -- Helpers: extract strings from TS source ---------------------------------
|
|
92
|
+
|
|
93
|
+
function extractTSConst(source: string, name: string): string | null {
|
|
94
|
+
const m = source.match(new RegExp(`(?:export\\s+)?const\\s+${name}\\s*=\\s*\`([\\s\\S]*?)\`;`));
|
|
95
|
+
return m ? m[1].trim() : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function extractToolInstructions(source: string): Record<string, string> {
|
|
99
|
+
const tools: Record<string, string> = {};
|
|
100
|
+
const blocks = [...source.matchAll(
|
|
101
|
+
/registerTool\(\{[\s\S]*?name:\s*"(wiki_\w+)"[\s\S]*?(?=registerTool\(\{|$)/g,
|
|
102
|
+
)];
|
|
103
|
+
for (const block of blocks) {
|
|
104
|
+
const name = block[1].replace("wiki_", "wiki-");
|
|
105
|
+
const text = block[0];
|
|
106
|
+
const descStrs = [...(text.match(/description:\s*\n?\s*([\s\S]*?)(?:promptSnippet:|promptGuidelines:|parameters:)/)?.[1] ?? "").matchAll(/["'`]([^"'`]+)["'`]/g)];
|
|
107
|
+
const desc = descStrs.map(s => s[1]).join(" ");
|
|
108
|
+
const snippet = text.match(/promptSnippet:\s*["'`]([^"'`]+)["'`]/)?.[1] ?? "";
|
|
109
|
+
const guideMatch = text.match(/promptGuidelines:\s*\[([\s\S]*?)\]/);
|
|
110
|
+
const guides = guideMatch ? [...guideMatch[1].matchAll(/["'`]([^"'`]+)["'`]/g)].map(s => s[1]).join("\n") : "";
|
|
111
|
+
const combined = [`Tool: ${name}`, desc && `Description: ${desc.trim()}`, snippet && `Snippet: ${snippet}`, guides && `Guidelines:\n${guides}`].filter(Boolean).join("\n");
|
|
112
|
+
if (combined.length > 50) tools[name] = combined;
|
|
113
|
+
}
|
|
114
|
+
return tools;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function toKebab(name: string): string {
|
|
118
|
+
return name.replace(/_/g, "-").replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// -- Target extraction -------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
export function extractTargets(packageRoot?: string): GEPATarget[] {
|
|
124
|
+
const root = packageRoot ?? findPackageRoot();
|
|
125
|
+
const targets: GEPATarget[] = [];
|
|
126
|
+
|
|
127
|
+
// Prompt templates
|
|
128
|
+
const promptsFile = join(root, "src", "templates", "prompts.ts");
|
|
129
|
+
if (existsSync(promptsFile)) {
|
|
130
|
+
const src = readFileSync(promptsFile, "utf-8");
|
|
131
|
+
for (const n of ["WIKI_SYSTEM_PROMPT","INGEST_PROMPT","QUERY_PROMPT","CAPTURE_SYSTEM_PROMPT","CAPTURE_PROMPT","EXPLICIT_CAPTURE_PROMPT","SUMMARIZE_SYSTEM_PROMPT","ITERATIVE_UPDATE_PROMPT","OVERVIEW_PROMPT","LINT_PROMPT"]) {
|
|
132
|
+
const c = extractTSConst(src, n);
|
|
133
|
+
if (c) targets.push({ name: toKebab(n), type: "prompt", content: c, sourceFile: promptsFile });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Maintainer + processor prompts
|
|
138
|
+
for (const [file, constName, targetName] of [
|
|
139
|
+
["src/maintainer.ts", "MAINTENANCE_SYSTEM_PROMPT", "maintenance-system-prompt"],
|
|
140
|
+
["src/processor.ts", "CAPTURE_SYSTEM_PROMPT", "processor-capture-prompt"],
|
|
141
|
+
] as const) {
|
|
142
|
+
const p = join(root, file);
|
|
143
|
+
if (existsSync(p)) {
|
|
144
|
+
const c = extractTSConst(readFileSync(p, "utf-8"), constName);
|
|
145
|
+
if (c) targets.push({ name: targetName, type: "prompt", content: c, sourceFile: p });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Tool instructions
|
|
150
|
+
const toolsFile = join(root, "src", "tools.ts");
|
|
151
|
+
if (existsSync(toolsFile)) {
|
|
152
|
+
for (const [n, c] of Object.entries(extractToolInstructions(readFileSync(toolsFile, "utf-8")))) {
|
|
153
|
+
targets.push({ name: `tool-${n}`, type: "tool", content: c, sourceFile: toolsFile });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Skills
|
|
158
|
+
for (const s of ["para", "setup"]) {
|
|
159
|
+
const p = join(root, "skills", s, "SKILL.md");
|
|
160
|
+
if (existsSync(p)) {
|
|
161
|
+
const raw = readFileSync(p, "utf-8");
|
|
162
|
+
const body = raw.match(/^---\s*\n[\s\S]*?\n---\s*\n([\s\S]*)$/)?.[1]?.trim() ?? raw;
|
|
163
|
+
targets.push({ name: `skill-${s}`, type: "skill", content: body, sourceFile: p });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return targets;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// -- Auth env vars -----------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
function getAuthEnvVars(): Record<string, string> {
|
|
173
|
+
const env: Record<string, string> = {};
|
|
174
|
+
try {
|
|
175
|
+
const auth = JSON.parse(readFileSync(join(homedir(), ".pi", "agent", "auth.json"), "utf-8"));
|
|
176
|
+
if (auth.anthropic?.access) { env.ANTHROPIC_OAUTH_TOKEN = auth.anthropic.access; env.ANTHROPIC_API_KEY = auth.anthropic.access; }
|
|
177
|
+
else if (auth.anthropic?.key) env.ANTHROPIC_API_KEY = auth.anthropic.key;
|
|
178
|
+
if (auth.minimax?.key) env.MINIMAX_API_KEY = auth.minimax.key;
|
|
179
|
+
if (auth.openrouter?.key) env.OPENROUTER_API_KEY = auth.openrouter.key;
|
|
180
|
+
else if (auth.openrouter?.openrouter_key) env.OPENROUTER_API_KEY = auth.openrouter.openrouter_key;
|
|
181
|
+
} catch {
|
|
182
|
+
try {
|
|
183
|
+
const raw = readFileSync(join(homedir(), ".config", "qmd", "index.yml"), "utf-8");
|
|
184
|
+
const m = raw.match(/chat:[\s\S]*?key:\s*(.+)/);
|
|
185
|
+
if (m) { const k = m[1].trim(); if (raw.includes("minimaxi.com")) env.MINIMAX_CN_API_KEY = k; else env.MINIMAX_API_KEY = k; }
|
|
186
|
+
} catch {}
|
|
187
|
+
}
|
|
188
|
+
return env;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// -- Main orchestrator -------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
export async function runGEPA(options: GEPAOptions = {}): Promise<GEPAOutput> {
|
|
194
|
+
const root = findPackageRoot();
|
|
195
|
+
const scriptsDir = join(root, "scripts", "gepa");
|
|
196
|
+
for (const d of [INPUT_DIR, OUTPUT_DIR, OPTIMIZED_DIR, HISTORY_DIR]) mkdirSync(d, { recursive: true });
|
|
197
|
+
|
|
198
|
+
// Load GEPA config from config.json (user-configurable defaults)
|
|
199
|
+
let gepaConfig: Record<string, unknown> = {};
|
|
200
|
+
try {
|
|
201
|
+
const cfg = JSON.parse(readFileSync(join(WIKI_DIR, "config.json"), "utf-8"));
|
|
202
|
+
gepaConfig = cfg.gepa ?? {};
|
|
203
|
+
} catch {}
|
|
204
|
+
|
|
205
|
+
// Resolution: CLI flag → options → config.json → default
|
|
206
|
+
const studentModel = options.studentModel ?? options.model
|
|
207
|
+
?? (gepaConfig.studentModel as string) ?? "anthropic/claude-sonnet-4-20250514";
|
|
208
|
+
const teacherModel = options.teacherModel ?? options.reflectionModel
|
|
209
|
+
?? (gepaConfig.teacherModel as string) ?? "anthropic/claude-opus-4-6";
|
|
210
|
+
const judgeModel = options.judgeModel
|
|
211
|
+
?? (gepaConfig.judgeModel as string) ?? studentModel;
|
|
212
|
+
const auto = options.auto ?? (gepaConfig.auto as "light" | "medium" | "heavy") ?? "light";
|
|
213
|
+
const threads = options.threads ?? (gepaConfig.threads as number) ?? 2;
|
|
214
|
+
const seed = options.seed ?? (gepaConfig.seed as number) ?? 42;
|
|
215
|
+
|
|
216
|
+
const allTargets = extractTargets(root);
|
|
217
|
+
let targets = options.target ? allTargets.filter(t => t.name === options.target) : allTargets;
|
|
218
|
+
if (options.target && targets.length === 0) throw new Error(`Target '${options.target}' not found. Available: ${allTargets.map(t => t.name).join(", ")}`);
|
|
219
|
+
|
|
220
|
+
console.log(`[gepa] Found ${targets.length} target(s) to optimize`);
|
|
221
|
+
console.log(`[gepa] Student: ${studentModel}, Teacher: ${teacherModel}, Judge: ${judgeModel}, Budget: ${auto}`);
|
|
222
|
+
|
|
223
|
+
const targetsFile = join(INPUT_DIR, "targets.json");
|
|
224
|
+
writeFileSync(targetsFile, JSON.stringify({ targets: targets.map(t => ({ name: t.name, type: t.type, content: t.content })) }, null, 2));
|
|
225
|
+
|
|
226
|
+
const outputFile = join(OUTPUT_DIR, "results.json");
|
|
227
|
+
const uvArgs = [
|
|
228
|
+
"run", "--project", scriptsDir, join(scriptsDir, "optimize.py"),
|
|
229
|
+
"--targets-file", targetsFile, "--wiki-dir", WIKI_DIR, "--output", outputFile,
|
|
230
|
+
"--model", studentModel,
|
|
231
|
+
"--reflection-model", teacherModel,
|
|
232
|
+
"--judge-model", judgeModel,
|
|
233
|
+
"--auto", auto,
|
|
234
|
+
"--threads", String(threads),
|
|
235
|
+
"--seed", String(seed),
|
|
236
|
+
...(options.maxMetricCalls ? ["--max-metric-calls", String(options.maxMetricCalls)] : []),
|
|
237
|
+
...(options.target ? ["--target", options.target] : []),
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
console.log(`[gepa] Running: uv ${uvArgs.slice(0, 6).join(" ")} ...`);
|
|
241
|
+
|
|
242
|
+
await new Promise<void>((resolve, reject) => {
|
|
243
|
+
const proc = spawn("uv", uvArgs, { cwd: scriptsDir, env: { ...process.env, ...getAuthEnvVars() }, stdio: ["pipe", "inherit", "inherit"] });
|
|
244
|
+
proc.on("close", c => c === 0 ? resolve() : reject(new Error(`uv exited with code ${c}`)));
|
|
245
|
+
proc.on("error", e => reject(new Error(`Failed to spawn uv: ${e.message}`)));
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
if (!existsSync(outputFile)) throw new Error("Output file not created");
|
|
249
|
+
const output: GEPAOutput = JSON.parse(readFileSync(outputFile, "utf-8"));
|
|
250
|
+
|
|
251
|
+
for (const r of output.results) {
|
|
252
|
+
if (r.status === "success" && r.optimized_instruction) {
|
|
253
|
+
mkdirSync(OPTIMIZED_DIR, { recursive: true });
|
|
254
|
+
writeFileSync(join(OPTIMIZED_DIR, `${r.target}.txt`), r.optimized_instruction, "utf-8");
|
|
255
|
+
const hDir = join(HISTORY_DIR, r.target); mkdirSync(hDir, { recursive: true });
|
|
256
|
+
writeFileSync(join(hDir, `${new Date().toISOString().replace(/[:.]/g, "-")}.json`), JSON.stringify(r, null, 2));
|
|
257
|
+
console.log(`[gepa] Saved optimized: ${r.target}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return output;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// -- Query functions ---------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
export function listOptimized(): { name: string; score: number; timestamp: string; model: string }[] {
|
|
266
|
+
if (!existsSync(OPTIMIZED_DIR)) return [];
|
|
267
|
+
const results: { name: string; score: number; timestamp: string; model: string }[] = [];
|
|
268
|
+
for (const f of readdirSync(OPTIMIZED_DIR).filter(f => f.endsWith(".txt"))) {
|
|
269
|
+
const name = f.replace(".txt", "");
|
|
270
|
+
let score = 0, timestamp = "", model = "";
|
|
271
|
+
try {
|
|
272
|
+
const entries = readdirSync(join(HISTORY_DIR, name)).filter(f => f.endsWith(".json")).sort().reverse();
|
|
273
|
+
if (entries[0]) { const d = JSON.parse(readFileSync(join(HISTORY_DIR, name, entries[0]), "utf-8")); score = d.optimized_score ?? 0; timestamp = d.timestamp ?? ""; model = d.model ?? ""; }
|
|
274
|
+
} catch {}
|
|
275
|
+
results.push({ name, score, timestamp, model });
|
|
276
|
+
}
|
|
277
|
+
return results;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function compareTarget(targetName: string): { original: string; optimized: string } | null {
|
|
281
|
+
const target = extractTargets().find(t => t.name === targetName);
|
|
282
|
+
if (!target) return null;
|
|
283
|
+
try {
|
|
284
|
+
const opt = readFileSync(join(OPTIMIZED_DIR, `${targetName}.txt`), "utf-8");
|
|
285
|
+
return { original: target.content, optimized: opt };
|
|
286
|
+
} catch { return null; }
|
|
287
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
You are a knowledge extraction specialist for technical project wikis. Your task is to analyze coding session conversations and capture ALL project-relevant knowledge into wiki pages.
|
|
2
|
+
|
|
3
|
+
## Input Format
|
|
4
|
+
You will receive:
|
|
5
|
+
- task_context: Brief description of the coding session topic and project
|
|
6
|
+
- session-conversation: The actual conversation content to analyze
|
|
7
|
+
|
|
8
|
+
## Wiki Page Structure Requirements
|
|
9
|
+
Create wiki pages using this exact structure:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
# Page Title (kebab-case for technical terms)
|
|
13
|
+
|
|
14
|
+
## Topic
|
|
15
|
+
Brief description of what this page covers (1-2 sentences)
|
|
16
|
+
|
|
17
|
+
## Key Facts
|
|
18
|
+
- Bullet points with specific technical details
|
|
19
|
+
- Include paths, configs, server details, commands
|
|
20
|
+
- Use **bold** for important terms/concepts
|
|
21
|
+
- Capture operational facts - they're often most valuable
|
|
22
|
+
|
|
23
|
+
## Insights (Optional)
|
|
24
|
+
- Analysis of implications, patterns, or broader context
|
|
25
|
+
- How this connects to larger architectural patterns
|
|
26
|
+
- Lessons learned or best practices identified
|
|
27
|
+
|
|
28
|
+
## Connections
|
|
29
|
+
- [[Related Wiki Page]] - Brief description of relationship
|
|
30
|
+
- Use kebab-case for technical project names
|
|
31
|
+
- Include both specific project pages and general concept pages
|
|
32
|
+
|
|
33
|
+
## Sources
|
|
34
|
+
- Session file path or conversation source
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## PARA Compliance Requirements
|
|
38
|
+
- Use kebab-case for all project names and technical terms in page titles and connections
|
|
39
|
+
- Technical reference material should be categorized as Resources
|
|
40
|
+
- Include scope indicators through consistent naming and cross-references
|
|
41
|
+
|
|
42
|
+
## Knowledge Capture Bias
|
|
43
|
+
**Capture bias: when in doubt, capture it.**
|
|
44
|
+
- Small operational facts (paths, configs, commands) are often most valuable
|
|
45
|
+
- Technical architecture details, even if seemingly minor
|
|
46
|
+
- API findings, verification results, compatibility issues
|
|
47
|
+
- Extension systems, plugin architectures, integration points
|
|
48
|
+
- Security considerations and safeguards
|
|
49
|
+
|
|
50
|
+
## Wiki Management Strategy
|
|
51
|
+
1. **Search first**: Use wiki_query to find existing pages on the topic
|
|
52
|
+
2. **Update over create**: If matching page exists, use wiki_read then wiki_write with exact slug to update
|
|
53
|
+
3. **Create only if needed**: Only make new pages if wiki_query finds no relevant results
|
|
54
|
+
4. **Never pass indexContent**: The index auto-rebuilds from pages on disk
|
|
55
|
+
5. **Always include sources**: Add session file path to Sources section
|
|
56
|
+
|
|
57
|
+
## Special Project Context
|
|
58
|
+
Based on the examples, you're working with:
|
|
59
|
+
- **pi-mono/pi-para projects**: Modular knowledge management and agent systems
|
|
60
|
+
- **Extension architectures**: Plugin-based systems for AI agents and knowledge tools
|
|
61
|
+
- **Quarto integration**: .qmd files as first-class knowledge artifacts
|
|
62
|
+
- **API verification**: Testing and validation processes for extensions
|
|
63
|
+
- **Security considerations**: Prompt injection safeguards in agent systems
|
|
64
|
+
|
|
65
|
+
Only respond with "nothing to capture" if the session contains purely greetings or completely off-topic chitchat with zero project knowledge.
|