linksee-memory 0.15.0 → 0.15.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.
@@ -95,6 +95,14 @@ export type ResolutionAction = 'fix' | 'supersede' | 'acknowledge' | 'dismiss';
95
95
  export interface ResolveInput {
96
96
  /** For dismiss: silence only this match term. Omitted → silence the anchor at the gate. */
97
97
  hit_term?: string;
98
+ /**
99
+ * For dismiss: false = close these drift edges as false positives but leave the gate
100
+ * watching. The default (true) also stops the gate. Two different verdicts hide behind
101
+ * "dismiss": "this detection was wrong" and "stop detecting this". Anchor #2 ("no destructive
102
+ * migrations") had a false positive from migrate.ts — the right answer was to close the edge
103
+ * and keep the gate, since "ALTER TABLE memories DROP" is exactly what it exists to catch.
104
+ */
105
+ gate?: boolean;
98
106
  anchor_id: number;
99
107
  action: ResolutionAction;
100
108
  rationale?: string;
@@ -543,12 +543,17 @@ export function resolveDrift(db, input) {
543
543
  // real detections survive.
544
544
  if (input.action === 'dismiss') {
545
545
  db.prepare("UPDATE drift_edges SET status = 'dismissed' WHERE anchor_id = ? AND status = 'open'").run(input.anchor_id);
546
- try {
547
- db.prepare(`INSERT INTO gate_dismissals (anchor_id, hit_term, rationale) VALUES (?, ?, ?)
548
- ON CONFLICT(anchor_id, COALESCE(hit_term, '')) DO UPDATE SET rationale = excluded.rationale`).run(input.anchor_id, input.hit_term ? input.hit_term.toLowerCase() : null, input.rationale ?? null);
549
- resolution.gate_dismissed = input.hit_term ? input.hit_term.toLowerCase() : 'all matches';
546
+ if (input.gate === false) {
547
+ resolution.gate_dismissed = 'none (edges closed; gate still watching)';
548
+ resolution.gate = false;
550
549
  }
551
- catch { /* pre-v16 DB → edges-only dismiss, as before */ }
550
+ else
551
+ try {
552
+ db.prepare(`INSERT INTO gate_dismissals (anchor_id, hit_term, rationale) VALUES (?, ?, ?)
553
+ ON CONFLICT(anchor_id, COALESCE(hit_term, '')) DO UPDATE SET rationale = excluded.rationale`).run(input.anchor_id, input.hit_term ? input.hit_term.toLowerCase() : null, input.rationale ?? null);
554
+ resolution.gate_dismissed = input.hit_term ? input.hit_term.toLowerCase() : 'all matches';
555
+ }
556
+ catch { /* pre-v16 DB → edges-only dismiss, as before */ }
552
557
  }
553
558
  // If action is 'fix', mark open edges as resolved
554
559
  if (input.action === 'fix') {
@@ -280,13 +280,14 @@ const TOOLS = [
280
280
  },
281
281
  {
282
282
  name: 'resolve_drift',
283
- description: 'Record a resolution for a drifting anchor — the human feedback loop.\n\n6 actions:\n• fix — "we fixed the code/reality to match intent" → state becomes aligned\n• supersede — "intent evolved, this is the new direction" → state becomes aligned\n• acknowledge — "we know, parking it for now" → state becomes held (with optional review date)\n• dismiss — "false positive, not actually drifting" → edges dismissed AND the gate stops firing on it (pass hit_term to silence just that word)\n• harden — "re-injected but still violated, enforce it" → card_policy.gate_mode=hard (PreToolUse will BLOCK)\n• soften — "back off to a warning" → gate_mode=soft\n\nWHEN TO CALL:\n• After drift_status shows 🔴 drift or 🟡 review items\n• When the user says "that\'s fixed" / "ignore that" / "we changed direction"\n• When acknowledging a known gap with a review date',
283
+ description: 'Record a resolution for a drifting anchor — the human feedback loop.\n\n6 actions:\n• fix — "we fixed the code/reality to match intent" → state becomes aligned\n• supersede — "intent evolved, this is the new direction" → state becomes aligned\n• acknowledge — "we know, parking it for now" → state becomes held (with optional review date)\n• dismiss — "false positive, not actually drifting" → edges dismissed AND the gate stops firing on it (hit_term: just that word; gate:false: close the edges but keep the gate watching)\n• harden — "re-injected but still violated, enforce it" → card_policy.gate_mode=hard (PreToolUse will BLOCK)\n• soften — "back off to a warning" → gate_mode=soft\n\nWHEN TO CALL:\n• After drift_status shows 🔴 drift or 🟡 review items\n• When the user says "that\'s fixed" / "ignore that" / "we changed direction"\n• When acknowledging a known gap with a review date',
284
284
  inputSchema: {
285
285
  type: 'object',
286
286
  properties: {
287
287
  anchor_id: { type: 'number', description: 'The drift_anchor ID to resolve' },
288
288
  action: { type: 'string', enum: ['fix', 'supersede', 'acknowledge', 'dismiss', 'harden', 'soften', 'surface'], description: "Resolution action. With candidate_id (an orphaned proposal): 'surface' keeps it visible for the human, 'dismiss' retires it." },
289
289
  candidate_id: { type: 'number', description: 'Resolve an orphaned proposal (from recall({ dream: true })) instead of an anchor: pass its candidate_id with action surface | dismiss and a rationale that references the North Star. (Absorbs resolve_proposal.)' },
290
+ gate: { type: 'boolean', default: true, description: "For dismiss: false = these detections were false positives, close them, but KEEP the gate watching this anchor (its signals still fire). Use when the anchor's rule is right and only this evidence was wrong — e.g. a lexical hit in a file that is not what the rule is about. Default true also silences the gate (whole anchor, or just hit_term)." },
290
291
  rationale: { type: 'string', description: 'Why this resolution (recorded for audit trail)' },
291
292
  review_after: { type: 'string', description: 'For acknowledge: ISO date to re-check (e.g. "2026-07-04")' },
292
293
  superseded_by: { type: 'number', description: 'For supersede: the new anchor ID that replaces this one' },
@@ -1751,6 +1752,7 @@ function handleResolveDrift(args) {
1751
1752
  review_after: args.review_after,
1752
1753
  superseded_by: args.superseded_by,
1753
1754
  hit_term: args.hit_term,
1755
+ gate: args.gate,
1754
1756
  });
1755
1757
  return JSON.stringify(result);
1756
1758
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linksee-memory",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "mcpName": "io.github.michielinksee/linksee-memory",
5
5
  "description": "Local-first agent memory MCP — cross-agent brain with drift detection, 6-layer structured memory + token-saving file diff cache",
6
6
  "type": "module",