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.
- package/dist/api.d.ts +49 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +78 -2
- package/dist/api.js.map +1 -1
- package/dist/audit.d.ts +1 -1
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js.map +1 -1
- package/dist/capture.d.ts.map +1 -1
- package/dist/capture.js +79 -23
- package/dist/capture.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +233 -13
- package/dist/cli.js.map +1 -1
- package/dist/connectors/github/ingest.d.ts.map +1 -1
- package/dist/connectors/github/ingest.js +17 -0
- package/dist/connectors/github/ingest.js.map +1 -1
- package/dist/connectors/slack/ingest.d.ts.map +1 -1
- package/dist/connectors/slack/ingest.js +15 -0
- package/dist/connectors/slack/ingest.js.map +1 -1
- package/dist/consolidate.d.ts +1 -0
- package/dist/consolidate.d.ts.map +1 -1
- package/dist/consolidate.js +471 -291
- package/dist/consolidate.js.map +1 -1
- package/dist/dag.d.ts +17 -1
- package/dist/dag.d.ts.map +1 -1
- package/dist/dag.js +115 -40
- package/dist/dag.js.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +43 -1
- package/dist/db.js.map +1 -1
- package/dist/extract.d.ts.map +1 -1
- package/dist/extract.js +24 -1
- package/dist/extract.js.map +1 -1
- package/dist/importers.d.ts +10 -0
- package/dist/importers.d.ts.map +1 -1
- package/dist/importers.js +242 -159
- package/dist/importers.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +58 -24
- package/dist/mcp/server.js.map +1 -1
- package/dist/reject-flow.d.ts +66 -0
- package/dist/reject-flow.d.ts.map +1 -0
- package/dist/reject-flow.js +207 -0
- package/dist/reject-flow.js.map +1 -0
- package/dist/rejection.d.ts +100 -0
- package/dist/rejection.d.ts.map +1 -0
- package/dist/rejection.js +157 -0
- package/dist/rejection.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +4 -0
- package/dist/server.js.map +1 -1
- package/dist/shared.d.ts +9 -0
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +57 -6
- package/dist/shared.js.map +1 -1
- package/dist/sleep-redact.d.ts +2 -0
- package/dist/sleep-redact.d.ts.map +1 -1
- package/dist/sleep-redact.js +2 -0
- package/dist/sleep-redact.js.map +1 -1
- package/dist/src/api.js +78 -2
- package/dist/src/api.js.map +1 -1
- package/dist/src/audit.js.map +1 -1
- package/dist/src/capture.js +79 -23
- package/dist/src/capture.js.map +1 -1
- package/dist/src/cli.js +233 -13
- package/dist/src/cli.js.map +1 -1
- package/dist/src/connectors/github/ingest.js +17 -0
- package/dist/src/connectors/github/ingest.js.map +1 -1
- package/dist/src/connectors/slack/ingest.js +15 -0
- package/dist/src/connectors/slack/ingest.js.map +1 -1
- package/dist/src/consolidate.js +471 -291
- package/dist/src/consolidate.js.map +1 -1
- package/dist/src/dag.js +115 -40
- package/dist/src/dag.js.map +1 -1
- package/dist/src/db.js +43 -1
- package/dist/src/db.js.map +1 -1
- package/dist/src/extract.js +24 -1
- package/dist/src/extract.js.map +1 -1
- package/dist/src/importers.js +242 -159
- package/dist/src/importers.js.map +1 -1
- package/dist/src/mcp/server.js +58 -24
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/reject-flow.js +207 -0
- package/dist/src/reject-flow.js.map +1 -0
- package/dist/src/rejection.js +157 -0
- package/dist/src/rejection.js.map +1 -0
- package/dist/src/server.js +4 -0
- package/dist/src/server.js.map +1 -1
- package/dist/src/shared.js +57 -6
- package/dist/src/shared.js.map +1 -1
- package/dist/src/sleep-redact.js +2 -0
- package/dist/src/sleep-redact.js.map +1 -1
- package/dist/src/store.js +511 -38
- package/dist/src/store.js.map +1 -1
- package/dist/src/version.js +1 -1
- package/dist/store.d.ts +116 -6
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +511 -38
- package/dist/store.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- 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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
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
|
-
|
|
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;
|