@pi-unipi/unipi 2.6.0 → 2.6.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 (205) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +21 -22
  3. package/packages/ask-user/package.json +2 -2
  4. package/packages/ask-user/settings-tui.ts +18 -34
  5. package/packages/autocomplete/package.json +1 -1
  6. package/packages/autocomplete/src/constants.ts +2 -15
  7. package/packages/autocomplete/src/provider.ts +3 -17
  8. package/packages/btw/extensions/btw.ts +18 -45
  9. package/packages/btw/package.json +2 -2
  10. package/packages/compactor/README.md +2 -2
  11. package/packages/compactor/package.json +3 -3
  12. package/packages/compactor/skills/compactor/SKILL.md +1 -1
  13. package/packages/compactor/skills/compactor-detail/SKILL.md +3 -5
  14. package/packages/compactor/skills/compactor-doctor/SKILL.md +1 -1
  15. package/packages/compactor/skills/compactor-stats/SKILL.md +1 -1
  16. package/packages/compactor/src/commands/index.ts +47 -78
  17. package/packages/compactor/src/compaction/brief.ts +161 -90
  18. package/packages/compactor/src/compaction/build-sections.ts +3 -4
  19. package/packages/compactor/src/compaction/compact-args.ts +86 -0
  20. package/packages/compactor/src/compaction/cut.ts +270 -28
  21. package/packages/compactor/src/compaction/drill-down.ts +261 -0
  22. package/packages/compactor/src/compaction/format-recall.ts +96 -0
  23. package/packages/compactor/src/compaction/format.ts +8 -3
  24. package/packages/compactor/src/compaction/hooks.ts +248 -72
  25. package/packages/compactor/src/compaction/merge.ts +34 -4
  26. package/packages/compactor/src/compaction/rank.ts +270 -0
  27. package/packages/compactor/src/compaction/recall-scope.ts +28 -0
  28. package/packages/compactor/src/compaction/search-entries.ts +333 -96
  29. package/packages/compactor/src/compaction/skill-collapse.ts +35 -0
  30. package/packages/compactor/src/compaction/summarize.ts +37 -6
  31. package/packages/compactor/src/compaction/token-estimate.ts +104 -0
  32. package/packages/compactor/src/compaction/touched-files.ts +35 -0
  33. package/packages/compactor/src/config/manager.ts +2 -27
  34. package/packages/compactor/src/config/presets.ts +0 -2
  35. package/packages/compactor/src/config/schema.ts +2 -16
  36. package/packages/compactor/src/executor/executor.ts +6 -15
  37. package/packages/compactor/src/executor/runtime.ts +2 -12
  38. package/packages/compactor/src/index.ts +4 -121
  39. package/packages/compactor/src/info-screen.ts +3 -10
  40. package/packages/compactor/src/security/evaluator.ts +0 -53
  41. package/packages/compactor/src/security/policy.ts +7 -8
  42. package/packages/compactor/src/session/db.ts +0 -6
  43. package/packages/compactor/src/tools/ctx-execute-file.ts +0 -5
  44. package/packages/compactor/src/tools/register.ts +27 -50
  45. package/packages/compactor/src/tools/vcc-recall.ts +86 -48
  46. package/packages/compactor/src/tui/settings-overlay.ts +20 -40
  47. package/packages/compactor/src/types.ts +43 -100
  48. package/packages/core/constants.ts +1 -28
  49. package/packages/core/events.ts +1 -83
  50. package/packages/core/index.ts +1 -0
  51. package/packages/core/model-cache.ts +18 -12
  52. package/packages/core/package.json +3 -1
  53. package/packages/core/tui-overlay.ts +89 -0
  54. package/packages/core/tui-width.ts +18 -0
  55. package/packages/core/utils.ts +43 -0
  56. package/packages/footer/package.json +2 -2
  57. package/packages/footer/src/help.ts +2 -2
  58. package/packages/footer/src/index.ts +9 -10
  59. package/packages/footer/src/presets.ts +1 -27
  60. package/packages/footer/src/registry/index.ts +0 -23
  61. package/packages/footer/src/rendering/icons.ts +0 -13
  62. package/packages/footer/src/rendering/renderer.ts +5 -40
  63. package/packages/footer/src/rendering/separators.ts +0 -10
  64. package/packages/footer/src/rendering/theme.ts +1 -15
  65. package/packages/footer/src/segments/compactor.ts +7 -14
  66. package/packages/footer/src/segments/core.ts +15 -54
  67. package/packages/footer/src/segments/kanboard.ts +5 -5
  68. package/packages/footer/src/segments/mcp.ts +5 -5
  69. package/packages/footer/src/segments/memory.ts +4 -4
  70. package/packages/footer/src/segments/notify.ts +2 -2
  71. package/packages/footer/src/segments/ralph.ts +3 -3
  72. package/packages/footer/src/segments/status-ext.ts +1 -1
  73. package/packages/footer/src/segments/workflow.ts +3 -3
  74. package/packages/footer/src/tps-tracker.ts +0 -8
  75. package/packages/footer/src/tui/settings-tui.ts +14 -31
  76. package/packages/footer/src/types.ts +0 -15
  77. package/packages/image/package.json +2 -2
  78. package/packages/image/src/register-providers.ts +0 -11
  79. package/packages/image/src/tools.ts +0 -3
  80. package/packages/image/src/tui/model-selector.ts +36 -71
  81. package/packages/info-screen/config.ts +0 -16
  82. package/packages/info-screen/core-groups.ts +0 -10
  83. package/packages/info-screen/index.ts +22 -13
  84. package/packages/info-screen/package.json +2 -2
  85. package/packages/info-screen/registry.ts +1 -72
  86. package/packages/info-screen/tui/info-overlay.ts +85 -100
  87. package/packages/info-screen/usage-parser.ts +12 -47
  88. package/packages/input-shortcuts/package.json +2 -2
  89. package/packages/input-shortcuts/src/index.ts +3 -3
  90. package/packages/input-shortcuts/src/registers.ts +0 -8
  91. package/packages/input-shortcuts/src/settings-overlay.ts +4 -13
  92. package/packages/input-shortcuts/src/undo-redo.ts +0 -12
  93. package/packages/kanboard/package.json +2 -2
  94. package/packages/mcp/package.json +2 -2
  95. package/packages/mcp/src/bridge/client.ts +0 -15
  96. package/packages/mcp/src/config/manager.ts +0 -38
  97. package/packages/mcp/src/config/schema.ts +0 -27
  98. package/packages/mcp/src/config/sync.ts +0 -29
  99. package/packages/mcp/src/index.ts +1 -1
  100. package/packages/mcp/src/tui/add-overlay.ts +6 -6
  101. package/packages/mcp/src/types.ts +0 -6
  102. package/packages/memory/embedding.ts +0 -31
  103. package/packages/memory/mempalace.ts +0 -16
  104. package/packages/memory/package.json +4 -8
  105. package/packages/memory/storage.ts +56 -753
  106. package/packages/milestone/hooks.ts +1 -6
  107. package/packages/milestone/milestone.ts +2 -41
  108. package/packages/milestone/package.json +2 -2
  109. package/packages/notify/README.md +1 -0
  110. package/packages/notify/commands.ts +61 -3
  111. package/packages/notify/events.ts +0 -10
  112. package/packages/notify/package.json +2 -2
  113. package/packages/notify/settings.ts +0 -19
  114. package/packages/notify/tui/gotify-setup.ts +108 -143
  115. package/packages/notify/tui/ntfy-setup.ts +137 -172
  116. package/packages/notify/tui/recap-model-selector.ts +107 -101
  117. package/packages/notify/tui/settings-overlay.ts +94 -117
  118. package/packages/notify/tui/telegram-setup.ts +48 -79
  119. package/packages/notify/types.ts +0 -2
  120. package/packages/ralph/index.ts +11 -35
  121. package/packages/ralph/package.json +3 -3
  122. package/packages/ralph/ralph-loop.ts +3 -44
  123. package/packages/subagents/package.json +2 -1
  124. package/packages/subagents/src/agent-manager.ts +0 -51
  125. package/packages/subagents/src/agent-runner.ts +6 -80
  126. package/packages/subagents/src/conversation-viewer.ts +1 -19
  127. package/packages/subagents/src/core-compat.ts +8 -25
  128. package/packages/subagents/src/custom-agents.ts +0 -1
  129. package/packages/subagents/src/index.ts +3 -48
  130. package/packages/subagents/src/types.ts +0 -11
  131. package/packages/subagents/src/widget.ts +5 -5
  132. package/packages/unipi/bundled.js +4468 -7769
  133. package/packages/unipi/index.ts +6 -3
  134. package/packages/updater/package.json +2 -2
  135. package/packages/updater/src/changelog.ts +1 -16
  136. package/packages/updater/src/index.ts +0 -5
  137. package/packages/updater/src/installer.ts +4 -26
  138. package/packages/updater/src/markdown.ts +6 -152
  139. package/packages/updater/src/settings.ts +0 -15
  140. package/packages/updater/src/tui/changelog-overlay.ts +42 -234
  141. package/packages/updater/src/tui/list-detail-overlay.ts +260 -0
  142. package/packages/updater/src/tui/readme-overlay.ts +53 -210
  143. package/packages/updater/src/tui/settings-overlay.ts +13 -26
  144. package/packages/updater/src/version.ts +2 -31
  145. package/packages/utility/package.json +2 -4
  146. package/packages/utility/src/commands.ts +4 -5
  147. package/packages/utility/src/herdr-sync.ts +136 -0
  148. package/packages/utility/src/index.ts +11 -68
  149. package/packages/utility/src/lifecycle/cleanup.ts +2 -16
  150. package/packages/utility/src/{diff/settings.ts → settings.ts} +42 -80
  151. package/packages/utility/src/tools/env.ts +0 -0
  152. package/packages/utility/src/tui/name-badge-state.ts +18 -2
  153. package/packages/utility/src/tui/name-badge.ts +5 -4
  154. package/packages/utility/src/tui/util-settings-tui.ts +41 -224
  155. package/packages/web-api/package.json +2 -6
  156. package/packages/web-api/src/engine/dependencies.ts +13 -134
  157. package/packages/web-api/src/engine/extract.ts +9 -99
  158. package/packages/web-api/src/engine/format.ts +0 -75
  159. package/packages/web-api/src/engine/types.ts +0 -39
  160. package/packages/web-api/src/index.ts +1 -2
  161. package/packages/web-api/src/providers/base.ts +0 -10
  162. package/packages/web-api/src/providers/duckduckgo.ts +0 -1
  163. package/packages/web-api/src/providers/firecrawl.ts +0 -9
  164. package/packages/web-api/src/providers/jina-reader.ts +0 -9
  165. package/packages/web-api/src/providers/jina-search.ts +0 -9
  166. package/packages/web-api/src/providers/perplexity.ts +0 -9
  167. package/packages/web-api/src/providers/registry.ts +0 -80
  168. package/packages/web-api/src/providers/serpapi.ts +0 -9
  169. package/packages/web-api/src/providers/tavily.ts +0 -9
  170. package/packages/web-api/src/providers/wigolo-client.ts +0 -30
  171. package/packages/web-api/src/providers/wigolo.ts +0 -1
  172. package/packages/web-api/src/settings.ts +17 -77
  173. package/packages/web-api/src/tools.ts +21 -24
  174. package/packages/web-api/src/tui/settings-dialog.ts +0 -1
  175. package/packages/workflow/commands.ts +38 -122
  176. package/packages/workflow/index.ts +1 -5
  177. package/packages/workflow/package.json +2 -2
  178. package/packages/cocoindex/README.md +0 -94
  179. package/packages/cocoindex/bridge.ts +0 -842
  180. package/packages/cocoindex/commands.ts +0 -175
  181. package/packages/cocoindex/index.ts +0 -48
  182. package/packages/cocoindex/installer.ts +0 -397
  183. package/packages/cocoindex/package.json +0 -49
  184. package/packages/cocoindex/skills/cocoindex/SKILL.md +0 -88
  185. package/packages/cocoindex/tools.ts +0 -157
  186. package/packages/compactor/src/display/diff-renderer.ts +0 -281
  187. package/packages/compactor/src/display/line-width-safety.ts +0 -28
  188. package/packages/compactor/src/display/render-utils.ts +0 -52
  189. package/packages/compactor/src/display/thinking-label.ts +0 -18
  190. package/packages/compactor/src/display/tool-overrides.ts +0 -136
  191. package/packages/compactor/src/tools/compact.ts +0 -20
  192. package/packages/subagents/src/file-lock.ts +0 -102
  193. package/packages/utility/src/cache/ttl-cache.ts +0 -311
  194. package/packages/utility/src/diff/highlighter.ts +0 -352
  195. package/packages/utility/src/diff/parser.ts +0 -191
  196. package/packages/utility/src/diff/renderer.ts +0 -395
  197. package/packages/utility/src/diff/theme.ts +0 -316
  198. package/packages/utility/src/diff/wrapper.ts +0 -339
  199. package/packages/utility/src/display/capabilities.ts +0 -214
  200. package/packages/utility/src/display/width.ts +0 -226
  201. package/packages/utility/src/tools/batch.ts +0 -229
  202. package/packages/utility/src/tui/badge-settings-tui.ts +0 -388
  203. package/packages/utility/src/tui/badge-settings.ts +0 -67
  204. package/packages/utility/src/tui/settings-inspector.ts +0 -303
  205. package/packages/web-api/src/providers/llm-summarize.ts +0 -71
@@ -1,352 +0,0 @@
1
- /**
2
- * @pi-unipi/utility — Shiki Highlighter
3
- *
4
- * Singleton Shiki ANSI highlighter with LRU cache, language detection,
5
- * and contrast normalization.
6
- */
7
-
8
- // ─── Constants ──────────────────────────────────────────────────────────────────
9
-
10
- /** Maximum number of cached highlight results */
11
- export const CACHE_LIMIT = 192;
12
-
13
- /** Maximum characters to highlight (skip Shiki above this) */
14
- export const MAX_HL_CHARS = 80_000;
15
-
16
- // ─── LRU Cache ──────────────────────────────────────────────────────────────────
17
-
18
- /**
19
- * Simple LRU cache with string keys.
20
- * Evicts oldest entries when capacity is reached.
21
- */
22
- export class LruCache<V> {
23
- private map = new Map<string, V>();
24
- private capacity: number;
25
-
26
- constructor(capacity: number) {
27
- this.capacity = capacity;
28
- }
29
-
30
- get(key: string): V | undefined {
31
- const value = this.map.get(key);
32
- if (value !== undefined) {
33
- // Move to end (most recently used)
34
- this.map.delete(key);
35
- this.map.set(key, value);
36
- }
37
- return value;
38
- }
39
-
40
- set(key: string, value: V): void {
41
- if (this.map.has(key)) {
42
- this.map.delete(key);
43
- } else if (this.map.size >= this.capacity) {
44
- // Evict oldest (first entry)
45
- const firstKey = this.map.keys().next().value;
46
- if (firstKey !== undefined) {
47
- this.map.delete(firstKey);
48
- }
49
- }
50
- this.map.set(key, value);
51
- }
52
-
53
- has(key: string): boolean {
54
- return this.map.has(key);
55
- }
56
-
57
- get size(): number {
58
- return this.map.size;
59
- }
60
-
61
- clear(): void {
62
- this.map.clear();
63
- }
64
- }
65
-
66
- // ─── Language Detection ─────────────────────────────────────────────────────────
67
-
68
- /** File extension → Shiki language mapping */
69
- export const EXT_LANG: Record<string, string> = {
70
- ".ts": "typescript",
71
- ".tsx": "tsx",
72
- ".js": "javascript",
73
- ".jsx": "jsx",
74
- ".mjs": "javascript",
75
- ".cjs": "javascript",
76
- ".mts": "typescript",
77
- ".cts": "typescript",
78
- ".json": "json",
79
- ".jsonc": "jsonc",
80
- ".json5": "json5",
81
- ".html": "html",
82
- ".htm": "html",
83
- ".css": "css",
84
- ".scss": "scss",
85
- ".sass": "sass",
86
- ".less": "less",
87
- ".md": "markdown",
88
- ".mdx": "mdx",
89
- ".py": "python",
90
- ".rb": "ruby",
91
- ".go": "go",
92
- ".rs": "rust",
93
- ".java": "java",
94
- ".kt": "kotlin",
95
- ".kts": "kotlin",
96
- ".c": "c",
97
- ".h": "c",
98
- ".cpp": "cpp",
99
- ".hpp": "cpp",
100
- ".cc": "cpp",
101
- ".cs": "csharp",
102
- ".swift": "swift",
103
- ".php": "php",
104
- ".sql": "sql",
105
- ".sh": "bash",
106
- ".bash": "bash",
107
- ".zsh": "bash",
108
- ".fish": "fish",
109
- ".yaml": "yaml",
110
- ".yml": "yaml",
111
- ".toml": "toml",
112
- ".xml": "xml",
113
- ".svg": "xml",
114
- ".graphql": "graphql",
115
- ".gql": "graphql",
116
- ".vue": "vue",
117
- ".svelte": "svelte",
118
- ".astro": "astro",
119
- ".prisma": "prisma",
120
- ".dockerfile": "dockerfile",
121
- ".tf": "hcl",
122
- ".hcl": "hcl",
123
- ".lua": "lua",
124
- ".r": "r",
125
- ".R": "r",
126
- ".dart": "dart",
127
- ".ex": "elixir",
128
- ".exs": "elixir",
129
- ".erl": "erlang",
130
- ".hrl": "erlang",
131
- ".clj": "clojure",
132
- ".cljs": "clojure",
133
- ".hs": "haskell",
134
- ".elm": "elm",
135
- ".nim": "nim",
136
- ".zig": "zig",
137
- ".v": "v",
138
- ".jl": "julia",
139
- ".ml": "ocaml",
140
- ".mli": "ocaml",
141
- ".fs": "fsharp",
142
- ".fsx": "fsharp",
143
- ".fsi": "fsharp",
144
- };
145
-
146
- /**
147
- * Detect the Shiki language from a file extension.
148
- * Returns "text" if the extension is unknown.
149
- */
150
- export function detectLanguage(extension: string): string {
151
- const ext = extension.startsWith(".") ? extension.toLowerCase() : `.${extension.toLowerCase()}`;
152
- return EXT_LANG[ext] ?? "text";
153
- }
154
-
155
- /**
156
- * Detect language from a file path.
157
- */
158
- export function detectLanguageFromPath(filePath: string): string {
159
- const ext = filePath.lastIndexOf(".") >= 0 ? filePath.substring(filePath.lastIndexOf(".")) : "";
160
- return detectLanguage(ext);
161
- }
162
-
163
- // ─── Contrast Normalization ─────────────────────────────────────────────────────
164
-
165
- /**
166
- * Calculate the relative luminance of a hex color.
167
- * Used for contrast ratio calculations.
168
- */
169
- function relativeLuminance(hex: string): number {
170
- const h = hex.replace(/^#/, "");
171
- const r = parseInt(h.substring(0, 2), 16) / 255;
172
- const g = parseInt(h.substring(2, 4), 16) / 255;
173
- const b = parseInt(h.substring(4, 6), 16) / 255;
174
-
175
- const toLinear = (c: number) => (c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
176
- return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
177
- }
178
-
179
- /**
180
- * Calculate contrast ratio between two colors.
181
- */
182
- function contrastRatio(fg: string, bg: string): number {
183
- const l1 = relativeLuminance(fg);
184
- const l2 = relativeLuminance(bg);
185
- const lighter = Math.max(l1, l2);
186
- const darker = Math.min(l1, l2);
187
- return (lighter + 0.05) / (darker + 0.05);
188
- }
189
-
190
- /**
191
- * Extract the foreground color from an ANSI 24-bit escape sequence.
192
- * Returns the hex color and the full match for replacement.
193
- */
194
- function extractAnsiFg(ansi: string): { hex: string; match: string } | null {
195
- const match = ansi.match(/\x1b\[38;2;(\d+);(\d+);(\d+)m/);
196
- if (!match) return null;
197
- const r = parseInt(match[1]);
198
- const g = parseInt(match[2]);
199
- const b = parseInt(match[3]);
200
- const hex = `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
201
- return { hex, match: match[0] };
202
- }
203
-
204
- /**
205
- * Normalize low-contrast Shiki foregrounds against a dark background.
206
- *
207
- * Shiki themes sometimes produce foreground colors with poor contrast
208
- * against diff backgrounds. This function bumps the brightness of any
209
- * foreground that falls below the minimum contrast ratio.
210
- *
211
- * @param ansi - ANSI string with 24-bit color codes
212
- * @param bgHex - Background hex color to test against (default: dark bg)
213
- * @param minRatio - Minimum contrast ratio (default: 3.0)
214
- */
215
- export function normalizeShikiContrast(
216
- ansi: string,
217
- bgHex: string = "#1a1a2e",
218
- minRatio: number = 3.0,
219
- ): string {
220
- // Find all 24-bit foreground sequences
221
- const fgRegex = /\x1b\[38;2;\d+;\d+;\d+m/g;
222
- let result = ansi;
223
- let match: RegExpExecArray | null;
224
-
225
- while ((match = fgRegex.exec(ansi)) !== null) {
226
- const fgInfo = extractAnsiFg(match[0] + ansi.substring(match.index + match[0].length));
227
- if (!fgInfo) continue;
228
-
229
- const ratio = contrastRatio(fgInfo.hex, bgHex);
230
- if (ratio < minRatio) {
231
- // Brighten the foreground by mixing with white
232
- const [r, g, b] = [
233
- parseInt(fgInfo.hex.slice(1, 3), 16),
234
- parseInt(fgInfo.hex.slice(3, 5), 16),
235
- parseInt(fgInfo.hex.slice(5, 7), 16),
236
- ];
237
- const factor = minRatio / Math.max(ratio, 0.01);
238
- const nr = Math.min(255, Math.round(r + (255 - r) * Math.min(1, factor * 0.5)));
239
- const ng = Math.min(255, Math.round(g + (255 - g) * Math.min(1, factor * 0.5)));
240
- const nb = Math.min(255, Math.round(b + (255 - b) * Math.min(1, factor * 0.5)));
241
- const newFg = `\x1b[38;2;${nr};${ng};${nb}m`;
242
- result = result.replace(match[0], newFg);
243
- }
244
- }
245
-
246
- return result;
247
- }
248
-
249
- // ─── Shiki Highlighter ──────────────────────────────────────────────────────────
250
-
251
- /** Shiki highlighter instance (lazy singleton) */
252
- let shikiHighlighter: import("shiki").Highlighter | null = null;
253
- let shikiInitPromise: Promise<any> | null = null;
254
-
255
- /**
256
- * Initialize the Shiki highlighter (singleton).
257
- * Returns the highlighter instance.
258
- */
259
- export async function getShikiHighlighter(): Promise<any> {
260
- if (shikiHighlighter) return shikiHighlighter;
261
- if (shikiInitPromise) return shikiInitPromise;
262
-
263
- shikiInitPromise = (async () => {
264
- try {
265
- const { createHighlighter } = await import("shiki");
266
- shikiHighlighter = await createHighlighter({
267
- themes: ["github-dark"],
268
- langs: [
269
- "typescript", "javascript", "tsx", "jsx", "json", "jsonc",
270
- "html", "css", "scss", "markdown", "python", "go", "rust",
271
- "java", "c", "cpp", "csharp", "ruby", "php", "swift", "kotlin",
272
- "bash", "yaml", "toml", "xml", "sql", "graphql", "vue", "svelte",
273
- ],
274
- });
275
- return shikiHighlighter;
276
- } catch {
277
- // If Shiki fails to load, return null — we'll use plain text
278
- shikiInitPromise = null;
279
- return null;
280
- }
281
- })();
282
-
283
- return shikiInitPromise;
284
- }
285
-
286
- /**
287
- * Pre-warm the Shiki highlighter.
288
- * Call this early in the extension lifecycle to avoid first-render delay.
289
- */
290
- export async function preWarmHighlighter(): Promise<void> {
291
- await getShikiHighlighter();
292
- }
293
-
294
- /** LRU cache for highlighted blocks */
295
- const hlCache = new LruCache<string[]>(CACHE_LIMIT);
296
-
297
- /**
298
- * Generate a cache key for a code block.
299
- */
300
- function hlCacheKey(code: string, language: string): string {
301
- // Use first 200 chars + length + language for cache key
302
- const prefix = code.substring(0, 200);
303
- return `${language}:${code.length}:${prefix}`;
304
- }
305
-
306
- /**
307
- * Highlight a code block to ANSI using Shiki.
308
- * Results are cached in an LRU cache (192 entries).
309
- *
310
- * @param code - Code to highlight
311
- * @param language - Shiki language identifier
312
- * @returns Array of ANSI-highlighted lines, or plain lines if Shiki unavailable
313
- */
314
- export async function hlBlock(code: string, language: string): Promise<string[]> {
315
- // Skip highlighting for very large content
316
- if (code.length > MAX_HL_CHARS) {
317
- return code.split("\n");
318
- }
319
-
320
- // Check cache
321
- const key = hlCacheKey(code, language);
322
- const cached = hlCache.get(key);
323
- if (cached) return cached;
324
-
325
- // Get highlighter
326
- const highlighter = await getShikiHighlighter();
327
- if (!highlighter) {
328
- // Shiki not available — return plain text
329
- return code.split("\n");
330
- }
331
-
332
- try {
333
- // Highlight with Shiki
334
- const ansi = highlighter.codeToANSI(code, {
335
- lang: language === "text" ? "text" : language,
336
- theme: "github-dark",
337
- });
338
-
339
- const lines = ansi.split("\n");
340
-
341
- // Normalize contrast
342
- const normalized = lines.map((line: string) => normalizeShikiContrast(line));
343
-
344
- // Cache result
345
- hlCache.set(key, normalized);
346
-
347
- return normalized;
348
- } catch {
349
- // Fallback on error
350
- return code.split("\n");
351
- }
352
- }
@@ -1,191 +0,0 @@
1
- /**
2
- * @pi-unipi/utility — Diff Parser
3
- *
4
- * Parses unified diffs from structuredPatch and provides word-level diff analysis.
5
- * Wraps the `diff` library for structured output.
6
- */
7
-
8
- import * as Diff from "diff";
9
-
10
- // ─── Types ──────────────────────────────────────────────────────────────────────
11
-
12
- /** A single line in a parsed diff */
13
- export interface DiffLine {
14
- /** Line type: added, removed, context, or hunk separator */
15
- type: "add" | "remove" | "context" | "hunk";
16
- /** Original line number (null for added lines) */
17
- oldLine: number | null;
18
- /** New line number (null for removed lines) */
19
- newLine: number | null;
20
- /** Line content (without +/- prefix) */
21
- content: string;
22
- }
23
-
24
- /** Result of parsing a diff */
25
- export interface ParsedDiff {
26
- /** Old file name */
27
- oldName: string;
28
- /** New file name */
29
- newName: string;
30
- /** Parsed lines */
31
- lines: DiffLine[];
32
- /** Total additions */
33
- additions: number;
34
- /** Total deletions */
35
- deletions: number;
36
- }
37
-
38
- /** Word-level diff result */
39
- export interface WordDiffResult {
40
- /** Similarity score (0-1, where 1 = identical) */
41
- similarity: number;
42
- /** Changed ranges in the new text */
43
- addedRanges: Array<{ start: number; end: number }>;
44
- /** Changed ranges in the old text */
45
- removedRanges: Array<{ start: number; end: number }>;
46
- }
47
-
48
- // ─── Diff Parsing ───────────────────────────────────────────────────────────────
49
-
50
- /**
51
- * Parse a unified diff from two content strings.
52
- * Returns structured DiffLine[] with line numbers and hunk separators.
53
- *
54
- * @param oldContent - Original content
55
- * @param newContent - Modified content
56
- * @param context - Number of context lines around changes (default 3)
57
- * @param oldName - Label for old file
58
- * @param newName - Label for new file
59
- */
60
- export function parseDiff(
61
- oldContent: string,
62
- newContent: string,
63
- context: number = 3,
64
- oldName: string = "old",
65
- newName: string = "new",
66
- ): ParsedDiff {
67
- // Handle edge cases
68
- if (oldContent === newContent) {
69
- return { oldName, newName, lines: [], additions: 0, deletions: 0 };
70
- }
71
-
72
- if (oldContent === "") {
73
- // New file — all lines are additions
74
- const lines = newContent.split("\n");
75
- const diffLines: DiffLine[] = lines.map((content, i) => ({
76
- type: "add" as const,
77
- oldLine: null,
78
- newLine: i + 1,
79
- content,
80
- }));
81
- return { oldName, newName, lines: diffLines, additions: lines.length, deletions: 0 };
82
- }
83
-
84
- if (newContent === "") {
85
- // Deleted file — all lines are removals
86
- const lines = oldContent.split("\n");
87
- const diffLines: DiffLine[] = lines.map((content, i) => ({
88
- type: "remove" as const,
89
- oldLine: i + 1,
90
- newLine: null,
91
- content,
92
- }));
93
- return { oldName, newName, lines: diffLines, additions: 0, deletions: lines.length };
94
- }
95
-
96
- // Use structuredPatch for unified diff
97
- const patches = Diff.structuredPatch(oldName, newName, oldContent, newContent, "", "", { context });
98
-
99
- const lines: DiffLine[] = [];
100
- let additions = 0;
101
- let deletions = 0;
102
-
103
- for (const hunk of patches.hunks) {
104
- // Add hunk separator
105
- lines.push({
106
- type: "hunk",
107
- oldLine: hunk.oldStart,
108
- newLine: hunk.newStart,
109
- content: `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`,
110
- });
111
-
112
- let oldLine = hunk.oldStart;
113
- let newLine = hunk.newStart;
114
-
115
- for (const line of hunk.lines) {
116
- const prefix = line[0];
117
- const content = line.substring(1);
118
-
119
- if (prefix === "+") {
120
- lines.push({ type: "add", oldLine: null, newLine, content });
121
- additions++;
122
- newLine++;
123
- } else if (prefix === "-") {
124
- lines.push({ type: "remove", oldLine, newLine: null, content });
125
- deletions++;
126
- oldLine++;
127
- } else {
128
- // Context line (space prefix)
129
- lines.push({ type: "context", oldLine, newLine, content });
130
- oldLine++;
131
- newLine++;
132
- }
133
- }
134
- }
135
-
136
- return { oldName, newName, lines, additions, deletions };
137
- }
138
-
139
- /**
140
- * Word-level diff analysis between two strings.
141
- * Returns similarity score and character ranges of changes.
142
- */
143
- export function wordDiffAnalysis(a: string, b: string): WordDiffResult {
144
- // Fast path: identical strings
145
- if (a === b) {
146
- return { similarity: 1, addedRanges: [], removedRanges: [] };
147
- }
148
-
149
- // Fast path: empty strings
150
- if (a.length === 0 && b.length === 0) {
151
- return { similarity: 1, addedRanges: [], removedRanges: [] };
152
- }
153
- if (a.length === 0) {
154
- return { similarity: 0, addedRanges: [{ start: 0, end: b.length }], removedRanges: [] };
155
- }
156
- if (b.length === 0) {
157
- return { similarity: 0, addedRanges: [], removedRanges: [{ start: 0, end: a.length }] };
158
- }
159
-
160
- const changes = Diff.diffWords(a, b);
161
-
162
- const addedRanges: Array<{ start: number; end: number }> = [];
163
- const removedRanges: Array<{ start: number; end: number }> = [];
164
- let aPos = 0;
165
- let bPos = 0;
166
- let unchangedChars = 0;
167
-
168
- for (const change of changes) {
169
- if (change.added) {
170
- addedRanges.push({ start: bPos, end: bPos + (change.value?.length ?? 0) });
171
- bPos += change.value?.length ?? 0;
172
- } else if (change.removed) {
173
- removedRanges.push({ start: aPos, end: aPos + (change.value?.length ?? 0) });
174
- aPos += change.value?.length ?? 0;
175
- } else {
176
- // Unchanged
177
- unchangedChars += change.value?.length ?? 0;
178
- aPos += change.value?.length ?? 0;
179
- bPos += change.value?.length ?? 0;
180
- }
181
- }
182
-
183
- const totalChars = Math.max(a.length + b.length, 1);
184
- const similarity = (unchangedChars * 2) / totalChars;
185
-
186
- return {
187
- similarity: Math.min(1, Math.max(0, similarity)),
188
- addedRanges,
189
- removedRanges,
190
- };
191
- }