@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 +59 -91
- package/dist/cli.js +153 -0
- package/dist/cli.js.map +1 -1
- package/dist/index-diagnostics.d.ts +3 -2
- package/dist/index-diagnostics.d.ts.map +1 -1
- package/dist/index-diagnostics.js +13 -12
- package/dist/index-diagnostics.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/query.js +7 -7
- package/dist/query.js.map +1 -1
- package/dist/setup.js +4 -1
- package/dist/setup.js.map +1 -1
- package/dist/team-diagnostics.d.ts +115 -0
- package/dist/team-diagnostics.d.ts.map +1 -0
- package/dist/team-diagnostics.js +479 -0
- package/dist/team-diagnostics.js.map +1 -0
- package/dist/upgrade.d.ts +26 -0
- package/dist/upgrade.d.ts.map +1 -0
- package/dist/upgrade.js +83 -0
- package/dist/upgrade.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/examples/document-evidence-benchmark/README.md +25 -64
- package/examples/library-api-demo/README.md +17 -57
- package/examples/sovereign-rag-demo/README.md +37 -97
- package/package.json +5 -5
- package/skills/ragmir/SKILL.md +33 -0
package/README.md
CHANGED
|
@@ -5,21 +5,18 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@jcode.labs/ragmir)
|
|
6
6
|
[](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
|
-
|
|
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
|
|
17
|
-
[API
|
|
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.
|
|
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
|
-
|
|
33
|
-
`.ragmir/` state. Ingestion is incremental, resumable,
|
|
34
|
-
|
|
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
|
|
62
|
-
| Local stdio MCP | Bounded context for compatible
|
|
63
|
-
|
|
64
|
-
The default `local-hash` provider
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
-
|
|
121
|
-
|
|
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
|
|
126
|
-
integration
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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}`);
|