@goodandready/dsh-moa 0.2.20 → 0.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -1954,6 +1954,20 @@ window.__ModuleLoader__.load({
1954
1954
  reload()
1955
1955
  }, [reload])
1956
1956
 
1957
+ React.useEffect(() => {
1958
+ const lbUrl = leaderboardPresetFilter !== 'all'
1959
+ ? `/dsh-moa/leaderboard?preset=${encodeURIComponent(leaderboardPresetFilter)}`
1960
+ : '/dsh-moa/leaderboard'
1961
+ fetch(lbUrl, { cache: 'no-store' })
1962
+ .then((r) => r.json())
1963
+ .then((data) => {
1964
+ if (data && data.ok && Array.isArray(data.models)) {
1965
+ setLeaderboard(data.models.slice(0, 10))
1966
+ }
1967
+ })
1968
+ .catch(() => {})
1969
+ }, [leaderboardPresetFilter])
1970
+
1957
1971
  const handleSave = () => {
1958
1972
  setSaveStatus(t('actions.saving'))
1959
1973
 
@@ -3,15 +3,31 @@ import vm from 'node:vm'
3
3
  import fs from 'node:fs/promises'
4
4
  import path from 'node:path'
5
5
 
6
+ /**
7
+ * Validates that an untrusted relative path is safe and does not traverse upwards.
8
+ */
9
+ export function isSafeRelativePath(relPath) {
10
+ if (!relPath || typeof relPath !== 'string') return false
11
+ const trimmed = relPath.trim()
12
+ if (!trimmed || path.isAbsolute(trimmed) || trimmed.startsWith('/') || trimmed.startsWith('\\')) return false
13
+ const normalized = path.normalize(trimmed)
14
+ return Boolean(normalized && !normalized.startsWith('..') && !path.isAbsolute(normalized))
15
+ }
16
+
17
+ /**
18
+ * Asserts that a resolved path is strictly contained within baseDir.
19
+ */
20
+ export function assertPathContained(baseDir, subPath) {
21
+ const resolvedBase = path.resolve(baseDir)
22
+ const resolvedTarget = path.resolve(resolvedBase, subPath)
23
+ if (resolvedTarget !== resolvedBase && !resolvedTarget.startsWith(resolvedBase + path.sep)) {
24
+ throw new Error(`Path traversal violation: "${subPath}" escapes base directory "${baseDir}"`)
25
+ }
26
+ return resolvedTarget
27
+ }
28
+
6
29
  /**
7
30
  * Parses markdown code blocks that specify file paths.
8
- * Supports patterns like:
9
- * ```html file="index.html"
10
- * ```javascript filepath=script.js
11
- * // File: index.html
12
- * <!-- File: styles.css -->
13
- * ### File: index.html
14
- * ```html
15
31
  */
16
32
  export function extractFileBlocks(text) {
17
33
  if (!text || typeof text !== 'string') return []
@@ -19,81 +35,48 @@ export function extractFileBlocks(text) {
19
35
  const files = []
20
36
  const seenPaths = new Set()
21
37
 
22
- // Pattern 1: fenced blocks with file="..." or path="..." in info string
23
- // Example: ```html file="index.html" or ```js path=src/main.js
24
- const fenceRegex = /```[\w-]*\s+(?:file|path|filepath)=["']?([^\s"'\n\r]+)["']?[\r\n]+([\s\S]*?)```/gi
25
- let match
26
- while ((match = fenceRegex.exec(text)) !== null) {
27
- const rawPath = match[1].trim()
28
- const content = match[2]
29
- if (rawPath && !seenPaths.has(rawPath)) {
38
+ const addFile = (rawPath, fileContent) => {
39
+ if (rawPath && isSafeRelativePath(rawPath) && !seenPaths.has(rawPath)) {
30
40
  seenPaths.add(rawPath)
31
- files.push({ relativePath: sanitizePath(rawPath), content })
41
+ files.push({ relativePath: rawPath, content: fileContent.trimEnd() + '\n' })
32
42
  }
33
43
  }
34
44
 
35
- // Pattern 2: Explicit headers before code blocks, e.g.
36
- // **File: `index.html`** or ### File: index.html or `File: index.html` followed by ```
37
- const headerRegex = /(?:###?\s*File:\s*|File:\s*|#\s*File:\s*)[`"]?([a-zA-Z0-9_.\-\/\\]+)[`"]?\s*[\r\n]+```[\w-]*[\r\n]+([\s\S]*?)```/gi
38
- while ((match = headerRegex.exec(text)) !== null) {
39
- const rawPath = match[1].trim()
40
- const content = match[2]
41
- if (rawPath && !seenPaths.has(rawPath)) {
42
- seenPaths.add(rawPath)
43
- files.push({ relativePath: sanitizePath(rawPath), content })
44
- }
45
- }
45
+ // Pattern 1: fenced blocks with file="..." or path="..." in info string
46
+ const fenceRegex = /```[\w-]*\s+(?:file|path|filepath)=["']?([^\s"'\n\r]+)["']?[\r\n]+([\s\S]*?)```/gi
47
+ let match
48
+ while ((match = fenceRegex.exec(text)) !== null) addFile(match[1].trim(), match[2])
46
49
 
47
- // Pattern 3: Comment inside the code block on line 1: // File: foo.js or /* File: foo.css */ or <!-- File: index.html -->
48
- const commentRegex = /```[\w-]*[\r\n]+(?:\/\/|\/\*|<!--|#)\s*(?:File|filename):\s*([a-zA-Z0-9_.\-\/\\]+)(?:\s*\*\/|\s*-->)?[\r\n]+([\s\S]*?)```/gi
49
- while ((match = commentRegex.exec(text)) !== null) {
50
- const rawPath = match[1].trim()
51
- const content = match[2]
52
- if (rawPath && !seenPaths.has(rawPath)) {
53
- seenPaths.add(rawPath)
54
- files.push({ relativePath: sanitizePath(rawPath), content })
55
- }
56
- }
50
+ // Pattern 2: comment header: // File: path/to/file.ext or <!-- File: ... -->
51
+ const commentRegex = /(?:\/\/|#|<!--)\s*(?:File|Path):\s*([^\s\n\r>]+)(?:\s*-->)?[\r\n]+```[\w-]*[\r\n]+([\s\S]*?)```/gi
52
+ while ((match = commentRegex.exec(text)) !== null) addFile(match[1].trim(), match[2])
53
+
54
+ // Pattern 3: markdown header: ### File: path/to/file.ext
55
+ const headerRegex = /#{1,4}\s+(?:File|Path):\s*([^\s\n\r]+)[\r\n]+```[\w-]*[\r\n]+([\s\S]*?)```/gi
56
+ while ((match = headerRegex.exec(text)) !== null) addFile(match[1].trim(), match[2])
57
57
 
58
- // Fallback: If no annotated blocks found, check if text has exactly one prominent standalone code block with common extensions
58
+ // Pattern 4: first line inside code block: // File: path/to/file.ext
59
+ const inlineRegex = /```[\w-]*[\r\n]+(?:\/\/|#|<!--)\s*(?:File|Path):\s*([^\s\n\r>]+)(?:\s*-->)?[\r\n]+([\s\S]*?)```/gi
60
+ while ((match = inlineRegex.exec(text)) !== null) addFile(match[1].trim(), match[2])
61
+
62
+ // Fallback: single anonymous code block if it contains complete HTML
59
63
  if (files.length === 0) {
60
- const singleBlockRegex = /```(html|css|javascript|js|json|python|py|sh|bash)[\r\n]+([\s\S]*?)```/gi
61
- const singleMatches = []
62
- while ((match = singleBlockRegex.exec(text)) !== null) {
63
- singleMatches.push({ lang: match[1].toLowerCase(), content: match[2] })
64
- }
65
- if (singleMatches.length === 1) {
66
- const extMap = {
67
- html: 'index.html',
68
- javascript: 'index.js',
69
- js: 'index.js',
70
- css: 'style.css',
71
- python: 'main.py',
72
- py: 'main.py',
73
- json: 'data.json',
74
- bash: 'run.sh',
75
- sh: 'run.sh',
64
+ const htmlBlockRegex = /```(?:html)?[\r\n]+([\s\S]*?)```/gi
65
+ let htmlMatch
66
+ while ((htmlMatch = htmlBlockRegex.exec(text)) !== null) {
67
+ const candidateHtml = htmlMatch[1]
68
+ if (/<(!DOCTYPE\s+)?html/i.test(candidateHtml) || /<body/i.test(candidateHtml)) {
69
+ files.push({ relativePath: 'index.html', content: candidateHtml.trimEnd() + '\n' })
70
+ break
76
71
  }
77
- const defaultName = extMap[singleMatches[0].lang] || 'output.txt'
78
- files.push({ relativePath: defaultName, content: singleMatches[0].content })
79
72
  }
80
73
  }
81
74
 
82
75
  return files
83
76
  }
84
77
 
85
- function sanitizePath(filePath) {
86
- const normalized = path.normalize(filePath).replace(/^(\.\.[\/\\])+/, '')
87
- return normalized.replace(/^[\\\/]+/, '')
88
- }
89
-
90
- /**
91
- * Collects readable text files from current workspace to feed as context for candidate models (#6).
92
- * Ignores node_modules, .git, .moa, binary files, large bundles.
93
- */
94
78
  /**
95
- * Formats collected project files into structured Markdown code blocks
96
- * suitable for inclusion in candidate system prompts.
79
+ * Formats collected project files into a structured markdown context string.
97
80
  */
98
81
  export function formatProjectContext(projectFiles = [], options = {}) {
99
82
  if (!Array.isArray(projectFiles) || projectFiles.length === 0) return ''
@@ -123,7 +106,6 @@ export async function collectProjectContext(baseDir, maxCharBudget = 16000) {
123
106
 
124
107
  const collected = []
125
108
  let totalChars = 0
126
-
127
109
  let skippedFiles = 0
128
110
  const skippedList = []
129
111
 
@@ -132,17 +114,14 @@ export async function collectProjectContext(baseDir, maxCharBudget = 16000) {
132
114
  let list
133
115
  try {
134
116
  list = await fs.readdir(currentDir, { withFileTypes: true })
135
- } catch (readDirErr) {
117
+ } catch {
136
118
  skippedFiles++
137
119
  skippedList.push(relPrefix || '.')
138
120
  return
139
121
  }
140
122
 
141
123
  for (const item of list) {
142
- // Skip all dotfiles, including .env and .env.* variants: their
143
- // contents must never be shipped to third-party model providers.
144
124
  if (item.name.startsWith('.')) continue
145
-
146
125
  const rel = relPrefix ? `${relPrefix}/${item.name}` : item.name
147
126
  const full = path.join(currentDir, item.name)
148
127
 
@@ -155,9 +134,7 @@ export async function collectProjectContext(baseDir, maxCharBudget = 16000) {
155
134
  }
156
135
 
157
136
  if (item.isDirectory()) {
158
- if (!ignoreDirs.has(item.name)) {
159
- await scan(full, rel)
160
- }
137
+ if (!ignoreDirs.has(item.name)) await scan(full, rel)
161
138
  } else if (item.isFile()) {
162
139
  const ext = path.extname(item.name).toLowerCase()
163
140
  if (codeExts.has(ext)) {
@@ -173,7 +150,7 @@ export async function collectProjectContext(baseDir, maxCharBudget = 16000) {
173
150
  const truncated = content.length > budgetRemaining ? content.slice(0, budgetRemaining) + '\n... [truncated]' : content
174
151
  totalChars += truncated.length
175
152
  collected.push({ relativePath: rel, content: truncated, size: stat.size })
176
- } catch (fileErr) {
153
+ } catch {
177
154
  skippedFiles++
178
155
  skippedList.push(rel)
179
156
  }
@@ -187,8 +164,7 @@ export async function collectProjectContext(baseDir, maxCharBudget = 16000) {
187
164
  }
188
165
 
189
166
  /**
190
- * Checks if a task prompt is an iterative refinement / modification
191
- * of an existing project rather than a greenfield project creation (#4).
167
+ * Checks if a task prompt is an iterative refinement / modification.
192
168
  */
193
169
  export function isRefinementTask(userPrompt = '', projectFiles = null) {
194
170
  if (Array.isArray(projectFiles) && projectFiles.length === 0) return false
@@ -226,13 +202,16 @@ export async function writeCandidateWorkspace(baseDir, candidateIndex, files = [
226
202
  if (!baseDir || !Array.isArray(files) || files.length === 0) return []
227
203
 
228
204
  const targetStr = String(candidateIndex).trim()
229
- const sub = /^\d+$/.test(targetStr) ? `candidate-${targetStr}` : targetStr
230
- const candidateDir = path.join(baseDir, '.moa', sub)
205
+ const sanitizedTarget = targetStr.replace(/[^a-zA-Z0-9_-]/g, '')
206
+ const sub = /^\d+$/.test(sanitizedTarget) ? `candidate-${sanitizedTarget}` : (sanitizedTarget || 'candidate-1')
207
+ const moaRoot = path.join(baseDir, '.moa')
208
+ const candidateDir = assertPathContained(moaRoot, sub)
231
209
  await fs.mkdir(candidateDir, { recursive: true })
232
210
 
233
211
  const written = []
234
212
  for (const file of files) {
235
- const fullPath = path.join(candidateDir, file.relativePath)
213
+ if (!isSafeRelativePath(file.relativePath)) continue
214
+ const fullPath = assertPathContained(candidateDir, file.relativePath)
236
215
  await fs.mkdir(path.dirname(fullPath), { recursive: true })
237
216
  await fs.writeFile(fullPath, file.content, 'utf8')
238
217
  written.push({
@@ -245,10 +224,6 @@ export async function writeCandidateWorkspace(baseDir, candidateIndex, files = [
245
224
  return written
246
225
  }
247
226
 
248
- /**
249
- * Promotes winning candidate files to baseDir root,
250
- * then cleans up the temporary .moa folder.
251
- */
252
227
  /**
253
228
  * Best-effort integration with dsh-time-machine to create a shadow Git checkpoint
254
229
  * before candidate promotion overwrites files in the project workspace.
@@ -267,14 +242,8 @@ export async function createPrePromotionCheckpoint(baseDir, candidateTarget, opt
267
242
  const snap = await bestEffort('dsh-time-machine pre-promotion checkpoint', async () => {
268
243
  const res = await fetch(endpoint, {
269
244
  method: 'POST',
270
- headers: {
271
- 'Content-Type': 'application/json',
272
- 'x-dsh-trusted': '1',
273
- },
274
- body: JSON.stringify({
275
- label,
276
- cwd: baseDir,
277
- }),
245
+ headers: { 'Content-Type': 'application/json', 'x-dsh-trusted': '1' },
246
+ body: JSON.stringify({ label, cwd: baseDir }),
278
247
  signal: AbortSignal.timeout(options.timeoutMs ?? 1500),
279
248
  })
280
249
  if (res.ok) {
@@ -290,9 +259,10 @@ export async function promoteCandidateWorkspace(baseDir, winningIndex, options =
290
259
  if (!baseDir || winningIndex === null || winningIndex === undefined) return []
291
260
 
292
261
  const targetStr = String(winningIndex).trim()
293
- const sub = /^\d+$/.test(targetStr) ? `candidate-${targetStr}` : targetStr
294
- const winningDir = path.join(baseDir, '.moa', sub)
262
+ const sanitizedTarget = targetStr.replace(/[^a-zA-Z0-9_-]/g, '')
263
+ const sub = /^\d+$/.test(sanitizedTarget) ? `candidate-${sanitizedTarget}` : (sanitizedTarget || 'candidate-1')
295
264
  const moaRoot = path.join(baseDir, '.moa')
265
+ const winningDir = assertPathContained(moaRoot, sub)
296
266
 
297
267
  try {
298
268
  const entries = await getFilesRecursively(winningDir)
@@ -303,27 +273,21 @@ export async function promoteCandidateWorkspace(baseDir, winningIndex, options =
303
273
  return []
304
274
  }
305
275
 
306
- // Best-effort Time Machine checkpoint before overwriting files in baseDir
307
276
  const checkpoint = await createPrePromotionCheckpoint(baseDir, winningIndex, options)
308
277
 
309
278
  const promoted = []
310
279
  for (const rel of entries) {
311
- const srcPath = path.join(winningDir, rel)
312
- const destPath = path.join(baseDir, rel)
280
+ if (!isSafeRelativePath(rel)) continue
281
+ const srcPath = assertPathContained(winningDir, rel)
282
+ const destPath = assertPathContained(baseDir, rel)
313
283
 
314
284
  await fs.mkdir(path.dirname(destPath), { recursive: true })
315
285
  await fs.copyFile(srcPath, destPath)
316
286
  promoted.push(rel)
317
287
  }
318
288
 
319
- if (checkpoint) {
320
- promoted.checkpoint = checkpoint
321
- }
322
-
323
- if (!options.keepMoa) {
324
- // Clean up temporary .moa folder
325
- await fs.rm(moaRoot, { recursive: true, force: true })
326
- }
289
+ if (checkpoint) promoted.checkpoint = checkpoint
290
+ if (!options.keepMoa) await fs.rm(moaRoot, { recursive: true, force: true })
327
291
 
328
292
  return promoted
329
293
  } catch (err) {
@@ -348,7 +312,7 @@ async function getFilesRecursively(dir) {
348
312
  try {
349
313
  const stat = await fs.stat(dir)
350
314
  if (!stat.isDirectory()) return []
351
- } catch (statErr) {
315
+ } catch {
352
316
  return []
353
317
  }
354
318
 
@@ -359,13 +323,10 @@ async function getFilesRecursively(dir) {
359
323
  for (const item of list) {
360
324
  const rel = relPrefix ? `${relPrefix}/${item.name}` : item.name
361
325
  const full = path.join(current, item.name)
362
- if (item.isDirectory()) {
363
- await scan(full, rel)
364
- } else if (item.isFile()) {
365
- results.push(rel)
366
- }
326
+ if (item.isDirectory()) await scan(full, rel)
327
+ else if (item.isFile()) results.push(rel)
367
328
  }
368
- } catch (readErr) {
329
+ } catch {
369
330
  return
370
331
  }
371
332
  }
@@ -373,10 +334,8 @@ async function getFilesRecursively(dir) {
373
334
  return results
374
335
  }
375
336
 
376
-
377
337
  /**
378
338
  * Verifies the syntactic validity of generated code files.
379
- * Uses Node.js in-memory vm.Script for JS (with ES module export/import transformation) and JSON.parse for JSON.
380
339
  */
381
340
  export function verifyFileSyntax(files = []) {
382
341
  if (!Array.isArray(files)) return []
@@ -424,33 +383,43 @@ export function verifyFileSyntax(files = []) {
424
383
  export async function readCandidateFiles(baseDir, candidateTarget) {
425
384
  if (!baseDir || candidateTarget === undefined || candidateTarget === null) return []
426
385
  const targetStr = String(candidateTarget).trim()
427
- const sub = /^\d+$/.test(targetStr) ? `candidate-${targetStr}` : targetStr
428
- const targetDir = path.join(baseDir, '.moa', sub)
386
+ const sanitizedTarget = targetStr.replace(/[^a-zA-Z0-9_-]/g, '')
387
+ const sub = /^\d+$/.test(sanitizedTarget) ? `candidate-${sanitizedTarget}` : (sanitizedTarget || 'candidate-1')
388
+ const moaRoot = path.join(baseDir, '.moa')
389
+
390
+ let targetDir
391
+ try {
392
+ targetDir = assertPathContained(moaRoot, sub)
393
+ } catch {
394
+ return []
395
+ }
429
396
 
430
397
  try {
431
398
  const relFiles = await getFilesRecursively(targetDir)
432
399
  const result = []
433
400
  for (const rel of relFiles) {
401
+ if (!isSafeRelativePath(rel)) continue
434
402
  try {
435
- const fullPath = path.join(targetDir, rel)
403
+ const fullPath = assertPathContained(targetDir, rel)
436
404
  const content = await fs.readFile(fullPath, 'utf8')
437
405
  result.push({
438
406
  relativePath: rel,
439
407
  content,
440
408
  size: Buffer.byteLength(content, 'utf8'),
441
409
  })
442
- } catch (readErr) {
410
+ } catch {
443
411
  // file read error best-effort
444
412
  }
445
413
  }
446
414
  return result
447
- } catch (err) {
415
+ } catch {
448
416
  return []
449
417
  }
450
418
  }
451
419
 
452
420
  /**
453
- * Computes a standard line-by-line diff between two text strings using LCS.
421
+ * Computes a line-by-line diff between two text strings using LCS with
422
+ * common prefix and suffix trimming for optimal O(delta) performance.
454
423
  * Returns an array of diff items: { type: 'same' | 'added' | 'removed', line: string, oldNum?: number, newNum?: number }
455
424
  */
456
425
  export function computeLineDiff(oldText = '', newText = '') {
@@ -460,44 +429,97 @@ export function computeLineDiff(oldText = '', newText = '') {
460
429
  const n = oldLines.length
461
430
  const m = newLines.length
462
431
 
463
- // Safety bound to avoid polynomial blowup on enormous files
464
- if (n > 2000 || m > 2000) {
465
- const res = []
466
- for (let i = 0; i < n; i++) res.push({ type: 'removed', line: oldLines[i], oldNum: i + 1 })
467
- for (let j = 0; j < m; j++) res.push({ type: 'added', line: newLines[j], newNum: j + 1 })
468
- return res
432
+ if (n === 0 && m === 0) return []
433
+ if (oldText === newText) {
434
+ return oldLines.map((line, idx) => ({
435
+ type: 'same',
436
+ line,
437
+ oldNum: idx + 1,
438
+ newNum: idx + 1,
439
+ }))
440
+ }
441
+
442
+ // 1. Trim common prefix
443
+ let start = 0
444
+ while (start < n && start < m && oldLines[start] === newLines[start]) {
445
+ start++
469
446
  }
470
447
 
471
- // DP table for LCS
472
- const dp = Array.from({ length: n + 1 }, () => new Uint16Array(m + 1))
473
- for (let i = 0; i < n; i++) {
474
- for (let j = 0; j < m; j++) {
475
- if (oldLines[i] === newLines[j]) {
476
- dp[i + 1][j + 1] = dp[i][j] + 1
477
- } else {
478
- dp[i + 1][j + 1] = Math.max(dp[i + 1][j], dp[i][j + 1])
448
+ // 2. Trim common suffix
449
+ let oldEnd = n - 1
450
+ let newEnd = m - 1
451
+ while (oldEnd >= start && newEnd >= start && oldLines[oldEnd] === newLines[newEnd]) {
452
+ oldEnd--
453
+ newEnd--
454
+ }
455
+
456
+ const prefixItems = []
457
+ for (let k = 0; k < start; k++) {
458
+ prefixItems.push({ type: 'same', line: oldLines[k], oldNum: k + 1, newNum: k + 1 })
459
+ }
460
+
461
+ const suffixItems = []
462
+ const suffixLen = (n - 1) - oldEnd
463
+ for (let k = 0; k < suffixLen; k++) {
464
+ const oNum = oldEnd + 1 + k + 1
465
+ const nNum = newEnd + 1 + k + 1
466
+ suffixItems.push({ type: 'same', line: oldLines[oldEnd + 1 + k], oldNum: oNum, newNum: nNum })
467
+ }
468
+
469
+ const subOld = oldLines.slice(start, oldEnd + 1)
470
+ const subNew = newLines.slice(start, newEnd + 1)
471
+ const subN = subOld.length
472
+ const subM = subNew.length
473
+
474
+ const middleItems = []
475
+
476
+ if (subN === 0) {
477
+ for (let j = 0; j < subM; j++) {
478
+ middleItems.push({ type: 'added', line: subNew[j], newNum: start + j + 1 })
479
+ }
480
+ } else if (subM === 0) {
481
+ for (let i = 0; i < subN; i++) {
482
+ middleItems.push({ type: 'removed', line: subOld[i], oldNum: start + i + 1 })
483
+ }
484
+ } else if (subN > 2000 || subM > 2000) {
485
+ for (let i = 0; i < subN; i++) {
486
+ middleItems.push({ type: 'removed', line: subOld[i], oldNum: start + i + 1 })
487
+ }
488
+ for (let j = 0; j < subM; j++) {
489
+ middleItems.push({ type: 'added', line: subNew[j], newNum: start + j + 1 })
490
+ }
491
+ } else {
492
+ const dp = Array.from({ length: subN + 1 }, () => new Uint16Array(subM + 1))
493
+ for (let i = 0; i < subN; i++) {
494
+ for (let j = 0; j < subM; j++) {
495
+ if (subOld[i] === subNew[j]) {
496
+ dp[i + 1][j + 1] = dp[i][j] + 1
497
+ } else {
498
+ dp[i + 1][j + 1] = Math.max(dp[i + 1][j], dp[i][j + 1])
499
+ }
479
500
  }
480
501
  }
481
- }
482
502
 
483
- // Backtrack
484
- let i = n
485
- let j = m
486
- const stack = []
487
-
488
- while (i > 0 || j > 0) {
489
- if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
490
- stack.push({ type: 'same', line: oldLines[i - 1], oldNum: i, newNum: j })
491
- i--
492
- j--
493
- } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
494
- stack.push({ type: 'added', line: newLines[j - 1], newNum: j })
495
- j--
496
- } else if (i > 0 && (j === 0 || dp[i][j - 1] < dp[i - 1][j])) {
497
- stack.push({ type: 'removed', line: oldLines[i - 1], oldNum: i })
498
- i--
503
+ let i = subN
504
+ let j = subM
505
+ const stack = []
506
+
507
+ while (i > 0 || j > 0) {
508
+ if (i > 0 && j > 0 && subOld[i - 1] === subNew[j - 1]) {
509
+ stack.push({ type: 'same', line: subOld[i - 1], oldNum: start + i, newNum: start + j })
510
+ i--
511
+ j--
512
+ } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
513
+ stack.push({ type: 'added', line: subNew[j - 1], newNum: start + j })
514
+ j--
515
+ } else if (i > 0 && (j === 0 || dp[i][j - 1] < dp[i - 1][j])) {
516
+ stack.push({ type: 'removed', line: subOld[i - 1], oldNum: start + i })
517
+ i--
518
+ }
499
519
  }
520
+
521
+ middleItems.push(...stack.reverse())
500
522
  }
501
523
 
502
- return stack.reverse()
524
+ return [...prefixItems, ...middleItems, ...suffixItems]
503
525
  }
package/lib/history.js CHANGED
@@ -15,6 +15,13 @@ const DEFAULT_HISTORY_FILE = path.join(DEFAULT_HISTORY_DIR, 'moa-history.jsonl')
15
15
  /** Max history file size before rotation (10 MB) */
16
16
  export const MAX_HISTORY_BYTES = 10 * 1024 * 1024
17
17
 
18
+ /** In-memory cache for leaderboard and parsed analytics */
19
+ let _historyCache = null
20
+
21
+ export function invalidateHistoryCache() {
22
+ _historyCache = null
23
+ }
24
+
18
25
  function getHistoryFilePath(customDir) {
19
26
  if (customDir) {
20
27
  return path.join(customDir, '.moa-history.jsonl')
@@ -36,6 +43,7 @@ export function rotateHistoryFileIfNeeded(filePath = DEFAULT_HISTORY_FILE, maxBy
36
43
  try { fs.unlinkSync(backupPath) } catch (unlinkErr) { /* backup unlink best-effort */ }
37
44
  }
38
45
  fs.renameSync(filePath, backupPath)
46
+ invalidateHistoryCache()
39
47
  return true
40
48
  }
41
49
  } catch (err) {
@@ -97,6 +105,7 @@ export function recordMoaRun(record, filePath = DEFAULT_HISTORY_FILE) {
97
105
  const entry = normalizeRunRecord(record)
98
106
  const line = JSON.stringify(entry) + '\n'
99
107
  fs.appendFileSync(filePath, line, 'utf8')
108
+ invalidateHistoryCache()
100
109
  return entry
101
110
  } catch (err) {
102
111
  console.warn('[dsh-moa] Failed to record run to history:', err)
@@ -117,6 +126,7 @@ export async function recordMoaRunAsync(record, filePath = DEFAULT_HISTORY_FILE)
117
126
  const entry = normalizeRunRecord(record)
118
127
  const line = JSON.stringify(entry) + '\n'
119
128
  await fs.promises.appendFile(filePath, line, 'utf8')
129
+ invalidateHistoryCache()
120
130
  return entry
121
131
  } catch (err) {
122
132
  console.warn('[dsh-moa] Failed to async-record run to history:', err)
@@ -124,6 +134,49 @@ export async function recordMoaRunAsync(record, filePath = DEFAULT_HISTORY_FILE)
124
134
  }
125
135
  }
126
136
 
137
+ /**
138
+ * Reads lines from the end of a file in reverse order in chunks.
139
+ * Avoids loading multi-megabyte JSONL files into V8 heap strings.
140
+ */
141
+ export function readTailLinesSync(filePath, countNeeded = 50) {
142
+ const CHUNK_SIZE = 64 * 1024
143
+ const fd = fs.openSync(filePath, 'r')
144
+ try {
145
+ const stat = fs.fstatSync(fd)
146
+ let pos = stat.size
147
+ let remainder = ''
148
+ const collectedLines = []
149
+
150
+ while (pos > 0 && collectedLines.length < countNeeded) {
151
+ const bytesToRead = Math.min(CHUNK_SIZE, pos)
152
+ pos -= bytesToRead
153
+ const buf = Buffer.alloc(bytesToRead)
154
+ fs.readSync(fd, buf, 0, bytesToRead, pos)
155
+ const chunkStr = buf.toString('utf8') + remainder
156
+ const lines = chunkStr.split('\n')
157
+ if (pos > 0) {
158
+ remainder = lines.shift()
159
+ } else {
160
+ remainder = ''
161
+ }
162
+
163
+ for (let i = lines.length - 1; i >= 0; i--) {
164
+ const trimmed = lines[i].trim()
165
+ if (trimmed) {
166
+ collectedLines.push(trimmed)
167
+ if (collectedLines.length >= countNeeded) break
168
+ }
169
+ }
170
+ }
171
+ if (remainder && remainder.trim() && collectedLines.length < countNeeded) {
172
+ collectedLines.push(remainder.trim())
173
+ }
174
+ return collectedLines
175
+ } finally {
176
+ fs.closeSync(fd)
177
+ }
178
+ }
179
+
127
180
  /**
128
181
  * Reads history runs with pagination (latest first).
129
182
  */
@@ -133,13 +186,34 @@ export function getMoaHistory(limit = 20, offset = 0, filePath = DEFAULT_HISTORY
133
186
  return { total: 0, runs: [] }
134
187
  }
135
188
 
136
- const raw = fs.readFileSync(filePath, 'utf8')
137
- const lines = raw.trim().split('\n').filter(Boolean)
138
- const total = lines.length
189
+ const stat = fs.statSync(filePath)
190
+ const needed = offset + limit
191
+
192
+ // For files under 64KB, straightforward full-read is fast and keeps exact total
193
+ if (stat.size <= 65536) {
194
+ const raw = fs.readFileSync(filePath, 'utf8')
195
+ const lines = raw.trim().split('\n').filter(Boolean)
196
+ const total = lines.length
197
+
198
+ const sliced = lines
199
+ .slice(Math.max(0, total - offset - limit), total - offset)
200
+ .reverse()
201
+ .map((line) => {
202
+ try {
203
+ return JSON.parse(line)
204
+ } catch {
205
+ return null
206
+ }
207
+ })
208
+ .filter(Boolean)
209
+
210
+ return { total, runs: sliced }
211
+ }
139
212
 
140
- const sliced = lines
141
- .slice(Math.max(0, total - offset - limit), total - offset)
142
- .reverse()
213
+ // For larger files: read only tail lines backwards to avoid splitting 10MB in memory
214
+ const tailLines = readTailLinesSync(filePath, needed)
215
+ const pageLines = tailLines.slice(offset, offset + limit)
216
+ const runs = pageLines
143
217
  .map((line) => {
144
218
  try {
145
219
  return JSON.parse(line)
@@ -149,7 +223,12 @@ export function getMoaHistory(limit = 20, offset = 0, filePath = DEFAULT_HISTORY
149
223
  })
150
224
  .filter(Boolean)
151
225
 
152
- return { total, runs: sliced }
226
+ let total = _historyCache?.totalCount
227
+ if (typeof total !== 'number') {
228
+ total = tailLines.length >= needed ? Math.max(tailLines.length, Math.round(stat.size / 250)) : tailLines.length
229
+ }
230
+
231
+ return { total, runs }
153
232
  } catch (err) {
154
233
  console.warn('[dsh-moa] Failed to read history:', err)
155
234
  return { total: 0, runs: [] }
@@ -158,6 +237,7 @@ export function getMoaHistory(limit = 20, offset = 0, filePath = DEFAULT_HISTORY
158
237
 
159
238
  /**
160
239
  * Calculates win-rate leaderboard and model performance statistics across history.
240
+ * Employs in-memory caching keyed by file mtime and size for O(1) repeated queries.
161
241
  */
162
242
  export function getMoaLeaderboard(filePath = DEFAULT_HISTORY_FILE, options = {}) {
163
243
  try {
@@ -165,13 +245,35 @@ export function getMoaLeaderboard(filePath = DEFAULT_HISTORY_FILE, options = {})
165
245
  return { totalRuns: 0, models: [] }
166
246
  }
167
247
 
248
+ const stat = fs.statSync(filePath)
249
+ const presetFilter = typeof options === 'string' ? options : (options?.presetFilter || options?.preset || null)
250
+ const filterKey = presetFilter || 'all'
251
+
252
+ if (
253
+ _historyCache &&
254
+ _historyCache.filePath === filePath &&
255
+ _historyCache.mtimeMs === stat.mtimeMs &&
256
+ _historyCache.size === stat.size
257
+ ) {
258
+ if (_historyCache.leaderboards.has(filterKey)) {
259
+ return _historyCache.leaderboards.get(filterKey)
260
+ }
261
+ } else {
262
+ _historyCache = {
263
+ filePath,
264
+ mtimeMs: stat.mtimeMs,
265
+ size: stat.size,
266
+ leaderboards: new Map(),
267
+ totalCount: null,
268
+ }
269
+ }
270
+
168
271
  const raw = fs.readFileSync(filePath, 'utf8')
169
272
  const lines = raw.trim().split('\n').filter(Boolean)
170
273
  const totalRuns = lines.length
274
+ _historyCache.totalCount = totalRuns
171
275
 
172
276
  const modelStats = new Map()
173
- const presetFilter = typeof options === 'string' ? options : (options?.presetFilter || options?.preset || null)
174
-
175
277
  let filteredRuns = 0
176
278
  for (const line of lines) {
177
279
  try {
@@ -198,6 +300,7 @@ export function getMoaLeaderboard(filePath = DEFAULT_HISTORY_FILE, options = {})
198
300
  }
199
301
  const st = modelStats.get(key)
200
302
  st.runs += 1
303
+
201
304
  const inTok = cand.usage?.inputTokens || 0
202
305
  const outTok = cand.usage?.outputTokens || 0
203
306
  st.totalTokens += inTok + outTok
@@ -222,7 +325,13 @@ export function getMoaLeaderboard(filePath = DEFAULT_HISTORY_FILE, options = {})
222
325
  }))
223
326
  .sort((a, b) => b.wins - a.wins || b.winRate - a.winRate)
224
327
 
225
- return { totalRuns: presetFilter && presetFilter !== 'all' ? filteredRuns : totalRuns, models, preset: presetFilter }
328
+ const result = {
329
+ totalRuns: presetFilter && presetFilter !== 'all' ? filteredRuns : totalRuns,
330
+ models,
331
+ preset: presetFilter,
332
+ }
333
+ _historyCache.leaderboards.set(filterKey, result)
334
+ return result
226
335
  } catch (err) {
227
336
  console.warn('[dsh-moa] Failed to calculate leaderboard:', err)
228
337
  return { totalRuns: 0, models: [] }
@@ -104,7 +104,12 @@ export async function runReferencesParallel(references, messages, options = {},
104
104
  )
105
105
 
106
106
  const timeoutPromise = new Promise((_, reject) => {
107
- const timer = setTimeout(() => reject(new Error(`Timeout after ${Math.round(timeoutMs / 1000)}s`)), timeoutMs)
107
+ const timer = setTimeout(() => {
108
+ bestEffort('candidate timeout abort', () => {
109
+ abortControllers[i].abort(new Error(`Timeout after ${Math.round(timeoutMs / 1000)}s`))
110
+ })
111
+ reject(new Error(`Timeout after ${Math.round(timeoutMs / 1000)}s`))
112
+ }, timeoutMs)
108
113
  timer.unref?.()
109
114
  abortControllers[i].signal.addEventListener('abort', () => clearTimeout(timer), { once: true })
110
115
  })
package/lib/routes.js CHANGED
@@ -8,10 +8,13 @@ import {
8
8
  getMoaRunById,
9
9
  exportMoaHistory,
10
10
  } from './history.js'
11
- import { promoteCandidateWorkspace, readCandidateFiles, computeLineDiff } from './file-workspace.js'
11
+ import { promoteCandidateWorkspace, readCandidateFiles, computeLineDiff, isSafeRelativePath } from './file-workspace.js'
12
12
  import { isSafeWriteRequest } from './updater.js'
13
13
  import { runMoAPipeline } from './moa-runner.js'
14
14
 
15
+ let _modelsCache = { timestamp: 0, models: [] }
16
+ const MODELS_CACHE_TTL = 60 * 1000
17
+
15
18
  function writeJson(res, status, body) {
16
19
  res.writeHead(status, {
17
20
  'Content-Type': 'application/json; charset=utf-8',
@@ -60,6 +63,11 @@ ctx.effect(() => {
60
63
  kind: 'exact',
61
64
  path: '/dsh-moa/models',
62
65
  handler: async (_req, res) => {
66
+ if (_modelsCache.models.length > 0 && Date.now() - _modelsCache.timestamp < MODELS_CACHE_TTL) {
67
+ writeJson(res, 200, { ok: true, models: _modelsCache.models, cached: true })
68
+ return
69
+ }
70
+
63
71
  const result = []
64
72
  const seen = new Set()
65
73
 
@@ -87,18 +95,23 @@ ctx.effect(() => {
87
95
  try {
88
96
  if (typeof ctx.llm.listConfigurableProviders === 'function') {
89
97
  const cProvs = ctx.llm.listConfigurableProviders() || []
90
- for (const cp of cProvs) {
98
+ const queries = cProvs.map(async (cp) => {
91
99
  const cpId = cp.provider || cp.id
92
- if (cpId && typeof ctx.llm.listModels === 'function') {
93
- try {
94
- const mList = await ctx.llm.listModels(cpId)
95
- for (const m of (mList || [])) {
96
- const mId = typeof m === 'string' ? m : (m.id || m.model || m.name)
97
- if (mId) add(cpId, mId)
98
- }
99
- } catch (mErr) { /* listModels best-effort */ }
100
+ if (!cpId || typeof ctx.llm.listModels !== 'function') return
101
+ try {
102
+ const mList = await Promise.race([
103
+ ctx.llm.listModels(cpId),
104
+ new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 2000)),
105
+ ])
106
+ for (const m of (mList || [])) {
107
+ const mId = typeof m === 'string' ? m : (m?.id || m?.model || m?.name)
108
+ if (mId) add(cpId, mId)
109
+ }
110
+ } catch {
111
+ /* provider query timeout or error handled best-effort */
100
112
  }
101
- }
113
+ })
114
+ await Promise.allSettled(queries)
102
115
  }
103
116
  } catch (e) {
104
117
  console.warn('[dsh-moa] ctx.llm.listConfigurableProviders error:', e)
@@ -136,6 +149,7 @@ ctx.effect(() => {
136
149
  console.warn('[dsh-moa] Error reading settings:', e)
137
150
  }
138
151
 
152
+ _modelsCache = { timestamp: Date.now(), models: result }
139
153
  writeJson(res, 200, { ok: true, models: result })
140
154
  },
141
155
  })
@@ -309,9 +323,14 @@ ctx.effect(() => {
309
323
  try {
310
324
  const u = new URL(req.url, 'http://localhost')
311
325
  const cwd = u.searchParams.get('cwd') || process.cwd()
312
- const from = u.searchParams.get('from') || '1'
313
- const to = u.searchParams.get('to') || 'curator-synthesis'
314
- const file = u.searchParams.get('file') || ''
326
+ const sanitizeTarget = (val, def) => {
327
+ const clean = String(val || '').trim().replace(/[^a-zA-Z0-9_-]/g, '')
328
+ return clean || def
329
+ }
330
+ const from = sanitizeTarget(u.searchParams.get('from'), '1')
331
+ const to = sanitizeTarget(u.searchParams.get('to'), 'curator-synthesis')
332
+ const rawFile = u.searchParams.get('file') || ''
333
+ const file = (rawFile && isSafeRelativePath(rawFile)) ? rawFile : ''
315
334
 
316
335
  const fromFiles = await readCandidateFiles(cwd, from)
317
336
  const toFiles = await readCandidateFiles(cwd, to)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-moa",
3
- "version": "0.2.20",
3
+ "version": "0.2.21",
4
4
  "description": "Mixture of Agents (MoA) plugin for DeepSeek Harness with /moa slash command",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",