@ijfw/memory-server 1.4.4 → 1.5.1
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/bin/ijfw-memorize +14 -7
- package/fixtures/team/book.json +6 -6
- package/fixtures/team/business.json +146 -20
- package/fixtures/team/content.json +6 -6
- package/fixtures/team/design.json +148 -20
- package/fixtures/team/mixed.json +206 -27
- package/fixtures/team/research.json +146 -20
- package/fixtures/team/software.json +148 -20
- package/fixtures/truncation-corpus/_generate-corpus.js +367 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/snapshots/v-midO-1-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/snapshots/v-midO-2-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/snapshots/v-midO-3-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/snapshots/v-midO-4-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/snapshots/v-midO-5-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/snapshots/v-noEv-1-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/snapshots/v-noEv-2-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/snapshots/v-noEv-3-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/snapshots/v-noEv-4-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/snapshots/v-noEv-5-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/snapshots/v-errT-1-partial.json +11 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/snapshots/v-errT-3-partial.json +11 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/snapshots/v-errT-5-partial.json +11 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/target/.ijfw/state/workflow.json +1 -0
- package/package.json +6 -3
- package/src/active-extension-writer.js +144 -64
- package/src/api-client.js +43 -5
- package/src/audit-roster.js +80 -5
- package/src/blackboard.js +298 -6
- package/src/cli-run.js +33 -5
- package/src/codex-agents.js +96 -5
- package/src/cost/aggregator.js +39 -9
- package/src/cost/pricing.js +57 -0
- package/src/cost/readers/gemini.js +1 -1
- package/src/cross-audit-chunker.js +189 -0
- package/src/cross-dispatcher.js +124 -21
- package/src/cross-orchestrator-cli.js +754 -159
- package/src/cross-orchestrator.js +1065 -17
- package/src/cross-project-search.js +195 -9
- package/src/dashboard-client-waves.html +304 -0
- package/src/dashboard-client.html +5 -1
- package/src/dashboard-server.js +73 -0
- package/src/deploy-alerts.js +150 -0
- package/src/design/iframe-bridge.js +242 -0
- package/src/design-companion.js +144 -0
- package/src/dispatch/checkpoint-cli.js +97 -0
- package/src/dispatch/colon-syntax.js +81 -1
- package/src/dispatch/extension.js +26 -2
- package/src/dispatch/registry-cli.js +4 -1
- package/src/dispatch/wave-cli.js +201 -6
- package/src/dispatch/worktree-cli.js +40 -0
- package/src/dispatch-planner.js +97 -2
- package/src/dream/runner.mjs +47 -11
- package/src/dream/stage-runner.js +40 -0
- package/src/dream/state-file.js +102 -0
- package/src/extension-installer.js +70 -24
- package/src/extension-quota-tracker.js +4 -2
- package/src/extension-registry.js +289 -35
- package/src/feedback-detector.js +26 -0
- package/src/fs-lock.js +259 -7
- package/src/gate-result.js +95 -1
- package/src/hardware-signer.js +4 -2
- package/src/hero-line.js +86 -5
- package/src/intent-router.js +35 -0
- package/src/lib/a11y-contract.js +117 -0
- package/src/lib/atomic-io.js +29 -8
- package/src/lib/cache-keepalive.js +150 -0
- package/src/lib/jsonl-rotation.js +104 -0
- package/src/lib/lighthouse-pillar.js +121 -0
- package/src/lib/llm-call.js +121 -0
- package/src/lib/playwright-baseline.js +205 -0
- package/src/lib/rekor-bridge.js +221 -0
- package/src/lib/repo-map.js +392 -0
- package/src/lib/shasum-verify.js +164 -0
- package/src/lib/sketches-gc.js +132 -0
- package/src/lib/tmp-suffix.js +62 -0
- package/src/lib/ui-review-runner.js +595 -0
- package/src/lib/uispec-drift.js +301 -0
- package/src/lib/uispec-intake.js +381 -0
- package/src/lib/worktree-guards.js +118 -0
- package/src/lib/worktree-recovery.js +100 -0
- package/src/memory/auto-linker.js +267 -0
- package/src/memory/benchmark.js +498 -0
- package/src/memory/dedup.js +126 -0
- package/src/memory/embedding-cache.js +136 -0
- package/src/memory/fact-extractor.js +168 -0
- package/src/memory/fts5.js +65 -1
- package/src/memory/migration-runner.js +6 -1
- package/src/memory/migrations/004-bitemporal.js +91 -0
- package/src/memory/migrations/005-vector-cache.js +61 -0
- package/src/memory/migrations/006-obsidian-graph.js +46 -0
- package/src/memory/migrations/007-skill-telemetry.js +24 -0
- package/src/memory/migrations/008-write-provenance.js +41 -0
- package/src/memory/migrations/009-obsidian-backfill.js +50 -0
- package/src/memory/obsidian-parser.js +152 -0
- package/src/memory/query-dataview.js +86 -0
- package/src/memory/search.js +46 -15
- package/src/memory/temporal.js +529 -0
- package/src/memory/tokenize.js +10 -0
- package/src/memory-facts-handler.js +37 -0
- package/src/memory-feedback.js +260 -2
- package/src/model-refresh.js +292 -0
- package/src/observability/cost-anomaly.js +166 -0
- package/src/observability/evaluator-checkpoint-contract.js +117 -0
- package/src/observability/trace-id.js +163 -0
- package/src/orchestrator/agents-md-blackboard.js +152 -0
- package/src/orchestrator/checkpoint-contract.md +140 -0
- package/src/orchestrator/debug-trident-trigger.js +374 -0
- package/src/orchestrator/debug-trident.js +570 -0
- package/src/orchestrator/merge-block-aware.js +350 -0
- package/src/orchestrator/plan-checker.js +475 -0
- package/src/orchestrator/post-done-runner.js +277 -0
- package/src/orchestrator/review.js +38 -3
- package/src/orchestrator/skill-telemetry-sink.js +29 -0
- package/src/orchestrator/skill-telemetry.js +37 -0
- package/src/orchestrator/state-events.js +459 -0
- package/src/orchestrator/state-sdk.js +1932 -0
- package/src/orchestrator/status-protocol.js +84 -17
- package/src/orchestrator/subagent-telemetry.js +471 -0
- package/src/orchestrator/termination.js +160 -0
- package/src/orchestrator/verification-gate.js +200 -16
- package/src/orchestrator/wave-state.js +332 -23
- package/src/orchestrator/worktree-provision.js +77 -0
- package/src/override-resolver.js +5 -3
- package/src/override-use-registry.js +111 -5
- package/src/receipts.js +36 -4
- package/src/recovery/checkpoint.js +56 -3
- package/src/recovery/code-fixer.js +961 -0
- package/src/recovery/truncation.js +317 -0
- package/src/redactor.js +75 -6
- package/src/runtime-mediator.js +15 -1
- package/src/sanitizer.js +10 -0
- package/src/search-hybrid.js +139 -0
- package/src/server.js +795 -112
- package/src/swarm/worktree.js +27 -4
- package/src/swarm-config.js +102 -17
- package/src/team/domain-templates/book.json +51 -0
- package/src/team/domain-templates/business.json +44 -0
- package/src/team/domain-templates/content.json +50 -0
- package/src/team/domain-templates/design.json +44 -0
- package/src/team/domain-templates/research.json +44 -0
- package/src/team/domain-templates/software.json +40 -0
- package/src/team/generator.js +440 -3
- package/src/team/modify.js +203 -0
- package/src/team/schemas.js +48 -0
- package/src/update-apply.js +19 -3
- package/src/dashboard-charts.js +0 -239
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// IJFW v1.5.1 -- memory migration 009: M1 obsidian-index backfill.
|
|
2
|
+
//
|
|
3
|
+
// Source authority: Trident r5 finding 1.2 (HIGH).
|
|
4
|
+
//
|
|
5
|
+
// Round-4 Fix-1 (commit 3218812) wired M1 (Obsidian wikilink/tag/meta
|
|
6
|
+
// indexing -- indexObsidianRelations) and M2 (A-Mem auto-linking -- autoLink)
|
|
7
|
+
// into the production memory-write path (handleStore, search.js#autoIndex).
|
|
8
|
+
// But the fix is forward-only: every memory_entries row written during
|
|
9
|
+
// v1.5.0 -- when M1/M2 were bypassed -- has empty memory_links / memory_tags
|
|
10
|
+
// / memory_meta. An existing user upgrading to v1.5.1 got auto-linking +
|
|
11
|
+
// wikilink indexing only on NEW entries; their accumulated memory stayed
|
|
12
|
+
// un-indexed.
|
|
13
|
+
//
|
|
14
|
+
// This migration runs a ONE-TIME M1 backfill: it walks every existing
|
|
15
|
+
// memory_entries row and runs indexObsidianRelations over its body. M1 is:
|
|
16
|
+
// - free -- pure markdown parse, zero LLM / network / cost
|
|
17
|
+
// - idempotent -- indexObsidianRelations does DELETE-then-INSERT per id,
|
|
18
|
+
// so re-applying produces identical aux rows
|
|
19
|
+
// which is exactly what makes it safe to run inside a schema migration: it
|
|
20
|
+
// runs once (user_version gates re-application), deterministically, and a
|
|
21
|
+
// crash rolls the whole txn back to user_version 8.
|
|
22
|
+
//
|
|
23
|
+
// M2 (autoLink) is NOT backfilled here -- it makes one LLM call per row and
|
|
24
|
+
// can cost real money over a large memory. M2 backfill is opt-in via the
|
|
25
|
+
// `ijfw memory reindex --m2` CLI verb, which is budget-gated (respects
|
|
26
|
+
// IJFW_AUTOLINK_BUDGET_USD / IJFW_AUTOLINK_OFF / IJFW_AUTOLINK_BACKFILL).
|
|
27
|
+
//
|
|
28
|
+
// Ordering: migration 001 creates memory_entries; migration 006 creates
|
|
29
|
+
// memory_links / memory_tags / memory_meta. Both run before 009, so by the
|
|
30
|
+
// time up() executes the source table and the three aux tables all exist.
|
|
31
|
+
//
|
|
32
|
+
// Crash safety: the migration runner wraps up() in BEGIN IMMEDIATE.
|
|
33
|
+
// backfillObsidianIndex's per-row indexObsidianRelations opens a nested
|
|
34
|
+
// SQLite transaction (savepoint) -- valid inside the outer txn -- and the
|
|
35
|
+
// whole thing rolls back to user_version 8 on any failure.
|
|
36
|
+
|
|
37
|
+
import { backfillObsidianIndex } from '../obsidian-parser.js';
|
|
38
|
+
|
|
39
|
+
export const VERSION = 9;
|
|
40
|
+
export const DESCRIPTION =
|
|
41
|
+
'memory v1.5.1 -- one-time M1 obsidian-index backfill for pre-fix rows (Trident r5 1.2)';
|
|
42
|
+
|
|
43
|
+
export function up(db) {
|
|
44
|
+
// backfillObsidianIndex tolerates a missing memory_entries table (returns
|
|
45
|
+
// zero counts) so a brand-new db that jumps straight to v9 is a clean
|
|
46
|
+
// no-op -- there are no pre-fix rows to backfill on a fresh install.
|
|
47
|
+
backfillObsidianIndex(db);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default { version: VERSION, description: DESCRIPTION, up };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// IJFW v1.5.0 -- Obsidian-grade markdown extractor.
|
|
2
|
+
//
|
|
3
|
+
// Pulls three structured signals out of memory body text:
|
|
4
|
+
// 1. [[wikilinks]] -> links: [{ target, line }]
|
|
5
|
+
// 2. #nested/tags -> tags: [{ path, depth }]
|
|
6
|
+
// 3. [key:: value] (Dataview) -> meta: [{ key, value }]
|
|
7
|
+
//
|
|
8
|
+
// Code fences and inline-code spans are masked before extraction so that
|
|
9
|
+
// example syntax never produces fake edges. DB writes happen in
|
|
10
|
+
// indexObsidianRelations (idempotent re-index: clears prior rows for the
|
|
11
|
+
// memory id before re-inserting).
|
|
12
|
+
|
|
13
|
+
const FENCE_RE = /```[\s\S]*?```/g;
|
|
14
|
+
const INLINE_CODE_RE = /`[^`\n]+`/g;
|
|
15
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- parses developer-authored markdown notes on local disk; negated [^\]\n] classes bound match to one line per token
|
|
16
|
+
const WIKILINK_RE = /\[\[([^\]\n|]+)(?:\|[^\]\n]+)?\]\]/g;
|
|
17
|
+
const TAG_RE = /(?:^|[^\w&])#([\w/-]+)/g;
|
|
18
|
+
const META_RE = /\[([A-Za-z_][\w-]*)::\s*([^\]\n]+?)\]/g;
|
|
19
|
+
|
|
20
|
+
function maskCode(text) {
|
|
21
|
+
return text
|
|
22
|
+
.replace(FENCE_RE, (m) => ' '.repeat(m.length))
|
|
23
|
+
.replace(INLINE_CODE_RE, (m) => ' '.repeat(m.length));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function lineOf(masked, idx) {
|
|
27
|
+
let line = 1;
|
|
28
|
+
for (let i = 0; i < idx; i++) if (masked.charCodeAt(i) === 10) line++;
|
|
29
|
+
return line;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normaliseLinkTarget(raw) {
|
|
33
|
+
return raw
|
|
34
|
+
.trim()
|
|
35
|
+
.toLowerCase()
|
|
36
|
+
.replace(/\s+/g, '-')
|
|
37
|
+
.replace(/[^a-z0-9/_-]/g, '');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function parseObsidian(text) {
|
|
41
|
+
if (typeof text !== 'string' || text.length === 0) {
|
|
42
|
+
return { links: [], tags: [], meta: [] };
|
|
43
|
+
}
|
|
44
|
+
const masked = maskCode(text);
|
|
45
|
+
|
|
46
|
+
const links = [];
|
|
47
|
+
for (const m of masked.matchAll(WIKILINK_RE)) {
|
|
48
|
+
const target = normaliseLinkTarget(m[1]);
|
|
49
|
+
if (target) links.push({ target, line: lineOf(masked, m.index) });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const tagSet = new Map();
|
|
53
|
+
for (const m of masked.matchAll(TAG_RE)) {
|
|
54
|
+
const path = m[1].toLowerCase().replace(/^\/+|\/+$/g, '');
|
|
55
|
+
if (!path) continue;
|
|
56
|
+
const depth = path.split('/').length;
|
|
57
|
+
if (!tagSet.has(path)) tagSet.set(path, { path, depth });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const meta = [];
|
|
61
|
+
for (const m of masked.matchAll(META_RE)) {
|
|
62
|
+
meta.push({ key: m[1].toLowerCase(), value: m[2].trim() });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { links, tags: [...tagSet.values()], meta };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function indexObsidianRelations(db, memoryId, text) {
|
|
69
|
+
const parsed = parseObsidian(text);
|
|
70
|
+
const insLink = db.prepare(
|
|
71
|
+
'INSERT OR IGNORE INTO memory_links (from_id, to_target, line) VALUES (?, ?, ?)',
|
|
72
|
+
);
|
|
73
|
+
const insTag = db.prepare(
|
|
74
|
+
'INSERT OR IGNORE INTO memory_tags (memory_id, tag_path, depth) VALUES (?, ?, ?)',
|
|
75
|
+
);
|
|
76
|
+
const insMeta = db.prepare(
|
|
77
|
+
'INSERT OR IGNORE INTO memory_meta (memory_id, key, value) VALUES (?, ?, ?)',
|
|
78
|
+
);
|
|
79
|
+
const tx = db.transaction(() => {
|
|
80
|
+
db.prepare('DELETE FROM memory_links WHERE from_id=?').run(memoryId);
|
|
81
|
+
db.prepare('DELETE FROM memory_tags WHERE memory_id=?').run(memoryId);
|
|
82
|
+
db.prepare('DELETE FROM memory_meta WHERE memory_id=?').run(memoryId);
|
|
83
|
+
for (const l of parsed.links) insLink.run(memoryId, l.target, l.line);
|
|
84
|
+
for (const t of parsed.tags) insTag.run(memoryId, t.path, t.depth);
|
|
85
|
+
for (const m of parsed.meta) insMeta.run(memoryId, m.key, m.value);
|
|
86
|
+
});
|
|
87
|
+
tx();
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// v1.5.1 R5-1.2 -- one-time M1 backfill for memory written during v1.5.0,
|
|
92
|
+
// when indexObsidianRelations was NOT wired into the production write path.
|
|
93
|
+
// Round-4 Fix-1 (commit 3218812) wired M1+M2 into handleStore/autoIndex but
|
|
94
|
+
// forward-only: rows already in memory_entries have empty memory_links /
|
|
95
|
+
// memory_tags / memory_meta. This walks EVERY row and re-runs M1 over it.
|
|
96
|
+
//
|
|
97
|
+
// Safe to run over everything:
|
|
98
|
+
// - free -- pure markdown parse, zero LLM / network
|
|
99
|
+
// - idempotent-- indexObsidianRelations clears prior aux rows per id before
|
|
100
|
+
// re-inserting, so a re-run produces identical state
|
|
101
|
+
//
|
|
102
|
+
// The walk reads ids in batches so a very large memory_entries doesn't pin
|
|
103
|
+
// the whole table in memory; each row's indexObsidianRelations call carries
|
|
104
|
+
// its own transaction (DELETE-then-INSERT) so a single bad row never aborts
|
|
105
|
+
// the rest of the backfill.
|
|
106
|
+
//
|
|
107
|
+
// Returns { rows, links, tags, meta } -- counts re-indexed across the run.
|
|
108
|
+
export function backfillObsidianIndex(db, opts = {}) {
|
|
109
|
+
if (!db || typeof db.prepare !== 'function') {
|
|
110
|
+
throw new Error('backfillObsidianIndex: db handle is invalid.');
|
|
111
|
+
}
|
|
112
|
+
const batchSize = Math.max(1, opts.batchSize || 500);
|
|
113
|
+
const result = { rows: 0, links: 0, tags: 0, meta: 0, errors: 0 };
|
|
114
|
+
let lastId = 0;
|
|
115
|
+
// eslint-disable-next-line no-constant-condition
|
|
116
|
+
while (true) {
|
|
117
|
+
let batch;
|
|
118
|
+
try {
|
|
119
|
+
batch = db
|
|
120
|
+
.prepare(
|
|
121
|
+
'SELECT id, body FROM memory_entries WHERE id > ? ORDER BY id ASC LIMIT ?',
|
|
122
|
+
)
|
|
123
|
+
.all(lastId, batchSize);
|
|
124
|
+
} catch {
|
|
125
|
+
// memory_entries missing (fresh db before migration 001) -- nothing to do.
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
if (!batch || batch.length === 0) break;
|
|
129
|
+
for (const row of batch) {
|
|
130
|
+
lastId = row.id;
|
|
131
|
+
if (typeof row.body !== 'string' || row.body.length === 0) continue;
|
|
132
|
+
try {
|
|
133
|
+
const parsed = indexObsidianRelations(db, String(row.id), row.body);
|
|
134
|
+
result.rows += 1;
|
|
135
|
+
result.links += parsed.links.length;
|
|
136
|
+
result.tags += parsed.tags.length;
|
|
137
|
+
result.meta += parsed.meta.length;
|
|
138
|
+
} catch (e) {
|
|
139
|
+
result.errors += 1;
|
|
140
|
+
try {
|
|
141
|
+
console.error(
|
|
142
|
+
'[obsidian] backfill failed for id', row.id, ':', e?.message || e,
|
|
143
|
+
);
|
|
144
|
+
} catch { /* never throw out of the backfill */ }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (batch.length < batchSize) break;
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default { parseObsidian, indexObsidianRelations, backfillObsidianIndex };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// IJFW v1.5.0 -- Dataview-grade declarative query for memory_entries.
|
|
2
|
+
//
|
|
3
|
+
// Supported grammar (v1 — intentionally minimal; expand in v1.6+):
|
|
4
|
+
// tag = #path[/sub]* prefix-matches memory_tags.tag_path
|
|
5
|
+
// linked_to = "target" matches memory_links.to_target
|
|
6
|
+
// created_after = <unix-secs> memory_entries.created_at > N
|
|
7
|
+
// created_before = <unix-secs> memory_entries.created_at < N
|
|
8
|
+
//
|
|
9
|
+
// Multiple filters AND together via case-insensitive " and ".
|
|
10
|
+
// Whitespace-tolerant. Unrecognised clauses are silently skipped
|
|
11
|
+
// (caller can inspect parsed.filters for __unrecognised entries).
|
|
12
|
+
|
|
13
|
+
const CLAUSE_AND = /\s+and\s+/i;
|
|
14
|
+
const TAG_RE = /^\s*tag\s*=\s*#?([\w/_-]+)\s*$/i;
|
|
15
|
+
const LINKED_RE = /^\s*linked_to\s*=\s*"([^"\n]+)"\s*$/i;
|
|
16
|
+
const CREATED_AFTER_RE = /^\s*created_after\s*=\s*(\d+)\s*$/i;
|
|
17
|
+
const CREATED_BEFORE_RE = /^\s*created_before\s*=\s*(\d+)\s*$/i;
|
|
18
|
+
|
|
19
|
+
export function parseDataviewQuery(input) {
|
|
20
|
+
const clauses = String(input || '').split(CLAUSE_AND);
|
|
21
|
+
const out = { filters: [] };
|
|
22
|
+
for (const raw of clauses) {
|
|
23
|
+
const c = raw.trim();
|
|
24
|
+
if (!c) continue;
|
|
25
|
+
let m;
|
|
26
|
+
if ((m = c.match(TAG_RE))) {
|
|
27
|
+
out.tag = m[1].toLowerCase().replace(/^\/+|\/+$/g, '');
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if ((m = c.match(LINKED_RE))) {
|
|
31
|
+
out.filters.push({ field: 'linked_to', op: '=', value: m[1] });
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if ((m = c.match(CREATED_AFTER_RE))) {
|
|
35
|
+
out.filters.push({ field: 'created_after', op: '=', value: Number(m[1]) });
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if ((m = c.match(CREATED_BEFORE_RE))) {
|
|
39
|
+
out.filters.push({ field: 'created_before', op: '=', value: Number(m[1]) });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
out.filters.push({ field: '__unrecognised', op: '=', value: c });
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function runDataviewQuery(db, parsed) {
|
|
48
|
+
const where = [];
|
|
49
|
+
const params = [];
|
|
50
|
+
let join = '';
|
|
51
|
+
if (parsed.tag) {
|
|
52
|
+
join += ' JOIN memory_tags t ON t.memory_id = e.id ';
|
|
53
|
+
where.push('(t.tag_path = ? OR t.tag_path LIKE ?)');
|
|
54
|
+
params.push(parsed.tag, `${parsed.tag}/%`);
|
|
55
|
+
}
|
|
56
|
+
for (const f of parsed.filters) {
|
|
57
|
+
switch (f.field) {
|
|
58
|
+
case 'linked_to':
|
|
59
|
+
join += ' JOIN memory_links l ON l.from_id = e.id ';
|
|
60
|
+
where.push('l.to_target = ?');
|
|
61
|
+
params.push(f.value);
|
|
62
|
+
break;
|
|
63
|
+
case 'created_after':
|
|
64
|
+
where.push('e.created_at > ?');
|
|
65
|
+
params.push(f.value);
|
|
66
|
+
break;
|
|
67
|
+
case 'created_before':
|
|
68
|
+
where.push('e.created_at < ?');
|
|
69
|
+
params.push(f.value);
|
|
70
|
+
break;
|
|
71
|
+
default: /* __unrecognised silently skipped */
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Note: memory_entries (migration 001 schema) has columns:
|
|
75
|
+
// id (INTEGER PK), body, source, session_id, created_at. No title column.
|
|
76
|
+
// memory_links/_tags/_meta store TEXT memory_id — SQLite coerces across
|
|
77
|
+
// the type boundary at JOIN time (SQLite is permissive without strict FKs).
|
|
78
|
+
const sql = `SELECT DISTINCT e.id, e.body, e.source, e.created_at
|
|
79
|
+
FROM memory_entries e ${join}
|
|
80
|
+
${where.length ? 'WHERE ' + where.join(' AND ') : ''}
|
|
81
|
+
ORDER BY e.created_at DESC`;
|
|
82
|
+
const rows = db.prepare(sql).all(...params);
|
|
83
|
+
return { rows, rowCount: rows.length, parsed, sql };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default { parseDataviewQuery, runDataviewQuery };
|
package/src/memory/search.js
CHANGED
|
@@ -31,6 +31,13 @@ import { readFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
|
31
31
|
import { dirname, join, resolve, normalize, isAbsolute } from 'node:path';
|
|
32
32
|
|
|
33
33
|
import { expandQuery } from '../compute/synonyms.js';
|
|
34
|
+
import { loadMigrations } from './migration-runner.js';
|
|
35
|
+
// v1.5.1 R4-H2 — auto-index rows must flow through indexEntry so the
|
|
36
|
+
// v1.5.0 memory-moat (M1 Obsidian indexing + M2 A-Mem auto-linking) fires
|
|
37
|
+
// for warm-tier rebuilds, not just the benchmark harness. obsidian-parser
|
|
38
|
+
// is imported directly so M1 runs synchronously inside the same txn batch.
|
|
39
|
+
import { indexObsidianRelations } from './obsidian-parser.js';
|
|
40
|
+
import { autoLink } from './auto-linker.js';
|
|
34
41
|
|
|
35
42
|
const MAX_RESULTS = 50;
|
|
36
43
|
const SNIPPET_HALF = 60;
|
|
@@ -50,20 +57,16 @@ try {
|
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
// Resolve migration modules synchronously at module load via top-level
|
|
53
|
-
// await. Replayed inside searchMemory's sync path.
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
{ version: v2.VERSION, description: v2.DESCRIPTION, up: v2.up },
|
|
64
|
-
{ version: v3.VERSION, description: v3.DESCRIPTION, up: v3.up },
|
|
65
|
-
].sort((a, b) => a.version - b.version);
|
|
66
|
-
}
|
|
60
|
+
// await. Replayed inside searchMemory's sync path.
|
|
61
|
+
//
|
|
62
|
+
// v1.5.1 W3.B: discovery is delegated to memory/migration-runner.js
|
|
63
|
+
// (readdirSync over ./migrations/) so a single source of truth governs
|
|
64
|
+
// which migrations search.js knows about. Prior to this, search.js
|
|
65
|
+
// carried its OWN hardcoded list -- the v1.5.0 INT.7 hotfix patched
|
|
66
|
+
// the symptom (006/007/008 missing); this kills the dual-registry bug
|
|
67
|
+
// class outright. Drop migration 009 into ./migrations/, and search.js
|
|
68
|
+
// will pick it up automatically.
|
|
69
|
+
const MEMORY_MIGRATIONS = await loadMigrations();
|
|
67
70
|
|
|
68
71
|
function highestMigrationVersion() {
|
|
69
72
|
if (!MEMORY_MIGRATIONS.length) return 0;
|
|
@@ -210,12 +213,20 @@ function runMemoryMigrationsSync(db, currentVersion, targetVersion) {
|
|
|
210
213
|
|
|
211
214
|
function autoIndex(db, files) {
|
|
212
215
|
let n = 0;
|
|
216
|
+
// v1.5.1 R4-H2 — capture the rowid of every inserted entry so the
|
|
217
|
+
// memory-moat aux indexing (M1 Obsidian relations, M2 auto-link) can run
|
|
218
|
+
// over the warm-tier rebuild, not just the benchmark harness. The bulk
|
|
219
|
+
// INSERT stays in one transaction for FTS write performance; M1/M2 run
|
|
220
|
+
// AFTER commit so a parse/link failure can never abort the rebuild.
|
|
221
|
+
const inserted = [];
|
|
213
222
|
const txfn = db.transaction((batch) => {
|
|
214
223
|
const stmt = db.prepare(
|
|
215
224
|
'INSERT INTO memory_entries (body, source, session_id, created_at) VALUES (?, ?, ?, ?)'
|
|
216
225
|
);
|
|
217
226
|
for (const item of batch) {
|
|
218
|
-
stmt.run(item.body, item.source, null, item.created_at);
|
|
227
|
+
const info = stmt.run(item.body, item.source, null, item.created_at);
|
|
228
|
+
const id = info && info.lastInsertRowid != null ? Number(info.lastInsertRowid) : null;
|
|
229
|
+
inserted.push({ id, body: item.body });
|
|
219
230
|
n++;
|
|
220
231
|
}
|
|
221
232
|
});
|
|
@@ -232,6 +243,26 @@ function autoIndex(db, files) {
|
|
|
232
243
|
}
|
|
233
244
|
if (batch.length === 0) return 0;
|
|
234
245
|
try { txfn.immediate(batch); } catch { /* one bad batch should not abort the search */ }
|
|
246
|
+
|
|
247
|
+
// v1.5.1 R4-H2 — M1: Obsidian wikilink/tag/meta indexing into
|
|
248
|
+
// memory_links/_tags/_meta. Synchronous + idempotent (indexObsidianRelations
|
|
249
|
+
// clears prior rows for the id before re-inserting). Best-effort: a missing
|
|
250
|
+
// migration-006 schema or a parse failure must never break the search path.
|
|
251
|
+
// M2: A-Mem auto-linking — fire-and-forget, env-gated (IJFW_AUTOLINK_OFF),
|
|
252
|
+
// budget-capped (IJFW_AUTOLINK_BUDGET_USD); returns skipped cleanly when no
|
|
253
|
+
// API key, so a bulk rebuild without credentials does no LLM work.
|
|
254
|
+
for (const row of inserted) {
|
|
255
|
+
if (row.id == null) continue;
|
|
256
|
+
try {
|
|
257
|
+
indexObsidianRelations(db, String(row.id), row.body);
|
|
258
|
+
} catch { /* M1 best-effort -- never abort the search */ }
|
|
259
|
+
try {
|
|
260
|
+
const p = autoLink(db, { id: row.id, body: row.body });
|
|
261
|
+
if (p && typeof p.catch === 'function') p.catch(() => {});
|
|
262
|
+
// expose for tests that want deterministic completion
|
|
263
|
+
autoIndex.__lastAutoLinkPromise = p;
|
|
264
|
+
} catch { /* M2 dispatch best-effort */ }
|
|
265
|
+
}
|
|
235
266
|
return n;
|
|
236
267
|
}
|
|
237
268
|
|