@theronap/agnoclast-mcp 0.9.148 → 0.9.149
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/edge_extract.mjs +54 -3
- package/package.json +1 -1
package/lib/edge_extract.mjs
CHANGED
|
@@ -289,6 +289,51 @@ export function verbatimIn(needle, haystack) {
|
|
|
289
289
|
return norm(haystack).includes(n)
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
// ── SNAPPING A NEAR-QUOTE TO ITS SOURCE (ADR-0059 §4.1, added 2026-09-11) ──────────────────────────
|
|
293
|
+
//
|
|
294
|
+
// 🔴 WHY. The STRAT 421 research brief was lost on two separate runs for one word. The model quoted "…but
|
|
295
|
+
// just have it on your radar. That will be September 22nd as well." The lecture says "on THE radar". The
|
|
296
|
+
// gate above is right to refuse that quote, because a paraphrase is not evidence. But refusing the whole
|
|
297
|
+
// CANDIDATE threw away a real, dated deadline whose source passage is right there, differing by one word.
|
|
298
|
+
//
|
|
299
|
+
// So a quote the gate refuses gets one more chance: find the SOURCE passage it came from, and use the
|
|
300
|
+
// SOURCE's words. The quote must be long (SNAP_MIN_WORDS). Its first and last SNAP_ANCHOR_WORDS words must
|
|
301
|
+
// each appear VERBATIM in the source, in order. The span between them must be within
|
|
302
|
+
// SNAP_LENGTH_TOLERANCE of the quote's length. And exactly ONE such span may exist.
|
|
303
|
+
//
|
|
304
|
+
// ⚠ THIS DOES NOT LOOSEN THE GATE. What is stored is still verbatim source text — the transcript's
|
|
305
|
+
// words, not the model's — so "the evidence sentence is in the source" stays true. What changes is who
|
|
306
|
+
// wrote the evidence. A fabrication does not survive: an invented middle must still sit between two exact,
|
|
307
|
+
// in-order, correctly-spaced anchors, and it is then REPLACED by what the source actually says there.
|
|
308
|
+
// That is the text the reviewer and the person read. Not snapped: short quotes (anchors too weak), a
|
|
309
|
+
// quote splicing distant passages (length check), and an anchor pair that matches in two places.
|
|
310
|
+
const SNAP_ANCHOR_WORDS = 6
|
|
311
|
+
const SNAP_MIN_WORDS = 14
|
|
312
|
+
const SNAP_LENGTH_TOLERANCE = 0.25
|
|
313
|
+
|
|
314
|
+
/** PURE. The source passage a near-verbatim quote was taken from (whitespace-normalised), or null. */
|
|
315
|
+
export function snapToSource(quote, source) {
|
|
316
|
+
const q = String(quote ?? '').replace(/\s+/g, ' ').trim()
|
|
317
|
+
const words = q.split(' ')
|
|
318
|
+
if (!q || words.length < SNAP_MIN_WORDS) return null
|
|
319
|
+
const src = String(source ?? '')
|
|
320
|
+
const esc = (w) => w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
321
|
+
const anchor = (ws) => new RegExp(ws.map(esc).join('\\s+'), 'g')
|
|
322
|
+
const lo = q.length * (1 - SNAP_LENGTH_TOLERANCE)
|
|
323
|
+
const hi = q.length * (1 + SNAP_LENGTH_TOLERANCE)
|
|
324
|
+
const found = new Set()
|
|
325
|
+
for (const h of src.matchAll(anchor(words.slice(0, SNAP_ANCHOR_WORDS)))) {
|
|
326
|
+
const tail = anchor(words.slice(-SNAP_ANCHOR_WORDS))
|
|
327
|
+
tail.lastIndex = h.index + h[0].length
|
|
328
|
+
for (let t = tail.exec(src); t; t = tail.exec(src)) {
|
|
329
|
+
const span = src.slice(h.index, t.index + t[0].length).replace(/\s+/g, ' ').trim()
|
|
330
|
+
if (span.length > hi) break
|
|
331
|
+
if (span.length >= lo) { found.add(span); break }
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return found.size === 1 ? [...found][0] : null
|
|
335
|
+
}
|
|
336
|
+
|
|
292
337
|
/**
|
|
293
338
|
* Drop every obligation candidate that cannot be verified against the text the model was shown.
|
|
294
339
|
*
|
|
@@ -309,18 +354,24 @@ export function keepVerifiableObligations(items, shown) {
|
|
|
309
354
|
const evidence = typeof it.evidence === 'string' ? it.evidence.trim() : ''
|
|
310
355
|
const party = typeof it.obligated_party === 'string' ? it.obligated_party.trim() : ''
|
|
311
356
|
if (!subject || !evidence || !OBLIGATION_PARTIES.has(party)) continue
|
|
312
|
-
|
|
357
|
+
// Verbatim, or a near-quote snapped to the source's own words (snapToSource). Nothing else.
|
|
358
|
+
let ev = evidence
|
|
359
|
+
if (!verbatimIn(ev, shown)) {
|
|
360
|
+
const snapped = snapToSource(ev, shown)
|
|
361
|
+
if (!snapped || !verbatimIn(snapped, shown)) continue
|
|
362
|
+
ev = snapped
|
|
363
|
+
}
|
|
313
364
|
const due = typeof it.due_at === 'string' && it.due_at.trim() ? it.due_at.trim() : null
|
|
314
365
|
// ADR-0059 §5.2: the WHEN words, which the server resolves into a date in code. Kept only if they
|
|
315
366
|
// appear inside the evidence sentence — the check that stops a paraphrase ("Sept 22" for "the
|
|
316
367
|
// 22nd") or a phrase from some other sentence from deciding the date. Dropped otherwise: the server
|
|
317
368
|
// then falls back to due_at, exactly as it did before phrases existed.
|
|
318
369
|
const phraseRaw = typeof it.due_phrase === 'string' ? it.due_phrase.trim() : ''
|
|
319
|
-
const due_phrase = phraseRaw && normWs(
|
|
370
|
+
const due_phrase = phraseRaw && normWs(ev).includes(normWs(phraseRaw)) ? phraseRaw.slice(0, 120) : null
|
|
320
371
|
out.push({
|
|
321
372
|
subject: subject.slice(0, 200), due_at: due,
|
|
322
373
|
...(due_phrase ? { due_phrase } : {}),
|
|
323
|
-
evidence:
|
|
374
|
+
evidence: ev.slice(0, 500), obligated_party: party,
|
|
324
375
|
})
|
|
325
376
|
if (out.length >= MAX_OBLIGATIONS) break
|
|
326
377
|
}
|
package/package.json
CHANGED