@jcode.labs/ragmir 6.1.6 → 6.2.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/README.md +43 -49
- package/dist/cli.js +194 -24
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +24 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +127 -24
- package/dist/config.js.map +1 -1
- package/dist/context-resources.d.ts.map +1 -1
- package/dist/context-resources.js +19 -8
- package/dist/context-resources.js.map +1 -1
- package/dist/defaults.d.ts +5 -0
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +16 -0
- package/dist/defaults.js.map +1 -1
- package/dist/destroy.d.ts.map +1 -1
- package/dist/destroy.js +3 -2
- package/dist/destroy.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +37 -5
- package/dist/doctor.js.map +1 -1
- package/dist/durable-file.d.ts.map +1 -1
- package/dist/durable-file.js +3 -2
- package/dist/durable-file.js.map +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +7 -6
- package/dist/embeddings.js.map +1 -1
- package/dist/evaluate.d.ts.map +1 -1
- package/dist/evaluate.js +11 -6
- package/dist/evaluate.js.map +1 -1
- package/dist/files.d.ts +27 -1
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +344 -100
- package/dist/files.js.map +1 -1
- package/dist/freshness.d.ts +16 -0
- package/dist/freshness.d.ts.map +1 -0
- package/dist/freshness.js +97 -0
- package/dist/freshness.js.map +1 -0
- package/dist/fs-retry.d.ts +16 -0
- package/dist/fs-retry.d.ts.map +1 -0
- package/dist/fs-retry.js +49 -0
- package/dist/fs-retry.js.map +1 -0
- package/dist/generation-retention.d.ts +12 -0
- package/dist/generation-retention.d.ts.map +1 -1
- package/dist/generation-retention.js +13 -2
- package/dist/generation-retention.js.map +1 -1
- package/dist/git-hooks.d.ts +25 -0
- package/dist/git-hooks.d.ts.map +1 -0
- package/dist/git-hooks.js +173 -0
- package/dist/git-hooks.js.map +1 -0
- package/dist/index-diagnostics.d.ts +9 -1
- package/dist/index-diagnostics.d.ts.map +1 -1
- package/dist/index-diagnostics.js +35 -0
- package/dist/index-diagnostics.js.map +1 -1
- package/dist/index-policy.d.ts.map +1 -1
- package/dist/index-policy.js +4 -0
- package/dist/index-policy.js.map +1 -1
- package/dist/index-write-lock.d.ts.map +1 -1
- package/dist/index-write-lock.js +8 -7
- package/dist/index-write-lock.js.map +1 -1
- package/dist/ingest.d.ts +6 -0
- package/dist/ingest.d.ts.map +1 -1
- package/dist/ingest.js +44 -21
- package/dist/ingest.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +19 -4
- package/dist/init.js.map +1 -1
- package/dist/knowledge-bases.d.ts +18 -1
- package/dist/knowledge-bases.d.ts.map +1 -1
- package/dist/knowledge-bases.js +73 -4
- package/dist/knowledge-bases.js.map +1 -1
- package/dist/mcp-output.d.ts +14 -3
- package/dist/mcp-output.d.ts.map +1 -1
- package/dist/mcp-output.js +37 -0
- package/dist/mcp-output.js.map +1 -1
- package/dist/mcp-registrations.d.ts +9 -0
- package/dist/mcp-registrations.d.ts.map +1 -0
- package/dist/mcp-registrations.js +247 -0
- package/dist/mcp-registrations.js.map +1 -0
- package/dist/mcp-scope.d.ts +32 -0
- package/dist/mcp-scope.d.ts.map +1 -0
- package/dist/mcp-scope.js +114 -0
- package/dist/mcp-scope.js.map +1 -0
- package/dist/mcp-session.d.ts +37 -0
- package/dist/mcp-session.d.ts.map +1 -0
- package/dist/mcp-session.js +53 -0
- package/dist/mcp-session.js.map +1 -0
- package/dist/mcp-summaries.d.ts +5 -0
- package/dist/mcp-summaries.d.ts.map +1 -1
- package/dist/mcp-summaries.js +30 -0
- package/dist/mcp-summaries.js.map +1 -1
- package/dist/mcp.d.ts +6 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +410 -103
- package/dist/mcp.js.map +1 -1
- package/dist/ocr-cache.d.ts.map +1 -1
- package/dist/ocr-cache.js +3 -2
- package/dist/ocr-cache.js.map +1 -1
- package/dist/ocr.d.ts +51 -0
- package/dist/ocr.d.ts.map +1 -1
- package/dist/ocr.js +213 -6
- package/dist/ocr.js.map +1 -1
- package/dist/package-manager.d.ts.map +1 -1
- package/dist/package-manager.js +2 -1
- package/dist/package-manager.js.map +1 -1
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +3 -7
- package/dist/preview.js.map +1 -1
- package/dist/project-config-file.d.ts.map +1 -1
- package/dist/project-config-file.js +3 -2
- package/dist/project-config-file.js.map +1 -1
- package/dist/quality-report.d.ts.map +1 -1
- package/dist/quality-report.js +1 -1
- package/dist/quality-report.js.map +1 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +235 -34
- package/dist/query.js.map +1 -1
- package/dist/ranking.d.ts +56 -6
- package/dist/ranking.d.ts.map +1 -1
- package/dist/ranking.js +187 -16
- package/dist/ranking.js.map +1 -1
- package/dist/retrieval-diversity.d.ts +5 -0
- package/dist/retrieval-diversity.d.ts.map +1 -1
- package/dist/retrieval-diversity.js +4 -2
- package/dist/retrieval-diversity.js.map +1 -1
- package/dist/search-output.d.ts.map +1 -1
- package/dist/search-output.js +2 -0
- package/dist/search-output.js.map +1 -1
- package/dist/security.js +1 -1
- package/dist/security.js.map +1 -1
- package/dist/setup-prompt.d.ts +1 -1
- package/dist/setup-prompt.d.ts.map +1 -1
- package/dist/setup-prompt.js +3 -3
- package/dist/setup.js +1 -1
- package/dist/setup.js.map +1 -1
- package/dist/skill.d.ts +2 -0
- package/dist/skill.d.ts.map +1 -1
- package/dist/skill.js +125 -16
- package/dist/skill.js.map +1 -1
- package/dist/source-fingerprint-cache.d.ts.map +1 -1
- package/dist/source-fingerprint-cache.js +3 -2
- package/dist/source-fingerprint-cache.js.map +1 -1
- package/dist/source-paths.d.ts +36 -0
- package/dist/source-paths.d.ts.map +1 -0
- package/dist/source-paths.js +157 -0
- package/dist/source-paths.js.map +1 -0
- package/dist/source-weights.d.ts +28 -0
- package/dist/source-weights.d.ts.map +1 -0
- package/dist/source-weights.js +241 -0
- package/dist/source-weights.js.map +1 -0
- package/dist/storage-maintenance.d.ts +3 -1
- package/dist/storage-maintenance.d.ts.map +1 -1
- package/dist/storage-maintenance.js +24 -4
- package/dist/storage-maintenance.js.map +1 -1
- package/dist/storage-usage.d.ts +21 -0
- package/dist/storage-usage.d.ts.map +1 -0
- package/dist/storage-usage.js +119 -0
- package/dist/storage-usage.js.map +1 -0
- package/dist/types.d.ts +127 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @jcode.labs/ragmir
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@jcode.labs/ragmir)
|
|
4
|
-
[](https://github.com/jcode-works/jcode-ragmir/actions/workflows/ci.yml)
|
|
5
5
|
[](https://github.com/jcode-works/jcode-ragmir/blob/main/LICENSE)
|
|
6
6
|
[](https://nodejs.org)
|
|
7
7
|
|
|
@@ -12,17 +12,18 @@ passages with checkable citations through a TypeScript library, the `rgr` CLI, o
|
|
|
12
12
|
server. Claude Code, Codex, or your own application decides what to search and generates with the
|
|
13
13
|
model you choose. Ragmir does not bundle a chat model or host your documents.
|
|
14
14
|
|
|
15
|
-
- **Code and documents in one index:** hybrid BM25 and vector search that understands
|
|
16
|
-
and
|
|
17
|
-
- **Fast on real repositories:**
|
|
18
|
-
|
|
15
|
+
- **Code and documents in one index:** hybrid BM25 and vector search that understands paths and
|
|
16
|
+
camelCase identifiers and boosts the code that defines what you ask about.
|
|
17
|
+
- **Fast on real repositories:** 40 ms median search on a 43,874-chunk monorepo, with the right
|
|
18
|
+
source in the top 10 for 93% of real developer questions
|
|
19
19
|
([benchmarks](https://github.com/jcode-works/jcode-ragmir#benchmarks)).
|
|
20
20
|
- **Evidence you can verify:** citations point to source lines or native page, slide, sheet/cell,
|
|
21
|
-
and EPUB coordinates.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
and EPUB coordinates. Results flag changed files (`sourceStatus`) and thin support
|
|
22
|
+
(`weakEvidence`); unrelated questions return nothing.
|
|
23
|
+
- **Always current:** `rgr doctor` checks the files on disk on every run, and `rgr hooks install`
|
|
24
|
+
refreshes the index after commits, merges, and checkouts.
|
|
25
|
+
- **Private and portable:** offline by default, with no account or telemetry, and the same paths,
|
|
26
|
+
exclusions, and citations on Linux, macOS, and Windows.
|
|
26
27
|
|
|
27
28
|
## Install and search
|
|
28
29
|
|
|
@@ -37,9 +38,20 @@ npx rgr search "authentication contract" --compact
|
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
`rgr setup` creates ignored local state under `.ragmir/` and installs the selected agent helpers.
|
|
40
|
-
|
|
41
|
+
A committed `ragmir.config.json` shares sources and settings with your team. The
|
|
42
|
+
[quick start](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/quick-start.md) also
|
|
41
43
|
covers pnpm and guided setup.
|
|
42
44
|
|
|
45
|
+
## Connect your agent
|
|
46
|
+
|
|
47
|
+
`rgr setup --agents claude,codex` prepares a project MCP server and a retrieval skill. The server
|
|
48
|
+
exposes four read-only tools: `ragmir_search` (one question or up to five related ones sharing a
|
|
49
|
+
budget, with named scopes), `ragmir_expand`, `ragmir_status`, and `ragmir_audit`. Passages
|
|
50
|
+
already returned in a session come back as one-line stubs. Its instructions name the base it
|
|
51
|
+
serves, and it warns or refuses agents working in unrelated projects. See
|
|
52
|
+
[agent integration](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/agent-integration.md)
|
|
53
|
+
for connection steps and local or self-hosted model examples.
|
|
54
|
+
|
|
43
55
|
## TypeScript API
|
|
44
56
|
|
|
45
57
|
Use one client per project root in a long-running process and close it during shutdown:
|
|
@@ -51,7 +63,7 @@ const ragmir = await createRagmirClient({ cwd: process.cwd() })
|
|
|
51
63
|
try {
|
|
52
64
|
const passages = await ragmir.search("authentication contract", { topK: 3 })
|
|
53
65
|
for (const passage of passages) {
|
|
54
|
-
console.log(passage.citation, passage.text)
|
|
66
|
+
console.log(passage.citation, passage.weakEvidence ? "(verify)" : "", passage.text)
|
|
55
67
|
}
|
|
56
68
|
if (passages[0]) {
|
|
57
69
|
console.log(await ragmir.expandCitation(passages[0].citation, {
|
|
@@ -66,43 +78,25 @@ try {
|
|
|
66
78
|
The library also exports top-level `ingest`, `search`, and `expandCitation` functions for one-shot
|
|
67
79
|
scripts. See the [API reference](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/api-reference.md).
|
|
68
80
|
|
|
69
|
-
##
|
|
81
|
+
## Four workflow examples
|
|
70
82
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
1. **Build a feature with Claude Code or Codex.** The agent finds the specification rules and ADR
|
|
84
|
+
exceptions with Ragmir, opens the cited passages, then changes the code and tests.
|
|
85
|
+
2. **Diagnose an incident with Claude Code or Codex.** It retrieves the runbook, incident report,
|
|
86
|
+
and retry implementation before it changes code and adds a regression test.
|
|
87
|
+
3. **Plan an API migration with Claude Code or Codex.** It retrieves compatibility, rollback, and
|
|
88
|
+
versioning rules before it updates callers and migration tests.
|
|
89
|
+
4. **Make a confidential architecture decision.** A local or self-hosted chat retrieves cited
|
|
90
|
+
evidence and sends it to a model you run, with remote calls disabled.
|
|
77
91
|
|
|
78
|
-
##
|
|
92
|
+
## Documents, semantic retrieval, and OCR
|
|
93
|
+
|
|
94
|
+
Index Markdown, code, JSON, CSV, HTML, PDF, DOCX, XLSX, PPTX, OpenDocument, EPUB, and RTF; scanned
|
|
95
|
+
PDFs, photos, and legacy `.doc` files use local extractors (`rgr ocr setup --images
|
|
96
|
+
--legacy-word`). The default `local-hash` provider needs no model download. For semantic
|
|
97
|
+
embeddings, install `@huggingface/transformers` and run `npx rgr setup --semantic`.
|
|
79
98
|
|
|
80
|
-
|
|
81
|
-
the project's specification and ADR. It searches with Ragmir, opens cited passages, checks
|
|
82
|
-
exceptions, then changes the code and tests.
|
|
83
|
-
2. **Diagnose an incident with Claude Code or Codex.** Ask the agent to investigate a checkout
|
|
84
|
-
timeout from the runbook, incident report, and retry implementation before it changes code and
|
|
85
|
-
adds a regression test.
|
|
86
|
-
3. **Plan an API migration with Claude Code or Codex.** Ask the agent to retrieve compatibility,
|
|
87
|
-
rollback, and versioning rules from the migration plan and ADR before it updates callers and
|
|
88
|
-
migration tests.
|
|
89
|
-
4. **Make a confidential architecture decision.** Ask a local or self-hosted chat whether internal
|
|
90
|
-
architecture rules call for attachments in PostgreSQL or object storage. The chat retrieves cited
|
|
91
|
-
evidence with Ragmir and sends it to a downloaded Ollama model with remote calls disabled, or to
|
|
92
|
-
a model on your own server.
|
|
93
|
-
|
|
94
|
-
## Retrieval options and documents
|
|
95
|
-
|
|
96
|
-
The default `local-hash` provider ranks BM25 keyword evidence first and uses deterministic local
|
|
97
|
-
vectors to break ties. For semantic embeddings, install `@huggingface/transformers`, then run `npx rgr setup --semantic` to
|
|
98
|
-
download and enable the selected embedding model. Index Markdown, code, JSON, CSV, HTML, PDF,
|
|
99
|
-
DOCX, XLSX, PPTX, OpenDocument, EPUB, and RTF. Scanned PDF pages require a supported local OCR
|
|
100
|
-
engine: inspect `npx rgr ocr doctor`, then configure it with `npx rgr ocr setup`.
|
|
101
|
-
|
|
102
|
-
The index stays on the machine running Ragmir, but retrieved text is not masked. With an entirely
|
|
103
|
-
local consumer it can stay there; a self-hosted or cloud consumer receives the selected passages.
|
|
104
|
-
Confidentiality depends on the full setup, including access controls, transport, tools, and logs.
|
|
105
|
-
Ragmir has no telemetry or hosted storage.
|
|
99
|
+
For guided setup, give your agent this prompt:
|
|
106
100
|
|
|
107
101
|
<!-- ragmir-setup-prompt:start -->
|
|
108
102
|
<details>
|
|
@@ -121,7 +115,7 @@ Outcome: Ragmir installed with the repository's package manager; useful sources
|
|
|
121
115
|
2. Configure:
|
|
122
116
|
- Install @jcode.labs/ragmir with the detected manager. Default to offline local-hash. Semantic retrieval additionally requires @huggingface/transformers and an explicitly approved model download; use rgr setup --semantic only after that approval.
|
|
123
117
|
- Run rgr setup --no-ingest --agents <selected> with the detected manager. Keep project scope. Review an unmanaged same-name skill before using --force-agent-skills.
|
|
124
|
-
- Select narrow relative source globs in .ragmir/config.json: guidance, docs/specs/ADRs, package READMEs, useful config, source, and tests. Scope nested bases separately and keep shared knowledge at the root.
|
|
118
|
+
- Select narrow relative source globs in ragmir.config.json (committed) or .ragmir/config.json: guidance, docs/specs/ADRs, package READMEs, useful config, source, and tests. Set name and description so agents know which project the base serves. Scope nested bases separately and keep shared knowledge at the root.
|
|
125
119
|
- Exclude .env*, credentials, keys, unapproved private data, dependencies, generated/build/cache/coverage/log folders, vendored files, and Ragmir storage/models. Review external folders before including them.
|
|
126
120
|
- Ragmir preserves source text without masking. The consuming application controls where passages go. For confidential work, use a local or self-hosted model and control the client, access, transport, and logs.
|
|
127
121
|
- Keep useful document formats. For scanned PDFs, inspect rgr ocr doctor and configure local OCR with rgr ocr setup only when needed and authorized.
|
|
@@ -129,11 +123,11 @@ Outcome: Ragmir installed with the repository's package manager; useful sources
|
|
|
129
123
|
3. Index and connect:
|
|
130
124
|
- Run rgr preview --json and rgr audit --unsupported. Review source coverage, skipped files, duplicates, chunk structure, and citation coordinates; fix config before ingesting.
|
|
131
125
|
- Run rgr ingest. For an incompatible old installation, run rgr upgrade, which backs up retired config and stages a replacement index. Never delete the active index first.
|
|
132
|
-
- Connect the generated MCP helper or native retrieval skill for the selected agents. Verify the owning base with rgr bases --json or ragmir_status.
|
|
126
|
+
- Connect the generated MCP helper or native retrieval skill for the selected agents, registered per project, never for every project. Verify the owning base with rgr bases --json or ragmir_status. Offer rgr hooks install to refresh the index after Git operations.
|
|
133
127
|
- Use ragmir_search for compact citations, then ragmir_expand for one exact passage. The consuming agent handles reasoning and synthesis with its chosen model.
|
|
134
128
|
|
|
135
129
|
4. Verify:
|
|
136
|
-
- Run rgr doctor --deep, rgr audit --unsupported, and rgr security-audit.
|
|
130
|
+
- Run rgr doctor --deep, rgr audit --unsupported, and rgr security-audit; fix reported source rules, MCP registration warnings, and index concentration.
|
|
137
131
|
- Run representative searches with citations and --explain. Evaluate a small local golden suite for project questions with rgr evaluate; do not weaken gates to pass.
|
|
138
132
|
- Report packages and downloads, selected sources/exclusions, changed files, readiness, citation examples, evaluation results, and any remaining actions.
|
|
139
133
|
|
package/dist/cli.js
CHANGED
|
@@ -10,15 +10,18 @@ import { doctor } from "./doctor.js";
|
|
|
10
10
|
import { pullEmbeddingModel } from "./embeddings.js";
|
|
11
11
|
import { evaluateGoldenQueries } from "./evaluate.js";
|
|
12
12
|
import { countSkippedByReason } from "./files.js";
|
|
13
|
+
import { checkIndexFreshness } from "./freshness.js";
|
|
13
14
|
import { collectGenerationGarbage } from "./generation-retention.js";
|
|
15
|
+
import { gitHooksStatus, installGitHooks, uninstallGitHooks, } from "./git-hooks.js";
|
|
14
16
|
import { indexFreshnessWarning, manifestReady } from "./index-diagnostics.js";
|
|
15
17
|
import { audit, ingest } from "./ingest.js";
|
|
16
18
|
import { getIngestionProgress } from "./ingestion-state.js";
|
|
17
19
|
import { initProject } from "./init.js";
|
|
18
|
-
import { discoverKnowledgeBases, knowledgeBaseIdentity } from "./knowledge-bases.js";
|
|
20
|
+
import { discoverKnowledgeBases, knowledgeBaseIdentity, knowledgeBaseProfile, } from "./knowledge-bases.js";
|
|
19
21
|
import { ingestionLimits } from "./limits.js";
|
|
20
22
|
import { serveMcp } from "./mcp.js";
|
|
21
|
-
import {
|
|
23
|
+
import { RAGMIR_CLIENT_CWD_ENV } from "./mcp-scope.js";
|
|
24
|
+
import { configureLocalExtractors, configurePdfOcr, extractImageText, extractLegacyWordText, extractPdfPage, extractPdfPages, inspectLocalExtractors, inspectPdfOcr, parseLegacyWordEngine, parsePdfOcrEngine, parsePdfOcrPages, } from "./ocr.js";
|
|
22
25
|
import { rgrCommand } from "./package-manager.js";
|
|
23
26
|
import { previewChunks } from "./preview.js";
|
|
24
27
|
import { search } from "./query.js";
|
|
@@ -83,48 +86,104 @@ modelsCommand
|
|
|
83
86
|
});
|
|
84
87
|
const ocrCommand = program
|
|
85
88
|
.command("ocr")
|
|
86
|
-
.description("Configure and diagnose local OCR for scanned
|
|
89
|
+
.description("Configure and diagnose local OCR for scanned PDFs, images, and legacy .doc files.");
|
|
87
90
|
ocrCommand
|
|
88
91
|
.command("doctor")
|
|
89
92
|
.description("Detect supported local PDF OCR engines and installed languages.")
|
|
90
93
|
.option("--json", "Print machine-readable JSON.")
|
|
91
94
|
.action(async (options, command) => {
|
|
92
|
-
const status = await
|
|
95
|
+
const [status, local] = await Promise.all([
|
|
96
|
+
inspectPdfOcr(projectRoot(command)),
|
|
97
|
+
inspectLocalExtractors(),
|
|
98
|
+
]);
|
|
93
99
|
if (options.json) {
|
|
94
|
-
console.log(JSON.stringify(status, null, 2));
|
|
100
|
+
console.log(JSON.stringify({ ...status, local }, null, 2));
|
|
95
101
|
return;
|
|
96
102
|
}
|
|
97
103
|
printPdfOcrStatus(status);
|
|
104
|
+
console.log(`imageOcr=${local.imageOcr.available ? "tesseract" : "unavailable"}${local.imageOcr.imageConverter ? ` (+${local.imageOcr.imageConverter} for HEIC/AVIF)` : ""}`);
|
|
105
|
+
console.log(`legacyWord=${local.legacyWord.recommendedEngine ?? "unavailable"}`);
|
|
106
|
+
if (local.imageOcr.available || local.legacyWord.recommendedEngine) {
|
|
107
|
+
console.log("Run `rgr ocr setup --images --legacy-word` to index photos and .doc files too.");
|
|
108
|
+
}
|
|
98
109
|
});
|
|
99
110
|
ocrCommand
|
|
100
111
|
.command("setup")
|
|
101
|
-
.description("Detect
|
|
102
|
-
.option("--engine <engine>", "OCR engine: auto, ocrmypdf, or tesseract.", "auto")
|
|
112
|
+
.description("Detect local OCR and converters, then configure scanned PDFs, images, and legacy .doc files.")
|
|
113
|
+
.option("--engine <engine>", "PDF OCR engine: auto, ocrmypdf, or tesseract.", "auto")
|
|
103
114
|
.option("--language <codes>", "Tesseract language codes such as eng, fra, or eng+fra.", "eng")
|
|
104
115
|
.option("--timeout-ms <number>", "Per-batch OCR timeout in milliseconds.", parsePositiveInt)
|
|
116
|
+
.option("--no-pdf", "Leave scanned-PDF OCR unchanged.")
|
|
117
|
+
.option("--images", "Also OCR photos and scanned images (PNG, JPEG, TIFF, HEIC, ...).")
|
|
118
|
+
.option("--legacy-word", "Also extract legacy Word .doc files with a local converter.")
|
|
105
119
|
.option("--json", "Print machine-readable JSON.")
|
|
106
120
|
.action(async (options, command) => {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
const cwd = projectRoot(command);
|
|
122
|
+
let pdf = null;
|
|
123
|
+
if (options.pdf) {
|
|
124
|
+
const configureOptions = {
|
|
125
|
+
cwd,
|
|
126
|
+
engine: parsePdfOcrEngine(options.engine, true),
|
|
127
|
+
language: options.language,
|
|
128
|
+
};
|
|
129
|
+
addOption(configureOptions, "timeoutMs", options.timeoutMs);
|
|
130
|
+
pdf = await configurePdfOcr(configureOptions);
|
|
131
|
+
}
|
|
132
|
+
const local = options.images || options.legacyWord
|
|
133
|
+
? await configureLocalExtractors({
|
|
134
|
+
cwd,
|
|
135
|
+
language: options.language,
|
|
136
|
+
...(options.images ? { images: true } : {}),
|
|
137
|
+
...(options.legacyWord ? { legacyWord: true } : {}),
|
|
138
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
139
|
+
})
|
|
140
|
+
: null;
|
|
114
141
|
if (options.json) {
|
|
115
|
-
console.log(JSON.stringify(
|
|
142
|
+
console.log(JSON.stringify({ pdf, local }, null, 2));
|
|
116
143
|
return;
|
|
117
144
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
145
|
+
if (pdf) {
|
|
146
|
+
console.log(pc.green("Local PDF OCR configured."));
|
|
147
|
+
console.log(`engine=${pdf.engine}`);
|
|
148
|
+
console.log(`language=${pdf.language}`);
|
|
149
|
+
console.log(`timeoutMs=${pdf.timeoutMs}`);
|
|
150
|
+
console.log(`configPath=${pdf.configPath}`);
|
|
151
|
+
}
|
|
152
|
+
if (local?.imageOcrCommand) {
|
|
153
|
+
console.log(pc.green("Local image OCR configured."));
|
|
154
|
+
}
|
|
155
|
+
if (local?.legacyWordCommand) {
|
|
156
|
+
console.log(pc.green(`Legacy Word extraction configured (${local.legacyWordCommand.at(-3)}).`));
|
|
157
|
+
}
|
|
123
158
|
console.log("");
|
|
124
159
|
console.log("Next steps:");
|
|
125
|
-
console.log(" 1. Run `rgr ingest` to index
|
|
160
|
+
console.log(" 1. Run `rgr ingest` to index the newly readable files with local OCR.");
|
|
126
161
|
console.log(" 2. Run `rgr doctor` to confirm complete coverage.");
|
|
127
162
|
});
|
|
163
|
+
ocrCommand
|
|
164
|
+
.command("extract-image", { hidden: true })
|
|
165
|
+
.requiredOption("--input <path>", "Image file to read.")
|
|
166
|
+
.option("--language <codes>", "Tesseract language codes.", "eng")
|
|
167
|
+
.option("--timeout-ms <number>", "OCR timeout in milliseconds.", parsePositiveInt)
|
|
168
|
+
.action(async (options, command) => {
|
|
169
|
+
process.stdout.write(await extractImageText({
|
|
170
|
+
input: path.resolve(projectRoot(command), options.input),
|
|
171
|
+
language: options.language,
|
|
172
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
173
|
+
}));
|
|
174
|
+
});
|
|
175
|
+
ocrCommand
|
|
176
|
+
.command("extract-doc", { hidden: true })
|
|
177
|
+
.requiredOption("--input <path>", "Legacy Word file to read.")
|
|
178
|
+
.requiredOption("--engine <engine>", "Converter: textutil, antiword, catdoc, or soffice.")
|
|
179
|
+
.option("--timeout-ms <number>", "Conversion timeout in milliseconds.", parsePositiveInt)
|
|
180
|
+
.action(async (options, command) => {
|
|
181
|
+
process.stdout.write(await extractLegacyWordText({
|
|
182
|
+
input: path.resolve(projectRoot(command), options.input),
|
|
183
|
+
engine: parseLegacyWordEngine(options.engine),
|
|
184
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
185
|
+
}));
|
|
186
|
+
});
|
|
128
187
|
ocrCommand
|
|
129
188
|
.command("extract-page", { hidden: true })
|
|
130
189
|
.requiredOption("--engine <engine>", "OCR engine: ocrmypdf or tesseract.")
|
|
@@ -449,6 +508,7 @@ program
|
|
|
449
508
|
.option("--include-path <prefix>", "Search only indexed source paths under this prefix. Repeat for multiple prefixes.", collectOptionValue, [])
|
|
450
509
|
.option("--exclude-path <prefix>", "Exclude indexed source paths under this prefix. Repeat for multiple prefixes.", collectOptionValue, [])
|
|
451
510
|
.option("--context-path <prefix>", "Search only chunks under this structural context. Repeat for multiple prefixes.", collectOptionValue, [])
|
|
511
|
+
.option("--scope <name>", "Search a named scope from the config's `scopes`.")
|
|
452
512
|
.option("--explain", "Include hybrid score contributions, ranks, and matched terms.")
|
|
453
513
|
.option("--exact-vector-search", "Bypass ANN and use exhaustive vector search for diagnostics.")
|
|
454
514
|
.option("--compact", "Return short snippets instead of full passages.")
|
|
@@ -481,7 +541,13 @@ program
|
|
|
481
541
|
}
|
|
482
542
|
for (const [index, result] of outputResults.entries()) {
|
|
483
543
|
const distance = result.distance === null ? "n/a" : result.distance.toFixed(4);
|
|
484
|
-
|
|
544
|
+
const flags = [
|
|
545
|
+
result.sourceStatus === undefined
|
|
546
|
+
? null
|
|
547
|
+
: pc.yellow(`source ${result.sourceStatus} since indexing`),
|
|
548
|
+
result.weakEvidence ? pc.yellow("weak evidence") : null,
|
|
549
|
+
].filter((flag) => flag !== null);
|
|
550
|
+
console.log(`\n${pc.cyan(`[${index + 1}] ${result.citation}`)} chunk=${result.chunkIndex} distance=${distance}${flags.length > 0 ? ` ${flags.join(" ")}` : ""}`);
|
|
485
551
|
console.log("snippet" in result ? result.snippet : result.text.slice(0, SEARCH_TEXT_PREVIEW_LENGTH));
|
|
486
552
|
if (result.score) {
|
|
487
553
|
const vectorRank = result.score.vectorRank ?? "n/a";
|
|
@@ -675,8 +741,12 @@ program
|
|
|
675
741
|
const freshnessWarning = indexFreshnessWarning(config, manifest);
|
|
676
742
|
const ready = manifestReady(manifest, freshnessWarning);
|
|
677
743
|
const ingestion = await getIngestionProgress(config);
|
|
744
|
+
const profile = knowledgeBaseProfile(config);
|
|
745
|
+
const sourceFreshness = manifest ? await checkIndexFreshness(config) : null;
|
|
678
746
|
const status = {
|
|
679
747
|
knowledgeBaseId: identity?.id ?? null,
|
|
748
|
+
knowledgeBase: { name: profile.name, description: profile.description },
|
|
749
|
+
scopes: profile.scopes,
|
|
680
750
|
projectRoot: config.projectRoot,
|
|
681
751
|
rawDir: config.rawDir,
|
|
682
752
|
storageDir: config.storageDir,
|
|
@@ -708,6 +778,7 @@ program
|
|
|
708
778
|
indexedFiles: manifest?.fileCount ?? 0,
|
|
709
779
|
chunksIndexed: manifest?.chunkCount ?? 0,
|
|
710
780
|
health: health ?? null,
|
|
781
|
+
sourceFreshness,
|
|
711
782
|
maintenance: manifest?.maintenance ?? null,
|
|
712
783
|
ingestion,
|
|
713
784
|
};
|
|
@@ -715,8 +786,15 @@ program
|
|
|
715
786
|
console.log(JSON.stringify(status, null, 2));
|
|
716
787
|
return;
|
|
717
788
|
}
|
|
789
|
+
console.log(`knowledgeBase=${profile.name}`);
|
|
790
|
+
if (profile.description) {
|
|
791
|
+
console.log(`description=${profile.description}`);
|
|
792
|
+
}
|
|
718
793
|
console.log(`knowledgeBaseId=${identity?.id ?? "none"}`);
|
|
719
794
|
console.log(`projectRoot=${config.projectRoot}`);
|
|
795
|
+
if (profile.scopes.length > 0) {
|
|
796
|
+
console.log(`scopes=${profile.scopes.map((scope) => scope.name).join(",")}`);
|
|
797
|
+
}
|
|
720
798
|
console.log(`rawDir=${config.rawDir}`);
|
|
721
799
|
console.log(`storageDir=${config.storageDir}`);
|
|
722
800
|
console.log(`sourcesFile=${config.sourcesFile}`);
|
|
@@ -745,6 +823,12 @@ program
|
|
|
745
823
|
console.log(`corpusFingerprint=${manifest?.corpusFingerprint ?? "unavailable"}`);
|
|
746
824
|
console.log(`indexedFiles=${manifest?.fileCount ?? 0}`);
|
|
747
825
|
console.log(`chunksIndexed=${manifest?.chunkCount ?? 0}`);
|
|
826
|
+
if (sourceFreshness?.verified) {
|
|
827
|
+
console.log(`sourceFreshness=verified missingFromIndex=${sourceFreshness.missingFromIndex} staleInIndex=${sourceFreshness.staleInIndex}`);
|
|
828
|
+
}
|
|
829
|
+
else if (sourceFreshness) {
|
|
830
|
+
console.log(`sourceFreshness=unverified (${sourceFreshness.error ?? "unknown"})`);
|
|
831
|
+
}
|
|
748
832
|
if (freshnessWarning) {
|
|
749
833
|
console.log(`indexFreshnessWarning=${freshnessWarning}`);
|
|
750
834
|
}
|
|
@@ -811,13 +895,15 @@ storageCommand
|
|
|
811
895
|
});
|
|
812
896
|
storageCommand
|
|
813
897
|
.command("gc")
|
|
814
|
-
.description("
|
|
898
|
+
.description("Reclaim expired index generations and report disk usage and orphaned models or copies.")
|
|
815
899
|
.option("--dry-run", "Report generation roles and reclaimable bytes without deleting tables.")
|
|
900
|
+
.option("--orphans", "Also delete the listed orphans: unused models (including retired chat models), storage copies, and retired outputs.")
|
|
816
901
|
.option("--json", "Print machine-readable JSON.")
|
|
817
902
|
.action(async (options, command) => {
|
|
818
903
|
const report = await collectGenerationGarbage({
|
|
819
904
|
cwd: projectRoot(command),
|
|
820
905
|
...(options.dryRun === undefined ? {} : { dryRun: options.dryRun }),
|
|
906
|
+
...(options.orphans === undefined ? {} : { orphans: options.orphans }),
|
|
821
907
|
});
|
|
822
908
|
if (options.json) {
|
|
823
909
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -848,6 +934,36 @@ storageCommand
|
|
|
848
934
|
process.exitCode = 1;
|
|
849
935
|
}
|
|
850
936
|
});
|
|
937
|
+
const hooksCommand = program
|
|
938
|
+
.command("hooks")
|
|
939
|
+
.description("Refresh the index automatically after commits, merges, checkouts, and rebases.");
|
|
940
|
+
hooksCommand
|
|
941
|
+
.command("install")
|
|
942
|
+
.description("Add a background `rgr ingest` to the Git hooks of this repository.")
|
|
943
|
+
.option("--force", "Also edit hooks in a directory tracked by the working tree, such as .husky.")
|
|
944
|
+
.option("--json", "Print machine-readable JSON.")
|
|
945
|
+
.action(async (options, command) => {
|
|
946
|
+
const report = await installGitHooks({
|
|
947
|
+
cwd: projectRoot(command),
|
|
948
|
+
...(options.force === undefined ? {} : { force: options.force }),
|
|
949
|
+
});
|
|
950
|
+
printGitHooks(report, options.json === true, "installed");
|
|
951
|
+
});
|
|
952
|
+
hooksCommand
|
|
953
|
+
.command("uninstall")
|
|
954
|
+
.description("Remove the Ragmir block from the Git hooks and keep any other hook content.")
|
|
955
|
+
.option("--json", "Print machine-readable JSON.")
|
|
956
|
+
.action(async (options, command) => {
|
|
957
|
+
const report = await uninstallGitHooks({ cwd: projectRoot(command) });
|
|
958
|
+
printGitHooks(report, options.json === true, "removed");
|
|
959
|
+
});
|
|
960
|
+
hooksCommand
|
|
961
|
+
.command("status")
|
|
962
|
+
.description("Show which Git hooks refresh the index.")
|
|
963
|
+
.option("--json", "Print machine-readable JSON.")
|
|
964
|
+
.action(async (options, command) => {
|
|
965
|
+
printGitHooks(await gitHooksStatus({ cwd: projectRoot(command) }), options.json === true);
|
|
966
|
+
});
|
|
851
967
|
program
|
|
852
968
|
.command("destroy-index")
|
|
853
969
|
.description("Remove the generated local vector index from Ragmir storage.")
|
|
@@ -869,6 +985,7 @@ program
|
|
|
869
985
|
.description("Start the MCP server over stdio for Claude, Codex, and other MCP-compatible agents.")
|
|
870
986
|
.action(async (_options, command) => {
|
|
871
987
|
const explicitRoot = explicitProjectRoot(command);
|
|
988
|
+
process.env[RAGMIR_CLIENT_CWD_ENV] ||= process.cwd();
|
|
872
989
|
await serveMcp(explicitRoot);
|
|
873
990
|
});
|
|
874
991
|
program
|
|
@@ -956,6 +1073,27 @@ program
|
|
|
956
1073
|
console.log(" 3. Wire the matching MCP helper if the agent should call Ragmir tools directly.");
|
|
957
1074
|
console.log(` 4. Run \`${(await rgrCommand(cwd, ["doctor"])).display}\`.`);
|
|
958
1075
|
});
|
|
1076
|
+
// Commands removed by the focused-retrieval release. Agents following old skills still call them,
|
|
1077
|
+
// so each one explains its replacement instead of failing as an unknown command.
|
|
1078
|
+
const RETIRED_COMMANDS = [
|
|
1079
|
+
[
|
|
1080
|
+
"ask",
|
|
1081
|
+
"Search with `rgr search` and let your agent or chat write the answer from the citations.",
|
|
1082
|
+
],
|
|
1083
|
+
["research", "Let your agent loop over `rgr search` and citation expansion."],
|
|
1084
|
+
["chat", "Use your coding agent or a local or self-hosted chat with Ragmir citations."],
|
|
1085
|
+
["audio", "Send retrieved text to a separate text-to-speech tool."],
|
|
1086
|
+
];
|
|
1087
|
+
for (const [name, replacement] of RETIRED_COMMANDS) {
|
|
1088
|
+
program
|
|
1089
|
+
.command(name, { hidden: true })
|
|
1090
|
+
.allowUnknownOption()
|
|
1091
|
+
.allowExcessArguments()
|
|
1092
|
+
.action(() => {
|
|
1093
|
+
console.error(`\`rgr ${name}\` was removed: Ragmir now returns cited evidence and your agent writes the result. ${replacement} See https://github.com/jcode-works/jcode-ragmir/blob/main/docs/migration.md`);
|
|
1094
|
+
process.exitCode = 1;
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
959
1097
|
try {
|
|
960
1098
|
await program.parseAsync(process.argv);
|
|
961
1099
|
}
|
|
@@ -975,6 +1113,7 @@ function explicitProjectRoot(command) {
|
|
|
975
1113
|
}
|
|
976
1114
|
function withSearchOptions(cwd, options) {
|
|
977
1115
|
const result = { cwd };
|
|
1116
|
+
addOption(result, "scope", options.scope);
|
|
978
1117
|
addOption(result, "topK", options.topK);
|
|
979
1118
|
addOption(result, "maxChunksPerDocument", options.maxChunksPerDocument);
|
|
980
1119
|
addOption(result, "contextRadius", options.contextRadius);
|
|
@@ -1022,6 +1161,9 @@ function printDoctor(report) {
|
|
|
1022
1161
|
console.log(`agentIntegration.projectAgents=${report.agentIntegration.projectAgents.join(",")}`);
|
|
1023
1162
|
console.log(`agentIntegration.userAgents=${report.agentIntegration.userAgents.join(",")}`);
|
|
1024
1163
|
console.log(`agentIntegration.nativeAgents=${report.agentIntegration.nativeAgents.join(",")}`);
|
|
1164
|
+
for (const server of report.agentIntegration.mcpServers) {
|
|
1165
|
+
console.log(`agentIntegration.mcpServer=${server.client}:${server.scope}:${server.name} servedRoot=${server.servedRoot ?? "unknown"}${server.approval ? ` approval=${server.approval}` : ""}`);
|
|
1166
|
+
}
|
|
1025
1167
|
for (const warning of report.agentIntegration.warnings) {
|
|
1026
1168
|
console.log(pc.yellow(`agentIntegration.warning: ${warning}`));
|
|
1027
1169
|
}
|
|
@@ -1040,6 +1182,10 @@ function printDoctor(report) {
|
|
|
1040
1182
|
console.log(`chunksIndexed=${report.chunksIndexed}`);
|
|
1041
1183
|
console.log(`missingFromIndex=${report.missingFromIndex}`);
|
|
1042
1184
|
console.log(`staleInIndex=${report.staleInIndex}`);
|
|
1185
|
+
console.log(`coverageVerified=${report.readiness.coverageComplete || report.sourceFreshness?.verified === true || report.inventoryVerified}`);
|
|
1186
|
+
if (report.sourceFreshness?.verified === false) {
|
|
1187
|
+
console.log(pc.yellow(`sourceFreshness: ${report.sourceFreshness.error ?? "unverified"}`));
|
|
1188
|
+
}
|
|
1043
1189
|
console.log(`corpusFingerprint=${report.corpusFingerprint ?? "unavailable"}`);
|
|
1044
1190
|
console.log(`securityWarnings=${report.securityWarnings.length}`);
|
|
1045
1191
|
if (report.securityWarnings.length > 0) {
|
|
@@ -1092,16 +1238,40 @@ function printStorageMaintenance(report) {
|
|
|
1092
1238
|
console.log(pc.yellow(`warning: ${report.warning}`));
|
|
1093
1239
|
}
|
|
1094
1240
|
}
|
|
1241
|
+
function printGitHooks(report, json, action) {
|
|
1242
|
+
if (json) {
|
|
1243
|
+
console.log(JSON.stringify(report, null, 2));
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
if (action) {
|
|
1247
|
+
console.log(report.changed.length > 0
|
|
1248
|
+
? pc.green(`Ragmir auto-ingest ${action} in ${report.changed.join(", ")}.`)
|
|
1249
|
+
: `Git hooks already ${action === "installed" ? "refresh" : "skip"} the index.`);
|
|
1250
|
+
}
|
|
1251
|
+
console.log(`hooksDir=${report.hooksDir}`);
|
|
1252
|
+
console.log(`installed=${report.installed.join(",") || "none"}`);
|
|
1253
|
+
if (report.missing.length > 0) {
|
|
1254
|
+
console.log(`missing=${report.missing.join(",")}`);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1095
1257
|
function printGenerationGarbageCollection(report) {
|
|
1096
1258
|
console.log(`activeTableName=${report.activeTableName}`);
|
|
1097
1259
|
console.log(`resumableTableName=${report.resumableTableName ?? "none"}`);
|
|
1098
1260
|
console.log(`rollbackTableName=${report.rollbackTableName ?? "none"}`);
|
|
1099
1261
|
console.log(`generations=${report.generations.length}`);
|
|
1262
|
+
console.log(`storageDiskBytes=${report.storageDiskBytes}`);
|
|
1100
1263
|
console.log(`reclaimableBytes=${report.reclaimableBytes}`);
|
|
1101
1264
|
console.log(`reclaimedBytes=${report.reclaimedBytes}`);
|
|
1102
1265
|
console.log(`deletedTables=${report.deletedTables.join(",")}`);
|
|
1103
1266
|
for (const generation of report.generations) {
|
|
1104
|
-
console.log(`generation=${generation.tableName} role=${generation.role} bytes=${generation.bytes} ageMs=${generation.ageMs} leased=${generation.leased} reclaimable=${generation.reclaimable} deleted=${generation.deleted}`);
|
|
1267
|
+
console.log(`generation=${generation.tableName} role=${generation.role} bytes=${generation.bytes} diskBytes=${generation.diskBytes} ageMs=${generation.ageMs} leased=${generation.leased} reclaimable=${generation.reclaimable} deleted=${generation.deleted}`);
|
|
1268
|
+
}
|
|
1269
|
+
for (const orphan of report.orphans) {
|
|
1270
|
+
const removed = report.removedOrphans.includes(orphan.path);
|
|
1271
|
+
console.log(`orphan=${orphan.path} kind=${orphan.kind} diskBytes=${orphan.diskBytes} removed=${removed} reason=${JSON.stringify(orphan.reason)}`);
|
|
1272
|
+
}
|
|
1273
|
+
if (report.orphans.length > 0 && report.removedOrphans.length === 0) {
|
|
1274
|
+
console.log("Run `rgr storage gc --orphans` to delete the orphans listed above.");
|
|
1105
1275
|
}
|
|
1106
1276
|
if (report.warning) {
|
|
1107
1277
|
console.log(pc.yellow(`warning: ${report.warning}`));
|