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/config.js
CHANGED
|
@@ -151,8 +151,15 @@ export function loadConfig(hippoRoot) {
|
|
|
151
151
|
`(got ${JSON.stringify(churnStalenessEnabled)}) - using false.`);
|
|
152
152
|
churnStalenessEnabled = false;
|
|
153
153
|
}
|
|
154
|
+
// Every writer starts a memory on this, and a zero or negative half-life scores zero strength, so sleep would retire it.
|
|
155
|
+
let defaultHalfLifeDays = raw.defaultHalfLifeDays ?? DEFAULT_CONFIG.defaultHalfLifeDays;
|
|
156
|
+
if (!Number.isFinite(defaultHalfLifeDays) || defaultHalfLifeDays <= 0) {
|
|
157
|
+
console.error(`Warning: config.json's "defaultHalfLifeDays" must be a number of days above 0 ` +
|
|
158
|
+
`(got ${JSON.stringify(defaultHalfLifeDays)}) - using ${DEFAULT_CONFIG.defaultHalfLifeDays}.`);
|
|
159
|
+
defaultHalfLifeDays = DEFAULT_CONFIG.defaultHalfLifeDays;
|
|
160
|
+
}
|
|
154
161
|
return {
|
|
155
|
-
defaultHalfLifeDays
|
|
162
|
+
defaultHalfLifeDays,
|
|
156
163
|
defaultBudget: raw.defaultBudget ?? DEFAULT_CONFIG.defaultBudget,
|
|
157
164
|
defaultContextBudget: raw.defaultContextBudget ?? DEFAULT_CONFIG.defaultContextBudget,
|
|
158
165
|
decayBasis: validBasis ? basis : DEFAULT_CONFIG.decayBasis,
|
package/dist/consolidate.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface ConsolidationResult {
|
|
|
37
37
|
/** Ids the decay pass removes (or would remove, under dryRun). */
|
|
38
38
|
removedIds?: string[];
|
|
39
39
|
}
|
|
40
|
+
/** Tables whose rows keep a first-class object's backing memory in `memory_id` (ON DELETE SET NULL); tests/dormant-memories.test.ts pins it to the schema. */
|
|
41
|
+
export declare const MEMORY_BACKED_TABLES: readonly ["predictions", "decisions", "incidents", "processes", "policies", "skills", "project_briefs", "customer_notes"];
|
|
40
42
|
/**
|
|
41
43
|
* Run a full consolidation pass.
|
|
42
44
|
*/
|
package/dist/consolidate.js
CHANGED
|
@@ -24,7 +24,7 @@ import { resolveTenantId } from './tenant.js';
|
|
|
24
24
|
import { rescueSet, rankNonPinnedByTenant, validateWeights } from './memory-value.js';
|
|
25
25
|
import { MEMORY_VALUE_WEIGHTS, SOURCE_ARTIFACT_SHA256 } from './memory-value-weights.js';
|
|
26
26
|
import { appendAuditEvent } from './audit.js';
|
|
27
|
-
import { migrateDefaultHalfLife } from './half-life-migration.js';
|
|
27
|
+
import { migrateDefaultHalfLife, LEGACY_TYPED_HALF_LIFE } from './half-life-migration.js';
|
|
28
28
|
import { derivationScope, commonDerivationScope, derivationPartitionKey } from './recall-scope.js';
|
|
29
29
|
import { isQuarantineScope } from './quarantine.js';
|
|
30
30
|
const DECAY_THRESHOLD = 0.05;
|
|
@@ -64,10 +64,10 @@ const REPLAY_COUNT_DEFAULT = 5;
|
|
|
64
64
|
function isJsonString(value) {
|
|
65
65
|
return typeof value === 'string';
|
|
66
66
|
}
|
|
67
|
-
/** Tables whose rows keep a first-class object's backing memory in `memory_id` (ON DELETE SET NULL). */
|
|
68
|
-
const MEMORY_BACKED_TABLES = ['predictions', 'decisions', 'processes', 'policies', 'skills', 'project_briefs', 'customer_notes'];
|
|
67
|
+
/** Tables whose rows keep a first-class object's backing memory in `memory_id` (ON DELETE SET NULL); tests/dormant-memories.test.ts pins it to the schema. */
|
|
68
|
+
export const MEMORY_BACKED_TABLES = ['predictions', 'decisions', 'incidents', 'processes', 'policies', 'skills', 'project_briefs', 'customer_notes'];
|
|
69
69
|
/**
|
|
70
|
-
* Ids of memories that back a first-class object (a decision, prediction,
|
|
70
|
+
* Ids of memories that back a first-class object (a decision, incident, prediction,
|
|
71
71
|
* process, policy, skill, project brief or customer note). Sleep never
|
|
72
72
|
* retires these: deleting or moving one to dormant storage fires the
|
|
73
73
|
* object's ON DELETE SET NULL and a restore cannot repair the link. Their
|
|
@@ -85,8 +85,10 @@ function memoriesBackingObjects(hippoRoot) {
|
|
|
85
85
|
for (const r of rows)
|
|
86
86
|
ids.add(r.memory_id);
|
|
87
87
|
}
|
|
88
|
-
catch {
|
|
89
|
-
//
|
|
88
|
+
catch (err) {
|
|
89
|
+
// A missing table is an older schema; any other error could hide a backing memory, so sleep stops.
|
|
90
|
+
if (!(err instanceof Error && err.message.includes('no such table')))
|
|
91
|
+
throw err;
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
}
|
|
@@ -131,6 +133,9 @@ export async function consolidate(hippoRoot, options = {}) {
|
|
|
131
133
|
if (halfLife.rescaled > 0) {
|
|
132
134
|
result.details.push(` ⏳ ${dryRun ? 'would move' : 'moved'} ${halfLife.rescaled} memories from the ${halfLife.from}-day to the ${halfLife.to}-day half-life`);
|
|
133
135
|
}
|
|
136
|
+
if (halfLife.typed > 0) {
|
|
137
|
+
result.details.push(` ⏳ ${dryRun ? 'would move' : 'moved'} ${halfLife.typed} memories of decisions, incidents and other objects from the ${LEGACY_TYPED_HALF_LIFE}-day to the ${halfLife.to}-day half-life`);
|
|
138
|
+
}
|
|
134
139
|
// L9: host-wide by design. Consolidation runs across all tenants in one
|
|
135
140
|
// pass — per-tenant filtering would create N consolidation runs per host
|
|
136
141
|
// with no cross-tenant dedup. The api.sleep audit row tags this with the
|
|
@@ -410,6 +415,7 @@ export async function consolidate(hippoRoot, options = {}) {
|
|
|
410
415
|
// consolidationTenant — for any non-default tenant that check never
|
|
411
416
|
// hit, and the trace regenerated every sleep.
|
|
412
417
|
tenantId: consolidationTenant,
|
|
418
|
+
baseHalfLifeDays: config.defaultHalfLifeDays,
|
|
413
419
|
});
|
|
414
420
|
// AT1 (same producer-side pattern as the merge pass below): traceExistsForSession
|
|
415
421
|
// only sees rows CURRENTLY in the store — once a rejected trace is
|
|
@@ -737,6 +743,7 @@ export async function consolidate(hippoRoot, options = {}) {
|
|
|
737
743
|
confidence: 'inferred',
|
|
738
744
|
tenantId: mergeTenant,
|
|
739
745
|
scope: mergeScope,
|
|
746
|
+
baseHalfLifeDays: config.defaultHalfLifeDays,
|
|
740
747
|
});
|
|
741
748
|
}
|
|
742
749
|
// mergeContents is DETERMINISTIC CONCATENATION (not an LLM paraphrase)
|
package/dist/customer-notes.js
CHANGED
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
23
23
|
import { writeEntry, assertTenantId } from './store.js';
|
|
24
24
|
import { markGraphDirty, removeGraphEntitiesForObject } from './graph.js';
|
|
25
|
-
import { createMemory, Layer
|
|
25
|
+
import { createMemory, Layer } from './memory.js';
|
|
26
26
|
import { appendAuditEvent } from './audit.js';
|
|
27
|
+
import { objectHalfLifeDays } from './half-life-migration.js';
|
|
27
28
|
export const VALID_NOTE_STATES = new Set([
|
|
28
29
|
'active',
|
|
29
30
|
'superseded',
|
|
@@ -118,9 +119,9 @@ export function saveCustomerNote(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
|
118
119
|
layer: Layer.Semantic,
|
|
119
120
|
confidence: 'verified',
|
|
120
121
|
source: 'customer_note',
|
|
122
|
+
baseHalfLifeDays: objectHalfLifeDays(hippoRoot),
|
|
121
123
|
tenantId,
|
|
122
124
|
});
|
|
123
|
-
mem.half_life_days = CUSTOMER_NOTE_HALF_LIFE_DAYS;
|
|
124
125
|
let savedRow;
|
|
125
126
|
writeEntry(hippoRoot, mem, {
|
|
126
127
|
actor,
|
package/dist/dag.js
CHANGED
|
@@ -3,6 +3,7 @@ import { writeEntry, loadAllDirtySummaries, loadChildrenOfSummary, applyRebuildR
|
|
|
3
3
|
import { RejectedValueError } from './rejection.js';
|
|
4
4
|
import { redactSecrets } from './secret-detect.js';
|
|
5
5
|
import { derivationScope, derivationPartitionKey } from './recall-scope.js';
|
|
6
|
+
import { loadConfig } from './config.js';
|
|
6
7
|
export function clusterFacts(facts) {
|
|
7
8
|
if (facts.length === 0)
|
|
8
9
|
return [];
|
|
@@ -84,6 +85,7 @@ export async function generateDagSummary(label, factContents, opts) {
|
|
|
84
85
|
}
|
|
85
86
|
export async function buildDag(hippoRoot, facts, opts) {
|
|
86
87
|
const result = { candidateClusters: 0, summariesCreated: 0, factsLinked: 0, rejected: 0 };
|
|
88
|
+
const baseHalfLifeDays = loadConfig(hippoRoot).defaultHalfLifeDays;
|
|
87
89
|
const unparented = facts.filter((f) => f.dag_level === 1 && !f.dag_parent_id && f.tags.includes('extracted'));
|
|
88
90
|
// Hardening follow-up (mirrors consolidate.ts's mergeCandidatesByTenant,
|
|
89
91
|
// T1): partition unparented facts by tenantId BEFORE clustering so a
|
|
@@ -123,6 +125,7 @@ export async function buildDag(hippoRoot, facts, opts) {
|
|
|
123
125
|
dag_level: 2,
|
|
124
126
|
tenantId: factTenant,
|
|
125
127
|
scope: factScope,
|
|
128
|
+
baseHalfLifeDays,
|
|
126
129
|
});
|
|
127
130
|
// Schema v25: cache descendant_count + earliest/latest_at on the summary
|
|
128
131
|
// row so DAG-aware recall (docs/plans/2026-05-05-dag-recall.md Task 2)
|
|
@@ -293,6 +296,7 @@ export async function buildEntityProfiles(hippoRoot, l2Summaries, opts) {
|
|
|
293
296
|
failed: 0,
|
|
294
297
|
rejected: 0,
|
|
295
298
|
};
|
|
299
|
+
const baseHalfLifeDays = loadConfig(hippoRoot).defaultHalfLifeDays;
|
|
296
300
|
// Only L2 with no L3 parent yet (avoid re-clustering already-profiled L2s).
|
|
297
301
|
const unparented = l2Summaries.filter((s) => s.dag_level === 2 && !s.dag_parent_id);
|
|
298
302
|
// independent-review HIGH #1 fold: cluster ONLY within-tenant.
|
|
@@ -330,6 +334,7 @@ export async function buildEntityProfiles(hippoRoot, l2Summaries, opts) {
|
|
|
330
334
|
dag_level: 3,
|
|
331
335
|
tenantId, // HIGH #1 fold: thread tenant explicitly
|
|
332
336
|
scope,
|
|
337
|
+
baseHalfLifeDays,
|
|
333
338
|
});
|
|
334
339
|
profileEntry.descendant_count = cluster.members.length;
|
|
335
340
|
profileEntry.earliest_at = memberCreatedAts[0];
|
package/dist/dashboard.js
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* Usage: hippo dashboard [--port 3333]
|
|
6
6
|
*/
|
|
7
7
|
import * as http from 'http';
|
|
8
|
+
import * as os from 'os';
|
|
8
9
|
import * as path from 'path';
|
|
9
10
|
import * as fs from 'fs';
|
|
11
|
+
import { extractPathTags } from './path-context.js';
|
|
10
12
|
import { loadAllEntries, listCards, listMemoryConflicts, readEntry, writeEntry } from './store.js';
|
|
11
13
|
import { calculateStrength, confidenceFacets } from './memory.js';
|
|
12
14
|
import { loadConfig } from './config.js';
|
|
@@ -110,6 +112,8 @@ function buildDashboardData(hippoRoot) {
|
|
|
110
112
|
defaultHalfLifeDays: config.defaultHalfLifeDays,
|
|
111
113
|
defaultBudget: config.defaultBudget,
|
|
112
114
|
embeddingsEnabled: config.embeddings.enabled,
|
|
115
|
+
// Every memory captured in the home folder carries these, so they name no project.
|
|
116
|
+
homePathTags: extractPathTags(os.homedir()),
|
|
113
117
|
},
|
|
114
118
|
};
|
|
115
119
|
}
|
package/dist/decisions.d.ts
CHANGED
|
@@ -64,7 +64,7 @@ export interface ListDecisionsOpts {
|
|
|
64
64
|
*
|
|
65
65
|
* The memory mirror preserves the legacy `hippo decide` shape: tags
|
|
66
66
|
* ['decision', ...extraTags], source 'decision', confidence 'verified',
|
|
67
|
-
*
|
|
67
|
+
* the half-life objectHalfLifeDays picks, content = "<text>\n\nContext: <context>"
|
|
68
68
|
* when context is given (so existing recall output is unchanged).
|
|
69
69
|
*/
|
|
70
70
|
export declare function saveDecision(hippoRoot: string, tenantId: string, opts: SaveDecisionOpts, actor?: string): Decision;
|
package/dist/decisions.js
CHANGED
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
27
27
|
import { writeEntry, assertTenantId } from './store.js';
|
|
28
28
|
import { markGraphDirty, removeGraphEntitiesForObject } from './graph.js';
|
|
29
|
-
import { createMemory, Layer
|
|
29
|
+
import { createMemory, Layer } from './memory.js';
|
|
30
30
|
import { appendAuditEvent } from './audit.js';
|
|
31
|
+
import { objectHalfLifeDays } from './half-life-migration.js';
|
|
31
32
|
export const VALID_DECISION_STATES = new Set([
|
|
32
33
|
'active',
|
|
33
34
|
'superseded',
|
|
@@ -65,7 +66,7 @@ const DECISION_COLS = `
|
|
|
65
66
|
*
|
|
66
67
|
* The memory mirror preserves the legacy `hippo decide` shape: tags
|
|
67
68
|
* ['decision', ...extraTags], source 'decision', confidence 'verified',
|
|
68
|
-
*
|
|
69
|
+
* the half-life objectHalfLifeDays picks, content = "<text>\n\nContext: <context>"
|
|
69
70
|
* when context is given (so existing recall output is unchanged).
|
|
70
71
|
*/
|
|
71
72
|
export function saveDecision(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
@@ -82,9 +83,9 @@ export function saveDecision(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
|
82
83
|
layer: Layer.Semantic,
|
|
83
84
|
confidence: 'verified',
|
|
84
85
|
source: 'decision',
|
|
86
|
+
baseHalfLifeDays: objectHalfLifeDays(hippoRoot),
|
|
85
87
|
tenantId,
|
|
86
88
|
});
|
|
87
|
-
mem.half_life_days = DECISION_HALF_LIFE_DAYS;
|
|
88
89
|
// Populated inside afterWrite so the INSERT, the supersede UPDATE, and the
|
|
89
90
|
// memory write all share one SAVEPOINT.
|
|
90
91
|
let savedRow;
|
|
@@ -172,6 +172,7 @@ function runHippo(args, cwd) {
|
|
|
172
172
|
encoding: 'utf8',
|
|
173
173
|
timeout: 30_000,
|
|
174
174
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
175
|
+
windowsHide: true,
|
|
175
176
|
});
|
|
176
177
|
// `encoding: 'utf8'` above selects the ExecFileSyncOptionsWithStringEncoding
|
|
177
178
|
// overload, so `result` is always a `string` here — no runtime check needed.
|
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
|
package/dist/graph-recall.js
CHANGED
|
@@ -61,12 +61,9 @@ function loadByIdsChunked(root, tenantId, ids) {
|
|
|
61
61
|
}
|
|
62
62
|
return out;
|
|
63
63
|
}
|
|
64
|
-
/**
|
|
65
|
-
*
|
|
66
|
-
|
|
67
|
-
* stores. Pure reads.
|
|
68
|
-
*/
|
|
69
|
-
function produceHitsForRoot(root, baseResults, baseScoreByMemId, seenMemoryIds, hitsByOrigin, opts) {
|
|
64
|
+
/** Traverse one store's graph from its seeds into `hitsByOrigin`. Pure reads; mutates `seenMemoryIds`
|
|
65
|
+
* and `seenContent` so a memory, or a share/promote copy of it, surfaces at most once across stores. */
|
|
66
|
+
function produceHitsForRoot(root, baseResults, baseScoreByMemId, seenMemoryIds, seenContent, hitsByOrigin, opts) {
|
|
70
67
|
const { hops, maxNeighbors, tenantId, includeSuperseded, asOfDate, recallScope } = opts;
|
|
71
68
|
// Seeds = graph entities (in THIS store) whose source memory is a base result.
|
|
72
69
|
const seedEntities = loadEntitiesByMemoryId(root, tenantId, baseResults.map((r) => r.entry.id));
|
|
@@ -147,6 +144,8 @@ function produceHitsForRoot(root, baseResults, baseScoreByMemId, seenMemoryIds,
|
|
|
147
144
|
continue; // not found / wrong tenant / already in base
|
|
148
145
|
if (seenMemoryIds.has(mem.id))
|
|
149
146
|
continue; // another reached entity already added it
|
|
147
|
+
if (seenContent.has(mem.content))
|
|
148
|
+
continue; // share/promote copy: same text, another id
|
|
150
149
|
const via = reached.get(ent.id);
|
|
151
150
|
// A node reached as the `to` endpoint of a `supersedes` edge IS the superseded
|
|
152
151
|
// (older) version — the graph is the authoritative signal (the memory mirror's
|
|
@@ -175,6 +174,7 @@ function produceHitsForRoot(root, baseResults, baseScoreByMemId, seenMemoryIds,
|
|
|
175
174
|
const origin = originMemByEntityId.get(ent.id) ?? baseResults[0].entry.id;
|
|
176
175
|
const originScore = baseScoreByMemId.get(origin) ?? baseResults[baseResults.length - 1].score;
|
|
177
176
|
seenMemoryIds.add(mem.id);
|
|
177
|
+
seenContent.add(mem.content);
|
|
178
178
|
const hit = {
|
|
179
179
|
entry: mem,
|
|
180
180
|
score: originScore * (1 - HOP_DISCOUNT * via.hops),
|
|
@@ -209,11 +209,12 @@ export function graphExpandRecall(baseResults, opts) {
|
|
|
209
209
|
const recallScope = opts.recallScope ?? {};
|
|
210
210
|
const baseScoreByMemId = new Map(baseResults.map((r) => [r.entry.id, r.score]));
|
|
211
211
|
const seenMemoryIds = new Set(baseResults.map((r) => r.entry.id));
|
|
212
|
+
const seenContent = new Set(baseResults.map((r) => r.entry.content));
|
|
212
213
|
const hitsByOrigin = new Map();
|
|
213
214
|
// Expand against each distinct store the seeds may live in (local + global).
|
|
214
215
|
const roots = globalRoot && globalRoot !== hippoRoot ? [hippoRoot, globalRoot] : [hippoRoot];
|
|
215
216
|
for (const root of roots) {
|
|
216
|
-
produceHitsForRoot(root, baseResults, baseScoreByMemId, seenMemoryIds, hitsByOrigin, {
|
|
217
|
+
produceHitsForRoot(root, baseResults, baseScoreByMemId, seenMemoryIds, seenContent, hitsByOrigin, {
|
|
217
218
|
hops, maxNeighbors, tenantId, includeSuperseded, asOfDate, recallScope,
|
|
218
219
|
});
|
|
219
220
|
}
|
|
@@ -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/handoff.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface HandoffEvidence {
|
|
|
12
12
|
gitRef?: string | null;
|
|
13
13
|
dirtyTree?: boolean | null;
|
|
14
14
|
testStatus?: 'pass' | 'fail' | 'unknown' | null;
|
|
15
|
+
/** 'transcript' when hippo read the handoff off the session's transcript at session end; a later exit may replace it. */
|
|
16
|
+
derivedFrom?: 'transcript';
|
|
15
17
|
}
|
|
16
18
|
/** Narrows an unvalidated value (e.g. CLI input or event content) to a HandoffOutcome. */
|
|
17
19
|
export declare function isHandoffOutcome(v: string | boolean | string[] | null | undefined): v is HandoffOutcome;
|
package/dist/hooks.d.ts
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* sequence, writing both outputs to the log file. The parent returns in
|
|
11
11
|
* <100ms so the TUI teardown can't kill the child before it finishes.
|
|
12
12
|
* - SessionStart: `hippo last-sleep --path <path>` - prints the log
|
|
13
|
-
* written by the previous session's detached worker
|
|
14
|
-
*
|
|
13
|
+
* written by the previous session's detached worker to stderr, which
|
|
14
|
+
* keeps it out of the model's context, and then clears it.
|
|
15
15
|
* Earlier Claude Code forms are detected and migrated automatically:
|
|
16
16
|
* - < 0.20.2: `Stop` hook firing `hippo sleep` on every assistant turn.
|
|
17
17
|
* - < 0.21.0: bare `hippo sleep` in SessionEnd, no `--log-file`.
|
|
@@ -60,7 +60,7 @@ export interface CodexWrapperMetadata {
|
|
|
60
60
|
installedAt: string;
|
|
61
61
|
}
|
|
62
62
|
export interface EnsureCodexWrapperResult {
|
|
63
|
-
status: 'installed' | 'already-installed' | 'not-found';
|
|
63
|
+
status: 'installed' | 'already-installed' | 'not-found' | 'source-checkout';
|
|
64
64
|
metadataPath?: string;
|
|
65
65
|
realCodexPath?: string;
|
|
66
66
|
commandPath?: string;
|
|
@@ -172,7 +172,7 @@ export declare function isCodexWrapperInstalled(): boolean;
|
|
|
172
172
|
* doing it from postinstall or routine commands is a consent violation and
|
|
173
173
|
* reads as binary hijacking to security scanners (issue #133).
|
|
174
174
|
*/
|
|
175
|
-
export declare function repairCodexWrapperIfInstalled(): EnsureCodexWrapperResult;
|
|
175
|
+
export declare function repairCodexWrapperIfInstalled(hippoCliPath?: string): EnsureCodexWrapperResult;
|
|
176
176
|
export declare function resolveCodexSessionTranscript(options: CodexSessionTranscriptOptions): string | null;
|
|
177
177
|
export declare function resolveJsonHookPaths(target: JsonHookTarget): JsonHookPaths;
|
|
178
178
|
export declare function installJsonHooks(target: JsonHookTarget): InstallResult;
|
package/dist/hooks.js
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* sequence, writing both outputs to the log file. The parent returns in
|
|
11
11
|
* <100ms so the TUI teardown can't kill the child before it finishes.
|
|
12
12
|
* - SessionStart: `hippo last-sleep --path <path>` - prints the log
|
|
13
|
-
* written by the previous session's detached worker
|
|
14
|
-
*
|
|
13
|
+
* written by the previous session's detached worker to stderr, which
|
|
14
|
+
* keeps it out of the model's context, and then clears it.
|
|
15
15
|
* Earlier Claude Code forms are detected and migrated automatically:
|
|
16
16
|
* - < 0.20.2: `Stop` hook firing `hippo sleep` on every assistant turn.
|
|
17
17
|
* - < 0.21.0: bare `hippo sleep` in SessionEnd, no `--log-file`.
|
|
@@ -412,10 +412,16 @@ export function isCodexWrapperInstalled() {
|
|
|
412
412
|
* doing it from postinstall or routine commands is a consent violation and
|
|
413
413
|
* reads as binary hijacking to security scanners (issue #133).
|
|
414
414
|
*/
|
|
415
|
-
export function repairCodexWrapperIfInstalled() {
|
|
415
|
+
export function repairCodexWrapperIfInstalled(hippoCliPath = resolveHippoCliPath()) {
|
|
416
416
|
if (readCodexWrapperMetadata() === null) {
|
|
417
417
|
return { status: 'not-found' };
|
|
418
418
|
}
|
|
419
|
+
// A checkout's own `npm install` or CLI would point the user's launcher at a folder that may be deleted.
|
|
420
|
+
// SHORTCUT: any node_modules copy (npx cache, a project dependency) still repairs; record the opted-in CLI path in the metadata to close that.
|
|
421
|
+
const packageDir = path.dirname(path.dirname(hippoCliPath));
|
|
422
|
+
if (path.basename(path.dirname(packageDir)).toLowerCase() !== 'node_modules') {
|
|
423
|
+
return { status: 'source-checkout' };
|
|
424
|
+
}
|
|
419
425
|
return ensureCodexWrapperInstalled();
|
|
420
426
|
}
|
|
421
427
|
function collectFiles(dir) {
|
|
@@ -966,7 +972,7 @@ export function detectInstalledTools() {
|
|
|
966
972
|
{ name: 'opencode', configDir: '~/.config/opencode', detected: exists('.config', 'opencode'), kind: 'plugin', notes: 'installs a TS plugin at ~/.config/opencode/plugins/hippo.ts' },
|
|
967
973
|
{ name: 'openclaw', configDir: '~/.openclaw', detected: exists('.openclaw'), kind: 'plugin', notes: 'install via `openclaw plugins install hippo-memory`' },
|
|
968
974
|
{ 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 .
|
|
975
|
+
{ name: 'cursor', configDir: '~/.cursor', detected: exists('.cursor'), kind: 'markdown-instruction', notes: 'no hook API - patches AGENTS.md in the project' },
|
|
970
976
|
{ name: 'pi', configDir: '~/.pi', detected: exists('.pi'), kind: 'markdown-instruction', notes: 'no hook API - patches AGENTS.md in the project' },
|
|
971
977
|
];
|
|
972
978
|
}
|
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
|
/**
|