hippo-memory 1.30.0 → 1.32.0

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 (106) hide show
  1. package/dist/api.d.ts +49 -0
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/api.js +78 -2
  4. package/dist/api.js.map +1 -1
  5. package/dist/audit.d.ts +1 -1
  6. package/dist/audit.d.ts.map +1 -1
  7. package/dist/audit.js.map +1 -1
  8. package/dist/capture.d.ts.map +1 -1
  9. package/dist/capture.js +79 -23
  10. package/dist/capture.js.map +1 -1
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -1
  13. package/dist/cli.js +233 -13
  14. package/dist/cli.js.map +1 -1
  15. package/dist/connectors/github/ingest.d.ts.map +1 -1
  16. package/dist/connectors/github/ingest.js +17 -0
  17. package/dist/connectors/github/ingest.js.map +1 -1
  18. package/dist/connectors/slack/ingest.d.ts.map +1 -1
  19. package/dist/connectors/slack/ingest.js +15 -0
  20. package/dist/connectors/slack/ingest.js.map +1 -1
  21. package/dist/consolidate.d.ts +1 -0
  22. package/dist/consolidate.d.ts.map +1 -1
  23. package/dist/consolidate.js +471 -291
  24. package/dist/consolidate.js.map +1 -1
  25. package/dist/dag.d.ts +17 -1
  26. package/dist/dag.d.ts.map +1 -1
  27. package/dist/dag.js +115 -40
  28. package/dist/dag.js.map +1 -1
  29. package/dist/db.d.ts.map +1 -1
  30. package/dist/db.js +43 -1
  31. package/dist/db.js.map +1 -1
  32. package/dist/extract.d.ts.map +1 -1
  33. package/dist/extract.js +24 -1
  34. package/dist/extract.js.map +1 -1
  35. package/dist/importers.d.ts +10 -0
  36. package/dist/importers.d.ts.map +1 -1
  37. package/dist/importers.js +242 -159
  38. package/dist/importers.js.map +1 -1
  39. package/dist/mcp/server.d.ts.map +1 -1
  40. package/dist/mcp/server.js +58 -24
  41. package/dist/mcp/server.js.map +1 -1
  42. package/dist/reject-flow.d.ts +66 -0
  43. package/dist/reject-flow.d.ts.map +1 -0
  44. package/dist/reject-flow.js +207 -0
  45. package/dist/reject-flow.js.map +1 -0
  46. package/dist/rejection.d.ts +100 -0
  47. package/dist/rejection.d.ts.map +1 -0
  48. package/dist/rejection.js +157 -0
  49. package/dist/rejection.js.map +1 -0
  50. package/dist/server.d.ts.map +1 -1
  51. package/dist/server.js +4 -0
  52. package/dist/server.js.map +1 -1
  53. package/dist/shared.d.ts +9 -0
  54. package/dist/shared.d.ts.map +1 -1
  55. package/dist/shared.js +57 -6
  56. package/dist/shared.js.map +1 -1
  57. package/dist/sleep-redact.d.ts +2 -0
  58. package/dist/sleep-redact.d.ts.map +1 -1
  59. package/dist/sleep-redact.js +2 -0
  60. package/dist/sleep-redact.js.map +1 -1
  61. package/dist/src/api.js +78 -2
  62. package/dist/src/api.js.map +1 -1
  63. package/dist/src/audit.js.map +1 -1
  64. package/dist/src/capture.js +79 -23
  65. package/dist/src/capture.js.map +1 -1
  66. package/dist/src/cli.js +233 -13
  67. package/dist/src/cli.js.map +1 -1
  68. package/dist/src/connectors/github/ingest.js +17 -0
  69. package/dist/src/connectors/github/ingest.js.map +1 -1
  70. package/dist/src/connectors/slack/ingest.js +15 -0
  71. package/dist/src/connectors/slack/ingest.js.map +1 -1
  72. package/dist/src/consolidate.js +471 -291
  73. package/dist/src/consolidate.js.map +1 -1
  74. package/dist/src/dag.js +115 -40
  75. package/dist/src/dag.js.map +1 -1
  76. package/dist/src/db.js +43 -1
  77. package/dist/src/db.js.map +1 -1
  78. package/dist/src/extract.js +24 -1
  79. package/dist/src/extract.js.map +1 -1
  80. package/dist/src/importers.js +242 -159
  81. package/dist/src/importers.js.map +1 -1
  82. package/dist/src/mcp/server.js +58 -24
  83. package/dist/src/mcp/server.js.map +1 -1
  84. package/dist/src/reject-flow.js +207 -0
  85. package/dist/src/reject-flow.js.map +1 -0
  86. package/dist/src/rejection.js +157 -0
  87. package/dist/src/rejection.js.map +1 -0
  88. package/dist/src/server.js +4 -0
  89. package/dist/src/server.js.map +1 -1
  90. package/dist/src/shared.js +57 -6
  91. package/dist/src/shared.js.map +1 -1
  92. package/dist/src/sleep-redact.js +2 -0
  93. package/dist/src/sleep-redact.js.map +1 -1
  94. package/dist/src/store.js +511 -38
  95. package/dist/src/store.js.map +1 -1
  96. package/dist/src/version.js +1 -1
  97. package/dist/store.d.ts +116 -6
  98. package/dist/store.d.ts.map +1 -1
  99. package/dist/store.js +511 -38
  100. package/dist/store.js.map +1 -1
  101. package/dist/version.d.ts +1 -1
  102. package/dist/version.js +1 -1
  103. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  104. package/extensions/openclaw-plugin/package.json +1 -1
  105. package/openclaw.plugin.json +1 -1
  106. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -15,6 +15,9 @@
15
15
  * hippo handoff <create|latest|show>
16
16
  * hippo current <show>
17
17
  * hippo forget <id> [--archive --reason "<why>"]
18
+ * hippo reject <id>|--value "<text>" --reason "<why>"
19
+ * hippo rejections
20
+ * hippo unreject <digest-prefix>
18
21
  * hippo inspect <id>
19
22
  * hippo embed [--status]
20
23
  * hippo watch "<command>"
@@ -36,6 +39,8 @@ import { createMemory, calculateStrength, calculateRewardFactor, deriveHalfLife,
36
39
  import { resolveProjectIdentity } from './project-identity.js';
37
40
  import { detectSecret } from './secret-detect.js';
38
41
  import { getHippoRoot, isInitialized, initStore, writeEntry, readEntry, deleteEntry, loadAllEntries, loadSearchEntries, loadRecallSearchEntries, loadIndex, saveIndex, loadStats, updateStats, saveActiveTaskSnapshot, loadActiveTaskSnapshot, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, RECALL_DEFAULT_DENY_SCOPES, } from './store.js';
42
+ import { rejectValue, unrejectValue, listRejectionsForTenant } from './reject-flow.js';
43
+ import { RejectedValueError } from './rejection.js';
39
44
  import { markRetrieved, hybridSearch, physicsSearch, explainMatch, textOverlap, tokenize as tokenizeQuery } from './search.js';
40
45
  import { compareEntryIdentity } from './compare.js';
41
46
  import { renderTraceContent, parseSteps } from './trace.js';
@@ -716,9 +721,27 @@ function cmdSupersede(hippoRoot, oldId, newContent, flags) {
716
721
  source: old.source,
717
722
  confidence: 'verified',
718
723
  });
724
+ // AT1: write the SUCCESSOR first. The rejection guard fires on the new
725
+ // content — if it refuses, nothing has been mutated yet (the old ordering
726
+ // committed old.superseded_by before the guarded new write, leaving a
727
+ // dangling pointer to an id that was never created). If the old-row write
728
+ // below fails instead, the new row exists unpointered — an orphan
729
+ // successor, strictly less harmful than a dangling pointer. NOTE: unlike
730
+ // api.supersede (whose CAS + insert commit in ONE transaction), this CLI
731
+ // path is two independent writes and stays non-atomic; write order is its
732
+ // only ordering guarantee.
733
+ try {
734
+ writeEntry(hippoRoot, newEntry);
735
+ }
736
+ catch (err) {
737
+ if (err instanceof RejectedValueError) {
738
+ console.error(`Error: ${err.message}`);
739
+ process.exit(1);
740
+ }
741
+ throw err;
742
+ }
719
743
  old.superseded_by = newEntry.id;
720
744
  writeEntry(hippoRoot, old);
721
- writeEntry(hippoRoot, newEntry);
722
745
  emitCliAudit(hippoRoot, 'supersede', oldId, { newId: newEntry.id });
723
746
  console.log(`Superseded ${oldId} → ${newEntry.id}`);
724
747
  }
@@ -2394,6 +2417,11 @@ export function learnFromMemoryMd(hippoRoot, homeDir = os.homedir()) {
2394
2417
  const existing = loadAllEntries(hippoRoot);
2395
2418
  let imported = 0;
2396
2419
  let skippedSecret = 0;
2420
+ // AT1 (plan §3 containment): a rejection guard refusal is per-VALUE — one
2421
+ // tombstoned memory file must not abort the whole directory scan. No
2422
+ // signature change (bare number return, cli.ts:540 + cli.ts:2903 callers
2423
+ // unchanged) — counted internally and printed as one summary line.
2424
+ let rejected = 0;
2397
2425
  for (const memDir of memoryDirs) {
2398
2426
  try {
2399
2427
  const files = fs.readdirSync(memDir).filter(f => f.endsWith('.md') && f !== 'MEMORY.md');
@@ -2430,7 +2458,16 @@ export function learnFromMemoryMd(hippoRoot, homeDir = os.homedir()) {
2430
2458
  source: `claude-memory:${file}`,
2431
2459
  confidence: 'observed',
2432
2460
  });
2433
- writeEntry(hippoRoot, entry);
2461
+ try {
2462
+ writeEntry(hippoRoot, entry);
2463
+ }
2464
+ catch (err) {
2465
+ if (err instanceof RejectedValueError) {
2466
+ rejected++;
2467
+ continue;
2468
+ }
2469
+ throw err;
2470
+ }
2434
2471
  existing.push(entry); // prevent self-dedup within batch
2435
2472
  imported++;
2436
2473
  }
@@ -2440,6 +2477,9 @@ export function learnFromMemoryMd(hippoRoot, homeDir = os.homedir()) {
2440
2477
  if (skippedSecret > 0) {
2441
2478
  console.log(`Skipped ${skippedSecret} secret-bearing memory file${skippedSecret === 1 ? '' : 's'} (not ingested).`);
2442
2479
  }
2480
+ if (rejected > 0) {
2481
+ console.log(`Skipped ${rejected} rejected value${rejected === 1 ? '' : 's'} (run \`hippo unreject\` to allow).`);
2482
+ }
2443
2483
  return imported;
2444
2484
  }
2445
2485
  function cmdDedup(hippoRoot, flags) {
@@ -3325,18 +3365,133 @@ function cmdResolve(hippoRoot, args, flags) {
3325
3365
  console.log(` ${entryB.content.slice(0, 120)}${entryB.content.length > 120 ? '...' : ''}`);
3326
3366
  }
3327
3367
  console.log('');
3328
- console.log(`Resolve with: hippo resolve ${conflictId} --keep <memory_id> [--forget]`);
3368
+ console.log(`Resolve with: hippo resolve ${conflictId} --keep <memory_id> [--forget] [--reject-loser [--reason "<why>"]]`);
3329
3369
  return;
3330
3370
  }
3331
3371
  const forgetLoser = Boolean(flags['forget']);
3332
- const result = resolveConflict(hippoRoot, conflictId, keepId, forgetLoser, tenantId);
3372
+ // AT1: --reject-loser tombstones the loser's normalized digest so it
3373
+ // cannot be re-asserted later, in addition to removing it (kind-aware).
3374
+ // --reason defaults to a conflict-context string when omitted (resolve
3375
+ // already has the conflict id + keepId; unlike `hippo reject`, a reason
3376
+ // is not strictly required here).
3377
+ const rejectLoser = Boolean(flags['reject-loser']);
3378
+ const reasonFlag = typeof flags['reason'] === 'string' ? flags['reason'] : undefined;
3379
+ const result = resolveConflict(hippoRoot, conflictId, keepId, forgetLoser, tenantId, {
3380
+ rejectLoserValue: rejectLoser,
3381
+ reason: reasonFlag,
3382
+ });
3333
3383
  if (!result) {
3334
3384
  console.error(`Could not resolve conflict ${conflictId}. Check the ID and --keep value.`);
3335
3385
  process.exit(1);
3336
3386
  }
3337
- const action = forgetLoser ? 'deleted' : 'weakened (half-life halved)';
3387
+ const action = rejectLoser
3388
+ ? 'rejected (tombstoned) and removed'
3389
+ : forgetLoser
3390
+ ? 'deleted'
3391
+ : 'weakened (half-life halved)';
3338
3392
  console.log(`Resolved conflict ${conflictId}: kept ${keepId}, ${action} ${result.loserId}`);
3339
3393
  }
3394
+ // ---------------------------------------------------------------------------
3395
+ // AT1: reject / rejections / unreject
3396
+ // docs/plans/2026-08-15-at1-rejected-value-tombstone.md §4
3397
+ // ---------------------------------------------------------------------------
3398
+ function cmdReject(hippoRoot, args, flags) {
3399
+ // Store resolution mirrors `hippo remember`: --global writes to the
3400
+ // global store, otherwise the local store (requireInit'd via resolveAuthRoot).
3401
+ const root = resolveAuthRoot(hippoRoot, flags);
3402
+ const tenantId = resolveTenantId({});
3403
+ // --reason is REQUIRED (plan §4, grill issue 4): the tombstone stores no
3404
+ // content, so reason is its only human-readable identity.
3405
+ const reason = typeof flags['reason'] === 'string' ? flags['reason'].trim() : '';
3406
+ if (!reason) {
3407
+ console.error('hippo reject requires --reason "<why>" (the tombstone stores no content; reason is its only identity).');
3408
+ process.exit(1);
3409
+ }
3410
+ const valueFlag = typeof flags['value'] === 'string' ? flags['value'] : undefined;
3411
+ const memoryId = args[0];
3412
+ if (!memoryId && valueFlag === undefined) {
3413
+ console.error('Usage: hippo reject <memory-id> --reason "<why>"');
3414
+ console.error(' or: hippo reject --value "<text>" --reason "<why>"');
3415
+ process.exit(1);
3416
+ }
3417
+ if (memoryId && valueFlag !== undefined) {
3418
+ // Ambiguous ask: silently preferring one form would ignore the other
3419
+ // without feedback (code-review round-1 low).
3420
+ console.error('hippo reject takes EITHER a memory id OR --value, not both.');
3421
+ process.exit(1);
3422
+ }
3423
+ try {
3424
+ const result = rejectValue({
3425
+ hippoRoot: root,
3426
+ tenantId,
3427
+ actor: 'cli',
3428
+ reason,
3429
+ memoryId: valueFlag === undefined ? memoryId : undefined,
3430
+ value: valueFlag,
3431
+ });
3432
+ const digestPrefix = result.digest.slice(0, 12);
3433
+ const preview = result.content.length > 80 ? `${result.content.slice(0, 80)}...` : result.content;
3434
+ console.log(`Rejected [${digestPrefix}...]: "${preview}"`);
3435
+ console.log(` Reason: ${reason}`);
3436
+ if (result.removedIds.length > 0) {
3437
+ console.log(` Removed ${result.removedIds.length} matching row(s): ${result.removedIds.join(', ')}`);
3438
+ }
3439
+ else {
3440
+ console.log(' No live rows matched (pre-emptive tombstone).');
3441
+ }
3442
+ }
3443
+ catch (err) {
3444
+ console.error(`Could not reject: ${err instanceof Error ? err.message : String(err)}`);
3445
+ process.exit(1);
3446
+ }
3447
+ }
3448
+ function cmdRejections(hippoRoot, flags) {
3449
+ const root = resolveAuthRoot(hippoRoot, flags);
3450
+ const tenantId = resolveTenantId({});
3451
+ const rows = listRejectionsForTenant(root, tenantId);
3452
+ if (flags['json']) {
3453
+ console.log(JSON.stringify({ rejections: rows }, null, 2));
3454
+ return;
3455
+ }
3456
+ if (rows.length === 0) {
3457
+ console.log('No rejected values.');
3458
+ return;
3459
+ }
3460
+ console.log(`${rows.length} rejected value(s):\n`);
3461
+ for (const row of rows) {
3462
+ console.log(`--- ${row.digest.slice(0, 12)}...`);
3463
+ console.log(` Reason: ${row.reason ?? 'none given'}`);
3464
+ console.log(` Rejected by: ${row.rejectedBy ?? 'unknown'}`);
3465
+ console.log(` Rejected at: ${row.rejectedAt}`);
3466
+ if (row.sourceMemoryId)
3467
+ console.log(` Source id: ${row.sourceMemoryId}`);
3468
+ if (row.normalizedChars !== null)
3469
+ console.log(` Chars: ${row.normalizedChars}`);
3470
+ console.log('');
3471
+ }
3472
+ }
3473
+ function cmdUnreject(hippoRoot, args, flags) {
3474
+ const root = resolveAuthRoot(hippoRoot, flags);
3475
+ const tenantId = resolveTenantId({});
3476
+ const digestOrPrefix = (args[0] ?? '').trim();
3477
+ if (!digestOrPrefix) {
3478
+ console.error('Usage: hippo unreject <digest-or-prefix>');
3479
+ process.exit(1);
3480
+ }
3481
+ const outcome = unrejectValue(root, tenantId, digestOrPrefix, 'cli');
3482
+ if (outcome.status === 'not_found') {
3483
+ console.error(`No rejected value matches "${digestOrPrefix}". Run \`hippo rejections\` to list tombstones.`);
3484
+ process.exit(1);
3485
+ }
3486
+ if (outcome.status === 'ambiguous') {
3487
+ console.error(`"${digestOrPrefix}" matches ${outcome.candidates.length} tombstones. Use a longer prefix:`);
3488
+ for (const c of outcome.candidates) {
3489
+ console.error(` ${c.digest.slice(0, 16)}... ${c.reason ?? 'none given'}`);
3490
+ }
3491
+ process.exit(1);
3492
+ }
3493
+ console.log(`Unrejected [${outcome.digest.slice(0, 12)}...] (was: ${outcome.reason ?? 'none given'})`);
3494
+ }
3340
3495
  function cmdSnapshot(hippoRoot, args, flags) {
3341
3496
  requireInit(hippoRoot);
3342
3497
  const subcommand = args[0] ?? 'show';
@@ -5511,13 +5666,27 @@ async function cmdWatch(command, hippoRoot) {
5511
5666
  entry.schema_fit = watchFit;
5512
5667
  entry.half_life_days = deriveHalfLife(7, entry);
5513
5668
  entry.strength = calculateStrength(entry);
5514
- writeEntry(hippoRoot, entry);
5515
- updateStats(hippoRoot, { remembered: 1 });
5516
- if (isEmbeddingConfigured(hippoRoot)) {
5517
- embedMemory(hippoRoot, entry).catch(() => { });
5669
+ // AT1 (plan §3 containment): mechanical content from a failed command — a
5670
+ // rejection-guard refusal here must not crash the watcher. Skip silently
5671
+ // (loud enough via the message below) and still exit with the wrapped
5672
+ // command's real exit code.
5673
+ try {
5674
+ writeEntry(hippoRoot, entry);
5675
+ updateStats(hippoRoot, { remembered: 1 });
5676
+ if (isEmbeddingConfigured(hippoRoot)) {
5677
+ embedMemory(hippoRoot, entry).catch(() => { });
5678
+ }
5679
+ const preview = stderr.trim().slice(0, 80);
5680
+ console.error(`\nHippo learned from failure: "${preview}"`);
5681
+ }
5682
+ catch (err) {
5683
+ if (err instanceof RejectedValueError) {
5684
+ console.error(`\nHippo: this failure matches a rejected value (${err.reason ?? 'no reason given'}); not stored.`);
5685
+ }
5686
+ else {
5687
+ throw err;
5688
+ }
5518
5689
  }
5519
- const preview = stderr.trim().slice(0, 80);
5520
- console.error(`\nHippo learned from failure: "${preview}"`);
5521
5690
  process.exit(exitCode);
5522
5691
  }
5523
5692
  // ---------------------------------------------------------------------------
@@ -5541,6 +5710,11 @@ function learnFromRepo(hippoRoot, repoPath, days, label) {
5541
5710
  }
5542
5711
  let added = 0;
5543
5712
  let skipped = 0;
5713
+ // AT1 (plan §3 containment): per-lesson refusal must not abort the rest
5714
+ // of the git-log scan. No signature change (added/skipped return shape
5715
+ // used by cmdLearn + cmdSleepCore callers) — counted locally, folded into
5716
+ // the existing summary line.
5717
+ let rejected = 0;
5544
5718
  const gitLearnTags = ['error', 'git-learned'];
5545
5719
  const existingForSchema = loadAllEntries(hippoRoot);
5546
5720
  for (const lesson of lessons) {
@@ -5575,14 +5749,25 @@ function learnFromRepo(hippoRoot, repoPath, days, label) {
5575
5749
  if (!entry.tags.includes(pt))
5576
5750
  entry.tags.push(pt);
5577
5751
  }
5578
- writeEntry(hippoRoot, entry);
5752
+ try {
5753
+ writeEntry(hippoRoot, entry);
5754
+ }
5755
+ catch (err) {
5756
+ if (err instanceof RejectedValueError) {
5757
+ rejected++;
5758
+ continue;
5759
+ }
5760
+ throw err;
5761
+ }
5579
5762
  updateStats(hippoRoot, { remembered: 1 });
5580
5763
  if (isEmbeddingConfigured(hippoRoot)) {
5581
5764
  embedMemory(hippoRoot, entry).catch(() => { });
5582
5765
  }
5583
5766
  added++;
5584
5767
  }
5585
- console.log(`${prefix}${added} new lessons added, ${skipped} duplicates skipped.`);
5768
+ console.log(`${prefix}${added} new lessons added, ${skipped} duplicates skipped` +
5769
+ (rejected > 0 ? `, ${rejected} rejected value(s) skipped` : '') +
5770
+ '.');
5586
5771
  return { added, skipped };
5587
5772
  }
5588
5773
  function cmdLearn(hippoRoot, flags) {
@@ -5679,6 +5864,9 @@ function cmdImport(hippoRoot, args, flags) {
5679
5864
  console.log(` Notes found: ${vaultResult.total}`);
5680
5865
  console.log(` ${dryRun ? 'Would import: ' : 'Imported: '}${vaultResult.imported}`);
5681
5866
  console.log(` Skipped (unchanged): ${vaultResult.skipped}`);
5867
+ if ((vaultResult.rejected ?? 0) > 0) {
5868
+ console.log(` Rejected (tombstoned): ${vaultResult.rejected}`);
5869
+ }
5682
5870
  console.log(` ${dryRun ? 'Would archive: ' : 'Archived (removed): '}${vaultResult.archived ?? 0}`);
5683
5871
  console.log(` Store: ${hippoRoot}`);
5684
5872
  // Batch producer, same contract as the single-file import below: vault rows
@@ -5747,6 +5935,9 @@ function cmdImport(hippoRoot, args, flags) {
5747
5935
  console.log(` Source entries found: ${result.total}`);
5748
5936
  console.log(` Imported: ${result.imported}`);
5749
5937
  console.log(` Skipped (dedup/noise): ${result.skipped}`);
5938
+ if ((result.rejected ?? 0) > 0) {
5939
+ console.log(` Rejected (tombstoned): ${result.rejected}`);
5940
+ }
5750
5941
  if (dryRun) {
5751
5942
  console.log('\n (dry run - nothing written)');
5752
5943
  if (result.entries.length > 0) {
@@ -6699,6 +6890,10 @@ const VALID_AUDIT_OPS = new Set([
6699
6890
  'customer_note_supersede', // E2 — emitted by saveCustomerNote on a supersession
6700
6891
  'customer_note_close', // E2 — emitted by closeCustomerNote
6701
6892
  'mv_rescue', // LC2-E3 — emitted by consolidate() per rescue; lockstep with AuditOp union + server.ts VALID_AUDIT_OPS
6893
+ 'reject_value', // AT1 — emitted by `hippo reject`; lockstep with AuditOp union + server.ts VALID_AUDIT_OPS
6894
+ 'reject_refusal', // AT1 — emitted when the rejection guard refuses a write; lockstep
6895
+ 'unreject_value', // AT1 — emitted by `hippo unreject`; lockstep
6896
+ 'conflict_resolve', // AT1 — emitted by resolveConflict on every resolution path; lockstep
6702
6897
  ]);
6703
6898
  function formatAuditRow(ev) {
6704
6899
  const target = ev.targetId ?? '-';
@@ -7406,6 +7601,18 @@ Commands:
7406
7601
  resolve <conflict_id> Resolve a memory conflict
7407
7602
  --keep <memory_id> Memory to keep (required)
7408
7603
  --forget Delete the losing memory (default: halve half-life)
7604
+ --reject-loser Tombstone the loser's value too (implies removal)
7605
+ --reason "<why>" Reason for --reject-loser (default: conflict context)
7606
+ reject <memory-id> Tombstone a value so it refuses re-ingestion
7607
+ reject --value "<t>" Pre-emptive form: tombstone a value not (currently) stored
7608
+ --reason "<why>" Required. The tombstone stores no content — this
7609
+ is its only human-readable identity.
7610
+ --global Reject in the global store
7611
+ rejections List rejected-value tombstones for the active tenant
7612
+ --json Output as JSON
7613
+ --global Operate on the global store
7614
+ unreject <digest-prefix> Delete a tombstone (the only escape hatch)
7615
+ --global Operate on the global store
7409
7616
  snapshot <sub> Persist or inspect the current active task
7410
7617
  snapshot save Save active task state
7411
7618
  --task <task>
@@ -7660,6 +7867,10 @@ Examples:
7660
7867
  hippo recall "data pipeline issues" --budget 2000
7661
7868
  hippo context --auto --budget 1500
7662
7869
  hippo conflicts
7870
+ hippo reject mem_abc123 --reason "leaked credential"
7871
+ hippo reject --value "never store my key again" --reason "secret"
7872
+ hippo rejections
7873
+ hippo unreject a1b2c3d4e5f6
7663
7874
  hippo session log --id sess_123 --task "Ship feature" --type progress --content "Build is green, next step is docs"
7664
7875
  hippo session latest --json
7665
7876
  hippo session resume
@@ -8031,6 +8242,15 @@ async function main() {
8031
8242
  case 'resolve':
8032
8243
  cmdResolve(hippoRoot, args, flags);
8033
8244
  break;
8245
+ case 'reject':
8246
+ cmdReject(hippoRoot, args, flags);
8247
+ break;
8248
+ case 'rejections':
8249
+ cmdRejections(hippoRoot, flags);
8250
+ break;
8251
+ case 'unreject':
8252
+ cmdUnreject(hippoRoot, args, flags);
8253
+ break;
8034
8254
  case 'snapshot':
8035
8255
  cmdSnapshot(hippoRoot, args, flags);
8036
8256
  break;