@jcode.labs/ragmir 6.1.6 → 6.2.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.
Files changed (137) hide show
  1. package/README.md +43 -49
  2. package/dist/cli.js +194 -24
  3. package/dist/cli.js.map +1 -1
  4. package/dist/config.d.ts +24 -2
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +127 -24
  7. package/dist/config.js.map +1 -1
  8. package/dist/context-resources.d.ts.map +1 -1
  9. package/dist/context-resources.js +19 -8
  10. package/dist/context-resources.js.map +1 -1
  11. package/dist/defaults.d.ts +5 -0
  12. package/dist/defaults.d.ts.map +1 -1
  13. package/dist/defaults.js +16 -0
  14. package/dist/defaults.js.map +1 -1
  15. package/dist/doctor.d.ts.map +1 -1
  16. package/dist/doctor.js +37 -5
  17. package/dist/doctor.js.map +1 -1
  18. package/dist/evaluate.d.ts.map +1 -1
  19. package/dist/evaluate.js +11 -6
  20. package/dist/evaluate.js.map +1 -1
  21. package/dist/files.d.ts +27 -1
  22. package/dist/files.d.ts.map +1 -1
  23. package/dist/files.js +344 -100
  24. package/dist/files.js.map +1 -1
  25. package/dist/freshness.d.ts +16 -0
  26. package/dist/freshness.d.ts.map +1 -0
  27. package/dist/freshness.js +97 -0
  28. package/dist/freshness.js.map +1 -0
  29. package/dist/generation-retention.d.ts +12 -0
  30. package/dist/generation-retention.d.ts.map +1 -1
  31. package/dist/generation-retention.js +13 -2
  32. package/dist/generation-retention.js.map +1 -1
  33. package/dist/git-hooks.d.ts +25 -0
  34. package/dist/git-hooks.d.ts.map +1 -0
  35. package/dist/git-hooks.js +173 -0
  36. package/dist/git-hooks.js.map +1 -0
  37. package/dist/index-diagnostics.d.ts +9 -1
  38. package/dist/index-diagnostics.d.ts.map +1 -1
  39. package/dist/index-diagnostics.js +35 -0
  40. package/dist/index-diagnostics.js.map +1 -1
  41. package/dist/index-policy.d.ts.map +1 -1
  42. package/dist/index-policy.js +4 -0
  43. package/dist/index-policy.js.map +1 -1
  44. package/dist/ingest.d.ts +6 -0
  45. package/dist/ingest.d.ts.map +1 -1
  46. package/dist/ingest.js +44 -21
  47. package/dist/ingest.js.map +1 -1
  48. package/dist/init.d.ts.map +1 -1
  49. package/dist/init.js +19 -4
  50. package/dist/init.js.map +1 -1
  51. package/dist/knowledge-bases.d.ts +18 -1
  52. package/dist/knowledge-bases.d.ts.map +1 -1
  53. package/dist/knowledge-bases.js +73 -4
  54. package/dist/knowledge-bases.js.map +1 -1
  55. package/dist/mcp-output.d.ts +14 -3
  56. package/dist/mcp-output.d.ts.map +1 -1
  57. package/dist/mcp-output.js +37 -0
  58. package/dist/mcp-output.js.map +1 -1
  59. package/dist/mcp-registrations.d.ts +9 -0
  60. package/dist/mcp-registrations.d.ts.map +1 -0
  61. package/dist/mcp-registrations.js +247 -0
  62. package/dist/mcp-registrations.js.map +1 -0
  63. package/dist/mcp-scope.d.ts +32 -0
  64. package/dist/mcp-scope.d.ts.map +1 -0
  65. package/dist/mcp-scope.js +114 -0
  66. package/dist/mcp-scope.js.map +1 -0
  67. package/dist/mcp-session.d.ts +37 -0
  68. package/dist/mcp-session.d.ts.map +1 -0
  69. package/dist/mcp-session.js +53 -0
  70. package/dist/mcp-session.js.map +1 -0
  71. package/dist/mcp-summaries.d.ts +5 -0
  72. package/dist/mcp-summaries.d.ts.map +1 -1
  73. package/dist/mcp-summaries.js +30 -0
  74. package/dist/mcp-summaries.js.map +1 -1
  75. package/dist/mcp.d.ts +6 -0
  76. package/dist/mcp.d.ts.map +1 -1
  77. package/dist/mcp.js +410 -103
  78. package/dist/mcp.js.map +1 -1
  79. package/dist/ocr.d.ts +51 -0
  80. package/dist/ocr.d.ts.map +1 -1
  81. package/dist/ocr.js +210 -4
  82. package/dist/ocr.js.map +1 -1
  83. package/dist/package-manager.d.ts.map +1 -1
  84. package/dist/package-manager.js +2 -1
  85. package/dist/package-manager.js.map +1 -1
  86. package/dist/preview.d.ts.map +1 -1
  87. package/dist/preview.js +3 -7
  88. package/dist/preview.js.map +1 -1
  89. package/dist/quality-report.d.ts.map +1 -1
  90. package/dist/quality-report.js +1 -1
  91. package/dist/quality-report.js.map +1 -1
  92. package/dist/query.d.ts.map +1 -1
  93. package/dist/query.js +235 -34
  94. package/dist/query.js.map +1 -1
  95. package/dist/ranking.d.ts +56 -6
  96. package/dist/ranking.d.ts.map +1 -1
  97. package/dist/ranking.js +187 -16
  98. package/dist/ranking.js.map +1 -1
  99. package/dist/retrieval-diversity.d.ts +5 -0
  100. package/dist/retrieval-diversity.d.ts.map +1 -1
  101. package/dist/retrieval-diversity.js +4 -2
  102. package/dist/retrieval-diversity.js.map +1 -1
  103. package/dist/search-output.d.ts.map +1 -1
  104. package/dist/search-output.js +2 -0
  105. package/dist/search-output.js.map +1 -1
  106. package/dist/security.js +1 -1
  107. package/dist/security.js.map +1 -1
  108. package/dist/setup-prompt.d.ts +1 -1
  109. package/dist/setup-prompt.d.ts.map +1 -1
  110. package/dist/setup-prompt.js +3 -3
  111. package/dist/setup.js +1 -1
  112. package/dist/setup.js.map +1 -1
  113. package/dist/skill.d.ts +2 -0
  114. package/dist/skill.d.ts.map +1 -1
  115. package/dist/skill.js +122 -14
  116. package/dist/skill.js.map +1 -1
  117. package/dist/source-paths.d.ts +36 -0
  118. package/dist/source-paths.d.ts.map +1 -0
  119. package/dist/source-paths.js +157 -0
  120. package/dist/source-paths.js.map +1 -0
  121. package/dist/source-weights.d.ts +28 -0
  122. package/dist/source-weights.d.ts.map +1 -0
  123. package/dist/source-weights.js +241 -0
  124. package/dist/source-weights.js.map +1 -0
  125. package/dist/storage-maintenance.d.ts +3 -1
  126. package/dist/storage-maintenance.d.ts.map +1 -1
  127. package/dist/storage-maintenance.js +21 -2
  128. package/dist/storage-maintenance.js.map +1 -1
  129. package/dist/storage-usage.d.ts +21 -0
  130. package/dist/storage-usage.d.ts.map +1 -0
  131. package/dist/storage-usage.js +118 -0
  132. package/dist/storage-usage.js.map +1 -0
  133. package/dist/types.d.ts +127 -3
  134. package/dist/types.d.ts.map +1 -1
  135. package/dist/version.d.ts +1 -1
  136. package/dist/version.js +1 -1
  137. package/package.json +3 -1
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @jcode.labs/ragmir
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@jcode.labs/ragmir?color=cb3837&logo=npm)](https://www.npmjs.com/package/@jcode.labs/ragmir)
4
- [![npm downloads](https://img.shields.io/npm/dm/@jcode.labs/ragmir?color=0b7285)](https://www.npmjs.com/package/@jcode.labs/ragmir)
4
+ [![CI](https://github.com/jcode-works/jcode-ragmir/actions/workflows/ci.yml/badge.svg)](https://github.com/jcode-works/jcode-ragmir/actions/workflows/ci.yml)
5
5
  [![License: AGPL-3.0](https://img.shields.io/badge/license-AGPL--3.0-2f9e44)](https://github.com/jcode-works/jcode-ragmir/blob/main/LICENSE)
6
6
  [![Node.js 22.12+](https://img.shields.io/badge/node-%E2%89%A522.12-339933?logo=node.js&logoColor=white)](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 file paths
16
- and camelCase identifiers.
17
- - **Fast on real repositories:** 25 ms median search on a 21,546-chunk monorepo, 14 times faster
18
- than the previous release, with up to 35% better nDCG@10 on code and documentation
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. Expansion can detect when indexed evidence has changed.
22
- - **Private by default:** the `local-hash` provider works offline without downloading a model.
23
- Semantic embeddings are an explicit option.
24
- - **Safe to keep running:** incremental, resumable ingestion and validated rebuilds. Search keeps
25
- working while an upgrade rebuilds the index.
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
- The [quick start](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/quick-start.md) also
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
- ## MCP for developer agents
81
+ ## Four workflow examples
70
82
 
71
- `rgr setup --agents claude,codex` prepares a local MCP helper and a retrieval skill for the agents
72
- you select. The server exposes four bounded tools: `ragmir_status`, `ragmir_search`,
73
- `ragmir_expand`, and `ragmir_audit`. An agent can search compact results, open an exact cited
74
- passage, and search again when the evidence is incomplete. The consuming agent owns reasoning,
75
- generation, and actions. See [agent integration](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/agent-integration.md)
76
- for connection steps and local or self-hosted model examples.
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
- ## Four workflow examples
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
- 1. **Build a feature with Claude Code or Codex.** Ask the agent to implement account recovery from
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 { configurePdfOcr, extractPdfPage, extractPdfPages, inspectPdfOcr, parsePdfOcrEngine, parsePdfOcrPages, } from "./ocr.js";
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 PDF pages.");
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 inspectPdfOcr(projectRoot(command));
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 a local PDF OCR engine and write a safe batched-page configuration.")
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 configureOptions = {
108
- cwd: projectRoot(command),
109
- engine: parsePdfOcrEngine(options.engine, true),
110
- language: options.language,
111
- };
112
- addOption(configureOptions, "timeoutMs", options.timeoutMs);
113
- const result = await configurePdfOcr(configureOptions);
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(result, null, 2));
142
+ console.log(JSON.stringify({ pdf, local }, null, 2));
116
143
  return;
117
144
  }
118
- console.log(pc.green("Local PDF OCR configured."));
119
- console.log(`engine=${result.engine}`);
120
- console.log(`language=${result.language}`);
121
- console.log(`timeoutMs=${result.timeoutMs}`);
122
- console.log(`configPath=${result.configPath}`);
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 scanned PDF pages with local OCR.");
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
- console.log(`\n${pc.cyan(`[${index + 1}] ${result.citation}`)} chunk=${result.chunkIndex} distance=${distance}`);
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("Inspect or reclaim expired local index generations.")
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}`));