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.
Files changed (198) hide show
  1. package/CHANGELOG.md +214 -0
  2. package/README.md +305 -0
  3. package/SETUP.md +171 -0
  4. package/dist/capture.d.ts +34 -0
  5. package/dist/capture.d.ts.map +1 -0
  6. package/dist/capture.js +461 -0
  7. package/dist/capture.js.map +1 -0
  8. package/dist/cli.d.ts +15 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +405 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/commands.d.ts +15 -0
  13. package/dist/commands.d.ts.map +1 -0
  14. package/dist/commands.js +786 -0
  15. package/dist/commands.js.map +1 -0
  16. package/dist/context.d.ts +61 -0
  17. package/dist/context.d.ts.map +1 -0
  18. package/dist/context.js +289 -0
  19. package/dist/context.js.map +1 -0
  20. package/dist/daemon.d.ts +49 -0
  21. package/dist/daemon.d.ts.map +1 -0
  22. package/dist/daemon.js +226 -0
  23. package/dist/daemon.js.map +1 -0
  24. package/dist/frontmatter.d.ts +59 -0
  25. package/dist/frontmatter.d.ts.map +1 -0
  26. package/dist/frontmatter.js +170 -0
  27. package/dist/frontmatter.js.map +1 -0
  28. package/dist/gepa/dataset.d.ts +36 -0
  29. package/dist/gepa/dataset.d.ts.map +1 -0
  30. package/dist/gepa/dataset.js +148 -0
  31. package/dist/gepa/dataset.js.map +1 -0
  32. package/dist/gepa/evaluator.d.ts +51 -0
  33. package/dist/gepa/evaluator.d.ts.map +1 -0
  34. package/dist/gepa/evaluator.js +317 -0
  35. package/dist/gepa/evaluator.js.map +1 -0
  36. package/dist/gepa/index.d.ts +66 -0
  37. package/dist/gepa/index.d.ts.map +1 -0
  38. package/dist/gepa/index.js +240 -0
  39. package/dist/gepa/index.js.map +1 -0
  40. package/dist/gepa/llm.d.ts +35 -0
  41. package/dist/gepa/llm.d.ts.map +1 -0
  42. package/dist/gepa/llm.js +268 -0
  43. package/dist/gepa/llm.js.map +1 -0
  44. package/dist/gepa/optimized/capture-prompt.txt +65 -0
  45. package/dist/gepa/optimizer.d.ts +29 -0
  46. package/dist/gepa/optimizer.d.ts.map +1 -0
  47. package/dist/gepa/optimizer.js +249 -0
  48. package/dist/gepa/optimizer.js.map +1 -0
  49. package/dist/gepa/targets.d.ts +39 -0
  50. package/dist/gepa/targets.d.ts.map +1 -0
  51. package/dist/gepa/targets.js +326 -0
  52. package/dist/gepa/targets.js.map +1 -0
  53. package/dist/gepa/types.d.ts +168 -0
  54. package/dist/gepa/types.d.ts.map +1 -0
  55. package/dist/gepa/types.js +124 -0
  56. package/dist/gepa/types.js.map +1 -0
  57. package/dist/index.d.ts +21 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +294 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/ingest.d.ts +88 -0
  62. package/dist/ingest.d.ts.map +1 -0
  63. package/dist/ingest.js +105 -0
  64. package/dist/ingest.js.map +1 -0
  65. package/dist/link-discovery.d.ts +40 -0
  66. package/dist/link-discovery.d.ts.map +1 -0
  67. package/dist/link-discovery.js +122 -0
  68. package/dist/link-discovery.js.map +1 -0
  69. package/dist/link-utils.d.ts +30 -0
  70. package/dist/link-utils.d.ts.map +1 -0
  71. package/dist/link-utils.js +145 -0
  72. package/dist/link-utils.js.map +1 -0
  73. package/dist/lint.d.ts +39 -0
  74. package/dist/lint.d.ts.map +1 -0
  75. package/dist/lint.js +827 -0
  76. package/dist/lint.js.map +1 -0
  77. package/dist/maintainer.d.ts +24 -0
  78. package/dist/maintainer.d.ts.map +1 -0
  79. package/dist/maintainer.js +358 -0
  80. package/dist/maintainer.js.map +1 -0
  81. package/dist/processor.d.ts +14 -0
  82. package/dist/processor.d.ts.map +1 -0
  83. package/dist/processor.js +361 -0
  84. package/dist/processor.js.map +1 -0
  85. package/dist/query.d.ts +69 -0
  86. package/dist/query.d.ts.map +1 -0
  87. package/dist/query.js +107 -0
  88. package/dist/query.js.map +1 -0
  89. package/dist/raw.d.ts +57 -0
  90. package/dist/raw.d.ts.map +1 -0
  91. package/dist/raw.js +219 -0
  92. package/dist/raw.js.map +1 -0
  93. package/dist/redact.d.ts +21 -0
  94. package/dist/redact.d.ts.map +1 -0
  95. package/dist/redact.js +143 -0
  96. package/dist/redact.js.map +1 -0
  97. package/dist/scope.d.ts +54 -0
  98. package/dist/scope.d.ts.map +1 -0
  99. package/dist/scope.js +161 -0
  100. package/dist/scope.js.map +1 -0
  101. package/dist/scripts/bulk-link-fix.d.ts +9 -0
  102. package/dist/scripts/bulk-link-fix.d.ts.map +1 -0
  103. package/dist/scripts/bulk-link-fix.js +106 -0
  104. package/dist/scripts/bulk-link-fix.js.map +1 -0
  105. package/dist/session-tools.d.ts +33 -0
  106. package/dist/session-tools.d.ts.map +1 -0
  107. package/dist/session-tools.js +211 -0
  108. package/dist/session-tools.js.map +1 -0
  109. package/dist/state.d.ts +54 -0
  110. package/dist/state.d.ts.map +1 -0
  111. package/dist/state.js +176 -0
  112. package/dist/state.js.map +1 -0
  113. package/dist/store.d.ts +56 -0
  114. package/dist/store.d.ts.map +1 -0
  115. package/dist/store.js +227 -0
  116. package/dist/store.js.map +1 -0
  117. package/dist/summarize.d.ts +56 -0
  118. package/dist/summarize.d.ts.map +1 -0
  119. package/dist/summarize.js +231 -0
  120. package/dist/summarize.js.map +1 -0
  121. package/dist/tag-registry.d.ts +56 -0
  122. package/dist/tag-registry.d.ts.map +1 -0
  123. package/dist/tag-registry.js +146 -0
  124. package/dist/tag-registry.js.map +1 -0
  125. package/dist/templates/prompts.d.ts +63 -0
  126. package/dist/templates/prompts.d.ts.map +1 -0
  127. package/dist/templates/prompts.js +260 -0
  128. package/dist/templates/prompts.js.map +1 -0
  129. package/dist/tools.d.ts +26 -0
  130. package/dist/tools.d.ts.map +1 -0
  131. package/dist/tools.js +1033 -0
  132. package/dist/tools.js.map +1 -0
  133. package/dist/watcher.d.ts +25 -0
  134. package/dist/watcher.d.ts.map +1 -0
  135. package/dist/watcher.js +84 -0
  136. package/dist/watcher.js.map +1 -0
  137. package/dist/webui/client/dist/index.html +1885 -0
  138. package/dist/webui/server.d.ts +31 -0
  139. package/dist/webui/server.d.ts.map +1 -0
  140. package/dist/webui/server.js +566 -0
  141. package/dist/webui/server.js.map +1 -0
  142. package/dist/wiki.d.ts +103 -0
  143. package/dist/wiki.d.ts.map +1 -0
  144. package/dist/wiki.js +428 -0
  145. package/dist/wiki.js.map +1 -0
  146. package/package.json +142 -0
  147. package/scripts/gepa/__pycache__/dataset.cpython-313.pyc +0 -0
  148. package/scripts/gepa/__pycache__/lm_providers.cpython-313.pyc +0 -0
  149. package/scripts/gepa/__pycache__/metric.cpython-313.pyc +0 -0
  150. package/scripts/gepa/__pycache__/program.cpython-313.pyc +0 -0
  151. package/scripts/gepa/__pycache__/wiki_reader.cpython-313.pyc +0 -0
  152. package/scripts/gepa/dataset.py +192 -0
  153. package/scripts/gepa/lm_providers.py +472 -0
  154. package/scripts/gepa/metric.py +279 -0
  155. package/scripts/gepa/optimize.py +367 -0
  156. package/scripts/gepa/program.py +99 -0
  157. package/scripts/gepa/pyproject.toml +19 -0
  158. package/scripts/gepa/run-all.sh +53 -0
  159. package/scripts/gepa/uv.lock +2241 -0
  160. package/scripts/gepa/wiki_reader.py +113 -0
  161. package/skills/para/SKILL.md +77 -0
  162. package/skills/setup/SKILL.md +20 -0
  163. package/src/capture.ts +563 -0
  164. package/src/cli.ts +416 -0
  165. package/src/commands.ts +878 -0
  166. package/src/context.ts +390 -0
  167. package/src/daemon.ts +288 -0
  168. package/src/frontmatter.ts +219 -0
  169. package/src/gepa/index.ts +287 -0
  170. package/src/gepa/optimized/capture-prompt.txt +65 -0
  171. package/src/index.ts +384 -0
  172. package/src/ingest.ts +177 -0
  173. package/src/link-discovery.ts +170 -0
  174. package/src/link-utils.ts +169 -0
  175. package/src/lint.ts +1018 -0
  176. package/src/maintainer.ts +404 -0
  177. package/src/processor.ts +415 -0
  178. package/src/python/__pycache__/minimax_lm.cpython-312.pyc +0 -0
  179. package/src/python/minimax_lm.py +198 -0
  180. package/src/python/rlm_capture.py +215 -0
  181. package/src/query.ts +148 -0
  182. package/src/raw.ts +276 -0
  183. package/src/redact.ts +159 -0
  184. package/src/scope.ts +214 -0
  185. package/src/scripts/bulk-link-fix.ts +126 -0
  186. package/src/session-tools.ts +260 -0
  187. package/src/state.ts +271 -0
  188. package/src/store.ts +288 -0
  189. package/src/summarize.ts +273 -0
  190. package/src/tag-registry.ts +157 -0
  191. package/src/templates/index.md +17 -0
  192. package/src/templates/prompts.ts +274 -0
  193. package/src/templates/schema.md +116 -0
  194. package/src/tools.ts +1293 -0
  195. package/src/watcher.ts +101 -0
  196. package/src/webui/client/dist/index.html +1885 -0
  197. package/src/webui/server.ts +765 -0
  198. 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.