@jcode.labs/ragmir 2.0.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/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/access-log.d.ts +11 -0
- package/dist/access-log.d.ts.map +1 -0
- package/dist/access-log.js +145 -0
- package/dist/access-log.js.map +1 -0
- package/dist/chunking.d.ts +3 -0
- package/dist/chunking.d.ts.map +1 -0
- package/dist/chunking.js +58 -0
- package/dist/chunking.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +944 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +224 -0
- package/dist/config.js.map +1 -0
- package/dist/defaults.d.ts +17 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +59 -0
- package/dist/defaults.js.map +1 -0
- package/dist/destroy.d.ts +3 -0
- package/dist/destroy.d.ts.map +1 -0
- package/dist/destroy.js +16 -0
- package/dist/destroy.js.map +1 -0
- package/dist/doctor.d.ts +3 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +117 -0
- package/dist/doctor.js.map +1 -0
- package/dist/embeddings.d.ts +9 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +110 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/evaluate.d.ts +3 -0
- package/dist/evaluate.d.ts.map +1 -0
- package/dist/evaluate.js +97 -0
- package/dist/evaluate.js.map +1 -0
- package/dist/files.d.ts +16 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +388 -0
- package/dist/files.js.map +1 -0
- package/dist/gitignore.d.ts +3 -0
- package/dist/gitignore.d.ts.map +1 -0
- package/dist/gitignore.js +27 -0
- package/dist/gitignore.js.map +1 -0
- package/dist/guards.d.ts +2 -0
- package/dist/guards.d.ts.map +1 -0
- package/dist/guards.js +4 -0
- package/dist/guards.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest.d.ts +4 -0
- package/dist/ingest.d.ts.map +1 -0
- package/dist/ingest.js +268 -0
- package/dist/ingest.js.map +1 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +33 -0
- package/dist/init.js.map +1 -0
- package/dist/mcp.d.ts +8 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +189 -0
- package/dist/mcp.js.map +1 -0
- package/dist/package-manager.d.ts +11 -0
- package/dist/package-manager.d.ts.map +1 -0
- package/dist/package-manager.js +93 -0
- package/dist/package-manager.js.map +1 -0
- package/dist/parsing.d.ts +12 -0
- package/dist/parsing.d.ts.map +1 -0
- package/dist/parsing.js +307 -0
- package/dist/parsing.js.map +1 -0
- package/dist/query.d.ts +5 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +180 -0
- package/dist/query.js.map +1 -0
- package/dist/redaction.d.ts +7 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +93 -0
- package/dist/redaction.js.map +1 -0
- package/dist/research.d.ts +9 -0
- package/dist/research.d.ts.map +1 -0
- package/dist/research.js +335 -0
- package/dist/research.js.map +1 -0
- package/dist/security.d.ts +3 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +106 -0
- package/dist/security.js.map +1 -0
- package/dist/semantic-config.d.ts +9 -0
- package/dist/semantic-config.d.ts.map +1 -0
- package/dist/semantic-config.js +35 -0
- package/dist/semantic-config.js.map +1 -0
- package/dist/setup.d.ts +32 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +74 -0
- package/dist/setup.js.map +1 -0
- package/dist/skill.d.ts +66 -0
- package/dist/skill.d.ts.map +1 -0
- package/dist/skill.js +652 -0
- package/dist/skill.js.map +1 -0
- package/dist/sources.d.ts +16 -0
- package/dist/sources.d.ts.map +1 -0
- package/dist/sources.js +71 -0
- package/dist/sources.js.map +1 -0
- package/dist/store.d.ts +13 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +94 -0
- package/dist/store.js.map +1 -0
- package/dist/text.d.ts +3 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +10 -0
- package/dist/text.js.map +1 -0
- package/dist/types.d.ts +322 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/examples/library-api-demo/README.md +42 -0
- package/examples/library-api-demo/run.mjs +61 -0
- package/examples/sovereign-rag-demo/.ragmir/config.json +22 -0
- package/examples/sovereign-rag-demo/.ragmir/sources.txt +2 -0
- package/examples/sovereign-rag-demo/README.md +95 -0
- package/examples/sovereign-rag-demo/golden-queries.json +29 -0
- package/examples/sovereign-rag-demo/raw/dataset-inventory.csv +5 -0
- package/examples/sovereign-rag-demo/raw/facility-scan.heic +3 -0
- package/examples/sovereign-rag-demo/raw/incident-timeline.jsonl +4 -0
- package/examples/sovereign-rag-demo/raw/operations-brief.md +16 -0
- package/examples/sovereign-rag-demo/raw/review-notes.evidence +11 -0
- package/examples/sovereign-rag-demo/raw/security-policy.yaml +14 -0
- package/package.json +87 -0
- package/skills/ragmir/SKILL.md +366 -0
- package/skills/ragmir-audio-summary/SKILL.md +150 -0
- package/skills/ragmir-audio-summary/forge-voice.sh +150 -0
- package/skills/ragmir-audio-summary/split-lines.py +13 -0
- package/skills/ragmir-audio-summary/xtts-voice.py +46 -0
- package/skills/ragmir-legal-dossier/SKILL.md +143 -0
- package/skills/ragmir-markdown-report/SKILL.md +110 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ragmir
|
|
3
|
+
description: Use this skill whenever a repository uses or should use Ragmir, local-first RAG, private project knowledge, document ingestion, knowledge-base audit, or MCP access to project documents. Use it before answering from memory when the user asks about facts that may be present in private files, asks to ingest/query/audit documents, or wants Claude Code, Codex, Kimi, OpenCode, Cline, or another AI agent to use the same local knowledge base.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Ragmir
|
|
7
|
+
|
|
8
|
+
Ragmir is a sovereign local RAG knowledge base for confidential project documents and datasets. It
|
|
9
|
+
indexes files from the current repository, stores vectors locally, and exposes both a CLI and an MCP
|
|
10
|
+
server.
|
|
11
|
+
|
|
12
|
+
Use this skill to help an AI agent work with a Ragmir-enabled repository without leaking private documents or relying on stale memory.
|
|
13
|
+
|
|
14
|
+
## Core Rule
|
|
15
|
+
|
|
16
|
+
Treat the repository where the user is working as the source of truth. Ragmir data belongs to that repository, not to the installed npm package.
|
|
17
|
+
|
|
18
|
+
Default project layout:
|
|
19
|
+
|
|
20
|
+
```plain text
|
|
21
|
+
.ragmir/config.json # local Ragmir config
|
|
22
|
+
.ragmir/sources.txt # optional extra source paths
|
|
23
|
+
.ragmir/raw/ # raw documents to ingest
|
|
24
|
+
.ragmir/storage/ # generated local index
|
|
25
|
+
.ragmir/access.log # metadata-only access log
|
|
26
|
+
.ragmir/reports/ # generated local Markdown reports
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Data Safety
|
|
30
|
+
|
|
31
|
+
- Do not commit raw documents, secrets, tax IDs, scans, bank documents, tokens, or generated vector stores.
|
|
32
|
+
- Keep `.ragmir/` ignored by Git. Legacy projects using `private/`, `private/**`, or `.kb/` must keep those paths
|
|
33
|
+
ignored too.
|
|
34
|
+
- Treat `ragmir search`, `ragmir ask`, `ragmir research`, and MCP results as sensitive because they can
|
|
35
|
+
contain private source passages even when redaction is enabled.
|
|
36
|
+
- Prefer summaries and citations over dumping long private passages into the chat.
|
|
37
|
+
- If the user asks for a high-stakes answer, identify which facts came from Ragmir and which still require professional or official verification.
|
|
38
|
+
|
|
39
|
+
## First Checks
|
|
40
|
+
|
|
41
|
+
From the repository root:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm exec ragmir doctor
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If Ragmir is installed but setup is incomplete or the index is stale:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm exec ragmir doctor --fix
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If Ragmir is not installed:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pnpm add -D @jcode.labs/ragmir
|
|
57
|
+
pnpm exec ragmir setup
|
|
58
|
+
# Optional: one-time model download for higher-quality semantic retrieval.
|
|
59
|
+
pnpm exec ragmir setup --semantic
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
When the repository should expose only specific agent helpers or must launch MCP through a local
|
|
63
|
+
wrapper, generate the agent kit explicitly:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pnpm exec ragmir setup --agents claude,codex --mcp-command ./scripts/serve-mcp.sh
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
If the package manager is npm:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm install --save-dev @jcode.labs/ragmir
|
|
73
|
+
npx ragmir setup
|
|
74
|
+
# Optional: one-time model download for higher-quality semantic retrieval.
|
|
75
|
+
npx ragmir setup --semantic
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Use `status`, `audit`, and `security-audit` for deeper checks after `doctor` explains the current
|
|
79
|
+
state. Use `audit --unsupported` when files exist but may not have been indexed.
|
|
80
|
+
|
|
81
|
+
## Agent Operating Loop
|
|
82
|
+
|
|
83
|
+
Prefer the smallest command that answers the user's task:
|
|
84
|
+
|
|
85
|
+
| Task | Preferred command or tool |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| Check whether Ragmir is usable | `ragmir doctor` or MCP `ragmir_status` |
|
|
88
|
+
| Repair missing setup or stale index | `ragmir doctor --fix` |
|
|
89
|
+
| Inspect skipped, duplicate, archive-like, or mirror-like sources | `ragmir audit --unsupported` |
|
|
90
|
+
| Find exact source passages | `ragmir search "<query>" --compact` or MCP `ragmir_search` with `compact: true` |
|
|
91
|
+
| Prepare a broad implementation, review, or planning answer | `ragmir research "<topic>" --compact` or MCP `ragmir_research` |
|
|
92
|
+
| Return deterministic cited context for a trusted model | `ragmir ask "<question>"` or MCP `ragmir_ask` |
|
|
93
|
+
| Check local privacy posture | `ragmir security-audit` or MCP `ragmir_security_audit` |
|
|
94
|
+
| Validate recall against known expected files | `ragmir evaluate --golden <file>` or MCP `ragmir_evaluate` |
|
|
95
|
+
|
|
96
|
+
## Provider Modes
|
|
97
|
+
|
|
98
|
+
Default retrieval mode:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"embeddingProvider": "local-hash"
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This supports ingestion, search, MCP retrieval, and `ragmir ask` with cited passages without a model
|
|
107
|
+
server. It is lexical/hash retrieval, not model-semantic search. Do not present it as equivalent to
|
|
108
|
+
semantic embeddings.
|
|
109
|
+
|
|
110
|
+
Optional semantic embedding mode:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"embeddingProvider": "transformers",
|
|
115
|
+
"embeddingModel": "mixedbread-ai/mxbai-embed-xsmall-v1",
|
|
116
|
+
"embeddingModelPath": ".ragmir/models",
|
|
117
|
+
"transformersAllowRemoteModels": false
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
This uses Transformers.js for embeddings only. Keep `transformersAllowRemoteModels` false for
|
|
122
|
+
air-gapped or confidential work and preload model files under `embeddingModelPath`. Use the
|
|
123
|
+
first-run shortcut when a one-time download is acceptable:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
pnpm exec ragmir setup --semantic
|
|
127
|
+
pnpm exec ragmir ingest --rebuild
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Or enable it later:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pnpm exec ragmir models pull --enable
|
|
134
|
+
pnpm exec ragmir ingest --rebuild
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Ingestion Workflow
|
|
138
|
+
|
|
139
|
+
After documents are added or changed:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
pnpm exec ragmir doctor --fix
|
|
143
|
+
pnpm exec ragmir audit
|
|
144
|
+
pnpm exec ragmir audit --unsupported
|
|
145
|
+
pnpm exec ragmir security-audit
|
|
146
|
+
pnpm exec ragmir status
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`ragmir doctor --fix` updates the index only when supported files are present and the privacy posture
|
|
150
|
+
has no warnings. Normal `ragmir ingest` reuses unchanged rows; use `ragmir ingest --rebuild` after changing
|
|
151
|
+
embedding provider/model or chunking settings. `ragmir ingest --json` reports `emptyTextFiles` when
|
|
152
|
+
supported files, typically scanned PDFs, produce no indexable text. `ragmir doctor` should show
|
|
153
|
+
`ready=true` before relying on the index. The audit must show no missing or stale supported files, and
|
|
154
|
+
the security audit should not show warnings before relying on Ragmir for sensitive work.
|
|
155
|
+
|
|
156
|
+
Default retrieval is tuned for broader recall (`topK: 8`, `chunkOverlap: 200`). Keep MCP retrieval
|
|
157
|
+
bounded by `mcpMaxTopK`, and raise `--top-k` only when the first results are too narrow.
|
|
158
|
+
|
|
159
|
+
For monorepos, keep raw confidential files local and list useful repo docs through `.ragmir/sources.txt`.
|
|
160
|
+
Entries can be paths or glob patterns relative to the Ragmir project root, with `!` exclusions:
|
|
161
|
+
|
|
162
|
+
```plain text
|
|
163
|
+
../apps/*/README.md
|
|
164
|
+
../apps/*/docs/**/*.md
|
|
165
|
+
!../apps/**/node_modules/**
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Use the CLI when you want agents or setup scripts to update the file without manual editing:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
pnpm exec ragmir sources add "../apps/*/README.md" "../apps/*/docs/**/*.md"
|
|
172
|
+
pnpm exec ragmir sources add "!../apps/**/node_modules/**"
|
|
173
|
+
pnpm exec ragmir sources list
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Query Workflow
|
|
177
|
+
|
|
178
|
+
Use search when you need exact source passages:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
pnpm exec ragmir search "your query"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Use research when the user asks for broad context, implementation planning, review preparation, or a
|
|
185
|
+
cross-document audit:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
pnpm exec ragmir research "your topic" --compact
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`ragmir research` runs audit and security checks, generates several related retrieval queries, merges
|
|
192
|
+
cited evidence, reports source diagnostics, and performs a lightweight repository code scan unless
|
|
193
|
+
`--no-code` is used.
|
|
194
|
+
|
|
195
|
+
Use ask when you need cited context for the current agent or an external LLM:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
pnpm exec ragmir ask "your question"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Ground answers in returned sources. If search results are weak, say that the current index does not
|
|
202
|
+
prove the point and ask for the missing document. `ragmir ask` returns cited passages rather than LLM
|
|
203
|
+
synthesis. Use those passages as context for the current agent, or tell the user that generative
|
|
204
|
+
synthesis needs a trusted external LLM or model runtime.
|
|
205
|
+
|
|
206
|
+
## Deep Research Workflow
|
|
207
|
+
|
|
208
|
+
For broad summaries, audits, planning, or institutional dossiers, do not rely on one query. Build a
|
|
209
|
+
small retrieval plan first:
|
|
210
|
+
|
|
211
|
+
- use `ragmir research` as the first pass when available;
|
|
212
|
+
- check `ragmir audit` and `ragmir security-audit`;
|
|
213
|
+
- query the main topic;
|
|
214
|
+
- query names, dates, amounts, obligations, risks, decisions, and missing evidence separately;
|
|
215
|
+
- compare the strongest passages across files;
|
|
216
|
+
- ask a synthesis question only after search has found enough grounded context;
|
|
217
|
+
- cite source paths and chunk numbers in the answer when useful;
|
|
218
|
+
- explicitly say when the index does not prove a claim.
|
|
219
|
+
|
|
220
|
+
For sensitive work, prefer the smallest useful `topK`; raise it only when the first results are too
|
|
221
|
+
thin. Do not dump large raw passages into the chat unless the user explicitly asks for extracts.
|
|
222
|
+
|
|
223
|
+
## MCP Usage
|
|
224
|
+
|
|
225
|
+
If the agent supports MCP, configure a server for the repository:
|
|
226
|
+
|
|
227
|
+
```json
|
|
228
|
+
{
|
|
229
|
+
"mcpServers": {
|
|
230
|
+
"ragmir": {
|
|
231
|
+
"command": "pnpm",
|
|
232
|
+
"args": ["exec", "ragmir", "serve-mcp"],
|
|
233
|
+
"cwd": "/absolute/path/to/the/repository"
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Generated MCP helpers use the `ragmir` server name by default. Use `ragmir setup --mcp-name <name>`
|
|
240
|
+
or `ragmir install-skill --mcp-name <name>` only when the repository needs a different stable MCP
|
|
241
|
+
key.
|
|
242
|
+
|
|
243
|
+
For Claude Code, run this from the target repository root after `pnpm exec ragmir setup`:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
claude mcp add-json --scope local ragmir "$(cat .ragmir/claude-mcp-server.json)"
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
For Codex, copy `.ragmir/codex-mcp.toml` into `~/.codex/config.toml` or another trusted Codex config
|
|
250
|
+
layer. It includes both the Ragmir MCP server and `skills.config` entries.
|
|
251
|
+
|
|
252
|
+
For Kimi Code CLI, run from the target repository root:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
kimi --mcp-config-file .ragmir/kimi-mcp.json
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
For OpenCode, merge `.ragmir/opencode.jsonc` into the OpenCode config layer used by the project.
|
|
259
|
+
|
|
260
|
+
For Cline, add `.ragmir/cline-mcp.json` under `mcpServers` in Cline's MCP configuration.
|
|
261
|
+
|
|
262
|
+
For other MCP clients that cannot set `cwd`, set `RAGMIR_PROJECT_ROOT=/absolute/path/to/repository`
|
|
263
|
+
when launching `ragmir serve-mcp`. `RAGMIR_PROJECT_ROOT` is the strongest signal; otherwise a
|
|
264
|
+
configured current working directory wins before agent-provided project environment variables.
|
|
265
|
+
|
|
266
|
+
Available MCP tools:
|
|
267
|
+
|
|
268
|
+
- `ragmir_status`: show config and chunk count.
|
|
269
|
+
- `ragmir_search`: retrieve source passages; set `compact: true` when the agent context is tight.
|
|
270
|
+
- `ragmir_ask`: return cited retrieval context.
|
|
271
|
+
- `ragmir_research`: run audit-backed multi-query retrieval with source diagnostics and optional code matches.
|
|
272
|
+
- `ragmir_audit`: compare source files with the current index.
|
|
273
|
+
- `ragmir_evaluate`: measure retrieval recall against a local golden query file.
|
|
274
|
+
- `ragmir_usage_report`: summarize metadata-only local access-log activity without query text or local paths.
|
|
275
|
+
- `ragmir_security_audit`: inspect local privacy, provider, redaction, MCP, and gitignore posture.
|
|
276
|
+
|
|
277
|
+
Prefer MCP tools over shell commands when the agent runtime provides them. Use shell commands when MCP is unavailable.
|
|
278
|
+
|
|
279
|
+
MCP is read-focused and intentionally does not expose index deletion. Use `pnpm exec ragmir
|
|
280
|
+
destroy-index --yes` from the shell when the user explicitly wants to remove the generated index.
|
|
281
|
+
|
|
282
|
+
## Optional Audio Summaries
|
|
283
|
+
|
|
284
|
+
If the user asks for a listenable or TTS summary, load the optional
|
|
285
|
+
`.ragmir/skills/ragmir-audio-summary/` skill installed by `pnpm exec ragmir setup`.
|
|
286
|
+
|
|
287
|
+
The audio skill should:
|
|
288
|
+
|
|
289
|
+
- gather evidence through Ragmir first;
|
|
290
|
+
- write narration text only to a temp file outside the repository;
|
|
291
|
+
- render generated audio under `.ragmir/audio/` by default;
|
|
292
|
+
- prefer offline TTS engines for confidential content.
|
|
293
|
+
|
|
294
|
+
## Optional Markdown Reports
|
|
295
|
+
|
|
296
|
+
If the user asks for a Markdown report, dossier, audit memo, planning note, or decision brief, load
|
|
297
|
+
the optional `.ragmir/skills/ragmir-markdown-report/` skill installed by `pnpm exec ragmir setup`.
|
|
298
|
+
|
|
299
|
+
The report skill should:
|
|
300
|
+
|
|
301
|
+
- gather evidence through multiple Ragmir searches first;
|
|
302
|
+
- cite source paths and chunk numbers;
|
|
303
|
+
- separate facts, inference, uncertainty, and missing evidence;
|
|
304
|
+
- write reports under `.ragmir/reports/` by default;
|
|
305
|
+
- keep generated reports uncommitted unless the user explicitly wants a sanitized tracked report.
|
|
306
|
+
|
|
307
|
+
## Installing This Skill Into A Repository
|
|
308
|
+
|
|
309
|
+
Most repositories should run the full setup command:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
pnpm exec ragmir setup
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Use the lower-level skill installer only when Ragmir is already initialized and you want to refresh
|
|
316
|
+
the local agent kit:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
pnpm exec ragmir install-skill
|
|
320
|
+
pnpm exec ragmir install-skill --agents claude,codex --mcp-command ./scripts/serve-mcp.sh
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
This creates:
|
|
324
|
+
|
|
325
|
+
```plain text
|
|
326
|
+
.ragmir/skills/ragmir/SKILL.md
|
|
327
|
+
.ragmir/skills/ragmir-audio-summary/SKILL.md
|
|
328
|
+
.ragmir/skills/ragmir-markdown-report/SKILL.md
|
|
329
|
+
.ragmir/skills/ragmir-legal-dossier/SKILL.md
|
|
330
|
+
.ragmir/mcp.json
|
|
331
|
+
.ragmir/claude-mcp-server.json
|
|
332
|
+
.ragmir/codex-mcp.toml
|
|
333
|
+
.ragmir/kimi-mcp.json
|
|
334
|
+
.ragmir/opencode.jsonc
|
|
335
|
+
.ragmir/cline-mcp.json
|
|
336
|
+
.ragmir/agent-setup.md
|
|
337
|
+
.ragmir/README.md
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
When `--agents` is used, only the selected agent-specific MCP helpers are written, and stale
|
|
341
|
+
unselected helpers in `.ragmir/` are removed.
|
|
342
|
+
|
|
343
|
+
For native discovery, install only the agent the user uses:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
pnpm exec ragmir install-agent --agents claude
|
|
347
|
+
pnpm exec ragmir install-agent --agents kimi
|
|
348
|
+
pnpm exec ragmir install-agent --agents claude,codex,kimi,opencode,cline
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
By default this writes project-scope skill folders such as `.claude/skills/`, `.kimi/skills/`,
|
|
352
|
+
`.opencode/skills/`, or `.cline/skills/` as links back to `.ragmir/skills/`. That keeps one original
|
|
353
|
+
skill source. Add `--scope user` for global installs, or `--mode copy` only when an agent/runtime
|
|
354
|
+
cannot follow symlinked skill directories.
|
|
355
|
+
|
|
356
|
+
Agents that understand skill folders can load `.ragmir/skills/ragmir/` directly when native discovery
|
|
357
|
+
is not installed. Other agents can read `.ragmir/README.md` and `.ragmir/mcp.json`.
|
|
358
|
+
|
|
359
|
+
## Answer Style
|
|
360
|
+
|
|
361
|
+
When answering from Ragmir:
|
|
362
|
+
|
|
363
|
+
- mention the source file paths and chunk/source labels when useful;
|
|
364
|
+
- distinguish facts found in documents from inference;
|
|
365
|
+
- keep operational/legal/financial claims conservative;
|
|
366
|
+
- recommend ingesting or providing missing documents when the index is incomplete.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ragmir-audio-summary
|
|
3
|
+
description: >-
|
|
4
|
+
Create an optional spoken audio summary from a Ragmir local knowledge base. Use when the user asks
|
|
5
|
+
for an audio, TTS, spoken brief, briefing, narration, or listenable summary based on private
|
|
6
|
+
repository documents indexed by Ragmir. The skill is confidentiality-first: gather facts through
|
|
7
|
+
Ragmir, write only a temporary narration text file outside the repository, and render the final
|
|
8
|
+
audio under ignored local Ragmir state unless the user explicitly chooses another output path.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Ragmir Audio Summary
|
|
12
|
+
|
|
13
|
+
Use this skill to turn a confidential local Ragmir knowledge base into an optional audio summary.
|
|
14
|
+
The knowledge base stays local; the final audio is a generated artifact and must not be committed.
|
|
15
|
+
|
|
16
|
+
## Confidentiality Rules
|
|
17
|
+
|
|
18
|
+
- Treat the source documents, retrieved passages, generated narration, and final audio as sensitive.
|
|
19
|
+
- Do not use online TTS for confidential content unless the user explicitly allows it.
|
|
20
|
+
- Prefer `pnpm exec ragmir audio` or `pnpm exec ragmir-tts render` for plug-and-play output.
|
|
21
|
+
- Use `--engine transformers --offline` when model files are already present and remote model
|
|
22
|
+
loading is not allowed.
|
|
23
|
+
- Use `--engine edge` only when online TTS is acceptable and global Voice Forge quality is required.
|
|
24
|
+
- Write the narration text to a temp file outside the repository, such as `/tmp/RAGMIR-SUMMARY-topic.txt`.
|
|
25
|
+
- Render audio under `.ragmir/audio/` by default. This directory is ignored by Git when Ragmir is installed.
|
|
26
|
+
- Never stage or commit generated audio, temporary text, WAV, AIFF, or intermediate files.
|
|
27
|
+
|
|
28
|
+
## 1. Verify The Knowledge Base
|
|
29
|
+
|
|
30
|
+
From the repository root, run:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm exec ragmir doctor
|
|
34
|
+
pnpm exec ragmir status
|
|
35
|
+
pnpm exec ragmir audit
|
|
36
|
+
pnpm exec ragmir audit --unsupported
|
|
37
|
+
pnpm exec ragmir security-audit
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
If the audit reports missing or stale files, run:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pnpm exec ragmir doctor --fix
|
|
44
|
+
pnpm exec ragmir audit --unsupported
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`ragmir doctor --fix` rebuilds the index only when supported files are present and the privacy posture
|
|
48
|
+
has no warnings. Do not create an audio summary from stale or incomplete evidence unless the user
|
|
49
|
+
explicitly accepts that limitation.
|
|
50
|
+
|
|
51
|
+
## 2. Search Deeply Before Writing
|
|
52
|
+
|
|
53
|
+
Use Ragmir search or MCP tools to gather evidence before drafting the narration.
|
|
54
|
+
|
|
55
|
+
For a broad summary, run multiple searches:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pnpm exec ragmir search "<main topic>" --top-k 8
|
|
59
|
+
pnpm exec ragmir search "<people, dates, money, obligations, risks, or decisions>" --top-k 8
|
|
60
|
+
pnpm exec ragmir ask "<specific synthesis question>" --top-k 8
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
When MCP is available, prefer `ragmir_search`, `ragmir_ask`, `ragmir_audit`, and
|
|
64
|
+
`ragmir_security_audit` over shell commands.
|
|
65
|
+
|
|
66
|
+
Keep citations in your working notes, but do not read long raw passages aloud. The audio should be a
|
|
67
|
+
clear synthesis, not a dump of source text.
|
|
68
|
+
|
|
69
|
+
## 3. Write For Listening
|
|
70
|
+
|
|
71
|
+
Write one flowing narration in the user's working language. Do not use markdown, headings, bullets,
|
|
72
|
+
tables, SSML, XML tags, or stage directions in the spoken text.
|
|
73
|
+
|
|
74
|
+
Good audio structure:
|
|
75
|
+
|
|
76
|
+
1. Start with the purpose of the summary and the two-to-four ideas to retain.
|
|
77
|
+
2. Explain the current evidence in plain language.
|
|
78
|
+
3. Separate proven facts from uncertainty.
|
|
79
|
+
4. Highlight decisions, risks, deadlines, and missing documents.
|
|
80
|
+
5. End with a concise recap and two or three self-check questions.
|
|
81
|
+
|
|
82
|
+
Use short sentences and natural punctuation. Spell acronyms and symbols in a way a TTS engine can
|
|
83
|
+
pronounce.
|
|
84
|
+
|
|
85
|
+
## 4. Render The Audio
|
|
86
|
+
|
|
87
|
+
Create the output directory and write the narration to a temp file outside the repo:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
mkdir -p .ragmir/audio
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Select the narration language with `--lang en|es|fr` (default `fr`). It picks the matching
|
|
94
|
+
self-contained offline model (English, Spanish, or French) for the Transformers.js path and a native
|
|
95
|
+
neural voice for the Edge path. Write the narration in the same language you pass to `--lang`.
|
|
96
|
+
|
|
97
|
+
For global Voice Forge quality on non-confidential text, render with Edge MP3:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pnpm exec ragmir audio /tmp/RAGMIR-SUMMARY-<subject-kebab>.txt \
|
|
101
|
+
--engine edge \
|
|
102
|
+
--lang <en|es|fr> \
|
|
103
|
+
--out .ragmir/audio/RAGMIR-SUMMARY-<subject-kebab>.mp3
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The Edge path uses the online Edge TTS service through the `edge-tts` CLI. Use it only when sending
|
|
107
|
+
the narration text to that service is acceptable.
|
|
108
|
+
|
|
109
|
+
For confidential or air-gapped operation, preload the model files under `.ragmir/models/tts` and run:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pnpm exec ragmir audio /tmp/RAGMIR-SUMMARY-<subject-kebab>.txt \
|
|
113
|
+
--engine transformers \
|
|
114
|
+
--offline \
|
|
115
|
+
--lang <en|es|fr> \
|
|
116
|
+
--model-path .ragmir/models/tts \
|
|
117
|
+
--out .ragmir/audio/RAGMIR-SUMMARY-<subject-kebab>.wav
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The Transformers.js path does not require Python, ffmpeg, Piper, XTTS, or a local TTS server. Remote
|
|
121
|
+
model downloads are disabled by default and require `--allow-remote-models` for an explicit
|
|
122
|
+
non-sensitive preload into `.ragmir/models/tts`. Preload with a synthetic non-sensitive sentence
|
|
123
|
+
first; the repository guide is `docs/offline-tts-preload.md`.
|
|
124
|
+
|
|
125
|
+
Use the voice-forge helper only when the user explicitly wants XTTS, macOS `say`, or Piper:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
OUT_MP3="<repo-root>/.ragmir/audio/RAGMIR-SUMMARY-<subject-kebab>.mp3" \
|
|
129
|
+
TTS_ENGINE=auto \
|
|
130
|
+
bash <this-skill-dir>/forge-voice.sh /tmp/RAGMIR-SUMMARY-<subject-kebab>.txt
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Helper engine selection:
|
|
134
|
+
|
|
135
|
+
- `auto`: Edge first when installed, then XTTS, macOS `say`, and Piper.
|
|
136
|
+
- `edge`: online Edge TTS with the global Voice Forge default voice.
|
|
137
|
+
- `xtts`: local Coqui XTTS-v2.
|
|
138
|
+
- `say`: local macOS speech engine, converted to MP3 with `ffmpeg`.
|
|
139
|
+
- `piper`: local neural TTS, converted to MP3 with `ffmpeg`.
|
|
140
|
+
|
|
141
|
+
Voice can be selected with `TTS_VOICE`. Speed for Edge can be selected with `TTS_RATE`.
|
|
142
|
+
|
|
143
|
+
## 5. Report The Result
|
|
144
|
+
|
|
145
|
+
After rendering, report:
|
|
146
|
+
|
|
147
|
+
- the audio path;
|
|
148
|
+
- which renderer/model was used or requested;
|
|
149
|
+
- whether remote model downloads or online TTS were allowed;
|
|
150
|
+
- any evidence limitation, such as stale index, missing documents, or weak search results.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Render a Ragmir audio-summary text file to MP3. The text is a throwaway intermediate written
|
|
3
|
+
# outside the repository by the skill. The final audio should normally be written under .ragmir/audio.
|
|
4
|
+
#
|
|
5
|
+
# Engine via TTS_ENGINE (auto|edge|xtts|say|piper); default "auto" matches the global Voice Forge
|
|
6
|
+
# quality path:
|
|
7
|
+
# edge - edge-tts neural voices, single request (online). Default; clean on normal text.
|
|
8
|
+
# Set TTS_SEGMENT=1 to render sentence-by-sentence for long-text truncation.
|
|
9
|
+
# xtts - local Coqui XTTS-v2 when installed.
|
|
10
|
+
# say - macOS built-in, offline, clean but robotic.
|
|
11
|
+
# piper - local neural TTS.
|
|
12
|
+
#
|
|
13
|
+
# Usage: forge-voice.sh <text-file> [voice]
|
|
14
|
+
# Env: OUT_MP3 explicit mp3 output path
|
|
15
|
+
# TTS_ENGINE force an engine (default: auto)
|
|
16
|
+
# TTS_VOICE voice/speaker (engine-specific); the [voice] arg overrides it
|
|
17
|
+
# TTS_RATE edge-tts speed delta, default +0%
|
|
18
|
+
# TTS_SEGMENT=1 edge: render sentence-by-sentence
|
|
19
|
+
# XTTS_SPEAKER xtts preset speaker (default Ana Florence)
|
|
20
|
+
# PIPER_MODEL piper onnx model path
|
|
21
|
+
# KEEP_TEXT=1 keep the source text file after a successful render
|
|
22
|
+
set -euo pipefail
|
|
23
|
+
|
|
24
|
+
TXT="${1:?usage: forge-voice.sh <text-file> [voice]}"
|
|
25
|
+
[ -f "$TXT" ] || { echo "error: file not found: $TXT" >&2; exit 1; }
|
|
26
|
+
VOICE="${2:-${TTS_VOICE:-}}"
|
|
27
|
+
ENGINE="${TTS_ENGINE:-auto}"
|
|
28
|
+
OUT_FINAL="${OUT_MP3:-${TXT%.txt}.mp3}"
|
|
29
|
+
OUTBASE="${OUT_FINAL%.mp3}"
|
|
30
|
+
|
|
31
|
+
cleanup() {
|
|
32
|
+
[ "${KEEP_TEXT:-0}" = "1" ] || rm -f "$TXT"
|
|
33
|
+
rm -f "${OUTBASE}.wav" "${OUTBASE}.aiff"
|
|
34
|
+
}
|
|
35
|
+
trap cleanup EXIT
|
|
36
|
+
|
|
37
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
38
|
+
XTTS_PY="${XTTS_PY:-$HOME/.local/share/voice-forge/xtts/bin/python}"
|
|
39
|
+
XTTS_HELPER="$SCRIPT_DIR/xtts-voice.py"
|
|
40
|
+
|
|
41
|
+
xtts_ready() { [ -x "$XTTS_PY" ] && [ -f "$XTTS_HELPER" ]; }
|
|
42
|
+
|
|
43
|
+
finish() {
|
|
44
|
+
echo "$1"
|
|
45
|
+
exit 0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
to_mp3() {
|
|
49
|
+
local src="$1" out="${OUTBASE}.mp3"
|
|
50
|
+
if ! command -v ffmpeg >/dev/null 2>&1; then
|
|
51
|
+
rm -f "$src"
|
|
52
|
+
echo "error: ffmpeg is required to convert local TTS output to mp3" >&2
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
if ffmpeg -y -loglevel error -i "$src" -ac 1 -c:a libmp3lame -q:a 4 "$out"; then
|
|
56
|
+
rm -f "$src"
|
|
57
|
+
finish "$out"
|
|
58
|
+
fi
|
|
59
|
+
rm -f "$src" "$out"
|
|
60
|
+
echo "error: failed to convert local TTS output to mp3" >&2
|
|
61
|
+
exit 1
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if [ "$ENGINE" = "edge" ] || [ "$ENGINE" = "auto" ]; then
|
|
65
|
+
if command -v edge-tts >/dev/null 2>&1; then
|
|
66
|
+
OUT="${OUTBASE}.mp3"
|
|
67
|
+
SPLIT="$SCRIPT_DIR/split-lines.py"
|
|
68
|
+
voice="${VOICE:-fr-FR-DeniseNeural}"
|
|
69
|
+
rate="${TTS_RATE:-+0%}"
|
|
70
|
+
if [ "${TTS_SEGMENT:-0}" = "1" ] && [ -f "$SPLIT" ] \
|
|
71
|
+
&& command -v ffmpeg >/dev/null 2>&1 && command -v python3 >/dev/null 2>&1; then
|
|
72
|
+
TMP="$(mktemp -d)"
|
|
73
|
+
ffmpeg -y -loglevel error -f lavfi -i anullsrc=r=24000:cl=mono -t 0.28 \
|
|
74
|
+
-c:a libmp3lame -q:a 4 "$TMP/sil.mp3"
|
|
75
|
+
i=0
|
|
76
|
+
: > "$TMP/list.txt"
|
|
77
|
+
while IFS= read -r line; do
|
|
78
|
+
[ -z "$line" ] && continue
|
|
79
|
+
i=$((i + 1))
|
|
80
|
+
if edge-tts --text "$line" --voice "$voice" --rate="$rate" \
|
|
81
|
+
--write-media "$TMP/seg_$i.mp3" >/dev/null 2>&1; then
|
|
82
|
+
printf "file '%s'\n" "$TMP/seg_$i.mp3" >> "$TMP/list.txt"
|
|
83
|
+
printf "file '%s'\n" "$TMP/sil.mp3" >> "$TMP/list.txt"
|
|
84
|
+
else
|
|
85
|
+
echo "warn: edge-tts failed on a sentence, skipping it" >&2
|
|
86
|
+
fi
|
|
87
|
+
done < <(python3 "$SPLIT" "$TXT")
|
|
88
|
+
ffmpeg -y -loglevel error -f concat -safe 0 -i "$TMP/list.txt" \
|
|
89
|
+
-ac 1 -c:a libmp3lame -q:a 4 "$OUT"
|
|
90
|
+
rm -rf "$TMP"
|
|
91
|
+
finish "$OUT"
|
|
92
|
+
fi
|
|
93
|
+
edge-tts --file "$TXT" --voice "$voice" --rate="$rate" --write-media "$OUT" >/dev/null
|
|
94
|
+
finish "$OUT"
|
|
95
|
+
fi
|
|
96
|
+
if [ "$ENGINE" = "edge" ]; then
|
|
97
|
+
echo "error: TTS_ENGINE=edge but edge-tts not installed (pipx install edge-tts)" >&2
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
if [ "$ENGINE" = "xtts" ] || { [ "$ENGINE" = "auto" ] && xtts_ready; }; then
|
|
103
|
+
if ! xtts_ready; then
|
|
104
|
+
echo "error: TTS_ENGINE=xtts but venv/helper missing ($XTTS_PY)" >&2
|
|
105
|
+
exit 1
|
|
106
|
+
fi
|
|
107
|
+
WAV="${OUTBASE}.wav"
|
|
108
|
+
if [ -n "$VOICE" ]; then
|
|
109
|
+
COQUI_TOS_AGREED=1 "$XTTS_PY" "$XTTS_HELPER" "$TXT" "$WAV" "$VOICE" >&2
|
|
110
|
+
else
|
|
111
|
+
COQUI_TOS_AGREED=1 "$XTTS_PY" "$XTTS_HELPER" "$TXT" "$WAV" >&2
|
|
112
|
+
fi
|
|
113
|
+
to_mp3 "$WAV"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
if [ "$ENGINE" = "say" ] || [ "$ENGINE" = "auto" ]; then
|
|
117
|
+
if command -v say >/dev/null 2>&1; then
|
|
118
|
+
AIFF="${OUTBASE}.aiff"
|
|
119
|
+
say -v "${VOICE:-Jacques}" -f "$TXT" -o "$AIFF" 2>/dev/null || say -f "$TXT" -o "$AIFF"
|
|
120
|
+
to_mp3 "$AIFF"
|
|
121
|
+
fi
|
|
122
|
+
if [ "$ENGINE" = "say" ]; then
|
|
123
|
+
echo "error: TTS_ENGINE=say but 'say' not available" >&2
|
|
124
|
+
exit 1
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
if [ "$ENGINE" = "piper" ] || [ "$ENGINE" = "auto" ]; then
|
|
129
|
+
if command -v piper >/dev/null 2>&1; then
|
|
130
|
+
WAV="${OUTBASE}.wav"
|
|
131
|
+
piper -m "${PIPER_MODEL:-fr_FR-siwis-medium.onnx}" -f "$WAV" < "$TXT"
|
|
132
|
+
to_mp3 "$WAV"
|
|
133
|
+
fi
|
|
134
|
+
if [ "$ENGINE" = "piper" ]; then
|
|
135
|
+
echo "error: TTS_ENGINE=piper but piper not installed (pip install piper-tts)" >&2
|
|
136
|
+
exit 1
|
|
137
|
+
fi
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
cat >&2 <<'EOF'
|
|
141
|
+
error: no TTS engine available. Install one:
|
|
142
|
+
edge-tts (cleanest, online): pipx install edge-tts
|
|
143
|
+
XTTS-v2 (local):
|
|
144
|
+
uv venv --python 3.11 ~/.local/share/voice-forge/xtts
|
|
145
|
+
uv pip install --python ~/.local/share/voice-forge/xtts/bin/python \
|
|
146
|
+
coqui-tts 'transformers>=4.57,<5' 'torch==2.8.*' 'torchaudio==2.8.*'
|
|
147
|
+
piper (local): pip install piper-tts
|
|
148
|
+
ffmpeg: required for local engine MP3 conversion
|
|
149
|
+
EOF
|
|
150
|
+
exit 127
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Print one sentence per line from a text file.
|
|
2
|
+
|
|
3
|
+
Used to render long narrations sentence-by-sentence when an engine benefits from segmentation.
|
|
4
|
+
"""
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
raw = open(sys.argv[1], encoding="utf-8").read()
|
|
9
|
+
text = re.sub(r"\s+", " ", raw).strip()
|
|
10
|
+
for part in re.split(r"(?<=[.!?…])\s+", text):
|
|
11
|
+
part = part.strip()
|
|
12
|
+
if part:
|
|
13
|
+
print(part)
|