recallnest 2.2.1 → 2.5.4
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/CHANGELOG.md +150 -0
- package/README.md +46 -35
- package/README_CN.md +41 -26
- package/assets/ui/app.js +1031 -0
- package/assets/ui/index.html +224 -0
- package/assets/ui/styles.css +781 -0
- package/config.json.example +4 -1
- package/connectors/examples/email-adapter.ts +73 -0
- package/connectors/examples/logs-adapter.ts +103 -0
- package/connectors/examples/rss-adapter.ts +95 -0
- package/docs/api-reference.md +1291 -0
- package/docs/architecture.md +168 -0
- package/docs/connector-spec.md +126 -0
- package/eval/cases-canary.json +58 -0
- package/eval/cases-scope-robustness.json +278 -0
- package/eval/cases.json +117 -20
- package/package.json +22 -1
- package/src/__tests__/access-tracker-novelty.test.ts +238 -0
- package/src/__tests__/activity-counter.test.ts +125 -0
- package/src/__tests__/adaptive-fidelity.test.ts +102 -0
- package/src/__tests__/admission-control.test.ts +206 -0
- package/src/__tests__/anchor-generator.test.ts +67 -0
- package/src/__tests__/auto-consolidation.test.ts +168 -0
- package/src/__tests__/auto-recall.test.ts +131 -0
- package/src/__tests__/babel-integration.test.ts +144 -0
- package/src/__tests__/batch-dedup.test.ts +65 -0
- package/src/__tests__/canary-eval.test.ts +114 -0
- package/src/__tests__/capture-engine.test.ts +1238 -0
- package/src/__tests__/capture-heuristic.test.ts +171 -0
- package/src/__tests__/cascade-forget.test.ts +210 -0
- package/src/__tests__/category-min-scores.test.ts +33 -0
- package/src/__tests__/cc8-cc1-essential-context.test.ts +298 -0
- package/src/__tests__/circuit-breaker.test.ts +99 -0
- package/src/__tests__/cluster-consolidation.test.ts +782 -0
- package/src/__tests__/confidence-metadata.test.ts +125 -0
- package/src/__tests__/confidence-tracker.test.ts +261 -0
- package/src/__tests__/conflict-advisor.test.ts +206 -0
- package/src/__tests__/conflict-escalation.test.ts +149 -0
- package/src/__tests__/conflict-lifecycle.test.ts +83 -0
- package/src/__tests__/conflict-output.test.ts +85 -0
- package/src/__tests__/conflict-store.test.ts +374 -0
- package/src/__tests__/connector-v1.test.ts +181 -0
- package/src/__tests__/consolidation-engine.test.ts +333 -0
- package/src/__tests__/constructive-retrieval.test.ts +509 -0
- package/src/__tests__/context-collapse-renderer.test.ts +214 -0
- package/src/__tests__/context-composer-pins.test.ts +64 -0
- package/src/__tests__/context-composer-task-selection.test.ts +24 -0
- package/src/__tests__/context-reconstructor.test.ts +65 -0
- package/src/__tests__/context-renderer.test.ts +91 -0
- package/src/__tests__/continuity-eval.test.ts +275 -0
- package/src/__tests__/continuity-schema.test.ts +97 -0
- package/src/__tests__/contracts.test.ts +216 -0
- package/src/__tests__/conversation-importer.test.ts +692 -0
- package/src/__tests__/core-recall-eval.test.ts +235 -0
- package/src/__tests__/core-summary.test.ts +109 -0
- package/src/__tests__/data-checkup.test.ts +276 -0
- package/src/__tests__/decay-exemption.test.ts +84 -0
- package/src/__tests__/dedup-actions.test.ts +58 -0
- package/src/__tests__/dedup-guard.test.ts +39 -0
- package/src/__tests__/deterministic-id.test.ts +39 -0
- package/src/__tests__/distill-lock.test.ts +305 -0
- package/src/__tests__/distill-session-argshape.test.ts +79 -0
- package/src/__tests__/doctor.test.ts +319 -0
- package/src/__tests__/dream-pipeline.test.ts +321 -0
- package/src/__tests__/drill-down.test.ts +108 -0
- package/src/__tests__/embedder.test.ts +122 -0
- package/src/__tests__/emotion-decay.test.ts +46 -0
- package/src/__tests__/emotion-detector.test.ts +56 -0
- package/src/__tests__/emotion-phase1-integration.test.ts +153 -0
- package/src/__tests__/emotion-retrieval.test.ts +56 -0
- package/src/__tests__/entity-resolver.test.ts +58 -0
- package/src/__tests__/env-config.test.ts +151 -0
- package/src/__tests__/error-signature-boost.test.ts +87 -0
- package/src/__tests__/error-signature.test.ts +95 -0
- package/src/__tests__/error-taxonomy.test.ts +129 -0
- package/src/__tests__/ethics-layer.test.ts +476 -0
- package/src/__tests__/event-segmenter.test.ts +153 -0
- package/src/__tests__/feature-flags-integration.test.ts +54 -0
- package/src/__tests__/frequency-tracker.test.ts +113 -0
- package/src/__tests__/graph-export.test.ts +489 -0
- package/src/__tests__/hotness-blend.test.ts +76 -0
- package/src/__tests__/ingest-chunking.test.ts +44 -0
- package/src/__tests__/ingest.test.ts +360 -0
- package/src/__tests__/interference-detector.test.ts +203 -0
- package/src/__tests__/kg-extractor.test.ts +192 -0
- package/src/__tests__/kg-store.test.ts +300 -0
- package/src/__tests__/llm-consolidation.test.ts +226 -0
- package/src/__tests__/maintenance-locks.test.ts +174 -0
- package/src/__tests__/mcp-efficiency.test.ts +288 -0
- package/src/__tests__/mcp-related-scopes.test.ts +151 -0
- package/src/__tests__/memory-boundaries.test.ts +250 -0
- package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +345 -0
- package/src/__tests__/memory-evolution.test.ts +271 -0
- package/src/__tests__/memory-health-rebalance.test.ts +144 -0
- package/src/__tests__/memory-lint.test.ts +421 -0
- package/src/__tests__/memory-output.test.ts +215 -0
- package/src/__tests__/memory-promotion.test.ts +315 -0
- package/src/__tests__/migrate-dedup-ids.test.ts +65 -0
- package/src/__tests__/multi-vector.test.ts +242 -0
- package/src/__tests__/narrative-architecture.test.ts +456 -0
- package/src/__tests__/noise-filter.test.ts +205 -0
- package/src/__tests__/obsidian-connector.test.ts +363 -0
- package/src/__tests__/omitted-reporting.test.ts +98 -0
- package/src/__tests__/pending-review.test.ts +77 -0
- package/src/__tests__/pii-redact.test.ts +314 -0
- package/src/__tests__/post-retrieval-filter.test.ts +178 -0
- package/src/__tests__/ppr-traversal.test.ts +256 -0
- package/src/__tests__/prediction-engine.test.ts +349 -0
- package/src/__tests__/predictive-prospective-memory.test.ts +416 -0
- package/src/__tests__/preference-guard-smoke.test.ts +211 -0
- package/src/__tests__/preference-matcher.test.ts +230 -0
- package/src/__tests__/preference-slots.test.ts +205 -0
- package/src/__tests__/prospective-memory.test.ts +283 -0
- package/src/__tests__/query-entity-detector.test.ts +171 -0
- package/src/__tests__/query-expander-alias.test.ts +146 -0
- package/src/__tests__/query-expander.test.ts +34 -0
- package/src/__tests__/recall-governor.test.ts +227 -0
- package/src/__tests__/recall-mode.test.ts +64 -0
- package/src/__tests__/result-synthesizer.test.ts +198 -0
- package/src/__tests__/resume-context.test.ts +4898 -0
- package/src/__tests__/retrieval-trace.test.ts +213 -0
- package/src/__tests__/retriever-boundary-weight.test.ts +136 -0
- package/src/__tests__/retroactive-boost.test.ts +223 -0
- package/src/__tests__/salience-scoring.test.ts +106 -0
- package/src/__tests__/scope-inventory.test.ts +200 -0
- package/src/__tests__/scope-policy.test.ts +69 -0
- package/src/__tests__/scope-suggester.test.ts +80 -0
- package/src/__tests__/session-checkpoint.test.ts +291 -0
- package/src/__tests__/session-distiller.test.ts +640 -0
- package/src/__tests__/session-gc.test.ts +215 -0
- package/src/__tests__/skill-engine.test.ts +552 -0
- package/src/__tests__/skill-promotion.test.ts +601 -0
- package/src/__tests__/skill-verifier.test.ts +122 -0
- package/src/__tests__/source-heartbeat.test.ts +250 -0
- package/src/__tests__/store-patch-metadata.test.ts +205 -0
- package/src/__tests__/store-scope-audit.test.ts +81 -0
- package/src/__tests__/store-upsert.test.ts +184 -0
- package/src/__tests__/supersede-chain.test.ts +154 -0
- package/src/__tests__/temporal-parser.test.ts +241 -0
- package/src/__tests__/temporal-validity.test.ts +160 -0
- package/src/__tests__/term-registry.test.ts +121 -0
- package/src/__tests__/tool-error-surfacing.test.ts +98 -0
- package/src/__tests__/tool-output-compressor.test.ts +160 -0
- package/src/__tests__/topic-tag.test.ts +113 -0
- package/src/__tests__/trusted-memory.test.ts +839 -0
- package/src/__tests__/usage-tracker.test.ts +287 -0
- package/src/__tests__/version-manager.test.ts +213 -0
- package/src/__tests__/workflow-observation-skill-outcome.test.ts +352 -0
- package/src/__tests__/workflow-observation.test.ts +433 -0
- package/src/__tests__/write-verifier.test.ts +122 -0
- package/src/access-tracker.ts +38 -32
- package/src/activity-counter.ts +46 -18
- package/src/aliases.ts +133 -0
- package/src/api-server.ts +27 -7
- package/src/audit-log.ts +4 -2
- package/src/auto-consolidation.ts +8 -3
- package/src/auto-gc.ts +85 -21
- package/src/capture-engine.ts +54 -21
- package/src/cli.ts +199 -37
- package/src/confidence-tracker.ts +36 -18
- package/src/connector-types.ts +57 -0
- package/src/consolidation-engine.ts +109 -11
- package/src/context-composer.ts +8 -1
- package/src/conversation-importer.ts +25 -1
- package/src/data-checkup.ts +126 -7
- package/src/distill-lock.ts +194 -15
- package/src/doctor.ts +38 -2
- package/src/dream-pipeline.ts +118 -34
- package/src/env-config.ts +77 -0
- package/src/error-signature.ts +153 -0
- package/src/error-taxonomy.ts +115 -0
- package/src/eval.ts +251 -6
- package/src/idempotency.ts +8 -0
- package/src/ingest.ts +234 -10
- package/src/kg-extractor.ts +6 -2
- package/src/kg-store.ts +236 -44
- package/src/llm-consolidation.ts +2 -1
- package/src/mcp-server.ts +85 -1799
- package/src/mcp-tool-deps.ts +48 -0
- package/src/mcp-tools-advanced.ts +941 -0
- package/src/mcp-tools-core.ts +633 -0
- package/src/mcp-tools-governance.ts +475 -0
- package/src/memory-boundaries.ts +3 -1
- package/src/memory-evolution.ts +73 -1
- package/src/memory-lint.ts +183 -10
- package/src/memory-output.ts +114 -3
- package/src/memory-promotion.ts +246 -0
- package/src/memory-schema.ts +25 -2
- package/src/memory-store-port.ts +43 -0
- package/src/multi-vector.ts +2 -1
- package/src/narrative-schema.ts +2 -1
- package/src/noise-filter.ts +57 -6
- package/src/obsidian-connector.ts +282 -0
- package/src/pii-detector.ts +157 -5
- package/src/query-expander.ts +90 -9
- package/src/result-synthesizer.ts +2 -1
- package/src/retention-policy.ts +2 -1
- package/src/retriever.ts +259 -139
- package/src/runtime-config.ts +8 -1
- package/src/scope-suggester.ts +113 -0
- package/src/session-distiller.ts +23 -2
- package/src/session-engine.ts +2 -1
- package/src/session-schema.ts +3 -2
- package/src/session-store.ts +5 -0
- package/src/skill-engine.ts +78 -0
- package/src/skill-promotion.ts +247 -47
- package/src/skill-schema.ts +16 -3
- package/src/skill-verifier.ts +152 -0
- package/src/source-heartbeat.ts +108 -0
- package/src/store.ts +267 -38
- package/src/tool-output-compressor.ts +3 -1
- package/src/ui-server.ts +2 -1
- package/src/usage-tracker.ts +242 -0
- package/src/version-manager.ts +3 -2
- package/src/workflow-observation-engine.ts +2 -1
- package/src/workflow-observation-schema.ts +2 -1
- package/src/workflow-observation-store.ts +6 -2
- package/.env +0 -4
- package/CLAUDE.md +0 -46
- package/config.json +0 -55
- package/daily-recall.sh +0 -110
- package/logs/daily-recall.error.log +0 -0
- package/logs/daily-recall.log +0 -30
- package/logs/ingest-2026-04-11.log +0 -110
- package/logs/ingest-2026-04-12.log +0 -90
- package/logs/launchagent-stderr.log +0 -0
- package/logs/launchagent-stdout.log +0 -0
package/src/ingest.ts
CHANGED
|
@@ -12,11 +12,16 @@ import { readFileSync, readdirSync, existsSync, statSync, writeFileSync } from "
|
|
|
12
12
|
import { join, basename, resolve, dirname } from "node:path";
|
|
13
13
|
import { homedir } from "node:os";
|
|
14
14
|
import { detectLang, tokenizeFts } from "./language-hook.js";
|
|
15
|
+
import { redactSecrets } from "./pii-detector.js";
|
|
15
16
|
import type { MemoryStore, MemoryEntry } from "./store.js";
|
|
16
17
|
import type { Embedder } from "./embedder.js";
|
|
17
18
|
import { chunkDocument, type ChunkerConfig } from "./chunker.js";
|
|
19
|
+
import type { ConnectorOutputV1, ConnectorRecord } from "./connector-types.js";
|
|
20
|
+
import { isConnectorOutputV1 } from "./connector-types.js";
|
|
21
|
+
import { isObsidianVault, scanVault } from "./obsidian-connector.js";
|
|
18
22
|
import { segmentEvents, type EventSegmenterConfig, DEFAULT_EVENT_SEGMENTER_CONFIG } from "./event-segmenter.js";
|
|
19
23
|
import { isProcessed, markProcessed } from "./tracker.js";
|
|
24
|
+
import * as envConfig from "./env-config.js";
|
|
20
25
|
import type { LLMClient, SmartExtraction } from "./llm-client.js";
|
|
21
26
|
import { resolveIngestBoundary } from "./memory-boundaries.js";
|
|
22
27
|
import { isNoise } from "./noise-filter.js";
|
|
@@ -504,7 +509,7 @@ async function smartExtractBatch(
|
|
|
504
509
|
* Tier 3.1: Check if core summary generation is enabled.
|
|
505
510
|
*/
|
|
506
511
|
function isCoreSummaryEnabled(): boolean {
|
|
507
|
-
return
|
|
512
|
+
return envConfig.coreSummary();
|
|
508
513
|
}
|
|
509
514
|
|
|
510
515
|
/**
|
|
@@ -622,7 +627,8 @@ export async function drainPendingQueue(
|
|
|
622
627
|
let processed = 0, errors = 0;
|
|
623
628
|
for (let i = 0; i < pending.length; i += 20) {
|
|
624
629
|
const batch = pending.slice(i, i + 20);
|
|
625
|
-
|
|
630
|
+
// F-3b: scrub before the LLM extraction call and downstream embedding
|
|
631
|
+
const texts = batch.map(c => redactSecrets(c.text).text);
|
|
626
632
|
const extractions = await smartExtractBatch(texts, llm);
|
|
627
633
|
const embeddingTexts = extractions.map(e => e.l1 || e.l0);
|
|
628
634
|
const vectors = await embedder.embedBatchPassage(embeddingTexts);
|
|
@@ -857,7 +863,7 @@ function filterNoiseTurns(turns: ConversationTurn[]): ConversationTurn[] {
|
|
|
857
863
|
return turns.filter((turn) => !isNoise(turn.text));
|
|
858
864
|
}
|
|
859
865
|
|
|
860
|
-
function groupTurnsIntoChunks(turns: ConversationTurn[]): Array<{
|
|
866
|
+
export function groupTurnsIntoChunks(turns: ConversationTurn[]): Array<{
|
|
861
867
|
text: string;
|
|
862
868
|
timestamp: string;
|
|
863
869
|
sessionId: string;
|
|
@@ -930,7 +936,18 @@ function groupTurnsIntoChunks(turns: ConversationTurn[]): Array<{
|
|
|
930
936
|
}
|
|
931
937
|
}
|
|
932
938
|
|
|
933
|
-
|
|
939
|
+
// P1 defence-in-depth: re-filter AFTER splitting. Long turns pass the
|
|
940
|
+
// turn-level filterNoiseTurns gate, but chunking can peel their tail into
|
|
941
|
+
// severed context-JSON fragments that the turn-level check never saw.
|
|
942
|
+
// F-3b: scrub secrets at the chunk boundary — this is the shared text
|
|
943
|
+
// finalization point for all session-ingest paths (CC/Codex/Gemini), before
|
|
944
|
+
// any chunk reaches embedding, LLM extraction, or storage.
|
|
945
|
+
return chunks
|
|
946
|
+
.filter((c) => !isNoise(c.text))
|
|
947
|
+
.map((c) => {
|
|
948
|
+
const r = redactSecrets(c.text);
|
|
949
|
+
return r.redacted > 0 ? { ...c, text: r.text } : c;
|
|
950
|
+
});
|
|
934
951
|
}
|
|
935
952
|
|
|
936
953
|
// ============================================================================
|
|
@@ -1024,9 +1041,14 @@ export async function ingestCodexSessions(
|
|
|
1024
1041
|
errors: [],
|
|
1025
1042
|
};
|
|
1026
1043
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1044
|
+
// 同时扫 sessions(活跃)+ archived_sessions(App 内归档会把文件移到这里,否则会漏)
|
|
1045
|
+
const baseDirs = [
|
|
1046
|
+
expandHome("~/.codex/sessions"),
|
|
1047
|
+
expandHome("~/.codex/archived_sessions"),
|
|
1048
|
+
];
|
|
1049
|
+
const existingDirs = baseDirs.filter((d) => existsSync(d));
|
|
1050
|
+
if (existingDirs.length === 0) {
|
|
1051
|
+
result.errors.push(`Codex sessions directory not found: ${baseDirs.join(", ")}`);
|
|
1030
1052
|
return result;
|
|
1031
1053
|
}
|
|
1032
1054
|
|
|
@@ -1045,7 +1067,7 @@ export async function ingestCodexSessions(
|
|
|
1045
1067
|
}
|
|
1046
1068
|
}
|
|
1047
1069
|
}
|
|
1048
|
-
walk(
|
|
1070
|
+
for (const dir of existingDirs) walk(dir);
|
|
1049
1071
|
allFiles.sort();
|
|
1050
1072
|
|
|
1051
1073
|
// --recent: only keep files modified within N hours
|
|
@@ -1647,7 +1669,8 @@ export async function ingestMarkdownFiles(
|
|
|
1647
1669
|
continue;
|
|
1648
1670
|
}
|
|
1649
1671
|
|
|
1650
|
-
|
|
1672
|
+
// F-3b: scrub before embedding and storage
|
|
1673
|
+
const texts = sections.map((s) => redactSecrets(s.text).text);
|
|
1651
1674
|
const batchSize = 32;
|
|
1652
1675
|
let fileChunks = 0;
|
|
1653
1676
|
|
|
@@ -1785,7 +1808,8 @@ export async function ingestGenericText(
|
|
|
1785
1808
|
|
|
1786
1809
|
const chunkResult = chunkDocument(textToChunk, CONVERSATION_CHUNK_CONFIG);
|
|
1787
1810
|
|
|
1788
|
-
|
|
1811
|
+
// F-3b: scrub before embedding and storage
|
|
1812
|
+
const texts = chunkResult.chunks.map((t) => redactSecrets(t).text);
|
|
1789
1813
|
const batchSize = 32;
|
|
1790
1814
|
|
|
1791
1815
|
for (let i = 0; i < texts.length; i += batchSize) {
|
|
@@ -1850,3 +1874,203 @@ export async function ingestGenericText(
|
|
|
1850
1874
|
|
|
1851
1875
|
return result;
|
|
1852
1876
|
}
|
|
1877
|
+
|
|
1878
|
+
// ============================================================================
|
|
1879
|
+
// Connector-v1 Ingest (GB-2)
|
|
1880
|
+
// ============================================================================
|
|
1881
|
+
|
|
1882
|
+
/**
|
|
1883
|
+
* Ingest a connector-v1 formatted JSON payload.
|
|
1884
|
+
*
|
|
1885
|
+
* External connectors (Obsidian, Email, RSS, etc.) produce a standard
|
|
1886
|
+
* ConnectorOutputV1 JSON → this function consumes it through the existing
|
|
1887
|
+
* dedup / smartExtract / buildIngestedEntry pipeline.
|
|
1888
|
+
*
|
|
1889
|
+
* contentHash on each record enables incremental sync: if a record with the
|
|
1890
|
+
* same hash is already in the store, it is skipped without embedding.
|
|
1891
|
+
*/
|
|
1892
|
+
export async function ingestConnectorFile(
|
|
1893
|
+
store: MemoryStore,
|
|
1894
|
+
embedder: Embedder,
|
|
1895
|
+
content: string,
|
|
1896
|
+
options: { verbose?: boolean; noDedup?: boolean; llm?: LLMClient | null } = {},
|
|
1897
|
+
): Promise<IngestResult> {
|
|
1898
|
+
const parsed = JSON.parse(content);
|
|
1899
|
+
if (!isConnectorOutputV1(parsed)) {
|
|
1900
|
+
return {
|
|
1901
|
+
source: "connector",
|
|
1902
|
+
filesProcessed: 0,
|
|
1903
|
+
chunksIngested: 0,
|
|
1904
|
+
chunksSkipped: 0,
|
|
1905
|
+
chunksDeduped: 0,
|
|
1906
|
+
dedupReasonCounts: createDedupReasonCounts(),
|
|
1907
|
+
errors: ["Invalid connector-v1 format: missing version/source/scope/records"],
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
const connectorData = parsed as ConnectorOutputV1;
|
|
1912
|
+
const { source, scope, records } = connectorData;
|
|
1913
|
+
|
|
1914
|
+
const result: IngestResult = {
|
|
1915
|
+
source: `connector:${source}`,
|
|
1916
|
+
filesProcessed: 1,
|
|
1917
|
+
chunksIngested: 0,
|
|
1918
|
+
chunksSkipped: 0,
|
|
1919
|
+
chunksDeduped: 0,
|
|
1920
|
+
dedupReasonCounts: createDedupReasonCounts(),
|
|
1921
|
+
errors: [],
|
|
1922
|
+
};
|
|
1923
|
+
|
|
1924
|
+
if (records.length === 0) return result;
|
|
1925
|
+
|
|
1926
|
+
const batchSize = 32;
|
|
1927
|
+
|
|
1928
|
+
for (let i = 0; i < records.length; i += batchSize) {
|
|
1929
|
+
const batch = records.slice(i, i + batchSize);
|
|
1930
|
+
const texts = batch.map((r: ConnectorRecord) =>
|
|
1931
|
+
r.title ? `[${r.title}] ${r.text}` : r.text,
|
|
1932
|
+
);
|
|
1933
|
+
|
|
1934
|
+
try {
|
|
1935
|
+
const vectors = await embedder.embedBatchPassage(texts);
|
|
1936
|
+
|
|
1937
|
+
// --- dedup pass ---
|
|
1938
|
+
const dedupedTexts: string[] = [];
|
|
1939
|
+
const dedupedVectors: number[][] = [];
|
|
1940
|
+
const dedupedRecords: ConnectorRecord[] = [];
|
|
1941
|
+
|
|
1942
|
+
for (let j = 0; j < batch.length; j++) {
|
|
1943
|
+
const vector = vectors[j];
|
|
1944
|
+
if (!vector || vector.length === 0) {
|
|
1945
|
+
result.chunksSkipped++;
|
|
1946
|
+
continue;
|
|
1947
|
+
}
|
|
1948
|
+
if (!options.noDedup) {
|
|
1949
|
+
const decision = await dedupCheck(store, vector, texts[j], options.llm);
|
|
1950
|
+
recordDedupDecision(result, decision);
|
|
1951
|
+
if (decision.secondaryDeletes?.length) {
|
|
1952
|
+
await executeSecondaryDeletes(store, decision.secondaryDeletes);
|
|
1953
|
+
}
|
|
1954
|
+
if (decision.action === "skip") continue;
|
|
1955
|
+
}
|
|
1956
|
+
dedupedTexts.push(texts[j]);
|
|
1957
|
+
dedupedVectors.push(vector);
|
|
1958
|
+
dedupedRecords.push(batch[j]);
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
if (dedupedTexts.length === 0) continue;
|
|
1962
|
+
|
|
1963
|
+
// --- extract + build entries ---
|
|
1964
|
+
const extractions = await smartExtractBatch(dedupedTexts, options.llm);
|
|
1965
|
+
const coreSummaries = await generateCoreSummaries(dedupedTexts, options.llm);
|
|
1966
|
+
const toStore: Array<{
|
|
1967
|
+
text: string;
|
|
1968
|
+
vector: number[];
|
|
1969
|
+
category: string;
|
|
1970
|
+
scope: string;
|
|
1971
|
+
importance: number;
|
|
1972
|
+
metadata: string;
|
|
1973
|
+
}> = [];
|
|
1974
|
+
|
|
1975
|
+
for (let j = 0; j < dedupedTexts.length; j++) {
|
|
1976
|
+
const record = dedupedRecords[j];
|
|
1977
|
+
const entry = buildIngestedEntry({
|
|
1978
|
+
source: `connector:${source}`,
|
|
1979
|
+
scope,
|
|
1980
|
+
text: dedupedTexts[j],
|
|
1981
|
+
vector: dedupedVectors[j],
|
|
1982
|
+
extraction: extractions[j],
|
|
1983
|
+
file: record.id,
|
|
1984
|
+
heading: record.title,
|
|
1985
|
+
coreSummary: coreSummaries[j],
|
|
1986
|
+
});
|
|
1987
|
+
|
|
1988
|
+
// Merge connector-specific metadata
|
|
1989
|
+
const meta = JSON.parse(entry.metadata);
|
|
1990
|
+
if (record.tags?.length) meta.connectorTags = record.tags;
|
|
1991
|
+
if (record.contentHash) meta.contentHash = record.contentHash;
|
|
1992
|
+
if (record.sourceMetadata) meta.sourceMetadata = record.sourceMetadata;
|
|
1993
|
+
entry.metadata = JSON.stringify(meta);
|
|
1994
|
+
|
|
1995
|
+
// Respect categoryHint / importanceHint from connector
|
|
1996
|
+
if (record.categoryHint) {
|
|
1997
|
+
entry.category = record.categoryHint;
|
|
1998
|
+
}
|
|
1999
|
+
if (record.importanceHint !== undefined) {
|
|
2000
|
+
entry.importance = record.importanceHint;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
toStore.push(entry);
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
if (toStore.length > 0) {
|
|
2007
|
+
await store.storeBatch(toStore);
|
|
2008
|
+
result.chunksIngested += toStore.length;
|
|
2009
|
+
}
|
|
2010
|
+
} catch (err: unknown) {
|
|
2011
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2012
|
+
result.errors.push(`Connector batch error: ${msg}`);
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
if (options.verbose) {
|
|
2017
|
+
console.log(` connector:${source}: ${records.length} records → ${result.chunksIngested} stored`);
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
// GB-3: Write source heartbeat after connector ingest
|
|
2021
|
+
try {
|
|
2022
|
+
const { writeHeartbeat } = await import("./source-heartbeat.js");
|
|
2023
|
+
writeHeartbeat(source, result.chunksIngested, result.errors);
|
|
2024
|
+
} catch {
|
|
2025
|
+
// Heartbeat write is best-effort — don't break ingest on failure
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
return result;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
// ============================================================================
|
|
2032
|
+
// Obsidian Vault Ingest (GB-1)
|
|
2033
|
+
// ============================================================================
|
|
2034
|
+
|
|
2035
|
+
/**
|
|
2036
|
+
* Scan an Obsidian vault and ingest its markdown files through the connector-v1 pipeline.
|
|
2037
|
+
*
|
|
2038
|
+
* Wrapper that:
|
|
2039
|
+
* 1. Validates the path is an Obsidian vault (.obsidian/ exists)
|
|
2040
|
+
* 2. Calls scanVault() to produce a ConnectorOutputV1 payload
|
|
2041
|
+
* 3. Feeds the payload to ingestConnectorFile()
|
|
2042
|
+
*/
|
|
2043
|
+
export async function ingestObsidianVault(
|
|
2044
|
+
store: MemoryStore,
|
|
2045
|
+
embedder: Embedder,
|
|
2046
|
+
vaultPath: string,
|
|
2047
|
+
options: {
|
|
2048
|
+
verbose?: boolean;
|
|
2049
|
+
noDedup?: boolean;
|
|
2050
|
+
llm?: LLMClient | null;
|
|
2051
|
+
scopePrefix?: string;
|
|
2052
|
+
} = {},
|
|
2053
|
+
): Promise<IngestResult> {
|
|
2054
|
+
const resolved = expandHome(vaultPath);
|
|
2055
|
+
|
|
2056
|
+
if (!isObsidianVault(resolved)) {
|
|
2057
|
+
return {
|
|
2058
|
+
source: "obsidian",
|
|
2059
|
+
filesProcessed: 0,
|
|
2060
|
+
chunksIngested: 0,
|
|
2061
|
+
chunksSkipped: 0,
|
|
2062
|
+
chunksDeduped: 0,
|
|
2063
|
+
dedupReasonCounts: createDedupReasonCounts(),
|
|
2064
|
+
errors: [`Not an Obsidian vault (missing .obsidian/ directory): ${resolved}`],
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
const output = scanVault(resolved, { scopePrefix: options.scopePrefix });
|
|
2069
|
+
const content = JSON.stringify(output);
|
|
2070
|
+
|
|
2071
|
+
if (options.verbose) {
|
|
2072
|
+
console.log(` obsidian: scanned ${output.records.length} notes from ${resolved}`);
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
return ingestConnectorFile(store, embedder, content, options);
|
|
2076
|
+
}
|
package/src/kg-extractor.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { LLMClient } from "./llm-client.js";
|
|
|
8
8
|
import type { KGStore } from "./kg-store.js";
|
|
9
9
|
import { logInfo, logWarn } from "./stderr-log.js";
|
|
10
10
|
import { detectLang, getKgPromptHook } from "./language-hook.js";
|
|
11
|
+
import * as envConfig from "./env-config.js";
|
|
11
12
|
|
|
12
13
|
// ============================================================================
|
|
13
14
|
// Types
|
|
@@ -39,7 +40,7 @@ interface LlmExtractionResponse {
|
|
|
39
40
|
|
|
40
41
|
/** Check if KG mode is enabled (env var) */
|
|
41
42
|
export function isKGModeEnabled(): boolean {
|
|
42
|
-
return
|
|
43
|
+
return envConfig.kgMode();
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
// ============================================================================
|
|
@@ -163,8 +164,11 @@ export class KGExtractor {
|
|
|
163
164
|
prompt,
|
|
164
165
|
);
|
|
165
166
|
} catch (err) {
|
|
167
|
+
// Transport/API failure is NOT "zero triples" — propagate so callers can
|
|
168
|
+
// distinguish (backfill must not journal it as processed). The capture
|
|
169
|
+
// path's fire-and-forget .catch() keeps memory writes unaffected.
|
|
166
170
|
logWarn(`[KG] LLM extraction failed: ${String(err)}`);
|
|
167
|
-
|
|
171
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
168
172
|
}
|
|
169
173
|
|
|
170
174
|
if (!response?.triples || !Array.isArray(response.triples)) {
|