@jcode.labs/ragmir 2.18.0 → 2.19.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/README.md CHANGED
@@ -5,21 +5,18 @@
5
5
  [![Node.js](https://img.shields.io/node/v/@jcode.labs/ragmir)](https://www.npmjs.com/package/@jcode.labs/ragmir)
6
6
  [![MIT](https://img.shields.io/npm/l/@jcode.labs/ragmir)](https://github.com/jcode-works/jcode-ragmir/blob/main/LICENSE)
7
7
 
8
- Confidential local RAG for coding agents and Node.js applications.
9
-
10
- *Stop sending confidential documents directly to the cloud.*
11
-
12
- Core indexes the project files you select and retrieves bounded, cited evidence offline by default.
13
- It does not upload the corpus, call an LLM, or open an HTTP port.
8
+ Confidential local RAG for coding agents and Node.js applications. Core indexes the project files
9
+ you choose and retrieves bounded, cited evidence offline by default. It uploads no corpus, calls no
10
+ LLM, and opens no HTTP port.
14
11
 
15
12
  [Project overview](https://github.com/jcode-works/jcode-ragmir#readme) ·
16
- [CLI reference](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/cli-reference.md) ·
17
- [API reference](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/api-reference.md) ·
13
+ [CLI](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/cli-reference.md) ·
14
+ [API](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/api-reference.md) ·
18
15
  [Agent integration](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/agent-integration.md)
19
16
 
20
17
  ## Install and retrieve
21
18
 
22
- Requires Node.js 22 or later. Releases are gated on Linux x64 and macOS ARM64 with Node.js 22.
19
+ Requires Node.js 22 or later.
23
20
 
24
21
  ```bash
25
22
  npm install --save-dev @jcode.labs/ragmir
@@ -29,55 +26,22 @@ npx rgr ingest
29
26
  npx rgr search "Which decision changed the rollout?"
30
27
  ```
31
28
 
32
- Setup keeps configuration, the index, helpers, reports, and metadata-only access logs under ignored
33
- `.ragmir/` state. Ingestion is incremental, resumable, and serialized across local writer
34
- processes. Source, chunk, vector, concurrency, and batch windows are bounded, with durable progress
35
- committed per file. Completed ingestion refreshes incomplete full-text coverage and runs bounded
36
- LanceDB maintenance when mutation or fragment thresholds are reached. Sidecar replacement flushes
37
- before rename, synchronizes the storage directory where supported, and retains one validated
38
- activation manifest for explicit recovery diagnostics. Inspect maintenance with
39
- `npx rgr storage optimize --dry-run --json`. Exact vector search is retained below 100,000 rows;
40
- larger tables use a benchmarked IVF-PQ policy only with complete coverage. Use
41
- `npx rgr search "query" --exact-vector-search` to bypass ANN for diagnostics. Rebuild generations
42
- use private reader leases and
43
- bounded retention; inspect them with `npx rgr storage generations --json` before running
44
- `npx rgr storage gc --dry-run --json`. Failed changed files keep explicitly stale
45
- last-known-good rows by default; repair or source deletion reconciles them deterministically.
46
- Returned citations expose only verifiable coordinates: source lines for line-preserving text, PDF
47
- pages, PPTX slides, XLSX sheets and cells, and EPUB spine positions.
48
- Run `npx rgr audit --unsupported` to compare the selected files with the index and see what was
49
- skipped.
50
- Custom redaction expressions are screened for catastrophic backtracking. `npx rgr security-audit`
51
- also reports permissions, ignored/tracked state for every private path, and local extractor
52
- authority.
53
- `npx rgr status` and normal `npx rgr doctor` consume compact manifest health without opening chunk
54
- storage. Use `npx rgr doctor --deep` or `npx rgr audit` for explicit O(corpus) live diagnostics.
29
+ Generated configuration, indexes, helpers, reports, and metadata-only logs stay under ignored
30
+ `.ragmir/` state. Ingestion is incremental, resumable, bounded by source, chunk, vector, file, batch,
31
+ and concurrency windows, and serialized across local writer processes.
55
32
 
56
33
  ## Choose an interface
57
34
 
58
- | Interface | Use |
35
+ | Interface | Use it for |
59
36
  | --- | --- |
60
- | `rgr` CLI | Setup, ingest, search, audit, and JSON automation |
61
- | TypeScript API | Typed retrieval in a script or stateful Node.js worker |
62
- | Local stdio MCP | Bounded context for compatible coding agents |
63
-
64
- The default `local-hash` provider needs no model download. It is lexical/hash retrieval, not
65
- semantic embeddings. Enable local Transformers.js embeddings explicitly with `rgr setup --semantic`
66
- and rebuild the index. Bundled profiles use pinned model commits, and setup stores the resolved
67
- artifact digest so persisted compatibility identifies the exact weights. `local-hash` does not
68
- resolve Transformers.js, ONNX Runtime, or Sharp.
69
-
70
- Hybrid retrieval has stable source-and-chunk tie-breaks, two-pass recall-safe diversification, and
71
- provider-aware abstention. Pass `explain: true` to inspect vector and lexical contributions, FTS or
72
- fallback activation and reason, candidate and coverage budgets, plus the active ranking-policy
73
- fingerprint. Search, embedding, and ingestion use independent bounded queues per project root;
74
- overload and queue deadlines return stable retryable errors, and explained searches include queue
75
- wait.
76
- An empty result means every candidate failed the evidence threshold.
77
-
78
- `research` uses language-aware query expansion with deterministic cross-query ranking. It reads
79
- manifest health by default and exposes explicit timeout, code-file, code-byte, concurrency, and
80
- result budgets. Request `fullAudit: true` only when the report also needs a fresh source inventory.
37
+ | `rgr` CLI | Setup, ingest, search, audit, maintenance, and JSON automation |
38
+ | TypeScript API | Typed retrieval in scripts and long-running Node.js workers |
39
+ | Local stdio MCP | Bounded, read-focused context for compatible agents |
40
+
41
+ The default `local-hash` provider works offline with no model download. Enable semantic
42
+ Transformers.js embeddings explicitly with `rgr setup --semantic`, then rebuild. Core remains
43
+ retrieval-only in both modes. Use `explain: true` or `--explain` to inspect lexical and vector
44
+ contributions, fallback decisions, budgets, queue wait, and the active ranking policy.
81
45
 
82
46
  ## TypeScript API
83
47
 
@@ -92,50 +56,54 @@ try {
92
56
  timeoutMs: 10_000,
93
57
  })
94
58
 
95
- for (const result of results) {
96
- console.log(result.citation, result.text)
97
- }
59
+ for (const result of results) console.log(result.citation, result.text)
98
60
  } finally {
99
61
  await ragmir.close()
100
62
  }
101
63
  ```
102
64
 
103
- Reuse one client per project root in long-running processes. It caches one immutable read snapshot
104
- until atomic generation replacement, closes retired table handles after their last active reader,
105
- and `close()` flushes metadata-only access logs before releasing model ownership. The final owner
106
- disposes its Transformers pipeline only after active inference finishes. Top-level
107
- `ingest`, `search`, `ask`, and `research` functions remain available for one-shot scripts. `ask`
108
- returns cited retrieval context, not a generated answer.
109
-
110
- Core also exports setup, preview, audit, evaluation, source-routing, privacy, OCR, MCP, and agent
111
- integration helpers. See the [complete API reference](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/api-reference.md)
112
- for public options and result types.
113
-
114
- Optional PDF OCR processes only blank pages. The generated setup batches bounded page groups and
115
- stores private content-addressed page results, so interrupted scans resume only missing pages and
116
- warm output remains identical without launching OCR subprocesses.
117
-
118
- ## Optional packages and boundaries
119
-
120
- - [`@jcode.labs/ragmir-chat`](https://www.npmjs.com/package/@jcode.labs/ragmir-chat) adds local GGUF
121
- answer generation from retrieved passages.
65
+ Reuse one client per project root in a long-running process. It owns the local connection, read
66
+ snapshot, active operation lifecycle, metadata-only log flush, and optional embedding-model lease.
67
+ Top-level `ingest`, `search`, `ask`, and `research` functions remain available for one-shot scripts.
68
+ `ask` returns cited context, not generated prose.
69
+
70
+ ## Guarantees and boundaries
71
+
72
+ - Citations use source lines only for line-preserving text, plus PDF pages, PPTX slides, XLSX
73
+ sheets and cells, and EPUB spine positions.
74
+ - Rebuilds activate only after row and manifest validation. Interrupted rebuilds leave the previous
75
+ searchable generation active.
76
+ - Exact vector search remains the policy below 100,000 rows. Larger tables use quality-gated IVF-PQ
77
+ with complete coverage and an exact diagnostic mode.
78
+ - Search, embedding, and ingestion use independent bounded queues. Overload and queue deadlines are
79
+ stable retryable errors.
80
+ - Optional OCR processes only blank PDF pages through a configured local executable and private
81
+ resumable cache.
82
+ - `rgr status` and normal `rgr doctor` read compact manifest health. Use `rgr doctor --deep` or
83
+ `rgr audit` for a live source inventory.
84
+ - `rgr security-audit` checks permissions, Git ignore coverage, tracked private paths, redaction,
85
+ and local extractor authority.
86
+ - `rgr team snapshot` and `rgr team compare` explain configuration and per-file drift without
87
+ sharing source text or guessing which copy is authoritative.
88
+ - After a package update, `rgr upgrade --check` previews compatibility; `rgr upgrade` safely stages
89
+ any required rebuild without deleting the active index first.
90
+
91
+ The [CLI reference](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/cli-reference.md),
92
+ [API reference](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/api-reference.md), and
93
+ [configuration guide](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/configuration.md)
94
+ hold the complete options and operational detail.
95
+
96
+ ## Optional packages
97
+
98
+ - [`@jcode.labs/ragmir-chat`](https://www.npmjs.com/package/@jcode.labs/ragmir-chat) adds cited
99
+ answer generation with a verified local GGUF profile.
122
100
  - [`@jcode.labs/ragmir-tts`](https://www.npmjs.com/package/@jcode.labs/ragmir-tts) renders reviewed
123
101
  text as local audio or explicit online speech.
124
102
 
125
- Core installs and starts without either add-on. A hosted agent receives only the passages your
126
- integration gives it, under that provider's data policy. Use a local consumer when no passage may
127
- leave the workstation. Ragmir has no hosted storage or cloud-sync layer; teams synchronize source
128
- files separately and build one local index per developer. Keep shared directory or glob contracts
129
- stable, align the Ragmir version, configuration, embedding provider, and model, then compare
130
- `corpusFingerprint` from `rgr status --json` after both indexes are ready with no missing or stale
131
- files. Matching values identify the same indexed relative paths and source bytes. Use
132
- `sourceFingerprintMode: "strict"` when a synchronization tool can preserve metadata while replacing
133
- content, and never share an actively written `.ragmir/storage/` directory. Older manifests expose a
134
- `null` fingerprint until the next successful ingestion.
135
-
136
- Read the [configuration guide](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/configuration.md),
137
- [security hardening guide](https://github.com/jcode-works/jcode-ragmir/blob/main/SECURITY-HARDENING.md),
138
- and [troubleshooting guide](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/troubleshooting.md)
139
- before indexing sensitive material.
103
+ Core installs and starts without either add-on. A hosted agent receives only passages your
104
+ integration sends under that provider's data policy; use a local consumer when no passage may leave
105
+ the workstation. Teams synchronize their source folder and tracked configuration, then build one
106
+ local index per developer. Exchange an authorized metadata-only snapshot and run `rgr team compare`
107
+ to resolve exact drift. See the [team workflow](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/agent-integration.md#team-knowledge-bases).
140
108
 
141
109
  Ragmir Core is open source under the [MIT License](https://github.com/jcode-works/jcode-ragmir/blob/main/LICENSE).
package/dist/cli.js CHANGED
@@ -33,12 +33,15 @@ import { bundledSkillPath, installAgentSkills, installSkill, parseAgentTargets,
33
33
  import { addSourceEntries, listSourceEntries } from "./sources.js";
34
34
  import { optimizeStorage } from "./storage-maintenance.js";
35
35
  import { readIndexManifestHeader } from "./store.js";
36
+ import { compareTeamSnapshots, createTeamSnapshot, readTeamSnapshot, writeTeamSnapshot, } from "./team-diagnostics.js";
37
+ import { inspectUpgrade, upgradeProject } from "./upgrade.js";
36
38
  import { VERSION } from "./version.js";
37
39
  const SEARCH_TEXT_PREVIEW_LENGTH = 900;
38
40
  const CHAT_PACKAGE_NAME = "@jcode.labs/ragmir-chat";
39
41
  const TTS_PACKAGE_NAME = "@jcode.labs/ragmir-tts";
40
42
  const DEPRECATED_CLI_NAMES = new Set(["ragmir", "kb"]);
41
43
  const PUBLIC_CLI_NAME = "rgr";
44
+ const TEAM_DIFF_PREVIEW_LIMIT = 20;
42
45
  const program = new Command();
43
46
  const deprecatedCliName = deprecatedCliInvocation();
44
47
  if (deprecatedCliName !== null) {
@@ -192,6 +195,53 @@ program
192
195
  }
193
196
  printDoctor(report);
194
197
  });
198
+ program
199
+ .command("upgrade")
200
+ .description("Check upgrade compatibility, refresh agent helpers, and safely rebuild an incompatible index.")
201
+ .option("--check", "Inspect the upgrade without changing local state.")
202
+ .option("--force-agent-skills", "Replace same-name native skills after reviewing that they can be overwritten.")
203
+ .option("--json", "Print machine-readable JSON.")
204
+ .action(async (options, command) => {
205
+ const cwd = projectRoot(command);
206
+ if (options.check) {
207
+ const inspection = await inspectUpgrade(cwd);
208
+ if (options.json) {
209
+ console.log(JSON.stringify(inspection, null, 2));
210
+ return;
211
+ }
212
+ console.log(`status=${inspection.status}`);
213
+ console.log(`runtimeRagmirVersion=${inspection.runtimeRagmirVersion}`);
214
+ console.log(`indexedWithRagmirVersion=${inspection.indexedWithRagmirVersion ?? "unavailable"}`);
215
+ console.log(`ready=${inspection.ready}`);
216
+ console.log(`safeActivation=${inspection.safeActivation}`);
217
+ if (inspection.reason) {
218
+ console.log(`reason=${inspection.reason}`);
219
+ }
220
+ return;
221
+ }
222
+ const result = await upgradeProject({
223
+ cwd,
224
+ forceAgentSkills: options.forceAgentSkills === true,
225
+ });
226
+ if (options.json) {
227
+ console.log(JSON.stringify(result, null, 2));
228
+ return;
229
+ }
230
+ console.log(`status=${result.status}`);
231
+ console.log(`runtimeRagmirVersion=${result.runtimeRagmirVersion}`);
232
+ console.log(`indexedWithRagmirVersion=${result.indexedWithRagmirVersion ?? "unavailable"}`);
233
+ console.log(`ready=${result.ready}`);
234
+ console.log(`safeActivation=${result.safeActivation}`);
235
+ console.log(`action=${result.action}`);
236
+ console.log(`previousIndexedWithRagmirVersion=${result.previousIndexedWithRagmirVersion ?? "unavailable"}`);
237
+ console.log(`previousIndexKeptUntilActivation=${result.previousIndexKeptUntilActivation}`);
238
+ if (result.reason) {
239
+ console.log(`reason=${result.reason}`);
240
+ }
241
+ if (!result.ready) {
242
+ process.exitCode = 1;
243
+ }
244
+ });
195
245
  program
196
246
  .command("setup")
197
247
  .description("Initialize Ragmir, expose native agent skills, generate MCP helpers, and ingest when safe.")
@@ -740,6 +790,55 @@ program
740
790
  console.log(`${marker} ${base.id} format=${format} root=${base.projectRoot}`);
741
791
  }
742
792
  });
793
+ const teamCommand = program
794
+ .command("team")
795
+ .description("Export and compare privacy-bounded team index snapshots.");
796
+ teamCommand
797
+ .command("snapshot")
798
+ .description("Export relative paths, checksums, configuration, readiness, and corpus identity.")
799
+ .requiredOption("--output <path>", "Snapshot file to create or replace.")
800
+ .option("--label <label>", "Human-readable environment label.", "local")
801
+ .option("--json", "Print machine-readable JSON.")
802
+ .action(async (options, command) => {
803
+ const cwd = projectRoot(command);
804
+ const snapshot = await createTeamSnapshot({ cwd, label: options.label });
805
+ const outputPath = await writeTeamSnapshot(snapshot, path.resolve(cwd, options.output));
806
+ if (options.json) {
807
+ console.log(JSON.stringify({ outputPath, snapshot }, null, 2));
808
+ return;
809
+ }
810
+ console.log(pc.green("Team snapshot ready."));
811
+ console.log(`outputPath=${outputPath}`);
812
+ console.log(`label=${snapshot.label}`);
813
+ console.log(`ready=${snapshot.ready}`);
814
+ console.log(`corpusFingerprint=${snapshot.corpus.fingerprint ?? "unavailable"}`);
815
+ console.log(`indexedFiles=${snapshot.corpus.indexedFiles}`);
816
+ console.log(`notice=${snapshot.notice}`);
817
+ });
818
+ teamCommand
819
+ .command("compare")
820
+ .description("Compare the current local index with a peer snapshot and print actionable drift.")
821
+ .argument("<snapshot>", "Peer snapshot created by `rgr team snapshot`.")
822
+ .option("--local-label <label>", "Human-readable label for this environment.", "local")
823
+ .option("--json", "Print machine-readable JSON.")
824
+ .option("--strict", "Exit with code 1 unless both indexes are synchronized.")
825
+ .action(async (snapshotPath, options, command) => {
826
+ const cwd = projectRoot(command);
827
+ const [local, peer] = await Promise.all([
828
+ createTeamSnapshot({ cwd, label: options.localLabel }),
829
+ readTeamSnapshot(path.resolve(cwd, snapshotPath)),
830
+ ]);
831
+ const comparison = compareTeamSnapshots(local, peer);
832
+ if (options.json) {
833
+ console.log(JSON.stringify(comparison, null, 2));
834
+ }
835
+ else {
836
+ printTeamComparison(comparison);
837
+ }
838
+ if (options.strict && !comparison.synchronized) {
839
+ process.exitCode = 1;
840
+ }
841
+ });
743
842
  program
744
843
  .command("status")
745
844
  .description("Show active configuration, readiness, corpus fingerprint, and index counts.")
@@ -1484,6 +1583,60 @@ function researchEvidencePreview(evidence) {
1484
1583
  }
1485
1584
  return evidence.text.replace(/\s+/gu, " ").trim().slice(0, SEARCH_TEXT_PREVIEW_LENGTH);
1486
1585
  }
1586
+ function printTeamComparison(comparison) {
1587
+ const statusColor = comparison.synchronized ? pc.green : pc.yellow;
1588
+ console.log(statusColor(`Team status: ${comparison.status}`));
1589
+ console.log(`summary=${comparison.summary}`);
1590
+ console.log(`local=${comparison.localLabel}`);
1591
+ console.log(`peer=${comparison.peerLabel}`);
1592
+ console.log(`sameConfiguration=${comparison.sameConfiguration}`);
1593
+ console.log(`sameCorpus=${comparison.sameCorpus}`);
1594
+ console.log(`authorityDecisionRequired=${comparison.authorityDecisionRequired}`);
1595
+ if (comparison.configurationDifferences.length > 0) {
1596
+ console.log("");
1597
+ console.log(pc.cyan("Configuration differences:"));
1598
+ for (const difference of comparison.configurationDifferences) {
1599
+ console.log(` - ${difference.field} (${difference.scope}, rebuild=${difference.requiresRebuild}): local=${formatTeamValue(difference.local)} peer=${formatTeamValue(difference.peer)}`);
1600
+ }
1601
+ }
1602
+ printTeamPaths("Local-only files", comparison.files.localOnly);
1603
+ printTeamPaths("Peer-only files", comparison.files.peerOnly);
1604
+ if (comparison.files.changed.length > 0) {
1605
+ console.log("");
1606
+ console.log(pc.cyan(`Changed files (${comparison.files.changed.length}):`));
1607
+ for (const file of comparison.files.changed.slice(0, TEAM_DIFF_PREVIEW_LIMIT)) {
1608
+ console.log(` - ${file.relativePath} local=${file.localChecksum.slice(0, 12)} peer=${file.peerChecksum.slice(0, 12)}`);
1609
+ }
1610
+ printTeamOmitted(comparison.files.changed.length);
1611
+ }
1612
+ if (comparison.recommendedActions.length > 0) {
1613
+ console.log("");
1614
+ console.log(pc.cyan("Recommended actions:"));
1615
+ for (const [index, action] of comparison.recommendedActions.entries()) {
1616
+ console.log(` ${index + 1}. ${action}`);
1617
+ }
1618
+ }
1619
+ }
1620
+ function printTeamPaths(title, files) {
1621
+ if (files.length === 0) {
1622
+ return;
1623
+ }
1624
+ console.log("");
1625
+ console.log(pc.cyan(`${title} (${files.length}):`));
1626
+ for (const file of files.slice(0, TEAM_DIFF_PREVIEW_LIMIT)) {
1627
+ console.log(` - ${file}`);
1628
+ }
1629
+ printTeamOmitted(files.length);
1630
+ }
1631
+ function printTeamOmitted(total) {
1632
+ const omitted = total - TEAM_DIFF_PREVIEW_LIMIT;
1633
+ if (omitted > 0) {
1634
+ console.log(` ... ${omitted} more; use --json for the complete diff.`);
1635
+ }
1636
+ }
1637
+ function formatTeamValue(value) {
1638
+ return Array.isArray(value) ? JSON.stringify(value) : String(value);
1639
+ }
1487
1640
  function printPdfOcrStatus(status) {
1488
1641
  console.log(`privacyProfile=${status.privacyProfile}`);
1489
1642
  console.log(`configured=${status.configured}`);