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,88 @@
1
+ /**
2
+ * Ingest pipeline — processes sources and integrates knowledge into the wiki.
3
+ *
4
+ * Resolves source content (URL, file, text), saves raw copy to the vault,
5
+ * and returns structured data for the caller (tool or standalone agent)
6
+ * to pass to the LLM for synthesis into wiki pages.
7
+ */
8
+ import type { ParaCategory, PageRef, LogEntry } from "./wiki.js";
9
+ import type { ProjectScope } from "./scope.js";
10
+ export interface IngestOptions {
11
+ source: string;
12
+ sourceType?: "url" | "file" | "text";
13
+ scope?: string[];
14
+ category?: ParaCategory;
15
+ }
16
+ export interface IngestReport {
17
+ pagesCreated: PageRef[];
18
+ pagesUpdated: PageRef[];
19
+ logEntry: LogEntry;
20
+ }
21
+ /** Structure returned by LLM during ingest. */
22
+ export interface IngestResult {
23
+ newPages: Array<{
24
+ category: ParaCategory;
25
+ slug: string;
26
+ title: string;
27
+ scope: string[];
28
+ tags: string[];
29
+ body: string;
30
+ }>;
31
+ updatedPages: Array<{
32
+ category: ParaCategory;
33
+ slug: string;
34
+ appendOrReplace: "append" | "replace";
35
+ body: string;
36
+ }>;
37
+ indexUpdate: string;
38
+ logSummary: string;
39
+ }
40
+ /** Resolved source data from the ingest pipeline. */
41
+ export interface ResolvedSource {
42
+ /** Original source string (URL, path, or text) */
43
+ source: string;
44
+ /** Detected or explicit source type */
45
+ sourceType: "url" | "file" | "text";
46
+ /** Fetched/read source content */
47
+ content: string;
48
+ /** Path in raw/ vault (undefined for inline text) */
49
+ rawPath: string | undefined;
50
+ /** Schema.md content for LLM context */
51
+ schema: string;
52
+ /** Index.md content for LLM context */
53
+ index: string;
54
+ /** Scope tags to assign */
55
+ scopeTags: string[];
56
+ /** Scope name */
57
+ scopeName: string;
58
+ /** Category hint (if provided) */
59
+ categoryHint: ParaCategory | undefined;
60
+ }
61
+ /** Detect source type from the source string. */
62
+ export declare function detectSourceType(source: string): "url" | "file" | "text";
63
+ /** Fetch content from a URL. */
64
+ export declare function fetchUrl(url: string): Promise<string>;
65
+ /** Read content from a file path. */
66
+ export declare function readFileSource(filePath: string): Promise<string>;
67
+ /**
68
+ * Resolve a source and prepare it for LLM synthesis.
69
+ *
70
+ * This is the reusable library function. It:
71
+ * 1. Detects or uses the explicit source type
72
+ * 2. Fetches URL / reads file / uses text directly
73
+ * 3. Saves a raw copy to the vault (for non-text sources)
74
+ * 4. Reads schema.md and index.md for LLM context
75
+ * 5. Returns everything the caller needs to build an LLM prompt
76
+ *
77
+ * The caller (tool or standalone agent) assembles the prompt and
78
+ * passes it to the LLM, which calls wiki_write to create pages.
79
+ */
80
+ export declare function resolveSource(wikiDir: string, options: IngestOptions, scope: ProjectScope): Promise<ResolvedSource>;
81
+ /** Maximum source content length before truncation. */
82
+ export declare const MAX_SOURCE_LENGTH = 50000;
83
+ /**
84
+ * Truncate source content if it exceeds the limit.
85
+ * Returns the truncated content with a marker.
86
+ */
87
+ export declare function truncateSource(content: string, maxLength?: number): string;
88
+ //# sourceMappingURL=ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../src/ingest.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,YAAY,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,OAAO,EAAE,CAAC;IACxB,YAAY,EAAE,OAAO,EAAE,CAAC;IACxB,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,+CAA+C;AAC/C,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,KAAK,CAAC;QACd,QAAQ,EAAE,YAAY,CAAC;QACvB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,YAAY,EAAE,KAAK,CAAC;QAClB,QAAQ,EAAE,YAAY,CAAC;QACvB,IAAI,EAAE,MAAM,CAAC;QACb,eAAe,EAAE,QAAQ,GAAG,SAAS,CAAC;QACtC,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,UAAU,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;IACpC,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,iBAAiB;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,YAAY,EAAE,YAAY,GAAG,SAAS,CAAC;CACxC;AAID,iDAAiD;AACjD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAQxE;AAED,gCAAgC;AAChC,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAM3D;AAED,qCAAqC;AACrC,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGtE;AAID;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,cAAc,CAAC,CA6CzB;AAED,uDAAuD;AACvD,eAAO,MAAM,iBAAiB,QAAS,CAAC;AAExC;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,GAAE,MAA0B,GAAG,MAAM,CAG7F"}
package/dist/ingest.js ADDED
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Ingest pipeline — processes sources and integrates knowledge into the wiki.
3
+ *
4
+ * Resolves source content (URL, file, text), saves raw copy to the vault,
5
+ * and returns structured data for the caller (tool or standalone agent)
6
+ * to pass to the LLM for synthesis into wiki pages.
7
+ */
8
+ import { readFile } from "node:fs/promises";
9
+ import { readSchema, readIndex } from "./wiki.js";
10
+ import { saveRawSource } from "./raw.js";
11
+ // -- Source resolution -------------------------------------------------------
12
+ /** Detect source type from the source string. */
13
+ export function detectSourceType(source) {
14
+ if (/^https?:\/\//i.test(source))
15
+ return "url";
16
+ if (/^\/|^\.\/|^~\/|^[a-zA-Z]:\\/.test(source))
17
+ return "file";
18
+ // Heuristic: if it contains newlines or is very long, treat as text
19
+ if (source.includes("\n") || source.length > 500)
20
+ return "text";
21
+ // Check for file-like paths (has extension)
22
+ if (/\.\w{1,10}$/.test(source) && !source.includes(" "))
23
+ return "file";
24
+ return "text";
25
+ }
26
+ /** Fetch content from a URL. */
27
+ export async function fetchUrl(url) {
28
+ const response = await fetch(url);
29
+ if (!response.ok) {
30
+ throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
31
+ }
32
+ return response.text();
33
+ }
34
+ /** Read content from a file path. */
35
+ export async function readFileSource(filePath) {
36
+ const resolved = filePath.replace(/^~\//, `${process.env.HOME ?? ""}/`);
37
+ return readFile(resolved, "utf-8");
38
+ }
39
+ // -- Main pipeline -----------------------------------------------------------
40
+ /**
41
+ * Resolve a source and prepare it for LLM synthesis.
42
+ *
43
+ * This is the reusable library function. It:
44
+ * 1. Detects or uses the explicit source type
45
+ * 2. Fetches URL / reads file / uses text directly
46
+ * 3. Saves a raw copy to the vault (for non-text sources)
47
+ * 4. Reads schema.md and index.md for LLM context
48
+ * 5. Returns everything the caller needs to build an LLM prompt
49
+ *
50
+ * The caller (tool or standalone agent) assembles the prompt and
51
+ * passes it to the LLM, which calls wiki_write to create pages.
52
+ */
53
+ export async function resolveSource(wikiDir, options, scope) {
54
+ const sourceType = options.sourceType ?? detectSourceType(options.source);
55
+ // Resolve source content
56
+ let content;
57
+ switch (sourceType) {
58
+ case "url":
59
+ content = await fetchUrl(options.source);
60
+ break;
61
+ case "file":
62
+ content = await readFileSource(options.source);
63
+ break;
64
+ case "text":
65
+ content = options.source;
66
+ break;
67
+ }
68
+ // Save raw source (skip for inline text — it's ephemeral)
69
+ let rawPath;
70
+ if (sourceType !== "text") {
71
+ rawPath = await saveRawSource(wikiDir, {
72
+ type: sourceType,
73
+ content,
74
+ originalPath: options.source,
75
+ });
76
+ }
77
+ // Read schema and index for LLM context
78
+ const schema = await readSchema(wikiDir);
79
+ const index = await readIndex(wikiDir);
80
+ // Build scope info
81
+ const scopeTags = options.scope ?? scope.include;
82
+ return {
83
+ source: options.source,
84
+ sourceType,
85
+ content,
86
+ rawPath,
87
+ schema,
88
+ index,
89
+ scopeTags,
90
+ scopeName: scope.name,
91
+ categoryHint: options.category,
92
+ };
93
+ }
94
+ /** Maximum source content length before truncation. */
95
+ export const MAX_SOURCE_LENGTH = 50_000;
96
+ /**
97
+ * Truncate source content if it exceeds the limit.
98
+ * Returns the truncated content with a marker.
99
+ */
100
+ export function truncateSource(content, maxLength = MAX_SOURCE_LENGTH) {
101
+ if (content.length <= maxLength)
102
+ return content;
103
+ return content.slice(0, maxLength) + "\n\n[... truncated]";
104
+ }
105
+ //# sourceMappingURL=ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../src/ingest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
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
+ import type { QMDStore } from "./store.js";
14
+ export interface LinkDiscoveryResult {
15
+ pagesUpdated: number;
16
+ linksAdded: number;
17
+ discoveries: Array<{
18
+ page: string;
19
+ newLinks: string[];
20
+ }>;
21
+ }
22
+ export interface LinkDiscoveryOptions {
23
+ /** Minimum BM25 score to consider a page related (0-1). Default 0.3 */
24
+ minScore?: number;
25
+ /** Max new links to add per page per run. Default 5 */
26
+ maxLinksPerPage?: number;
27
+ /** Dry run — report but don't write. Default false */
28
+ dryRun?: boolean;
29
+ }
30
+ /**
31
+ * Discover and add missing [[wikilinks]] between related pages.
32
+ *
33
+ * For each page:
34
+ * 1. Search wiki using the page's title as query
35
+ * 2. Find high-scoring pages not already linked
36
+ * 3. Add them to ## Connections section
37
+ * 4. Sync frontmatter.links
38
+ */
39
+ export declare function discoverLinks(wikiDir: string, store: QMDStore, options?: LinkDiscoveryOptions): Promise<LinkDiscoveryResult>;
40
+ //# sourceMappingURL=link-discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-discovery.d.ts","sourceRoot":"","sources":["../src/link-discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAK3C,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,KAAK,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,oBAAoB;IACnC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sDAAsD;IACtD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAID;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,QAAQ,EACf,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,mBAAmB,CAAC,CA+G9B"}
@@ -0,0 +1,122 @@
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
+ import { listPages, readPage, writePage, } from "./wiki.js";
14
+ import { searchWiki } from "./store.js";
15
+ import { extractWikilinks, syncFrontmatterLinks } from "./link-utils.js";
16
+ // -- Discovery ---------------------------------------------------------------
17
+ /**
18
+ * Discover and add missing [[wikilinks]] between related pages.
19
+ *
20
+ * For each page:
21
+ * 1. Search wiki using the page's title as query
22
+ * 2. Find high-scoring pages not already linked
23
+ * 3. Add them to ## Connections section
24
+ * 4. Sync frontmatter.links
25
+ */
26
+ export async function discoverLinks(wikiDir, store, options) {
27
+ const minScore = options?.minScore ?? 0.3;
28
+ const maxLinksPerPage = options?.maxLinksPerPage ?? 5;
29
+ const dryRun = options?.dryRun ?? false;
30
+ const refs = await listPages(wikiDir);
31
+ const pages = [];
32
+ for (const ref of refs) {
33
+ const page = await readPage(wikiDir, ref.category, ref.slug);
34
+ if (page)
35
+ pages.push(page);
36
+ }
37
+ const allSlugs = new Set(pages.map(p => p.slug));
38
+ const result = {
39
+ pagesUpdated: 0,
40
+ linksAdded: 0,
41
+ discoveries: [],
42
+ };
43
+ for (const page of pages) {
44
+ // Get existing outgoing links
45
+ const existingLinks = new Set(extractWikilinks(page.body));
46
+ // Also include frontmatter links
47
+ for (const l of page.frontmatter.links)
48
+ existingLinks.add(l);
49
+ // Search for related pages using title
50
+ const query = page.frontmatter.title;
51
+ let related;
52
+ try {
53
+ related = await searchWiki(store, query, { limit: 10 });
54
+ }
55
+ catch {
56
+ continue; // search failure — skip this page
57
+ }
58
+ // Filter: not self, not already linked, above score threshold
59
+ const candidates = related
60
+ .filter(r => r.page.slug !== page.slug &&
61
+ !existingLinks.has(r.page.slug) &&
62
+ r.score >= minScore)
63
+ .slice(0, maxLinksPerPage);
64
+ if (candidates.length === 0)
65
+ continue;
66
+ const newLinks = candidates.map(c => c.page.slug);
67
+ // Build ## Connections addition
68
+ const connectionsLines = newLinks.map(slug => {
69
+ const targetPage = pages.find(p => p.slug === slug);
70
+ const title = targetPage?.frontmatter.title ?? slug;
71
+ return `- [[${slug}]] — ${title}`;
72
+ });
73
+ // Update page body
74
+ let newBody = page.body;
75
+ const hasConnections = /^## Connections/m.test(newBody);
76
+ if (hasConnections) {
77
+ // Append to existing Connections section
78
+ // Find the end of the Connections section (next ## heading or EOF)
79
+ const connectionsIdx = newBody.indexOf("## Connections");
80
+ const afterConnections = newBody.indexOf("\n## ", connectionsIdx + 1);
81
+ const insertPos = afterConnections === -1 ? newBody.length : afterConnections;
82
+ // Insert before the next section
83
+ const insertion = "\n" + connectionsLines.join("\n");
84
+ newBody = newBody.slice(0, insertPos) + insertion + newBody.slice(insertPos);
85
+ }
86
+ else {
87
+ // Add new Connections section at the end (before ## Sources or ## Open Questions if they exist)
88
+ const sourcesIdx = newBody.indexOf("\n## Sources");
89
+ const questionsIdx = newBody.indexOf("\n## Open Questions");
90
+ const insertBefore = Math.min(sourcesIdx === -1 ? Infinity : sourcesIdx, questionsIdx === -1 ? Infinity : questionsIdx);
91
+ const section = "\n\n## Connections\n" + connectionsLines.join("\n");
92
+ if (insertBefore === Infinity) {
93
+ newBody = newBody.trimEnd() + section + "\n";
94
+ }
95
+ else {
96
+ newBody = newBody.slice(0, insertBefore) + section + newBody.slice(insertBefore);
97
+ }
98
+ }
99
+ // Sync frontmatter.links
100
+ const updatedLinks = syncFrontmatterLinks(newBody);
101
+ if (!dryRun) {
102
+ const updatedPage = {
103
+ ...page,
104
+ body: newBody,
105
+ frontmatter: {
106
+ ...page.frontmatter,
107
+ links: updatedLinks,
108
+ updated: new Date().toISOString(),
109
+ },
110
+ };
111
+ await writePage(wikiDir, updatedPage);
112
+ }
113
+ result.pagesUpdated++;
114
+ result.linksAdded += newLinks.length;
115
+ result.discoveries.push({
116
+ page: `${page.category}/${page.slug}`,
117
+ newLinks,
118
+ });
119
+ }
120
+ return result;
121
+ }
122
+ //# sourceMappingURL=link-discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-discovery.js","sourceRoot":"","sources":["../src/link-discovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
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
+ /** Extract all [[wikilink]] targets from markdown body. Returns deduplicated slugs. */
8
+ export declare function extractWikilinks(body: string): string[];
9
+ /** Remove [[target]] (and optional display text) from body text. */
10
+ export declare function removeWikilink(body: string, target: string): string;
11
+ /**
12
+ * Auto-link page slug mentions in body text with [[wikilinks]].
13
+ *
14
+ * - Wraps unlinked slug mentions in [[slug]]
15
+ * - Skips code blocks, URLs, existing [[wikilinks]], headings
16
+ * - Matches longest slugs first to avoid partial matches
17
+ * - Uses word-boundary matching (won't match "pi-para" inside "pi-para-daemon")
18
+ * - Only links each slug once per page (first occurrence) to avoid noise
19
+ *
20
+ * @param body - Page body markdown
21
+ * @param allSlugs - Set of all existing page slugs
22
+ * @param ownSlug - This page's own slug (excluded from auto-linking)
23
+ * @returns Updated body with [[wikilinks]] inserted
24
+ */
25
+ export declare function autoLinkSlugs(body: string, allSlugs: Set<string>, ownSlug?: string): string;
26
+ /**
27
+ * Sync frontmatter.links to match all [[wikilinks]] found in body text.
28
+ */
29
+ export declare function syncFrontmatterLinks(body: string): string[];
30
+ //# sourceMappingURL=link-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-utils.d.ts","sourceRoot":"","sources":["../src/link-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,uFAAuF;AACvF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAQvD;AAED,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAInE;AAgED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,EACrB,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,CAoDR;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAE3D"}
@@ -0,0 +1,145 @@
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
+ // -- Wikilink extraction ----------------------------------------------------
8
+ const WIKILINK_RE = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g;
9
+ /** Extract all [[wikilink]] targets from markdown body. Returns deduplicated slugs. */
10
+ export function extractWikilinks(body) {
11
+ const links = [];
12
+ let m;
13
+ const re = new RegExp(WIKILINK_RE.source, WIKILINK_RE.flags);
14
+ while ((m = re.exec(body)) !== null) {
15
+ links.push(m[1].trim());
16
+ }
17
+ return [...new Set(links)];
18
+ }
19
+ /** Remove [[target]] (and optional display text) from body text. */
20
+ export function removeWikilink(body, target) {
21
+ const escaped = target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22
+ const re = new RegExp(`\\[\\[${escaped}(?:\\|[^\\]]+)?\\]\\]`, "g");
23
+ return body.replace(re, "");
24
+ }
25
+ // -- Auto-linking -----------------------------------------------------------
26
+ /**
27
+ * Identify regions in markdown body that should NOT be auto-linked.
28
+ * Returns array of [start, end] ranges to skip.
29
+ *
30
+ * Protected regions:
31
+ * - Fenced code blocks (``` ... ```)
32
+ * - Inline code (`...`)
33
+ * - Existing [[wikilinks]]
34
+ * - URLs (http:// https://)
35
+ * - YAML frontmatter lines referencing slugs
36
+ */
37
+ function findProtectedRanges(body) {
38
+ const ranges = [];
39
+ // Fenced code blocks
40
+ const fencedRe = /```[\s\S]*?```/g;
41
+ let m;
42
+ while ((m = fencedRe.exec(body)) !== null) {
43
+ ranges.push([m.index, m.index + m[0].length]);
44
+ }
45
+ // Inline code
46
+ const inlineCodeRe = /`[^`]+`/g;
47
+ while ((m = inlineCodeRe.exec(body)) !== null) {
48
+ ranges.push([m.index, m.index + m[0].length]);
49
+ }
50
+ // Existing [[wikilinks]]
51
+ const wlRe = /\[\[[^\]]+\]\]/g;
52
+ while ((m = wlRe.exec(body)) !== null) {
53
+ ranges.push([m.index, m.index + m[0].length]);
54
+ }
55
+ // URLs
56
+ const urlRe = /https?:\/\/[^\s)>]+/g;
57
+ while ((m = urlRe.exec(body)) !== null) {
58
+ ranges.push([m.index, m.index + m[0].length]);
59
+ }
60
+ // Markdown headings (don't wrap slugs in heading lines)
61
+ const headingRe = /^#{1,6}\s+.*$/gm;
62
+ while ((m = headingRe.exec(body)) !== null) {
63
+ ranges.push([m.index, m.index + m[0].length]);
64
+ }
65
+ return ranges.sort((a, b) => a[0] - b[0]);
66
+ }
67
+ /**
68
+ * Check if a position falls inside any protected range.
69
+ */
70
+ function isProtected(pos, end, ranges) {
71
+ for (const [rStart, rEnd] of ranges) {
72
+ if (rStart > end)
73
+ break; // ranges are sorted
74
+ if (pos >= rStart && end <= rEnd)
75
+ return true;
76
+ if (pos < rEnd && end > rStart)
77
+ return true; // overlap
78
+ }
79
+ return false;
80
+ }
81
+ /**
82
+ * Auto-link page slug mentions in body text with [[wikilinks]].
83
+ *
84
+ * - Wraps unlinked slug mentions in [[slug]]
85
+ * - Skips code blocks, URLs, existing [[wikilinks]], headings
86
+ * - Matches longest slugs first to avoid partial matches
87
+ * - Uses word-boundary matching (won't match "pi-para" inside "pi-para-daemon")
88
+ * - Only links each slug once per page (first occurrence) to avoid noise
89
+ *
90
+ * @param body - Page body markdown
91
+ * @param allSlugs - Set of all existing page slugs
92
+ * @param ownSlug - This page's own slug (excluded from auto-linking)
93
+ * @returns Updated body with [[wikilinks]] inserted
94
+ */
95
+ export function autoLinkSlugs(body, allSlugs, ownSlug) {
96
+ if (allSlugs.size === 0)
97
+ return body;
98
+ // Sort slugs longest-first so "pi-para-daemon" is matched before "pi-para"
99
+ const sortedSlugs = [...allSlugs]
100
+ .filter(s => s !== ownSlug)
101
+ .sort((a, b) => b.length - a.length);
102
+ const protectedRanges = findProtectedRanges(body);
103
+ const linkedSlugs = new Set(extractWikilinks(body));
104
+ let result = body;
105
+ for (const slug of sortedSlugs) {
106
+ // Skip if already linked
107
+ if (linkedSlugs.has(slug))
108
+ continue;
109
+ // Build regex: match slug at word boundaries
110
+ // Slug chars are [a-z0-9-], so word boundary = not preceded/followed by these chars
111
+ const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
112
+ // Use negative lookbehind/lookahead for slug characters
113
+ const re = new RegExp(`(?<![a-z0-9-])${escaped}(?![a-z0-9-])`, "g");
114
+ // Re-compute protected ranges for current state of result
115
+ const currentProtected = findProtectedRanges(result);
116
+ let match;
117
+ let linked = false;
118
+ // Reset regex
119
+ re.lastIndex = 0;
120
+ while ((match = re.exec(result)) !== null) {
121
+ if (linked)
122
+ break; // only link first occurrence
123
+ const pos = match.index;
124
+ const end = pos + match[0].length;
125
+ if (isProtected(pos, end, currentProtected))
126
+ continue;
127
+ // Replace this occurrence with [[slug]]
128
+ const before = result.slice(0, pos);
129
+ const after = result.slice(end);
130
+ const replacement = `[[${slug}]]`;
131
+ result = before + replacement + after;
132
+ linked = true;
133
+ // Update regex index to account for replacement length difference
134
+ re.lastIndex = pos + replacement.length;
135
+ }
136
+ }
137
+ return result;
138
+ }
139
+ /**
140
+ * Sync frontmatter.links to match all [[wikilinks]] found in body text.
141
+ */
142
+ export function syncFrontmatterLinks(body) {
143
+ return extractWikilinks(body);
144
+ }
145
+ //# sourceMappingURL=link-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-utils.js","sourceRoot":"","sources":["../src/link-utils.ts"],"names":[],"mappings":"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"}
package/dist/lint.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Wiki health checks — validates wiki structure and content integrity.
3
+ *
4
+ * Checks for orphan pages, broken links, stale pages, scope drift,
5
+ * archive candidates, missing pages, frontmatter issues, index drift,
6
+ * empty categories, and duplicate slugs.
7
+ *
8
+ * When autoFix is true (default), fixable issues are repaired in-place:
9
+ * broken links removed, index rebuilt for drift, missing frontmatter fields set.
10
+ */
11
+ import type { ParaCategory } from "./wiki.js";
12
+ export interface LintOptions {
13
+ staleDays?: number;
14
+ autoFix?: boolean;
15
+ }
16
+ export interface LintReport {
17
+ issues: LintIssue[];
18
+ fixed: LintIssue[];
19
+ stats: WikiStats;
20
+ }
21
+ export interface LintIssue {
22
+ severity: "error" | "warning" | "info";
23
+ category: string;
24
+ page?: string;
25
+ message: string;
26
+ autoFixable: boolean;
27
+ }
28
+ export interface WikiStats {
29
+ totalPages: number;
30
+ byCategory: Record<ParaCategory, number>;
31
+ totalLinks: number;
32
+ brokenLinks: number;
33
+ orphanPages: number;
34
+ oldestPage: string;
35
+ newestPage: string;
36
+ lastIngest: string;
37
+ }
38
+ export declare function lintWiki(wikiDir: string, options?: LintOptions): Promise<LintReport>;
39
+ //# sourceMappingURL=lint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAYH,OAAO,KAAK,EAAE,YAAY,EAAqB,MAAM,WAAW,CAAC;AAkBjE,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAw1BD,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,UAAU,CAAC,CA2FrB"}