klypix-mcp 1.20.0 → 1.20.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klypix-mcp",
3
- "version": "1.20.0",
3
+ "version": "1.20.1",
4
4
  "description": "An open, local-first, agent-neutral canvas file your AI reads and writes over MCP — works with Claude, Cursor, Cline, any model.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1334,7 +1334,11 @@ const POLARITY_RES = POLARITY_PAIRS.map(([x, y]) => ({ x, y, rx: new RegExp(`\\b
1334
1334
  export function detectContradictions(struct, { minOverlap = 0.45, topK = 12 } = {}) {
1335
1335
  if (!struct || !Array.isArray(struct.cards)) return [];
1336
1336
  const isArchived = (c) => /^archive$/i.test(c.area || '');
1337
- const live = struct.cards.filter(c => c.type !== 'container' && (c.text || '').trim() && !isArchived(c));
1337
+ // Legacy pre-v1.15 raw-bash ship cards are noise (path-scraped junk); they poison
1338
+ // reconcile with vocabulary-overlap false positives just as they did repeat-
1339
+ // matching, so exclude them here too (they're surfaced for cleanup by
1340
+ // findLegacyShipCards / brain_reconcile mode:legacy instead).
1341
+ const live = struct.cards.filter(c => c.type !== 'container' && (c.text || '').trim() && !isArchived(c) && !isLegacyRawShipCard(c.text));
1338
1342
  // Cue meta words stripped up front — same-subject matching must compare the
1339
1343
  // SUBJECT, not the vocabulary of the correction act (see stripCueMeta above).
1340
1344
  const wset = new Map(live.map(c => [c.id, stripCueMeta(new Set(queryTokens(c.text)))]));
@@ -1635,15 +1639,27 @@ export function correctionOverlaysFor(struct, cards, { at = CORRECTION_SUPERSEDE
1635
1639
  // caller annotates the card (never retires it — the fact is right, only its
1636
1640
  // status decayed). Pure + cheap. PR numbers are capped at 5 digits — a 6+ digit
1637
1641
  // "#N" is a path-scraped junk number (see isLegacyRawShipCard), never a real PR.
1638
- const PR_MERGE_AWAIT_RE = /\b(?:await(?:s|ing)?|pending|not\s+yet|yet\s+to\s+be|to\s+be|unmerged|needs?)\b[^.\n]{0,24}?\bmerg/i;
1639
1642
  const PR_MERGED_RE = /\bmerged\b[^.\n]{0,16}?(?:PR|pull\s+request|#)\s*#?(\d{1,5})\b/i;
1640
1643
  const PR_REFS_RE = /\b(?:PR|pull\s+request|#)\s*#?(\d{1,5})\b/ig;
1644
+ // The awaits/pending-merge cue fragment, reused both directions below.
1645
+ const AWAIT_CUE = String.raw`(?:await(?:s|ing)?|pending|not\s+yet|yet\s+to\s+be|unmerged|needs?)\b[^.\n]{0,20}?merg`;
1641
1646
  function prNumbersIn(text) {
1642
1647
  const out = new Set(); const s = String(text || '');
1643
1648
  PR_REFS_RE.lastIndex = 0; let m;
1644
1649
  while ((m = PR_REFS_RE.exec(s))) out.add(m[1]);
1645
1650
  return out;
1646
1651
  }
1652
+ // PRECISION (field 2026-07-04): the awaits/pending cue must sit ADJACENT to THIS PR
1653
+ // ref — otherwise a long card that names "#850" only as an EXAMPLE and separately
1654
+ // says "awaits merge" (about something else) over-triggered the overlay. Require the
1655
+ // cue within ~30 chars of the specific "PR #N", in either order, same sentence.
1656
+ function awaitNearRef(text, num) {
1657
+ const n = String(num).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1658
+ const ref = String.raw`(?:PR|pull\s+request|#)\s*#?${n}\b`;
1659
+ const before = new RegExp(`${ref}[^.\\n]{0,30}?${AWAIT_CUE}`, 'i'); // "PR #7 awaits merge"
1660
+ const after = new RegExp(`${AWAIT_CUE}[^.\\n]{0,30}?${ref}`, 'i'); // "awaiting merge of PR #7"
1661
+ return before.test(text) || after.test(text);
1662
+ }
1647
1663
  export function mergeOverlaysFor(struct, cards) {
1648
1664
  const out = new Map();
1649
1665
  if (!struct || !Array.isArray(struct.cards) || !Array.isArray(cards) || !cards.length) return out;
@@ -1660,10 +1676,12 @@ export function mergeOverlaysFor(struct, cards) {
1660
1676
  for (const card of cards) {
1661
1677
  if (!card || !card.id || !(card.text || '').trim()) continue;
1662
1678
  if (PR_MERGED_RE.test(card.text)) continue; // the hit IS a merge note — nothing to overlay
1663
- if (!PR_MERGE_AWAIT_RE.test(card.text)) continue; // must actually claim it's awaiting a merge
1664
1679
  for (const num of prNumbersIn(card.text)) {
1665
1680
  const by = mergedBy.get(num);
1666
- if (by && by.id !== card.id) { out.set(card.id, { kind: 'merged', num, by, date: by.createdAt ? new Date(by.createdAt).toISOString().slice(0, 10) : null }); break; }
1681
+ if (!by || by.id === card.id) continue;
1682
+ if (!awaitNearRef(card.text, num)) continue; // the awaits cue must be ADJACENT to THIS ref (no example-#N over-trigger)
1683
+ out.set(card.id, { kind: 'merged', num, by, date: by.createdAt ? new Date(by.createdAt).toISOString().slice(0, 10) : null });
1684
+ break;
1667
1685
  }
1668
1686
  }
1669
1687
  return out;