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,170 @@
1
+ /**
2
+ * Link discovery — finds related wiki pages and adds [[wikilinks]].
3
+ *
4
+ * Uses BM25 search to find pages that are semantically related but
5
+ * not yet linked. Runs as a periodic daemon job or on-demand.
6
+ *
7
+ * Strategy:
8
+ * 1. For each page, search the wiki using its title + key terms
9
+ * 2. Filter results to pages not already linked
10
+ * 3. If a related page scores above threshold, add it to ## Connections
11
+ * 4. Sync frontmatter.links
12
+ */
13
+
14
+ import {
15
+ listPages,
16
+ readPage,
17
+ writePage,
18
+ } from "./wiki.js";
19
+ import type { WikiPage } from "./wiki.js";
20
+ import { searchWiki } from "./store.js";
21
+ import type { QMDStore } from "./store.js";
22
+ import { extractWikilinks, syncFrontmatterLinks } from "./link-utils.js";
23
+
24
+ // -- Types -------------------------------------------------------------------
25
+
26
+ export interface LinkDiscoveryResult {
27
+ pagesUpdated: number;
28
+ linksAdded: number;
29
+ discoveries: Array<{
30
+ page: string;
31
+ newLinks: string[];
32
+ }>;
33
+ }
34
+
35
+ export interface LinkDiscoveryOptions {
36
+ /** Minimum BM25 score to consider a page related (0-1). Default 0.3 */
37
+ minScore?: number;
38
+ /** Max new links to add per page per run. Default 5 */
39
+ maxLinksPerPage?: number;
40
+ /** Dry run — report but don't write. Default false */
41
+ dryRun?: boolean;
42
+ }
43
+
44
+ // -- Discovery ---------------------------------------------------------------
45
+
46
+ /**
47
+ * Discover and add missing [[wikilinks]] between related pages.
48
+ *
49
+ * For each page:
50
+ * 1. Search wiki using the page's title as query
51
+ * 2. Find high-scoring pages not already linked
52
+ * 3. Add them to ## Connections section
53
+ * 4. Sync frontmatter.links
54
+ */
55
+ export async function discoverLinks(
56
+ wikiDir: string,
57
+ store: QMDStore,
58
+ options?: LinkDiscoveryOptions,
59
+ ): Promise<LinkDiscoveryResult> {
60
+ const minScore = options?.minScore ?? 0.3;
61
+ const maxLinksPerPage = options?.maxLinksPerPage ?? 5;
62
+ const dryRun = options?.dryRun ?? false;
63
+
64
+ const refs = await listPages(wikiDir);
65
+ const pages: WikiPage[] = [];
66
+ for (const ref of refs) {
67
+ const page = await readPage(wikiDir, ref.category, ref.slug);
68
+ if (page) pages.push(page);
69
+ }
70
+
71
+ const allSlugs = new Set(pages.map(p => p.slug));
72
+ const result: LinkDiscoveryResult = {
73
+ pagesUpdated: 0,
74
+ linksAdded: 0,
75
+ discoveries: [],
76
+ };
77
+
78
+ for (const page of pages) {
79
+ // Get existing outgoing links
80
+ const existingLinks = new Set(extractWikilinks(page.body));
81
+ // Also include frontmatter links
82
+ for (const l of page.frontmatter.links) existingLinks.add(l);
83
+
84
+ // Search for related pages using title
85
+ const query = page.frontmatter.title;
86
+ let related;
87
+ try {
88
+ related = await searchWiki(store, query, { limit: 10 });
89
+ } catch {
90
+ continue; // search failure — skip this page
91
+ }
92
+
93
+ // Filter: not self, not already linked, above score threshold
94
+ const candidates = related
95
+ .filter(r =>
96
+ r.page.slug !== page.slug &&
97
+ !existingLinks.has(r.page.slug) &&
98
+ r.score >= minScore
99
+ )
100
+ .slice(0, maxLinksPerPage);
101
+
102
+ if (candidates.length === 0) continue;
103
+
104
+ const newLinks = candidates.map(c => c.page.slug);
105
+
106
+ // Build ## Connections addition
107
+ const connectionsLines = newLinks.map(slug => {
108
+ const targetPage = pages.find(p => p.slug === slug);
109
+ const title = targetPage?.frontmatter.title ?? slug;
110
+ return `- [[${slug}]] — ${title}`;
111
+ });
112
+
113
+ // Update page body
114
+ let newBody = page.body;
115
+ const hasConnections = /^## Connections/m.test(newBody);
116
+
117
+ if (hasConnections) {
118
+ // Append to existing Connections section
119
+ // Find the end of the Connections section (next ## heading or EOF)
120
+ const connectionsIdx = newBody.indexOf("## Connections");
121
+ const afterConnections = newBody.indexOf("\n## ", connectionsIdx + 1);
122
+ const insertPos = afterConnections === -1 ? newBody.length : afterConnections;
123
+
124
+ // Insert before the next section
125
+ const insertion = "\n" + connectionsLines.join("\n");
126
+ newBody = newBody.slice(0, insertPos) + insertion + newBody.slice(insertPos);
127
+ } else {
128
+ // Add new Connections section at the end (before ## Sources or ## Open Questions if they exist)
129
+ const sourcesIdx = newBody.indexOf("\n## Sources");
130
+ const questionsIdx = newBody.indexOf("\n## Open Questions");
131
+ const insertBefore = Math.min(
132
+ sourcesIdx === -1 ? Infinity : sourcesIdx,
133
+ questionsIdx === -1 ? Infinity : questionsIdx,
134
+ );
135
+
136
+ const section = "\n\n## Connections\n" + connectionsLines.join("\n");
137
+
138
+ if (insertBefore === Infinity) {
139
+ newBody = newBody.trimEnd() + section + "\n";
140
+ } else {
141
+ newBody = newBody.slice(0, insertBefore) + section + newBody.slice(insertBefore);
142
+ }
143
+ }
144
+
145
+ // Sync frontmatter.links
146
+ const updatedLinks = syncFrontmatterLinks(newBody);
147
+
148
+ if (!dryRun) {
149
+ const updatedPage: WikiPage = {
150
+ ...page,
151
+ body: newBody,
152
+ frontmatter: {
153
+ ...page.frontmatter,
154
+ links: updatedLinks,
155
+ updated: new Date().toISOString(),
156
+ },
157
+ };
158
+ await writePage(wikiDir, updatedPage);
159
+ }
160
+
161
+ result.pagesUpdated++;
162
+ result.linksAdded += newLinks.length;
163
+ result.discoveries.push({
164
+ page: `${page.category}/${page.slug}`,
165
+ newLinks,
166
+ });
167
+ }
168
+
169
+ return result;
170
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Shared wikilink utilities — extraction, auto-linking, and sync.
3
+ *
4
+ * Centralizes the [[wikilink]] logic that was duplicated across
5
+ * tools.ts, lint.ts, processor.ts, and webui/server.ts.
6
+ */
7
+
8
+ // -- Wikilink extraction ----------------------------------------------------
9
+
10
+ const WIKILINK_RE = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g;
11
+
12
+ /** Extract all [[wikilink]] targets from markdown body. Returns deduplicated slugs. */
13
+ export function extractWikilinks(body: string): string[] {
14
+ const links: string[] = [];
15
+ let m: RegExpExecArray | null;
16
+ const re = new RegExp(WIKILINK_RE.source, WIKILINK_RE.flags);
17
+ while ((m = re.exec(body)) !== null) {
18
+ links.push(m[1].trim());
19
+ }
20
+ return [...new Set(links)];
21
+ }
22
+
23
+ /** Remove [[target]] (and optional display text) from body text. */
24
+ export function removeWikilink(body: string, target: string): string {
25
+ const escaped = target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26
+ const re = new RegExp(`\\[\\[${escaped}(?:\\|[^\\]]+)?\\]\\]`, "g");
27
+ return body.replace(re, "");
28
+ }
29
+
30
+ // -- Auto-linking -----------------------------------------------------------
31
+
32
+ /**
33
+ * Identify regions in markdown body that should NOT be auto-linked.
34
+ * Returns array of [start, end] ranges to skip.
35
+ *
36
+ * Protected regions:
37
+ * - Fenced code blocks (``` ... ```)
38
+ * - Inline code (`...`)
39
+ * - Existing [[wikilinks]]
40
+ * - URLs (http:// https://)
41
+ * - YAML frontmatter lines referencing slugs
42
+ */
43
+ function findProtectedRanges(body: string): Array<[number, number]> {
44
+ const ranges: Array<[number, number]> = [];
45
+
46
+ // Fenced code blocks
47
+ const fencedRe = /```[\s\S]*?```/g;
48
+ let m: RegExpExecArray | null;
49
+ while ((m = fencedRe.exec(body)) !== null) {
50
+ ranges.push([m.index, m.index + m[0].length]);
51
+ }
52
+
53
+ // Inline code
54
+ const inlineCodeRe = /`[^`]+`/g;
55
+ while ((m = inlineCodeRe.exec(body)) !== null) {
56
+ ranges.push([m.index, m.index + m[0].length]);
57
+ }
58
+
59
+ // Existing [[wikilinks]]
60
+ const wlRe = /\[\[[^\]]+\]\]/g;
61
+ while ((m = wlRe.exec(body)) !== null) {
62
+ ranges.push([m.index, m.index + m[0].length]);
63
+ }
64
+
65
+ // URLs
66
+ const urlRe = /https?:\/\/[^\s)>]+/g;
67
+ while ((m = urlRe.exec(body)) !== null) {
68
+ ranges.push([m.index, m.index + m[0].length]);
69
+ }
70
+
71
+ // Markdown headings (don't wrap slugs in heading lines)
72
+ const headingRe = /^#{1,6}\s+.*$/gm;
73
+ while ((m = headingRe.exec(body)) !== null) {
74
+ ranges.push([m.index, m.index + m[0].length]);
75
+ }
76
+
77
+ return ranges.sort((a, b) => a[0] - b[0]);
78
+ }
79
+
80
+ /**
81
+ * Check if a position falls inside any protected range.
82
+ */
83
+ function isProtected(pos: number, end: number, ranges: Array<[number, number]>): boolean {
84
+ for (const [rStart, rEnd] of ranges) {
85
+ if (rStart > end) break; // ranges are sorted
86
+ if (pos >= rStart && end <= rEnd) return true;
87
+ if (pos < rEnd && end > rStart) return true; // overlap
88
+ }
89
+ return false;
90
+ }
91
+
92
+ /**
93
+ * Auto-link page slug mentions in body text with [[wikilinks]].
94
+ *
95
+ * - Wraps unlinked slug mentions in [[slug]]
96
+ * - Skips code blocks, URLs, existing [[wikilinks]], headings
97
+ * - Matches longest slugs first to avoid partial matches
98
+ * - Uses word-boundary matching (won't match "pi-para" inside "pi-para-daemon")
99
+ * - Only links each slug once per page (first occurrence) to avoid noise
100
+ *
101
+ * @param body - Page body markdown
102
+ * @param allSlugs - Set of all existing page slugs
103
+ * @param ownSlug - This page's own slug (excluded from auto-linking)
104
+ * @returns Updated body with [[wikilinks]] inserted
105
+ */
106
+ export function autoLinkSlugs(
107
+ body: string,
108
+ allSlugs: Set<string>,
109
+ ownSlug?: string,
110
+ ): string {
111
+ if (allSlugs.size === 0) return body;
112
+
113
+ // Sort slugs longest-first so "pi-para-daemon" is matched before "pi-para"
114
+ const sortedSlugs = [...allSlugs]
115
+ .filter(s => s !== ownSlug)
116
+ .sort((a, b) => b.length - a.length);
117
+
118
+ const protectedRanges = findProtectedRanges(body);
119
+ const linkedSlugs = new Set(extractWikilinks(body));
120
+
121
+ let result = body;
122
+
123
+ for (const slug of sortedSlugs) {
124
+ // Skip if already linked
125
+ if (linkedSlugs.has(slug)) continue;
126
+
127
+ // Build regex: match slug at word boundaries
128
+ // Slug chars are [a-z0-9-], so word boundary = not preceded/followed by these chars
129
+ const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
130
+ // Use negative lookbehind/lookahead for slug characters
131
+ const re = new RegExp(`(?<![a-z0-9-])${escaped}(?![a-z0-9-])`, "g");
132
+
133
+ // Re-compute protected ranges for current state of result
134
+ const currentProtected = findProtectedRanges(result);
135
+
136
+ let match: RegExpExecArray | null;
137
+ let linked = false;
138
+
139
+ // Reset regex
140
+ re.lastIndex = 0;
141
+ while ((match = re.exec(result)) !== null) {
142
+ if (linked) break; // only link first occurrence
143
+
144
+ const pos = match.index;
145
+ const end = pos + match[0].length;
146
+
147
+ if (isProtected(pos, end, currentProtected)) continue;
148
+
149
+ // Replace this occurrence with [[slug]]
150
+ const before = result.slice(0, pos);
151
+ const after = result.slice(end);
152
+ const replacement = `[[${slug}]]`;
153
+ result = before + replacement + after;
154
+ linked = true;
155
+
156
+ // Update regex index to account for replacement length difference
157
+ re.lastIndex = pos + replacement.length;
158
+ }
159
+ }
160
+
161
+ return result;
162
+ }
163
+
164
+ /**
165
+ * Sync frontmatter.links to match all [[wikilinks]] found in body text.
166
+ */
167
+ export function syncFrontmatterLinks(body: string): string[] {
168
+ return extractWikilinks(body);
169
+ }