hippo-memory 1.52.5 → 1.52.7
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 +56 -33
- package/dist/api.js +2 -1
- package/dist/autolearn.d.ts +1 -4
- package/dist/autolearn.js +3 -5
- package/dist/capture.js +3 -0
- package/dist/cli.js +167 -97
- 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/decisions.d.ts +1 -1
- package/dist/decisions.js +4 -3
- package/dist/extract.js +3 -0
- package/dist/half-life-migration.d.ts +14 -5
- package/dist/half-life-migration.js +89 -21
- package/dist/hooks.js +1 -1
- 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/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/skills.js +3 -2
- package/dist/store.d.ts +2 -0
- package/dist/store.js +3 -0
- package/dist/support-bundle.js +35 -14
- 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/extract.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Layer, createMemory } from './memory.js';
|
|
2
2
|
import { writeEntry } from './store.js';
|
|
3
|
+
import { loadConfig } from './config.js';
|
|
3
4
|
import { RejectedValueError } from './rejection.js';
|
|
4
5
|
import { redactSecrets } from './secret-detect.js';
|
|
5
6
|
function isJsonString(value) {
|
|
@@ -83,6 +84,7 @@ export function storeExtractedFacts(hippoRoot, source, facts) {
|
|
|
83
84
|
const inheritedTags = source.tags.filter((t) => INHERITABLE_PREFIXES.some((p) => t.startsWith(p)));
|
|
84
85
|
const entries = [];
|
|
85
86
|
let rejected = 0;
|
|
87
|
+
const baseHalfLifeDays = loadConfig(hippoRoot).defaultHalfLifeDays;
|
|
86
88
|
for (const fact of facts) {
|
|
87
89
|
const tags = ['extracted', ...inheritedTags, ...fact.tags];
|
|
88
90
|
const entry = createMemory(fact.content, {
|
|
@@ -99,6 +101,7 @@ export function storeExtractedFacts(hippoRoot, source, facts) {
|
|
|
99
101
|
// entry's own tenant. Thread it through so extracted facts land in
|
|
100
102
|
// the same tenant as the episodic memory they were extracted from.
|
|
101
103
|
tenantId: source.tenantId,
|
|
104
|
+
baseHalfLifeDays,
|
|
102
105
|
});
|
|
103
106
|
// AT1 containment: a refusal is per-VALUE — one rejected fact must not
|
|
104
107
|
// drop the rest of this batch. writeEntry has already audited the
|
|
@@ -17,12 +17,15 @@
|
|
|
17
17
|
* migration runs once.
|
|
18
18
|
*
|
|
19
19
|
* `hippo sleep` runs it before its decay pass, from the base the store is on
|
|
20
|
-
* (7 days when never recorded) to the configured `defaultHalfLifeDays`.
|
|
20
|
+
* (7 days when never recorded) to the configured `defaultHalfLifeDays`. Once per store it also
|
|
21
|
+
* moves memories of live decisions, incidents and other objects off the flat 90 days they used to get.
|
|
21
22
|
*/
|
|
22
23
|
import { type MemoryEntry } from './memory.js';
|
|
23
24
|
import { HALF_LIFE_BASE_META_KEY } from './store.js';
|
|
24
25
|
/** The base every store used before the base was recorded. */
|
|
25
26
|
export declare const LEGACY_HALF_LIFE_BASE = 7;
|
|
27
|
+
/** The flat half-life the decision, incident and other object writers gave their memories before they took the default. */
|
|
28
|
+
export declare const LEGACY_TYPED_HALF_LIFE = 90;
|
|
26
29
|
export { HALF_LIFE_BASE_META_KEY };
|
|
27
30
|
/** What {@link migrateDefaultHalfLife} did, or would do under `dryRun`. */
|
|
28
31
|
export interface HalfLifeMigrationResult {
|
|
@@ -30,6 +33,8 @@ export interface HalfLifeMigrationResult {
|
|
|
30
33
|
to: number;
|
|
31
34
|
/** Memories moved to the new base. */
|
|
32
35
|
rescaled: number;
|
|
36
|
+
/** Memories of decisions, incidents and other objects moved off the flat 90 days. */
|
|
37
|
+
typed: number;
|
|
33
38
|
/** Memories left alone because they are not on the old base. */
|
|
34
39
|
kept: number;
|
|
35
40
|
dryRun: boolean;
|
|
@@ -37,15 +42,19 @@ export interface HalfLifeMigrationResult {
|
|
|
37
42
|
halfLives: ReadonlyMap<string, number>;
|
|
38
43
|
}
|
|
39
44
|
type HalfLifeFields = Pick<MemoryEntry, 'half_life_days' | 'tags' | 'schema_fit' | 'retrieval_count' | 'superseded_by'>;
|
|
40
|
-
/** Recall bonus over
|
|
41
|
-
export declare function halfLifeRecallBonus(entry: HalfLifeFields,
|
|
45
|
+
/** Recall bonus over `written`, the half-life `entry` got at write, or null when off it; each recall added 2 days. */
|
|
46
|
+
export declare function halfLifeRecallBonus(entry: HalfLifeFields, written: number): number | null;
|
|
42
47
|
/** The entries to rescale from `from` to `to`, as copies that keep their recall bonus. Pure. */
|
|
43
48
|
export declare function planHalfLifeMigration(entries: readonly MemoryEntry[], from: number, to: number): MemoryEntry[];
|
|
49
|
+
/** Memories of decisions, incidents and other objects still on the flat 90 days, as copies on `to` that keep their recall bonus. Pure. */
|
|
50
|
+
export declare function planTypedHalfLifeMigration(entries: readonly MemoryEntry[], to: number): MemoryEntry[];
|
|
44
51
|
/** The base this store's memories are on. */
|
|
45
52
|
export declare function storeHalfLifeBase(hippoRoot: string): number;
|
|
53
|
+
/** The base an object writer gives its memory: the flat 90 days until this store's typed migration has run, which then moves them, and the default after. */
|
|
54
|
+
export declare function objectHalfLifeDays(hippoRoot: string): number;
|
|
46
55
|
/**
|
|
47
|
-
* Move the store's memories from the base they are on
|
|
48
|
-
*
|
|
56
|
+
* Move the store's memories from the base they are on, and those of objects from
|
|
57
|
+
* the old flat 90 days, to `to`, once. Under `dryRun` nothing is written, the recorded
|
|
49
58
|
* base included.
|
|
50
59
|
*/
|
|
51
60
|
export declare function migrateDefaultHalfLife(hippoRoot: string, to: number, opts?: {
|
|
@@ -17,20 +17,26 @@
|
|
|
17
17
|
* migration runs once.
|
|
18
18
|
*
|
|
19
19
|
* `hippo sleep` runs it before its decay pass, from the base the store is on
|
|
20
|
-
* (7 days when never recorded) to the configured `defaultHalfLifeDays`.
|
|
20
|
+
* (7 days when never recorded) to the configured `defaultHalfLifeDays`. Once per store it also
|
|
21
|
+
* moves memories of live decisions, incidents and other objects off the flat 90 days they used to get.
|
|
21
22
|
*/
|
|
22
23
|
import { deriveHalfLife } from './memory.js';
|
|
23
|
-
import { openStore, selectAllEntries, HALF_LIFE_BASE_META_KEY } from './store.js';
|
|
24
|
+
import { openStore, selectAllEntries, HALF_LIFE_BASE_META_KEY, TYPED_HALF_LIFE_META_KEY } from './store.js';
|
|
24
25
|
import { openHippoDb, closeHippoDb, getMeta, setMeta } from './db.js';
|
|
25
26
|
import { appendAuditEvent } from './audit.js';
|
|
27
|
+
import { loadConfig } from './config.js';
|
|
26
28
|
/** The base every store used before the base was recorded. */
|
|
27
29
|
export const LEGACY_HALF_LIFE_BASE = 7;
|
|
30
|
+
/** The flat half-life the decision, incident and other object writers gave their memories before they took the default. */
|
|
31
|
+
export const LEGACY_TYPED_HALF_LIFE = 90;
|
|
32
|
+
const TYPED_SOURCES = new Set(['decision', 'incident', 'process', 'policy', 'skill', 'project_brief', 'customer_note']);
|
|
33
|
+
const OBJECT_TABLES = ['decisions', 'incidents', 'processes', 'policies', 'skills', 'project_briefs', 'customer_notes'];
|
|
28
34
|
export { HALF_LIFE_BASE_META_KEY };
|
|
29
|
-
/** Recall bonus over
|
|
30
|
-
export function halfLifeRecallBonus(entry,
|
|
35
|
+
/** Recall bonus over `written`, the half-life `entry` got at write, or null when off it; each recall added 2 days. */
|
|
36
|
+
export function halfLifeRecallBonus(entry, written) {
|
|
31
37
|
if (entry.superseded_by || entry.tags.includes('invalidated') || entry.tags.includes('superseded'))
|
|
32
38
|
return null;
|
|
33
|
-
const bonus = entry.half_life_days -
|
|
39
|
+
const bonus = entry.half_life_days - written;
|
|
34
40
|
const k = Math.round(bonus / 2);
|
|
35
41
|
return Math.abs(bonus - 2 * k) < 1e-9 && k >= 0 && k <= entry.retrieval_count ? bonus : null;
|
|
36
42
|
}
|
|
@@ -39,10 +45,18 @@ export function planHalfLifeMigration(entries, from, to) {
|
|
|
39
45
|
if (from === to)
|
|
40
46
|
return [];
|
|
41
47
|
return entries.flatMap((e) => {
|
|
42
|
-
const bonus = halfLifeRecallBonus(e, from);
|
|
48
|
+
const bonus = halfLifeRecallBonus(e, deriveHalfLife(from, e));
|
|
43
49
|
return bonus === null ? [] : [{ ...e, half_life_days: deriveHalfLife(to, e) + bonus }];
|
|
44
50
|
});
|
|
45
51
|
}
|
|
52
|
+
/** Memories of decisions, incidents and other objects still on the flat 90 days, as copies on `to` that keep their recall bonus. Pure. */
|
|
53
|
+
export function planTypedHalfLifeMigration(entries, to) {
|
|
54
|
+
return entries.flatMap((e) => {
|
|
55
|
+
const bonus = TYPED_SOURCES.has(e.source) ? halfLifeRecallBonus(e, LEGACY_TYPED_HALF_LIFE) : null;
|
|
56
|
+
const next = bonus === null ? e.half_life_days : deriveHalfLife(to, e) + bonus;
|
|
57
|
+
return next === e.half_life_days ? [] : [{ ...e, half_life_days: next }];
|
|
58
|
+
});
|
|
59
|
+
}
|
|
46
60
|
/** The base this store's memories are on. */
|
|
47
61
|
export function storeHalfLifeBase(hippoRoot) {
|
|
48
62
|
const db = openHippoDb(hippoRoot);
|
|
@@ -57,14 +71,25 @@ function readBase(db) {
|
|
|
57
71
|
const raw = Number(getMeta(db, HALF_LIFE_BASE_META_KEY, String(LEGACY_HALF_LIFE_BASE)));
|
|
58
72
|
return Number.isFinite(raw) && raw > 0 ? raw : LEGACY_HALF_LIFE_BASE;
|
|
59
73
|
}
|
|
74
|
+
/** The base an object writer gives its memory: the flat 90 days until this store's typed migration has run, which then moves them, and the default after. */
|
|
75
|
+
export function objectHalfLifeDays(hippoRoot) {
|
|
76
|
+
// SHORTCUT: read outside the write's transaction, so a write racing the typed migration keeps 90; read inside writeEntry if that ever matters.
|
|
77
|
+
const db = openStore(hippoRoot);
|
|
78
|
+
try {
|
|
79
|
+
return getMeta(db, TYPED_HALF_LIFE_META_KEY, '') === '' ? LEGACY_TYPED_HALF_LIFE : loadConfig(hippoRoot).defaultHalfLifeDays;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
closeHippoDb(db);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
60
85
|
/**
|
|
61
|
-
* Move the store's memories from the base they are on
|
|
62
|
-
*
|
|
86
|
+
* Move the store's memories from the base they are on, and those of objects from
|
|
87
|
+
* the old flat 90 days, to `to`, once. Under `dryRun` nothing is written, the recorded
|
|
63
88
|
* base included.
|
|
64
89
|
*/
|
|
65
90
|
export function migrateDefaultHalfLife(hippoRoot, to, opts = {}) {
|
|
66
91
|
const dryRun = opts.dryRun ?? false;
|
|
67
|
-
const noop = (from) => ({ from, to, rescaled: 0, kept: 0, dryRun, halfLives: new Map() });
|
|
92
|
+
const noop = (from) => ({ from, to, rescaled: 0, typed: 0, kept: 0, dryRun, halfLives: new Map() });
|
|
68
93
|
const db = openStore(hippoRoot);
|
|
69
94
|
try {
|
|
70
95
|
// Plan, write, audit and record the base under one write lock, so a concurrent write or sleep cannot interleave.
|
|
@@ -72,28 +97,31 @@ export function migrateDefaultHalfLife(hippoRoot, to, opts = {}) {
|
|
|
72
97
|
db.exec('BEGIN IMMEDIATE');
|
|
73
98
|
try {
|
|
74
99
|
const from = readBase(db);
|
|
75
|
-
|
|
100
|
+
const typedPending = getMeta(db, TYPED_HALF_LIFE_META_KEY, '') === '';
|
|
101
|
+
if (!(Number.isFinite(to) && to > 0) || (from === to && !typedPending)) {
|
|
76
102
|
if (!dryRun)
|
|
77
103
|
db.exec('COMMIT');
|
|
78
104
|
return noop(from);
|
|
79
105
|
}
|
|
80
106
|
const all = selectAllEntries(db);
|
|
81
|
-
const
|
|
107
|
+
const objects = typedPending ? objectMemoryIds(db) : { all: new Set(), retired: new Set() };
|
|
108
|
+
const losers = typedPending ? conflictLosers(db) : new Set();
|
|
109
|
+
const copies = new Set(all.flatMap((e) => (e.superseded_by ? [e.superseded_by] : [])));
|
|
110
|
+
// Provenance before shape: an object's memory came from its writer, a supersede copy from the base (it keeps the source). Shape decides the rest.
|
|
111
|
+
const objectWritten = (e) => objects.all.has(e.id) || (!copies.has(e.id) && TYPED_SOURCES.has(e.source) && halfLifeRecallBonus(e, LEGACY_TYPED_HALF_LIFE) !== null);
|
|
112
|
+
const typedPlan = typedPending ? planTypedHalfLifeMigration(all.filter((e) => objectWritten(e) && !objects.retired.has(e.id) && !losers.has(e.id)), to) : [];
|
|
113
|
+
const basePlan = planHalfLifeMigration(typedPending ? all.filter((e) => !objectWritten(e)) : all, from, to);
|
|
114
|
+
const plan = [...basePlan, ...typedPlan];
|
|
82
115
|
const halfLives = new Map(plan.map((e) => [e.id, e.half_life_days]));
|
|
83
|
-
const result = { from, to, rescaled:
|
|
116
|
+
const result = { from, to, rescaled: basePlan.length, typed: typedPlan.length, kept: all.length - plan.length, dryRun, halfLives };
|
|
84
117
|
if (dryRun)
|
|
85
118
|
return result;
|
|
86
119
|
const old = new Map(all.map((e) => [e.id, e.half_life_days]));
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
update.run(e.half_life_days, e.id);
|
|
91
|
-
byTenant.set(e.tenantId, { ...byTenant.get(e.tenantId), [e.id]: old.get(e.id) });
|
|
92
|
-
}
|
|
93
|
-
for (const [tenantId, oldHalfLives] of byTenant) {
|
|
94
|
-
appendAuditEvent(db, { tenantId, actor: opts.actor ?? 'system', op: 'half_life_migrate', metadata: { from, to, ids: Object.keys(oldHalfLives), oldHalfLives } });
|
|
95
|
-
}
|
|
120
|
+
const actor = opts.actor ?? 'system';
|
|
121
|
+
writePlan(db, basePlan, old, { from, to, actor });
|
|
122
|
+
writePlan(db, typedPlan, old, { from: LEGACY_TYPED_HALF_LIFE, to, actor });
|
|
96
123
|
setMeta(db, HALF_LIFE_BASE_META_KEY, String(to));
|
|
124
|
+
setMeta(db, TYPED_HALF_LIFE_META_KEY, '1');
|
|
97
125
|
db.exec('COMMIT');
|
|
98
126
|
return result;
|
|
99
127
|
}
|
|
@@ -107,4 +135,44 @@ export function migrateDefaultHalfLife(hippoRoot, to, opts = {}) {
|
|
|
107
135
|
closeHippoDb(db);
|
|
108
136
|
}
|
|
109
137
|
}
|
|
138
|
+
/** Memories behind every object, and those behind a superseded or closed one. Retiring an object leaves its memory untouched, so only its table knows. */
|
|
139
|
+
function objectMemoryIds(db) {
|
|
140
|
+
const all = new Set();
|
|
141
|
+
const retired = new Set();
|
|
142
|
+
for (const table of OBJECT_TABLES) {
|
|
143
|
+
// SAFETY: SELECT of two TEXT columns, filtered to a non-null memory_id.
|
|
144
|
+
const rows = db.prepare(`SELECT memory_id, status FROM ${table} WHERE memory_id IS NOT NULL`).all();
|
|
145
|
+
for (const r of rows) {
|
|
146
|
+
all.add(r.memory_id);
|
|
147
|
+
if (r.status === 'superseded' || r.status === 'closed')
|
|
148
|
+
retired.add(r.memory_id);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return { all, retired };
|
|
152
|
+
}
|
|
153
|
+
/** Memories that lost a conflict, which resolveConflict halved untagged. A resolved conflict with no audit row (before v1.31.0, or found stale) names no winner, so both sides count. */
|
|
154
|
+
function conflictLosers(db) {
|
|
155
|
+
// SAFETY: SELECT of two fields every conflict_resolve audit row carries (ConflictResolveMeta in store.ts).
|
|
156
|
+
const audited = db.prepare(`SELECT json_extract(metadata_json, '$.conflictId') AS conflictId, json_extract(metadata_json, '$.loserId') AS loserId FROM audit_log WHERE op = 'conflict_resolve'`).all();
|
|
157
|
+
const losers = new Set(audited.map((a) => a.loserId));
|
|
158
|
+
const named = new Set(audited.map((a) => a.conflictId));
|
|
159
|
+
// SAFETY: SELECT of three columns of resolved conflicts.
|
|
160
|
+
const resolved = db.prepare(`SELECT id, memory_a_id, memory_b_id FROM memory_conflicts WHERE status = 'resolved'`).all();
|
|
161
|
+
for (const c of resolved)
|
|
162
|
+
if (!named.has(c.id))
|
|
163
|
+
losers.add(c.memory_a_id).add(c.memory_b_id);
|
|
164
|
+
return losers;
|
|
165
|
+
}
|
|
166
|
+
/** Writes `plan`, then one audit event per tenant with each id's old half-life, so the move can be undone. */
|
|
167
|
+
function writePlan(db, plan, old, move) {
|
|
168
|
+
const update = db.prepare('UPDATE memories SET half_life_days = ? WHERE id = ?');
|
|
169
|
+
const byTenant = new Map();
|
|
170
|
+
for (const e of plan) {
|
|
171
|
+
update.run(e.half_life_days, e.id);
|
|
172
|
+
byTenant.set(e.tenantId, { ...byTenant.get(e.tenantId), [e.id]: old.get(e.id) });
|
|
173
|
+
}
|
|
174
|
+
for (const [tenantId, oldHalfLives] of byTenant) {
|
|
175
|
+
appendAuditEvent(db, { tenantId, actor: move.actor, op: 'half_life_migrate', metadata: { from: move.from, to: move.to, ids: Object.keys(oldHalfLives), oldHalfLives } });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
110
178
|
//# sourceMappingURL=half-life-migration.js.map
|
package/dist/hooks.js
CHANGED
|
@@ -966,7 +966,7 @@ export function detectInstalledTools() {
|
|
|
966
966
|
{ name: 'opencode', configDir: '~/.config/opencode', detected: exists('.config', 'opencode'), kind: 'plugin', notes: 'installs a TS plugin at ~/.config/opencode/plugins/hippo.ts' },
|
|
967
967
|
{ name: 'openclaw', configDir: '~/.openclaw', detected: exists('.openclaw'), kind: 'plugin', notes: 'install via `openclaw plugins install hippo-memory`' },
|
|
968
968
|
{ name: 'codex', configDir: '~/.codex', detected: exists('.codex'), kind: 'wrapper', notes: 'wraps the detected codex launcher for session-end consolidation' },
|
|
969
|
-
{ name: 'cursor', configDir: '~/.cursor', detected: exists('.cursor'), kind: 'markdown-instruction', notes: 'no hook API - patches .
|
|
969
|
+
{ name: 'cursor', configDir: '~/.cursor', detected: exists('.cursor'), kind: 'markdown-instruction', notes: 'no hook API - patches AGENTS.md in the project' },
|
|
970
970
|
{ name: 'pi', configDir: '~/.pi', detected: exists('.pi'), kind: 'markdown-instruction', notes: 'no hook API - patches AGENTS.md in the project' },
|
|
971
971
|
];
|
|
972
972
|
}
|
package/dist/importers.d.ts
CHANGED
|
@@ -58,7 +58,8 @@ export interface ImportOptions {
|
|
|
58
58
|
export declare function importEntries(chunks: string[], source: string, tags: string[], options: ImportOptions): ImportResult;
|
|
59
59
|
export declare function importChatGPT(filePath: string, options: ImportOptions): ImportResult;
|
|
60
60
|
export declare function importClaude(filePath: string, options: ImportOptions): ImportResult;
|
|
61
|
-
|
|
61
|
+
/** Import `.cursorrules`, one rule file, or a `.cursor/rules` tree of `.mdc` and `.md` rules. */
|
|
62
|
+
export declare function importCursor(sourcePath: string, options: ImportOptions): ImportResult;
|
|
62
63
|
export declare function importGenericFile(filePath: string, options: ImportOptions): ImportResult;
|
|
63
64
|
export declare function importMarkdown(filePath: string, options: ImportOptions): ImportResult;
|
|
64
65
|
/**
|
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/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/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
|
*/
|
package/dist/store.js
CHANGED
|
@@ -140,6 +140,8 @@ export function openStore(hippoRoot) {
|
|
|
140
140
|
}
|
|
141
141
|
/** `meta` key holding the default half-life base a store's memories are on (src/half-life-migration.ts). */
|
|
142
142
|
export const HALF_LIFE_BASE_META_KEY = 'default_half_life_base';
|
|
143
|
+
/** `meta` key set once no memory of a decision, incident or other object sits on the old flat 90 days. */
|
|
144
|
+
export const TYPED_HALF_LIFE_META_KEY = 'typed_half_life_on_default';
|
|
143
145
|
/**
|
|
144
146
|
* A store with no memories starts on the current default half-life base, so
|
|
145
147
|
* `hippo sleep` never migrates it. A store that already holds memories and
|
|
@@ -152,6 +154,7 @@ function recordHalfLifeBaseForNewStore(db) {
|
|
|
152
154
|
if (db.prepare(`SELECT 1 AS x FROM memories LIMIT 1`).get() !== undefined)
|
|
153
155
|
return;
|
|
154
156
|
setMeta(db, HALF_LIFE_BASE_META_KEY, String(DEFAULT_HALF_LIFE_DAYS));
|
|
157
|
+
setMeta(db, TYPED_HALF_LIFE_META_KEY, '1');
|
|
155
158
|
}
|
|
156
159
|
function ensureMirrorDirectories(hippoRoot) {
|
|
157
160
|
const dirs = [
|