@loreai/core 0.17.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bun/agents-file.d.ts +4 -0
- package/dist/bun/agents-file.d.ts.map +1 -1
- package/dist/bun/config.d.ts +2 -0
- package/dist/bun/config.d.ts.map +1 -1
- package/dist/bun/curator.d.ts +45 -0
- package/dist/bun/curator.d.ts.map +1 -1
- package/dist/bun/data-dir.d.ts +18 -0
- package/dist/bun/data-dir.d.ts.map +1 -0
- package/dist/bun/db.d.ts +12 -0
- package/dist/bun/db.d.ts.map +1 -1
- package/dist/bun/distillation.d.ts.map +1 -1
- package/dist/bun/embedding-vendor.d.ts +22 -38
- package/dist/bun/embedding-vendor.d.ts.map +1 -1
- package/dist/bun/embedding-worker-types.d.ts +17 -12
- package/dist/bun/embedding-worker-types.d.ts.map +1 -1
- package/dist/bun/embedding-worker.d.ts +9 -2
- package/dist/bun/embedding-worker.d.ts.map +1 -1
- package/dist/bun/embedding-worker.js +38864 -33
- package/dist/bun/embedding-worker.js.map +4 -4
- package/dist/bun/embedding.d.ts +30 -22
- package/dist/bun/embedding.d.ts.map +1 -1
- package/dist/bun/gradient.d.ts +8 -1
- package/dist/bun/gradient.d.ts.map +1 -1
- package/dist/bun/import/detect.d.ts +14 -0
- package/dist/bun/import/detect.d.ts.map +1 -0
- package/dist/bun/import/extract.d.ts +43 -0
- package/dist/bun/import/extract.d.ts.map +1 -0
- package/dist/bun/import/history.d.ts +40 -0
- package/dist/bun/import/history.d.ts.map +1 -0
- package/dist/bun/import/index.d.ts +17 -0
- package/dist/bun/import/index.d.ts.map +1 -0
- package/dist/bun/import/providers/aider.d.ts +2 -0
- package/dist/bun/import/providers/aider.d.ts.map +1 -0
- package/dist/bun/import/providers/claude-code.d.ts +2 -0
- package/dist/bun/import/providers/claude-code.d.ts.map +1 -0
- package/dist/bun/import/providers/cline.d.ts +2 -0
- package/dist/bun/import/providers/cline.d.ts.map +1 -0
- package/dist/bun/import/providers/codex.d.ts +2 -0
- package/dist/bun/import/providers/codex.d.ts.map +1 -0
- package/dist/bun/import/providers/continue.d.ts +2 -0
- package/dist/bun/import/providers/continue.d.ts.map +1 -0
- package/dist/bun/import/providers/index.d.ts +19 -0
- package/dist/bun/import/providers/index.d.ts.map +1 -0
- package/dist/bun/import/providers/opencode.d.ts +2 -0
- package/dist/bun/import/providers/opencode.d.ts.map +1 -0
- package/dist/bun/import/providers/pi.d.ts +2 -0
- package/dist/bun/import/providers/pi.d.ts.map +1 -0
- package/dist/bun/import/types.d.ts +82 -0
- package/dist/bun/import/types.d.ts.map +1 -0
- package/dist/bun/index.d.ts +4 -1
- package/dist/bun/index.d.ts.map +1 -1
- package/dist/bun/index.js +2217 -224
- package/dist/bun/index.js.map +4 -4
- package/dist/bun/instruction-detect.d.ts +66 -0
- package/dist/bun/instruction-detect.d.ts.map +1 -0
- package/dist/bun/log.d.ts +9 -0
- package/dist/bun/log.d.ts.map +1 -1
- package/dist/bun/ltm.d.ts +40 -0
- package/dist/bun/ltm.d.ts.map +1 -1
- package/dist/bun/pattern-extract.d.ts +7 -0
- package/dist/bun/pattern-extract.d.ts.map +1 -1
- package/dist/bun/prompt.d.ts +1 -1
- package/dist/bun/prompt.d.ts.map +1 -1
- package/dist/bun/recall.d.ts.map +1 -1
- package/dist/bun/search.d.ts +5 -3
- package/dist/bun/search.d.ts.map +1 -1
- package/dist/bun/temporal.d.ts.map +1 -1
- package/dist/bun/types.d.ts +1 -1
- package/dist/node/agents-file.d.ts +4 -0
- package/dist/node/agents-file.d.ts.map +1 -1
- package/dist/node/config.d.ts +2 -0
- package/dist/node/config.d.ts.map +1 -1
- package/dist/node/curator.d.ts +45 -0
- package/dist/node/curator.d.ts.map +1 -1
- package/dist/node/data-dir.d.ts +18 -0
- package/dist/node/data-dir.d.ts.map +1 -0
- package/dist/node/db.d.ts +12 -0
- package/dist/node/db.d.ts.map +1 -1
- package/dist/node/distillation.d.ts.map +1 -1
- package/dist/node/embedding-vendor.d.ts +22 -38
- package/dist/node/embedding-vendor.d.ts.map +1 -1
- package/dist/node/embedding-worker-types.d.ts +17 -12
- package/dist/node/embedding-worker-types.d.ts.map +1 -1
- package/dist/node/embedding-worker.d.ts +9 -2
- package/dist/node/embedding-worker.d.ts.map +1 -1
- package/dist/node/embedding-worker.js +38864 -33
- package/dist/node/embedding-worker.js.map +4 -4
- package/dist/node/embedding.d.ts +30 -22
- package/dist/node/embedding.d.ts.map +1 -1
- package/dist/node/gradient.d.ts +8 -1
- package/dist/node/gradient.d.ts.map +1 -1
- package/dist/node/import/detect.d.ts +14 -0
- package/dist/node/import/detect.d.ts.map +1 -0
- package/dist/node/import/extract.d.ts +43 -0
- package/dist/node/import/extract.d.ts.map +1 -0
- package/dist/node/import/history.d.ts +40 -0
- package/dist/node/import/history.d.ts.map +1 -0
- package/dist/node/import/index.d.ts +17 -0
- package/dist/node/import/index.d.ts.map +1 -0
- package/dist/node/import/providers/aider.d.ts +2 -0
- package/dist/node/import/providers/aider.d.ts.map +1 -0
- package/dist/node/import/providers/claude-code.d.ts +2 -0
- package/dist/node/import/providers/claude-code.d.ts.map +1 -0
- package/dist/node/import/providers/cline.d.ts +2 -0
- package/dist/node/import/providers/cline.d.ts.map +1 -0
- package/dist/node/import/providers/codex.d.ts +2 -0
- package/dist/node/import/providers/codex.d.ts.map +1 -0
- package/dist/node/import/providers/continue.d.ts +2 -0
- package/dist/node/import/providers/continue.d.ts.map +1 -0
- package/dist/node/import/providers/index.d.ts +19 -0
- package/dist/node/import/providers/index.d.ts.map +1 -0
- package/dist/node/import/providers/opencode.d.ts +2 -0
- package/dist/node/import/providers/opencode.d.ts.map +1 -0
- package/dist/node/import/providers/pi.d.ts +2 -0
- package/dist/node/import/providers/pi.d.ts.map +1 -0
- package/dist/node/import/types.d.ts +82 -0
- package/dist/node/import/types.d.ts.map +1 -0
- package/dist/node/index.d.ts +4 -1
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +2217 -224
- package/dist/node/index.js.map +4 -4
- package/dist/node/instruction-detect.d.ts +66 -0
- package/dist/node/instruction-detect.d.ts.map +1 -0
- package/dist/node/log.d.ts +9 -0
- package/dist/node/log.d.ts.map +1 -1
- package/dist/node/ltm.d.ts +40 -0
- package/dist/node/ltm.d.ts.map +1 -1
- package/dist/node/pattern-extract.d.ts +7 -0
- package/dist/node/pattern-extract.d.ts.map +1 -1
- package/dist/node/prompt.d.ts +1 -1
- package/dist/node/prompt.d.ts.map +1 -1
- package/dist/node/recall.d.ts.map +1 -1
- package/dist/node/search.d.ts +5 -3
- package/dist/node/search.d.ts.map +1 -1
- package/dist/node/temporal.d.ts.map +1 -1
- package/dist/node/types.d.ts +1 -1
- package/dist/types/agents-file.d.ts +4 -0
- package/dist/types/agents-file.d.ts.map +1 -1
- package/dist/types/config.d.ts +2 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/curator.d.ts +45 -0
- package/dist/types/curator.d.ts.map +1 -1
- package/dist/types/data-dir.d.ts +18 -0
- package/dist/types/data-dir.d.ts.map +1 -0
- package/dist/types/db.d.ts +12 -0
- package/dist/types/db.d.ts.map +1 -1
- package/dist/types/distillation.d.ts.map +1 -1
- package/dist/types/embedding-vendor.d.ts +22 -38
- package/dist/types/embedding-vendor.d.ts.map +1 -1
- package/dist/types/embedding-worker-types.d.ts +17 -12
- package/dist/types/embedding-worker-types.d.ts.map +1 -1
- package/dist/types/embedding-worker.d.ts +9 -2
- package/dist/types/embedding-worker.d.ts.map +1 -1
- package/dist/types/embedding.d.ts +30 -22
- package/dist/types/embedding.d.ts.map +1 -1
- package/dist/types/gradient.d.ts +8 -1
- package/dist/types/gradient.d.ts.map +1 -1
- package/dist/types/import/detect.d.ts +14 -0
- package/dist/types/import/detect.d.ts.map +1 -0
- package/dist/types/import/extract.d.ts +43 -0
- package/dist/types/import/extract.d.ts.map +1 -0
- package/dist/types/import/history.d.ts +40 -0
- package/dist/types/import/history.d.ts.map +1 -0
- package/dist/types/import/index.d.ts +17 -0
- package/dist/types/import/index.d.ts.map +1 -0
- package/dist/types/import/providers/aider.d.ts +2 -0
- package/dist/types/import/providers/aider.d.ts.map +1 -0
- package/dist/types/import/providers/claude-code.d.ts +2 -0
- package/dist/types/import/providers/claude-code.d.ts.map +1 -0
- package/dist/types/import/providers/cline.d.ts +2 -0
- package/dist/types/import/providers/cline.d.ts.map +1 -0
- package/dist/types/import/providers/codex.d.ts +2 -0
- package/dist/types/import/providers/codex.d.ts.map +1 -0
- package/dist/types/import/providers/continue.d.ts +2 -0
- package/dist/types/import/providers/continue.d.ts.map +1 -0
- package/dist/types/import/providers/index.d.ts +19 -0
- package/dist/types/import/providers/index.d.ts.map +1 -0
- package/dist/types/import/providers/opencode.d.ts +2 -0
- package/dist/types/import/providers/opencode.d.ts.map +1 -0
- package/dist/types/import/providers/pi.d.ts +2 -0
- package/dist/types/import/providers/pi.d.ts.map +1 -0
- package/dist/types/import/types.d.ts +82 -0
- package/dist/types/import/types.d.ts.map +1 -0
- package/dist/types/index.d.ts +4 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/instruction-detect.d.ts +66 -0
- package/dist/types/instruction-detect.d.ts.map +1 -0
- package/dist/types/log.d.ts +9 -0
- package/dist/types/log.d.ts.map +1 -1
- package/dist/types/ltm.d.ts +40 -0
- package/dist/types/ltm.d.ts.map +1 -1
- package/dist/types/pattern-extract.d.ts +7 -0
- package/dist/types/pattern-extract.d.ts.map +1 -1
- package/dist/types/prompt.d.ts +1 -1
- package/dist/types/prompt.d.ts.map +1 -1
- package/dist/types/recall.d.ts.map +1 -1
- package/dist/types/search.d.ts +5 -3
- package/dist/types/search.d.ts.map +1 -1
- package/dist/types/temporal.d.ts.map +1 -1
- package/dist/types/types.d.ts +1 -1
- package/package.json +2 -4
- package/src/agents-file.ts +41 -13
- package/src/config.ts +31 -18
- package/src/curator.ts +111 -75
- package/src/data-dir.ts +76 -0
- package/src/db.ts +110 -11
- package/src/distillation.ts +10 -2
- package/src/embedding-vendor.ts +23 -40
- package/src/embedding-worker-types.ts +19 -11
- package/src/embedding-worker.ts +111 -47
- package/src/embedding.ts +196 -171
- package/src/gradient.ts +9 -1
- package/src/import/detect.ts +37 -0
- package/src/import/extract.ts +137 -0
- package/src/import/history.ts +99 -0
- package/src/import/index.ts +45 -0
- package/src/import/providers/aider.ts +207 -0
- package/src/import/providers/claude-code.ts +339 -0
- package/src/import/providers/cline.ts +324 -0
- package/src/import/providers/codex.ts +369 -0
- package/src/import/providers/continue.ts +304 -0
- package/src/import/providers/index.ts +32 -0
- package/src/import/providers/opencode.ts +272 -0
- package/src/import/providers/pi.ts +332 -0
- package/src/import/types.ts +91 -0
- package/src/index.ts +5 -0
- package/src/instruction-detect.ts +275 -0
- package/src/log.ts +91 -3
- package/src/ltm.ts +316 -3
- package/src/pattern-extract.ts +41 -0
- package/src/prompt.ts +7 -1
- package/src/recall.ts +43 -5
- package/src/search.ts +7 -5
- package/src/temporal.ts +8 -6
- package/src/types.ts +1 -1
package/src/index.ts
CHANGED
|
@@ -18,7 +18,9 @@ export * as embedding from "./embedding";
|
|
|
18
18
|
export * as embeddingVendor from "./embedding-vendor";
|
|
19
19
|
export * as latReader from "./lat-reader";
|
|
20
20
|
export * as patternExtract from "./pattern-extract";
|
|
21
|
+
export * as instructionDetect from "./instruction-detect";
|
|
21
22
|
export * as log from "./log";
|
|
23
|
+
export * as conversationImport from "./import";
|
|
22
24
|
|
|
23
25
|
export {
|
|
24
26
|
runRecall,
|
|
@@ -53,11 +55,14 @@ export type {
|
|
|
53
55
|
} from "./types";
|
|
54
56
|
export { isTextPart, isReasoningPart, isToolPart } from "./types";
|
|
55
57
|
|
|
58
|
+
export { dataDir } from "./data-dir";
|
|
56
59
|
export { load, config, type LoreConfig } from "./config";
|
|
57
60
|
export {
|
|
58
61
|
db,
|
|
59
62
|
dbPath,
|
|
60
63
|
ensureProject,
|
|
64
|
+
getLastImportAt,
|
|
65
|
+
setLastImportAt,
|
|
61
66
|
isFirstRun,
|
|
62
67
|
projectId,
|
|
63
68
|
projectName,
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-session repeated-instruction detection.
|
|
3
|
+
*
|
|
4
|
+
* Identifies instruction-like user messages in the current session
|
|
5
|
+
* and searches for similar instructions in prior sessions using both
|
|
6
|
+
* embedding-based vector search (semantic similarity) and FTS5 (exact terms).
|
|
7
|
+
*
|
|
8
|
+
* When an instruction appears in N+ prior sessions, it's flagged as a
|
|
9
|
+
* strong LTM candidate and formatted as additional context for the curator.
|
|
10
|
+
*
|
|
11
|
+
* This module does NOT auto-create knowledge entries — it augments the
|
|
12
|
+
* curator's input so the LLM can make the final judgment call on whether
|
|
13
|
+
* a repeated instruction warrants a persistent preference entry.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { db, ensureProject } from "./db";
|
|
17
|
+
import * as temporal from "./temporal";
|
|
18
|
+
import * as embedding from "./embedding";
|
|
19
|
+
import { filterTerms, ftsQueryOr, EMPTY_QUERY } from "./search";
|
|
20
|
+
import * as log from "./log";
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Configuration
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/** Minimum distinct prior sessions to consider an instruction "repeated". */
|
|
27
|
+
const DEFAULT_REPETITION_THRESHOLD = 2;
|
|
28
|
+
|
|
29
|
+
/** Minimum cosine similarity for a vector search hit to count. */
|
|
30
|
+
const VECTOR_SIMILARITY_THRESHOLD = 0.5;
|
|
31
|
+
|
|
32
|
+
/** Maximum number of instruction candidates to process per curation run. */
|
|
33
|
+
const MAX_CANDIDATES = 5;
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Instruction candidate extraction
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Patterns that identify instruction-like language in raw user messages.
|
|
41
|
+
* These are intentionally broader than the distillation patterns in
|
|
42
|
+
* pattern-extract.ts because they match the user's raw words, not the
|
|
43
|
+
* observer's normalized phrasing.
|
|
44
|
+
*/
|
|
45
|
+
const INSTRUCTION_PATTERNS: RegExp[] = [
|
|
46
|
+
/\balways\b (.{10,80}?)(?:\.|,|!|$)/gi,
|
|
47
|
+
/\bnever\b (.{10,80}?)(?:\.|,|!|$)/gi,
|
|
48
|
+
/\bmake sure to (.{10,80}?)(?:\.|,|!|$)/gi,
|
|
49
|
+
/\bdon'?t forget (?:to )?(.{10,80}?)(?:\.|,|!|$)/gi,
|
|
50
|
+
/\bplease (?:always |make sure (?:to )?)(.{10,80}?)(?:\.|,|!|$)/gi,
|
|
51
|
+
/\bI (?:want|need|prefer|expect) (?:you to )?(.{10,80}?)(?:\.|,|!|$)/gi,
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
export type InstructionCandidate = {
|
|
55
|
+
/** The matched instruction text. */
|
|
56
|
+
text: string;
|
|
57
|
+
/** Session this candidate was found in. */
|
|
58
|
+
sessionID: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type RepeatedInstruction = {
|
|
62
|
+
/** The instruction text from the current session. */
|
|
63
|
+
instruction: string;
|
|
64
|
+
/** Number of distinct prior sessions containing similar instructions. */
|
|
65
|
+
priorSessionCount: number;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Extract instruction-like phrases from user messages.
|
|
70
|
+
* Scans raw user message content for instruction keywords and returns
|
|
71
|
+
* deduplicated candidates.
|
|
72
|
+
*/
|
|
73
|
+
export function extractInstructionCandidates(
|
|
74
|
+
messages: Array<{ role: string; content: string; session_id: string }>,
|
|
75
|
+
): InstructionCandidate[] {
|
|
76
|
+
const candidates: InstructionCandidate[] = [];
|
|
77
|
+
const seen = new Set<string>();
|
|
78
|
+
|
|
79
|
+
for (const msg of messages) {
|
|
80
|
+
if (msg.role !== "user") continue;
|
|
81
|
+
|
|
82
|
+
for (const pattern of INSTRUCTION_PATTERNS) {
|
|
83
|
+
pattern.lastIndex = 0;
|
|
84
|
+
let match: RegExpMatchArray | null;
|
|
85
|
+
while ((match = pattern.exec(msg.content)) !== null) {
|
|
86
|
+
const text = match[1]?.trim();
|
|
87
|
+
if (!text || text.length < 10) continue;
|
|
88
|
+
|
|
89
|
+
// Dedup by lowercased text within this extraction
|
|
90
|
+
const key = text.toLowerCase();
|
|
91
|
+
if (seen.has(key)) continue;
|
|
92
|
+
seen.add(key);
|
|
93
|
+
|
|
94
|
+
candidates.push({
|
|
95
|
+
text,
|
|
96
|
+
sessionID: msg.session_id,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Cap total candidates to bound search cost
|
|
100
|
+
if (candidates.length >= MAX_CANDIDATES) return candidates;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return candidates;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Cross-session search
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Search for similar instructions in OTHER sessions via distillation
|
|
114
|
+
* embeddings (semantic) and FTS5 (keyword). Returns instructions that
|
|
115
|
+
* appear in >= threshold prior sessions.
|
|
116
|
+
*/
|
|
117
|
+
export async function findRepeatedInstructions(input: {
|
|
118
|
+
projectPath: string;
|
|
119
|
+
currentSessionID: string;
|
|
120
|
+
candidates: InstructionCandidate[];
|
|
121
|
+
threshold?: number;
|
|
122
|
+
}): Promise<RepeatedInstruction[]> {
|
|
123
|
+
const threshold = input.threshold ?? DEFAULT_REPETITION_THRESHOLD;
|
|
124
|
+
if (!input.candidates.length) return [];
|
|
125
|
+
|
|
126
|
+
const pid = ensureProject(input.projectPath);
|
|
127
|
+
|
|
128
|
+
// Batch-embed all candidate texts in a single call (1×RTT instead of N×RTT)
|
|
129
|
+
let candidateEmbeddings: Float32Array[] = [];
|
|
130
|
+
if (embedding.isAvailable()) {
|
|
131
|
+
try {
|
|
132
|
+
candidateEmbeddings = await embedding.embed(
|
|
133
|
+
input.candidates.map((c) => c.text),
|
|
134
|
+
"query",
|
|
135
|
+
);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
log.warn("instruction-detect: batch embedding failed:", err);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const results: RepeatedInstruction[] = [];
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i < input.candidates.length; i++) {
|
|
144
|
+
const candidate = input.candidates[i];
|
|
145
|
+
const sessionIDs = new Set<string>();
|
|
146
|
+
|
|
147
|
+
// Path A: Vector search (when embeddings succeeded)
|
|
148
|
+
if (candidateEmbeddings.length > i) {
|
|
149
|
+
const hits = embedding.vectorSearchAllDistillations(candidateEmbeddings[i], pid, 20);
|
|
150
|
+
for (const hit of hits) {
|
|
151
|
+
if (
|
|
152
|
+
hit.similarity >= VECTOR_SIMILARITY_THRESHOLD &&
|
|
153
|
+
hit.session_id !== input.currentSessionID
|
|
154
|
+
) {
|
|
155
|
+
sessionIDs.add(hit.session_id);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Path B: FTS fallback (always runs to complement vector search)
|
|
161
|
+
const terms = filterTerms(candidate.text);
|
|
162
|
+
if (terms.length >= 2) {
|
|
163
|
+
// Cap at 5 terms to keep queries focused
|
|
164
|
+
const searchText = terms.slice(0, 5).join(" ");
|
|
165
|
+
const ftsHits = searchDistillationsFTS(pid, searchText);
|
|
166
|
+
for (const hit of ftsHits) {
|
|
167
|
+
if (hit.session_id !== input.currentSessionID) {
|
|
168
|
+
sessionIDs.add(hit.session_id);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (sessionIDs.size >= threshold) {
|
|
174
|
+
results.push({
|
|
175
|
+
instruction: candidate.text,
|
|
176
|
+
priorSessionCount: sessionIDs.size,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return results;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Simple FTS5 search over distillation observations, returning session_id
|
|
186
|
+
* for cross-session counting. Searches all distillations (including archived).
|
|
187
|
+
*
|
|
188
|
+
* Uses OR semantics — we want to find any distillation mentioning any of
|
|
189
|
+
* the instruction's key terms, since paraphrased instructions may share
|
|
190
|
+
* only some terms. This is a recall-oriented search (find all possible
|
|
191
|
+
* matches), not a precision-oriented one.
|
|
192
|
+
*
|
|
193
|
+
* @param projectId The resolved project ID (from ensureProject).
|
|
194
|
+
* @param rawQuery Raw search text — will be converted to OR-based FTS expression.
|
|
195
|
+
*/
|
|
196
|
+
function searchDistillationsFTS(
|
|
197
|
+
projectId: string,
|
|
198
|
+
rawQuery: string,
|
|
199
|
+
): Array<{ id: string; session_id: string }> {
|
|
200
|
+
const matchExpr = ftsQueryOr(rawQuery);
|
|
201
|
+
if (matchExpr === EMPTY_QUERY) return [];
|
|
202
|
+
|
|
203
|
+
const sql = `SELECT d.id, d.session_id
|
|
204
|
+
FROM distillation_fts f
|
|
205
|
+
CROSS JOIN distillations d ON d.rowid = f.rowid
|
|
206
|
+
WHERE distillation_fts MATCH ?
|
|
207
|
+
AND d.project_id = ?
|
|
208
|
+
ORDER BY rank LIMIT 30`;
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
return db().query(sql).all(matchExpr, projectId) as Array<{
|
|
212
|
+
id: string;
|
|
213
|
+
session_id: string;
|
|
214
|
+
}>;
|
|
215
|
+
} catch (err) {
|
|
216
|
+
log.warn("instruction-detect: FTS search failed:", err);
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Curator context formatting
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Format repeated instructions as additional context for the curator prompt.
|
|
227
|
+
* Returns empty string if no repeated instructions found.
|
|
228
|
+
*/
|
|
229
|
+
export function formatForCurator(instructions: RepeatedInstruction[]): string {
|
|
230
|
+
if (!instructions.length) return "";
|
|
231
|
+
|
|
232
|
+
const lines = instructions.map(
|
|
233
|
+
(i) =>
|
|
234
|
+
`- "${i.instruction}" (seen in ${i.priorSessionCount} prior session${i.priorSessionCount !== 1 ? "s" : ""})`,
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
return `\n\n---\nCROSS-SESSION REPEATED INSTRUCTIONS (high-confidence preference candidates):\nThe following user instructions have appeared in multiple prior sessions. These are strong candidates for "preference" entries:\n${lines.join("\n")}`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// Full pipeline
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Full detection pipeline: extract candidates from current session →
|
|
246
|
+
* search for repetitions across other sessions → format for curator.
|
|
247
|
+
*
|
|
248
|
+
* Returns a string to append to the curator's user prompt, or "" if
|
|
249
|
+
* nothing was found. Safe to call even when embeddings are unavailable
|
|
250
|
+
* (falls back to FTS-only).
|
|
251
|
+
*/
|
|
252
|
+
export async function detectAndFormat(input: {
|
|
253
|
+
projectPath: string;
|
|
254
|
+
sessionID: string;
|
|
255
|
+
threshold?: number;
|
|
256
|
+
}): Promise<string> {
|
|
257
|
+
const messages = temporal.bySession(input.projectPath, input.sessionID);
|
|
258
|
+
const candidates = extractInstructionCandidates(messages);
|
|
259
|
+
if (!candidates.length) return "";
|
|
260
|
+
|
|
261
|
+
const repeated = await findRepeatedInstructions({
|
|
262
|
+
projectPath: input.projectPath,
|
|
263
|
+
currentSessionID: input.sessionID,
|
|
264
|
+
candidates,
|
|
265
|
+
threshold: input.threshold,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (repeated.length) {
|
|
269
|
+
log.info(
|
|
270
|
+
`instruction-detect: ${repeated.length} repeated instruction(s) found across sessions`,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return formatForCurator(repeated);
|
|
275
|
+
}
|
package/src/log.ts
CHANGED
|
@@ -14,8 +14,19 @@
|
|
|
14
14
|
* When registered, every log call (regardless of `isDebug`) also forwards
|
|
15
15
|
* to the sink. This is used by the gateway to bridge logs → Sentry without
|
|
16
16
|
* adding a Sentry dependency to `@loreai/core`.
|
|
17
|
+
*
|
|
18
|
+
* ## File logging
|
|
19
|
+
*
|
|
20
|
+
* All log calls (info, warn, error) are written to a persistent log file
|
|
21
|
+
* at `~/.local/share/lore/lore.log` regardless of `LORE_DEBUG`.
|
|
22
|
+
* The file is rotated when it exceeds 5 MB (single `.log.1` backup).
|
|
23
|
+
* Use `lore logs` to view; disabled during tests (`NODE_ENV=test`).
|
|
17
24
|
*/
|
|
18
25
|
|
|
26
|
+
import { appendFileSync, renameSync, statSync, mkdirSync } from "node:fs";
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
import { dataDir } from "./data-dir";
|
|
29
|
+
|
|
19
30
|
// ---------------------------------------------------------------------------
|
|
20
31
|
// Sink — optional external log consumer (e.g. Sentry)
|
|
21
32
|
// ---------------------------------------------------------------------------
|
|
@@ -56,6 +67,77 @@ function findError(args: unknown[]): Error | undefined {
|
|
|
56
67
|
return undefined;
|
|
57
68
|
}
|
|
58
69
|
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// File sink — persistent log file, independent of LORE_DEBUG
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
const LOG_MAX_BYTES = 5 * 1024 * 1024; // 5 MB
|
|
75
|
+
const ROTATION_CHECK_INTERVAL = 1000; // check size every N writes
|
|
76
|
+
|
|
77
|
+
let logPath: string | undefined;
|
|
78
|
+
let logPathResolved = false;
|
|
79
|
+
let writeCount = 0;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Resolve the log file path. Returns `undefined` in test environments
|
|
83
|
+
* or if the directory cannot be created.
|
|
84
|
+
*/
|
|
85
|
+
function resolveLogPath(): string | undefined {
|
|
86
|
+
if (process.env.NODE_ENV === "test") return undefined;
|
|
87
|
+
try {
|
|
88
|
+
const dir = dataDir();
|
|
89
|
+
mkdirSync(dir, { recursive: true });
|
|
90
|
+
return join(dir, "lore.log");
|
|
91
|
+
} catch {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Return the resolved log file path (or `undefined` if unavailable). */
|
|
97
|
+
export function logFilePath(): string | undefined {
|
|
98
|
+
if (!logPathResolved) {
|
|
99
|
+
logPath = resolveLogPath();
|
|
100
|
+
logPathResolved = true;
|
|
101
|
+
}
|
|
102
|
+
return logPath;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Rotate the log file if it exceeds the size cap. */
|
|
106
|
+
function maybeRotate(): void {
|
|
107
|
+
if (!logPath) return;
|
|
108
|
+
try {
|
|
109
|
+
const stat = statSync(logPath);
|
|
110
|
+
if (stat.size > LOG_MAX_BYTES) {
|
|
111
|
+
renameSync(logPath, logPath + ".1");
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
// File doesn't exist yet or stat failed — fine
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Append a single log line to the persistent log file. */
|
|
119
|
+
function writeToFile(level: string, message: string): void {
|
|
120
|
+
const path = logFilePath();
|
|
121
|
+
if (!path) return;
|
|
122
|
+
|
|
123
|
+
// Periodic rotation check
|
|
124
|
+
if (++writeCount % ROTATION_CHECK_INTERVAL === 0) {
|
|
125
|
+
maybeRotate();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const ts = new Date().toISOString();
|
|
129
|
+
const tag = level.toUpperCase().padEnd(5);
|
|
130
|
+
// Flatten multiline messages for clean tail -f output
|
|
131
|
+
const flat = message.replace(/\n/g, "\\n");
|
|
132
|
+
const line = `${ts} [${tag}] ${flat}\n`;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
appendFileSync(path, line);
|
|
136
|
+
} catch {
|
|
137
|
+
// Silently degrade — logging failure shouldn't crash the app
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
59
141
|
// ---------------------------------------------------------------------------
|
|
60
142
|
// Public API
|
|
61
143
|
// ---------------------------------------------------------------------------
|
|
@@ -63,19 +145,25 @@ function findError(args: unknown[]): Error | undefined {
|
|
|
63
145
|
/** Log an informational status message. Suppressed unless LORE_DEBUG=1. */
|
|
64
146
|
export function info(...args: unknown[]): void {
|
|
65
147
|
if (isDebug) console.error("[lore]", ...args);
|
|
66
|
-
|
|
148
|
+
const msg = formatArgs(args);
|
|
149
|
+
sink?.info(msg);
|
|
150
|
+
writeToFile("info", msg);
|
|
67
151
|
}
|
|
68
152
|
|
|
69
153
|
/** Log a warning. Suppressed unless LORE_DEBUG=1. */
|
|
70
154
|
export function warn(...args: unknown[]): void {
|
|
71
155
|
if (isDebug) console.error("[lore] WARN:", ...args);
|
|
72
|
-
|
|
156
|
+
const msg = formatArgs(args);
|
|
157
|
+
sink?.warn(msg);
|
|
158
|
+
writeToFile("warn", msg);
|
|
73
159
|
}
|
|
74
160
|
|
|
75
161
|
/** Log an error. Always visible — these indicate real failures. */
|
|
76
162
|
export function error(...args: unknown[]): void {
|
|
77
163
|
console.error("[lore]", ...args);
|
|
78
|
-
|
|
164
|
+
const msg = formatArgs(args);
|
|
165
|
+
sink?.error(msg);
|
|
166
|
+
writeToFile("error", msg);
|
|
79
167
|
|
|
80
168
|
const err = findError(args);
|
|
81
169
|
if (err) sink?.captureException(err);
|