hippo-memory 1.52.6 → 1.52.8
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/README.md +57 -34
- package/dist/api.d.ts +1 -0
- package/dist/api.js +36 -11
- package/dist/autolearn.d.ts +1 -4
- package/dist/autolearn.js +6 -7
- package/dist/capture.d.ts +3 -0
- package/dist/capture.js +51 -56
- package/dist/churn-git.js +2 -2
- package/dist/cli.d.ts +1 -4
- package/dist/cli.js +240 -150
- package/dist/config.js +8 -1
- package/dist/consolidate.d.ts +2 -0
- package/dist/consolidate.js +13 -6
- package/dist/customer-notes.js +3 -2
- package/dist/dag.js +5 -0
- package/dist/dashboard.js +4 -0
- package/dist/decisions.d.ts +1 -1
- package/dist/decisions.js +4 -3
- package/dist/extensions/openclaw-plugin/index.js +1 -0
- package/dist/extract.js +3 -0
- package/dist/graph-recall.js +8 -7
- package/dist/half-life-migration.d.ts +14 -5
- package/dist/half-life-migration.js +89 -21
- package/dist/handoff.d.ts +2 -0
- package/dist/hooks.d.ts +4 -4
- package/dist/hooks.js +10 -4
- package/dist/importers.d.ts +2 -1
- package/dist/importers.js +19 -7
- package/dist/incidents.d.ts +1 -1
- package/dist/incidents.js +4 -3
- package/dist/index.d.ts +4 -1
- package/dist/index.js +6 -1
- package/dist/mcp/server.js +3 -3
- package/dist/memory.d.ts +6 -13
- package/dist/memory.js +0 -10
- package/dist/policies.js +3 -2
- package/dist/predictions.js +2 -0
- package/dist/processes.js +3 -2
- package/dist/project-briefs.js +3 -2
- package/dist/secret-detect.js +3 -3
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +6 -1
- package/dist/skills.js +3 -2
- package/dist/store.d.ts +5 -5
- package/dist/store.js +25 -13
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist-ui/assets/index-BhT8RvO6.js +61 -0
- package/dist-ui/index.html +1 -1
- package/extensions/openclaw-plugin/index.ts +1 -0
- 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-ui/assets/index-BgmA7Hwe.js +0 -61
package/dist/importers.js
CHANGED
|
@@ -12,6 +12,7 @@ import { getGlobalRoot, initGlobal } from './shared.js';
|
|
|
12
12
|
import { remember, archiveRaw, isPrivateScope } from './api.js';
|
|
13
13
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
14
14
|
import { RejectedValueError, checkRejectionGuard } from './rejection.js';
|
|
15
|
+
import { loadConfig } from './config.js';
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
16
17
|
// Shared core: dedup + write
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
@@ -27,6 +28,7 @@ export function importEntries(chunks, source, tags, options) {
|
|
|
27
28
|
}
|
|
28
29
|
const existing = loadAllEntries(targetRoot, options.global ? undefined : options.tenantId);
|
|
29
30
|
const allTags = [...new Set([...tags, ...(options.extraTags ?? [])])];
|
|
31
|
+
const baseHalfLifeDays = loadConfig(targetRoot).defaultHalfLifeDays;
|
|
30
32
|
let total = 0;
|
|
31
33
|
let imported = 0;
|
|
32
34
|
let skipped = 0;
|
|
@@ -80,6 +82,7 @@ export function importEntries(chunks, source, tags, options) {
|
|
|
80
82
|
source,
|
|
81
83
|
confidence: 'observed',
|
|
82
84
|
tenantId: options.global ? undefined : options.tenantId,
|
|
85
|
+
baseHalfLifeDays,
|
|
83
86
|
});
|
|
84
87
|
if (options.dryRun) {
|
|
85
88
|
if (dryRunDb) {
|
|
@@ -314,9 +317,16 @@ function parseCursorFile(content) {
|
|
|
314
317
|
return parts.map((p) => p.trim()).filter(Boolean);
|
|
315
318
|
});
|
|
316
319
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
+
const CURSOR_RULE_FILE = /\.mdc?$/i;
|
|
321
|
+
/** Import `.cursorrules`, one rule file, or a `.cursor/rules` tree of `.mdc` and `.md` rules. */
|
|
322
|
+
export function importCursor(sourcePath, options) {
|
|
323
|
+
const files = fs.statSync(sourcePath).isDirectory()
|
|
324
|
+
? collectMarkdownFiles(sourcePath, options.hippoRoot, CURSOR_RULE_FILE).map((rel) => path.join(sourcePath, rel))
|
|
325
|
+
: [sourcePath];
|
|
326
|
+
const chunks = files.flatMap((file) => {
|
|
327
|
+
const raw = fs.readFileSync(file, 'utf8');
|
|
328
|
+
return parseCursorFile(CURSOR_RULE_FILE.test(file) ? parseFrontmatter(raw).body : raw);
|
|
329
|
+
});
|
|
320
330
|
return importEntries(chunks, 'import:cursor', ['imported', 'cursor'], options);
|
|
321
331
|
}
|
|
322
332
|
// ---------------------------------------------------------------------------
|
|
@@ -521,8 +531,8 @@ function parseWikilinks(body) {
|
|
|
521
531
|
}
|
|
522
532
|
return out;
|
|
523
533
|
}
|
|
524
|
-
/** Recursively collect `*.md`
|
|
525
|
-
* `root` with forward-slash separators (stable artifactRef keys across OSes).
|
|
534
|
+
/** Recursively collect files whose name matches `match` (`*.md` by default) under `root`,
|
|
535
|
+
* as paths relative to `root` with forward-slash separators (stable artifactRef keys across OSes).
|
|
526
536
|
* Symlinks are not followed. Skips dot-directories (the default `.hippo` store,
|
|
527
537
|
* `.git`, `.obsidian`, `.trash`) AND the canonicalized Hippo store path during
|
|
528
538
|
* the walk, so re-importing a vault that CONTAINS the store never ingests its own
|
|
@@ -531,7 +541,7 @@ function parseWikilinks(body) {
|
|
|
531
541
|
* every run). The root-IS-the-store case is handled one level up in
|
|
532
542
|
* importVault (a no-op early return), NOT here: returning [] for it would feed
|
|
533
543
|
* the deletion-sync an empty scan that mass-archives every live row (codex R8). */
|
|
534
|
-
function collectMarkdownFiles(root, hippoRoot) {
|
|
544
|
+
function collectMarkdownFiles(root, hippoRoot, match = /\.md$/i) {
|
|
535
545
|
const out = [];
|
|
536
546
|
// Canonicalize (realpath) so a non-dot HIPPO_HOME store nested in the vault is
|
|
537
547
|
// skipped even when hippoRoot is an aliased path (junction / Windows case
|
|
@@ -553,7 +563,7 @@ function collectMarkdownFiles(root, hippoRoot) {
|
|
|
553
563
|
continue;
|
|
554
564
|
walk(abs);
|
|
555
565
|
}
|
|
556
|
-
else if (ent.isFile() && ent.name
|
|
566
|
+
else if (ent.isFile() && match.test(ent.name)) {
|
|
557
567
|
out.push(path.relative(root, abs).split(path.sep).join('/'));
|
|
558
568
|
}
|
|
559
569
|
}
|
|
@@ -693,6 +703,7 @@ export function importVault(folderPath, options) {
|
|
|
693
703
|
let rejected = 0;
|
|
694
704
|
let archived = 0;
|
|
695
705
|
const entries = [];
|
|
706
|
+
const baseHalfLifeDays = loadConfig(hippoRoot).defaultHalfLifeDays;
|
|
696
707
|
// AT1 P2 fix: dry-run never called remember(), so it never probed
|
|
697
708
|
// tombstones — every note that reached the write step counted as
|
|
698
709
|
// `imported` even when a real run would refuse it. Probe (read-only) via
|
|
@@ -799,6 +810,7 @@ export function importVault(folderPath, options) {
|
|
|
799
810
|
owner: 'agent:vault-import',
|
|
800
811
|
artifact_ref: artifactRef,
|
|
801
812
|
tenantId,
|
|
813
|
+
baseHalfLifeDays,
|
|
802
814
|
});
|
|
803
815
|
// dryRun preview: count what WOULD import, but make no writes (codex P2).
|
|
804
816
|
if (!dryRun) {
|
package/dist/incidents.d.ts
CHANGED
|
@@ -63,7 +63,7 @@ export interface ListIncidentsOpts {
|
|
|
63
63
|
* inside writeEntry's SAVEPOINT 'write_entry'.
|
|
64
64
|
*
|
|
65
65
|
* The memory mirror: tags ['incident', ...extraTags], source 'incident',
|
|
66
|
-
* confidence 'verified',
|
|
66
|
+
* confidence 'verified', the half-life objectHalfLifeDays picks, content =
|
|
67
67
|
* "<text>\n\nContext: <context>" when context is given.
|
|
68
68
|
*
|
|
69
69
|
* linked_memory_ids are validated BEFORE insert: each must exist in the SAME
|
package/dist/incidents.js
CHANGED
|
@@ -27,8 +27,9 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
29
29
|
import { writeEntry, assertTenantId } from './store.js';
|
|
30
|
-
import { createMemory, Layer
|
|
30
|
+
import { createMemory, Layer } from './memory.js';
|
|
31
31
|
import { appendAuditEvent } from './audit.js';
|
|
32
|
+
import { objectHalfLifeDays } from './half-life-migration.js';
|
|
32
33
|
export const VALID_INCIDENT_STATES = new Set([
|
|
33
34
|
'open',
|
|
34
35
|
'resolved',
|
|
@@ -77,7 +78,7 @@ const INCIDENT_COLS = `
|
|
|
77
78
|
* inside writeEntry's SAVEPOINT 'write_entry'.
|
|
78
79
|
*
|
|
79
80
|
* The memory mirror: tags ['incident', ...extraTags], source 'incident',
|
|
80
|
-
* confidence 'verified',
|
|
81
|
+
* confidence 'verified', the half-life objectHalfLifeDays picks, content =
|
|
81
82
|
* "<text>\n\nContext: <context>" when context is given.
|
|
82
83
|
*
|
|
83
84
|
* linked_memory_ids are validated BEFORE insert: each must exist in the SAME
|
|
@@ -98,9 +99,9 @@ export function saveIncident(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
|
98
99
|
layer: Layer.Semantic,
|
|
99
100
|
confidence: 'verified',
|
|
100
101
|
source: 'incident',
|
|
102
|
+
baseHalfLifeDays: objectHalfLifeDays(hippoRoot),
|
|
101
103
|
tenantId,
|
|
102
104
|
});
|
|
103
|
-
mem.half_life_days = INCIDENT_HALF_LIFE_DAYS;
|
|
104
105
|
const linkInput = opts.linkedMemoryIds ?? [];
|
|
105
106
|
// Populated inside afterWrite so the linked-id validation, the INSERT, and the
|
|
106
107
|
// memory write all share one SAVEPOINT.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hippo public API - re-exports for programmatic use.
|
|
3
3
|
*/
|
|
4
|
-
|
|
4
|
+
import { type CreateMemoryOptions, type MemoryEntry } from './memory.js';
|
|
5
|
+
export { MemoryEntry, Layer, EmotionalValence, ConfidenceLevel, DecayOptions, calculateStrength, resolveConfidence, confidenceFacets, type ConfidenceFacets, applyOutcome, generateId, computeSchemaFit } from './memory.js';
|
|
6
|
+
/** Published signature, so `baseHalfLifeDays` stays optional here; hippo's own writers use the strict one in memory.ts. */
|
|
7
|
+
export declare function createMemory(content: string, options?: Partial<CreateMemoryOptions>): MemoryEntry;
|
|
5
8
|
export { search, hybridSearch, physicsSearch, markRetrieved, estimateTokens, textOverlap, tokenize, explainMatch, detectTemporalDirection, temporalBoost, computeTemporalRange, SearchResult, MatchExplanation } from './search.js';
|
|
6
9
|
export { multihopSearch } from './multihop.js';
|
|
7
10
|
export { graphExpandRecall, MAX_HOPS, DEFAULT_MAX_NEIGHBORS, type GraphExpandOpts } from './graph-recall.js';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hippo public API - re-exports for programmatic use.
|
|
3
3
|
*/
|
|
4
|
-
|
|
4
|
+
import { createMemory as createStoreMemory, DEFAULT_HALF_LIFE_DAYS } from './memory.js';
|
|
5
|
+
export { Layer, calculateStrength, resolveConfidence, confidenceFacets, applyOutcome, generateId, computeSchemaFit } from './memory.js';
|
|
6
|
+
/** Published signature, so `baseHalfLifeDays` stays optional here; hippo's own writers use the strict one in memory.ts. */
|
|
7
|
+
export function createMemory(content, options = {}) {
|
|
8
|
+
return createStoreMemory(content, { ...options, baseHalfLifeDays: options.baseHalfLifeDays ?? DEFAULT_HALF_LIFE_DAYS });
|
|
9
|
+
}
|
|
5
10
|
export { search, hybridSearch, physicsSearch, markRetrieved, estimateTokens, textOverlap, tokenize, explainMatch, detectTemporalDirection, temporalBoost, computeTemporalRange } from './search.js';
|
|
6
11
|
export { multihopSearch } from './multihop.js';
|
|
7
12
|
export { graphExpandRecall, MAX_HOPS, DEFAULT_MAX_NEIGHBORS } from './graph-recall.js';
|
package/dist/mcp/server.js
CHANGED
|
@@ -989,9 +989,9 @@ async function executeTool(name, args, ctx) {
|
|
|
989
989
|
// Auto-detect query from git
|
|
990
990
|
let query = '';
|
|
991
991
|
try {
|
|
992
|
-
const branch = execSync('git rev-parse --abbrev-ref HEAD 2>/dev/null', { encoding: 'utf-8' }).trim();
|
|
993
|
-
const diff = execSync('git diff --cached --stat 2>/dev/null', { encoding: 'utf-8' }).trim();
|
|
994
|
-
const log = execSync('git log -1 --pretty=format:"%s" 2>/dev/null', { encoding: 'utf-8' }).trim();
|
|
992
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD 2>/dev/null', { encoding: 'utf-8', windowsHide: true }).trim();
|
|
993
|
+
const diff = execSync('git diff --cached --stat 2>/dev/null', { encoding: 'utf-8', windowsHide: true }).trim();
|
|
994
|
+
const log = execSync('git log -1 --pretty=format:"%s" 2>/dev/null', { encoding: 'utf-8', windowsHide: true }).trim();
|
|
995
995
|
query = [branch, log, diff].filter(Boolean).join(' ');
|
|
996
996
|
}
|
|
997
997
|
catch { /* not a git repo */ }
|
package/dist/memory.d.ts
CHANGED
|
@@ -113,13 +113,6 @@ export interface MemoryEntry {
|
|
|
113
113
|
*/
|
|
114
114
|
bm25_score?: number;
|
|
115
115
|
}
|
|
116
|
-
export declare const DECISION_HALF_LIFE_DAYS = 90;
|
|
117
|
-
export declare const INCIDENT_HALF_LIFE_DAYS = 90;
|
|
118
|
-
export declare const PROCESS_HALF_LIFE_DAYS = 90;
|
|
119
|
-
export declare const POLICY_HALF_LIFE_DAYS = 90;
|
|
120
|
-
export declare const SKILL_HALF_LIFE_DAYS = 90;
|
|
121
|
-
export declare const PROJECT_BRIEF_HALF_LIFE_DAYS = 90;
|
|
122
|
-
export declare const CUSTOMER_NOTE_HALF_LIFE_DAYS = 90;
|
|
123
116
|
/** FE2: tag on a memory whose named file/symbol/script changed after it was stored. */
|
|
124
117
|
export declare const CHURN_STALE_TAG = "churn-stale";
|
|
125
118
|
/**
|
|
@@ -222,10 +215,7 @@ export declare function resolveConfidence(entry: MemoryEntry, now?: Date): Confi
|
|
|
222
215
|
export declare const DEFAULT_HALF_LIFE_DAYS = 365;
|
|
223
216
|
export declare const AUTO_DELETABLE_SQL = "pinned = 0 AND kind != 'raw'";
|
|
224
217
|
export declare function canAutoDelete(entry: Pick<MemoryEntry, 'pinned' | 'kind'>): boolean;
|
|
225
|
-
|
|
226
|
-
* Create a new memory entry with defaults.
|
|
227
|
-
*/
|
|
228
|
-
export declare function createMemory(content: string, options?: {
|
|
218
|
+
export interface CreateMemoryOptions {
|
|
229
219
|
layer?: Layer;
|
|
230
220
|
tags?: string[];
|
|
231
221
|
emotional_valence?: EmotionalValence;
|
|
@@ -233,7 +223,8 @@ export declare function createMemory(content: string, options?: {
|
|
|
233
223
|
schema_fit?: number;
|
|
234
224
|
source?: string;
|
|
235
225
|
confidence?: ConfidenceLevel;
|
|
236
|
-
|
|
226
|
+
/** The store's `loadConfig(hippoRoot).defaultHalfLifeDays`; required so no writer falls back to the compiled default by leaving it out. */
|
|
227
|
+
baseHalfLifeDays: number;
|
|
237
228
|
trace_outcome?: TraceOutcome;
|
|
238
229
|
source_session_id?: string | null;
|
|
239
230
|
valid_from?: string;
|
|
@@ -245,7 +236,9 @@ export declare function createMemory(content: string, options?: {
|
|
|
245
236
|
owner?: string | null;
|
|
246
237
|
artifact_ref?: string | null;
|
|
247
238
|
tenantId?: string;
|
|
248
|
-
}
|
|
239
|
+
}
|
|
240
|
+
/** Create a new memory entry with defaults. Untyped JavaScript callers that omit the options get the compiled default half-life. */
|
|
241
|
+
export declare function createMemory(content: string, options: CreateMemoryOptions): MemoryEntry;
|
|
249
242
|
/**
|
|
250
243
|
* Compute how well new content fits existing knowledge patterns.
|
|
251
244
|
* Returns 0..1 where:
|
package/dist/memory.js
CHANGED
|
@@ -11,13 +11,6 @@ export var Layer;
|
|
|
11
11
|
Layer["Semantic"] = "semantic";
|
|
12
12
|
Layer["Trace"] = "trace";
|
|
13
13
|
})(Layer || (Layer = {}));
|
|
14
|
-
export const DECISION_HALF_LIFE_DAYS = 90;
|
|
15
|
-
export const INCIDENT_HALF_LIFE_DAYS = 90;
|
|
16
|
-
export const PROCESS_HALF_LIFE_DAYS = 90;
|
|
17
|
-
export const POLICY_HALF_LIFE_DAYS = 90;
|
|
18
|
-
export const SKILL_HALF_LIFE_DAYS = 90;
|
|
19
|
-
export const PROJECT_BRIEF_HALF_LIFE_DAYS = 90;
|
|
20
|
-
export const CUSTOMER_NOTE_HALF_LIFE_DAYS = 90;
|
|
21
14
|
/** FE2: tag on a memory whose named file/symbol/script changed after it was stored. */
|
|
22
15
|
export const CHURN_STALE_TAG = 'churn-stale';
|
|
23
16
|
// Emotional multipliers from PLAN.md.
|
|
@@ -326,9 +319,6 @@ export const AUTO_DELETABLE_SQL = "pinned = 0 AND kind != 'raw'";
|
|
|
326
319
|
export function canAutoDelete(entry) {
|
|
327
320
|
return !entry.pinned && entry.kind !== 'raw';
|
|
328
321
|
}
|
|
329
|
-
/**
|
|
330
|
-
* Create a new memory entry with defaults.
|
|
331
|
-
*/
|
|
332
322
|
export function createMemory(content, options = {}) {
|
|
333
323
|
const trimmed = content.trim();
|
|
334
324
|
if (trimmed.length < 3) {
|
package/dist/policies.js
CHANGED
|
@@ -39,8 +39,9 @@
|
|
|
39
39
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
40
40
|
import { writeEntry, assertTenantId } from './store.js';
|
|
41
41
|
import { markGraphDirty, removeGraphEntitiesForObject } from './graph.js';
|
|
42
|
-
import { createMemory, Layer
|
|
42
|
+
import { createMemory, Layer } from './memory.js';
|
|
43
43
|
import { appendAuditEvent } from './audit.js';
|
|
44
|
+
import { objectHalfLifeDays } from './half-life-migration.js';
|
|
44
45
|
export const VALID_POLICY_STATES = new Set([
|
|
45
46
|
'active',
|
|
46
47
|
'superseded',
|
|
@@ -144,9 +145,9 @@ export function savePolicy(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
|
144
145
|
layer: Layer.Semantic,
|
|
145
146
|
confidence: 'verified',
|
|
146
147
|
source: 'policy',
|
|
148
|
+
baseHalfLifeDays: objectHalfLifeDays(hippoRoot),
|
|
147
149
|
tenantId,
|
|
148
150
|
});
|
|
149
|
-
mem.half_life_days = POLICY_HALF_LIFE_DAYS;
|
|
150
151
|
let savedRow;
|
|
151
152
|
writeEntry(hippoRoot, mem, {
|
|
152
153
|
actor,
|
package/dist/predictions.js
CHANGED
|
@@ -28,6 +28,7 @@ import { openHippoDb, closeHippoDb } from './db.js';
|
|
|
28
28
|
import { writeEntry, assertTenantId } from './store.js';
|
|
29
29
|
import { createMemory, Layer } from './memory.js';
|
|
30
30
|
import { appendAuditEvent } from './audit.js';
|
|
31
|
+
import { loadConfig } from './config.js';
|
|
31
32
|
import { detectForwardClaim } from './forward-claim-detector.js';
|
|
32
33
|
export const VALID_CLOSURE_STATES = new Set([
|
|
33
34
|
'open',
|
|
@@ -81,6 +82,7 @@ export function savePrediction(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
|
81
82
|
source: 'prediction',
|
|
82
83
|
// SAFETY: 'distilled' is a valid MemoryKind literal (see memory.ts).
|
|
83
84
|
kind: 'distilled',
|
|
85
|
+
baseHalfLifeDays: loadConfig(hippoRoot).defaultHalfLifeDays,
|
|
84
86
|
tenantId,
|
|
85
87
|
});
|
|
86
88
|
// Captured for the return value; populated inside afterWrite hook so the
|
package/dist/processes.js
CHANGED
|
@@ -32,8 +32,9 @@
|
|
|
32
32
|
*/
|
|
33
33
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
34
34
|
import { writeEntry, assertTenantId } from './store.js';
|
|
35
|
-
import { createMemory, Layer
|
|
35
|
+
import { createMemory, Layer } from './memory.js';
|
|
36
36
|
import { appendAuditEvent } from './audit.js';
|
|
37
|
+
import { objectHalfLifeDays } from './half-life-migration.js';
|
|
37
38
|
export const VALID_PROCESS_STATES = new Set([
|
|
38
39
|
'active',
|
|
39
40
|
'superseded',
|
|
@@ -155,9 +156,9 @@ export function saveProcess(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
|
155
156
|
layer: Layer.Semantic,
|
|
156
157
|
confidence: 'verified',
|
|
157
158
|
source: 'process',
|
|
159
|
+
baseHalfLifeDays: objectHalfLifeDays(hippoRoot),
|
|
158
160
|
tenantId,
|
|
159
161
|
});
|
|
160
|
-
mem.half_life_days = PROCESS_HALF_LIFE_DAYS;
|
|
161
162
|
let savedRow;
|
|
162
163
|
writeEntry(hippoRoot, mem, {
|
|
163
164
|
actor,
|
package/dist/project-briefs.js
CHANGED
|
@@ -24,8 +24,9 @@
|
|
|
24
24
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
25
25
|
import { writeEntry, assertTenantId, RECALL_DEFAULT_DENY_SCOPES } from './store.js';
|
|
26
26
|
import { markGraphDirty, removeGraphEntitiesForObject } from './graph.js';
|
|
27
|
-
import { createMemory, Layer
|
|
27
|
+
import { createMemory, Layer } from './memory.js';
|
|
28
28
|
import { appendAuditEvent } from './audit.js';
|
|
29
|
+
import { objectHalfLifeDays } from './half-life-migration.js';
|
|
29
30
|
export const VALID_BRIEF_STATES = new Set([
|
|
30
31
|
'active',
|
|
31
32
|
'superseded',
|
|
@@ -125,9 +126,9 @@ export function saveProjectBrief(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
|
125
126
|
layer: Layer.Semantic,
|
|
126
127
|
confidence: 'verified',
|
|
127
128
|
source: 'project_brief',
|
|
129
|
+
baseHalfLifeDays: objectHalfLifeDays(hippoRoot),
|
|
128
130
|
tenantId,
|
|
129
131
|
});
|
|
130
|
-
mem.half_life_days = PROJECT_BRIEF_HALF_LIFE_DAYS;
|
|
131
132
|
let savedRow;
|
|
132
133
|
writeEntry(hippoRoot, mem, {
|
|
133
134
|
actor,
|
package/dist/secret-detect.js
CHANGED
|
@@ -39,7 +39,7 @@ const SECRET_PATTERNS = [
|
|
|
39
39
|
// sk-... (OpenAI/Anthropic style) and sk_<vendor>_... shapes. Both require
|
|
40
40
|
// a key-ish noun somewhere in the content (co-occurrence guard) so prose
|
|
41
41
|
// like "the sk- prefix identifies API keys" without an actual long token
|
|
42
|
-
// does not flag, but a stored key ("
|
|
42
|
+
// does not flag, but a stored key ("prod API key sk_live_...")
|
|
43
43
|
// does.
|
|
44
44
|
{ name: 'sk-style-key', re: /\bsk-[A-Za-z0-9_-]{20,}\b/ },
|
|
45
45
|
{ name: 'sk-underscore-key', re: /\bsk_[A-Za-z0-9]+_[A-Za-z0-9_]{6,}\b/ },
|
|
@@ -51,7 +51,7 @@ const SECRET_PATTERNS = [
|
|
|
51
51
|
// rollout worked well" were verified false positives that would silently
|
|
52
52
|
// hide real code-lesson memories from ambient context (post-merge
|
|
53
53
|
// adversarial review, 2026-07-02).
|
|
54
|
-
{ name: 'secret-assignment', re: /\b(?:api[_-]?key|secret|token|password)\s*[:=]\s*['"]?(?=[A-Za-z0-9_
|
|
54
|
+
{ name: 'secret-assignment', re: /\b(?:api[_-]?key|secret|token|password)\s*[:=]\s*['"]?(?=[A-Za-z0-9_\-+/]*\d)[A-Za-z0-9_\-+/=]{12,}/i },
|
|
55
55
|
];
|
|
56
56
|
const KEYISH_CONTEXT_RE = /key|token|secret|credential|bearer|auth|password/i;
|
|
57
57
|
const CO_OCCURRENCE_GUARDED = new Set(['sk-style-key', 'sk-underscore-key']);
|
|
@@ -59,7 +59,7 @@ const CO_OCCURRENCE_GUARDED = new Set(['sk-style-key', 'sk-underscore-key']);
|
|
|
59
59
|
const STRICT_ONLY_PATTERNS = [
|
|
60
60
|
/\bbearer\s+[A-Za-z0-9._~+/-]{16,}=*/gi,
|
|
61
61
|
/\bauthorization["']?\s*[:=]\s*["']?basic\s+[A-Za-z0-9+/]{8,}={0,2}/gi,
|
|
62
|
-
|
|
62
|
+
/(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]*/g,
|
|
63
63
|
];
|
|
64
64
|
/**
|
|
65
65
|
* Scan a memory's tags + content for secret material.
|
package/dist/shared.d.ts
CHANGED
|
@@ -157,7 +157,7 @@ export declare function autoShare(localRoot: string, options?: {
|
|
|
157
157
|
}): MemoryEntry[];
|
|
158
158
|
/**
|
|
159
159
|
* Copy all global memories into the local store.
|
|
160
|
-
* Skips entries that already exist locally
|
|
160
|
+
* Skips entries that already exist locally, by ID or by text (promote and share copy under a new ID).
|
|
161
161
|
* Returns the count of newly copied entries.
|
|
162
162
|
*/
|
|
163
163
|
export declare function syncGlobalToLocal(localRoot: string, globalRoot: string, opts?: {
|
package/dist/shared.js
CHANGED
|
@@ -457,7 +457,7 @@ export function autoShare(localRoot, options = {}) {
|
|
|
457
457
|
}
|
|
458
458
|
/**
|
|
459
459
|
* Copy all global memories into the local store.
|
|
460
|
-
* Skips entries that already exist locally
|
|
460
|
+
* Skips entries that already exist locally, by ID or by text (promote and share copy under a new ID).
|
|
461
461
|
* Returns the count of newly copied entries.
|
|
462
462
|
*/
|
|
463
463
|
export function syncGlobalToLocal(localRoot, globalRoot, opts = {}) {
|
|
@@ -468,6 +468,8 @@ export function syncGlobalToLocal(localRoot, globalRoot, opts = {}) {
|
|
|
468
468
|
// the local-root context provides one.
|
|
469
469
|
const globalEntries = loadAllEntries(globalRoot);
|
|
470
470
|
const localIndex = loadIndex(localRoot);
|
|
471
|
+
const textKey = (e) => `${e.tenantId}\n${e.content}`;
|
|
472
|
+
const localText = new Set(loadAllEntries(localRoot).map(textKey));
|
|
471
473
|
// v39 (codex P1-4): syncing down must not re-import what ambient context
|
|
472
474
|
// excludes - other-project rows are skipped by default and secret rows
|
|
473
475
|
// are never copied. origin_project is preserved on the copy (writeEntry
|
|
@@ -484,6 +486,8 @@ export function syncGlobalToLocal(localRoot, globalRoot, opts = {}) {
|
|
|
484
486
|
// Skip if already present by ID
|
|
485
487
|
if (localIndex.entries[entry.id])
|
|
486
488
|
continue;
|
|
489
|
+
if (localText.has(textKey(entry)))
|
|
490
|
+
continue;
|
|
487
491
|
if (detectSecret(entry).flagged)
|
|
488
492
|
continue;
|
|
489
493
|
if (!opts.includeCrossProject &&
|
|
@@ -499,6 +503,7 @@ export function syncGlobalToLocal(localRoot, globalRoot, opts = {}) {
|
|
|
499
503
|
}
|
|
500
504
|
throw err;
|
|
501
505
|
}
|
|
506
|
+
localText.add(textKey(entry));
|
|
502
507
|
count++;
|
|
503
508
|
}
|
|
504
509
|
if (rejected > 0) {
|
package/dist/skills.js
CHANGED
|
@@ -23,8 +23,9 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
25
25
|
import { writeEntry, assertTenantId } from './store.js';
|
|
26
|
-
import { createMemory, Layer
|
|
26
|
+
import { createMemory, Layer } from './memory.js';
|
|
27
27
|
import { appendAuditEvent } from './audit.js';
|
|
28
|
+
import { objectHalfLifeDays } from './half-life-migration.js';
|
|
28
29
|
export const VALID_SKILL_STATES = new Set([
|
|
29
30
|
'active',
|
|
30
31
|
'superseded',
|
|
@@ -130,9 +131,9 @@ export function saveSkill(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
|
130
131
|
layer: Layer.Semantic,
|
|
131
132
|
confidence: 'verified',
|
|
132
133
|
source: 'skill',
|
|
134
|
+
baseHalfLifeDays: objectHalfLifeDays(hippoRoot),
|
|
133
135
|
tenantId,
|
|
134
136
|
});
|
|
135
|
-
mem.half_life_days = SKILL_HALF_LIFE_DAYS;
|
|
136
137
|
let savedRow;
|
|
137
138
|
writeEntry(hippoRoot, mem, {
|
|
138
139
|
actor,
|
package/dist/store.d.ts
CHANGED
|
@@ -113,6 +113,8 @@ export declare function initStore(hippoRoot: string): void;
|
|
|
113
113
|
export declare function openStore(hippoRoot: string): DatabaseSyncLike;
|
|
114
114
|
/** `meta` key holding the default half-life base a store's memories are on (src/half-life-migration.ts). */
|
|
115
115
|
export declare const HALF_LIFE_BASE_META_KEY = "default_half_life_base";
|
|
116
|
+
/** `meta` key set once no memory of a decision, incident or other object sits on the old flat 90 days. */
|
|
117
|
+
export declare const TYPED_HALF_LIFE_META_KEY = "typed_half_life_on_default";
|
|
116
118
|
/**
|
|
117
119
|
* Serialize a MemoryEntry to markdown with YAML frontmatter.
|
|
118
120
|
*/
|
|
@@ -650,12 +652,10 @@ export declare function loadLatestHandoff(hippoRoot: string, tenantId: string, s
|
|
|
650
652
|
export declare function loadHandoffById(hippoRoot: string, tenantId: string, id: number): SessionHandoff | null;
|
|
651
653
|
/** Stamp the outcome on a session's newest handoff, only if it has none yet. Returns rows changed. */
|
|
652
654
|
export declare function stampHandoffOutcome(hippoRoot: string, tenantId: string, sessionId: string, outcome: HandoffOutcome): number;
|
|
653
|
-
/**
|
|
654
|
-
* Auto-write a handoff at session-end from the session's active snapshot (DF1 T3).
|
|
655
|
+
/** Auto-write a handoff at session-end (DF1 T3) from the session's active snapshot, else from `derived`, its transcript state.
|
|
655
656
|
* @param evidence best-effort git state; outcome comes from the newest session_complete event.
|
|
656
|
-
* @returns null
|
|
657
|
-
|
|
658
|
-
export declare function writeSessionEndHandoff(hippoRoot: string, tenantId: string, sessionId: string, evidence: HandoffEvidence | null): SessionHandoff | null;
|
|
657
|
+
* @returns null when neither source is the session's, a newer handoff covers the snapshot, or the session's latest handoff was not read off its transcript. */
|
|
658
|
+
export declare function writeSessionEndHandoff(hippoRoot: string, tenantId: string, sessionId: string, evidence: HandoffEvidence | null, derived?: Pick<TaskSnapshot, 'task' | 'summary' | 'next_step'> | null): SessionHandoff | null;
|
|
659
659
|
export declare function transitionCard(db: DatabaseSyncLike, tenantId: string, cardId: string, from: CardStatus[], to: CardStatus, extra?: {
|
|
660
660
|
setSql?: string;
|
|
661
661
|
whereSql?: string;
|
package/dist/store.js
CHANGED
|
@@ -16,6 +16,7 @@ import { isRecallBoostAblated } from './ablation.js';
|
|
|
16
16
|
import { rarestPromptTerms, RAREST_TERM_COUNT } from './prompt-recall.js';
|
|
17
17
|
import { appendAuditEvent } from './audit.js';
|
|
18
18
|
import { resolveTenantId } from './tenant.js';
|
|
19
|
+
import { redactSecretsStrict } from './secret-detect.js';
|
|
19
20
|
import { deriveOriginProject, originFromSource, findHippoStoreDir, realpathOrResolve } from './project-identity.js';
|
|
20
21
|
import { checkRejectionGuard, RejectedValueError, rejectionDigest, normalizeValueForRejection, insertRejectedValue, findRejectedValue, } from './rejection.js';
|
|
21
22
|
// AT1 (plan §5): resolveConflict's kind-aware loser removal needs
|
|
@@ -140,6 +141,8 @@ export function openStore(hippoRoot) {
|
|
|
140
141
|
}
|
|
141
142
|
/** `meta` key holding the default half-life base a store's memories are on (src/half-life-migration.ts). */
|
|
142
143
|
export const HALF_LIFE_BASE_META_KEY = 'default_half_life_base';
|
|
144
|
+
/** `meta` key set once no memory of a decision, incident or other object sits on the old flat 90 days. */
|
|
145
|
+
export const TYPED_HALF_LIFE_META_KEY = 'typed_half_life_on_default';
|
|
143
146
|
/**
|
|
144
147
|
* A store with no memories starts on the current default half-life base, so
|
|
145
148
|
* `hippo sleep` never migrates it. A store that already holds memories and
|
|
@@ -152,6 +155,7 @@ function recordHalfLifeBaseForNewStore(db) {
|
|
|
152
155
|
if (db.prepare(`SELECT 1 AS x FROM memories LIMIT 1`).get() !== undefined)
|
|
153
156
|
return;
|
|
154
157
|
setMeta(db, HALF_LIFE_BASE_META_KEY, String(DEFAULT_HALF_LIFE_DAYS));
|
|
158
|
+
setMeta(db, TYPED_HALF_LIFE_META_KEY, '1');
|
|
155
159
|
}
|
|
156
160
|
function ensureMirrorDirectories(hippoRoot) {
|
|
157
161
|
const dirs = [
|
|
@@ -2143,7 +2147,7 @@ export function saveActiveTaskSnapshot(hippoRoot, tenantId, snapshot) {
|
|
|
2143
2147
|
const result = db.prepare(`
|
|
2144
2148
|
INSERT INTO task_snapshots(task, summary, next_step, status, source, session_id, scope, tenant_id, created_at, updated_at)
|
|
2145
2149
|
VALUES (?, ?, ?, 'active', ?, ?, ?, ?, ?, ?)
|
|
2146
|
-
`).run(snapshot.task, snapshot.summary, snapshot.next_step, snapshot.source ?? 'cli', snapshot.session_id ?? null, snapshot.scope ?? null, tenantId, now, now);
|
|
2150
|
+
`).run(redactSecretsStrict(snapshot.task), redactSecretsStrict(snapshot.summary), redactSecretsStrict(snapshot.next_step), snapshot.source ?? 'cli', snapshot.session_id ?? null, snapshot.scope ?? null, tenantId, now, now);
|
|
2147
2151
|
db.exec('COMMIT');
|
|
2148
2152
|
const id = Number(result.lastInsertRowid ?? 0);
|
|
2149
2153
|
// SAFETY: row's shape matches the ten columns named in the SELECT above.
|
|
@@ -2901,20 +2905,28 @@ export function stampHandoffOutcome(hippoRoot, tenantId, sessionId, outcome) {
|
|
|
2901
2905
|
closeHippoDb(db);
|
|
2902
2906
|
}
|
|
2903
2907
|
}
|
|
2904
|
-
/**
|
|
2905
|
-
* Auto-write a handoff at session-end from the session's active snapshot (DF1 T3).
|
|
2908
|
+
/** Auto-write a handoff at session-end (DF1 T3) from the session's active snapshot, else from `derived`, its transcript state.
|
|
2906
2909
|
* @param evidence best-effort git state; outcome comes from the newest session_complete event.
|
|
2907
|
-
* @returns null
|
|
2908
|
-
|
|
2909
|
-
export function writeSessionEndHandoff(hippoRoot, tenantId, sessionId, evidence) {
|
|
2910
|
+
* @returns null when neither source is the session's, a newer handoff covers the snapshot, or the session's latest handoff was not read off its transcript. */
|
|
2911
|
+
export function writeSessionEndHandoff(hippoRoot, tenantId, sessionId, evidence, derived = null) {
|
|
2910
2912
|
assertTenantId('writeSessionEndHandoff', tenantId);
|
|
2911
|
-
const
|
|
2912
|
-
if (!snapshot || snapshot.session_id !== sessionId)
|
|
2913
|
-
return null;
|
|
2913
|
+
const active = loadActiveTaskSnapshot(hippoRoot, tenantId);
|
|
2914
2914
|
const existing = loadLatestHandoff(hippoRoot, tenantId, sessionId);
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2915
|
+
let snapshot;
|
|
2916
|
+
let handoffEvidence = evidence;
|
|
2917
|
+
if (active && active.session_id === sessionId) {
|
|
2918
|
+
// Strict '>': a same-millisecond tie must not swallow the session's only write (test 6e).
|
|
2919
|
+
if (existing && existing.updatedAt > active.updated_at)
|
|
2920
|
+
return null;
|
|
2921
|
+
snapshot = active;
|
|
2922
|
+
}
|
|
2923
|
+
else {
|
|
2924
|
+
// Only an earlier transcript read gives way; `hippo handoff create` and unmarked older handoffs keep winning.
|
|
2925
|
+
if (!derived || (existing && existing.evidence?.derivedFrom !== 'transcript'))
|
|
2926
|
+
return null;
|
|
2927
|
+
snapshot = { ...derived, scope: null };
|
|
2928
|
+
handoffEvidence = { ...evidence, derivedFrom: 'transcript' };
|
|
2929
|
+
}
|
|
2918
2930
|
const db = openHippoDb(hippoRoot);
|
|
2919
2931
|
let outcome = null;
|
|
2920
2932
|
try {
|
|
@@ -2944,7 +2956,7 @@ export function writeSessionEndHandoff(hippoRoot, tenantId, sessionId, evidence)
|
|
|
2944
2956
|
nextAction: snapshot.next_step,
|
|
2945
2957
|
artifacts: carryForward ? existing.artifacts : [],
|
|
2946
2958
|
scope: snapshot.scope,
|
|
2947
|
-
evidence,
|
|
2959
|
+
evidence: handoffEvidence,
|
|
2948
2960
|
outcome,
|
|
2949
2961
|
constraints: carryForward ? existing.constraints : undefined,
|
|
2950
2962
|
targetRuntime: carryForward ? existing.targetRuntime : undefined,
|
package/dist/version.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* an ESM `import` can resolve cleanly, and a hardcoded constant survives
|
|
17
17
|
* any packager that drops .json files.
|
|
18
18
|
*/
|
|
19
|
-
export declare const PACKAGE_VERSION = "1.52.
|
|
19
|
+
export declare const PACKAGE_VERSION = "1.52.8";
|
|
20
20
|
/** Compares plain x.y.z versions, positive if a > b; tags throw so the rollback guard never misfires silently. */
|
|
21
21
|
export declare function compareSemver(a: string, b: string): number;
|
|
22
22
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* an ESM `import` can resolve cleanly, and a hardcoded constant survives
|
|
17
17
|
* any packager that drops .json files.
|
|
18
18
|
*/
|
|
19
|
-
export const PACKAGE_VERSION = '1.52.
|
|
19
|
+
export const PACKAGE_VERSION = '1.52.8';
|
|
20
20
|
/** Compares plain x.y.z versions, positive if a > b; tags throw so the rollback guard never misfires silently. */
|
|
21
21
|
export function compareSemver(a, b) {
|
|
22
22
|
const parse = (v) => {
|