Remove a document from the knowledge base. IDENTIFIER may be the original filename ("paper.pdf"), the doc_name slug ("paper-a1b2c3d4e5f6"), or a substring that uniquely matches one. Deletes the doc's summary and source files, prunes the doc from concept- and entity-page frontmatter
(ctx, identifier, keep_raw, keep_empty, dry_run, yes)
| 1243 | @click.pass_context |
| 1244 | @_with_kb_lock(exclusive=True) |
| 1245 | def remove(ctx, identifier, keep_raw, keep_empty, dry_run, yes): |
| 1246 | """Remove a document from the knowledge base. |
| 1247 | |
| 1248 | IDENTIFIER may be the original filename ("paper.pdf"), the doc_name |
| 1249 | slug ("paper-a1b2c3d4e5f6"), or a substring that uniquely matches one. |
| 1250 | |
| 1251 | Deletes the doc's summary and source files, prunes the doc from |
| 1252 | concept- and entity-page frontmatter and Related Documents sections, |
| 1253 | drops the Documents entry from index.md, removes the hash entry, and |
| 1254 | finally runs `lint --fix` to clean any dangling wikilinks. |
| 1255 | |
| 1256 | Concept and entity pages whose only source was this doc are deleted by |
| 1257 | default; use --keep-empty to retain them. |
| 1258 | """ |
| 1259 | from openkb.agent.compiler import ( |
| 1260 | remove_doc_from_concept_pages, |
| 1261 | remove_doc_from_entity_pages, |
| 1262 | remove_doc_from_index, |
| 1263 | scan_affected_pages, |
| 1264 | ) |
| 1265 | from openkb.lint import fix_broken_links |
| 1266 | from openkb.state import HashRegistry |
| 1267 | |
| 1268 | kb_dir = _find_kb_dir(ctx.obj.get("kb_dir_override")) |
| 1269 | if kb_dir is None: |
| 1270 | click.echo("No knowledge base found. Run `openkb init` first.") |
| 1271 | return |
| 1272 | |
| 1273 | openkb_dir = kb_dir / ".openkb" |
| 1274 | registry = HashRegistry(openkb_dir / "hashes.json") |
| 1275 | |
| 1276 | matches = _resolve_doc_identifier(registry, identifier) |
| 1277 | if not matches: |
| 1278 | click.echo(f"No document matching '{identifier}' found in the KB.") |
| 1279 | click.echo("Try `openkb list` to see indexed documents.") |
| 1280 | return |
| 1281 | if len(matches) > 1: |
| 1282 | click.echo(f"'{identifier}' matches multiple documents:") |
| 1283 | for _, m in matches: |
| 1284 | click.echo(f" - {m.get('name', '?')} (doc_name: {m.get('doc_name', '?')})") |
| 1285 | click.echo("Use a more specific name or the exact doc_name slug.") |
| 1286 | return |
| 1287 | |
| 1288 | file_hash, meta = matches[0] |
| 1289 | name = meta.get("name", "?") |
| 1290 | doc_name = meta.get("doc_name") or Path(name).stem |
| 1291 | doc_type = meta.get("type", "") |
| 1292 | wiki_dir = kb_dir / "wiki" |
| 1293 | |
| 1294 | # ----- Build the plan (no side effects) ----- |
| 1295 | actions: list[tuple[str, str]] = [] |
| 1296 | |
| 1297 | summary_path = wiki_dir / "summaries" / f"{doc_name}.md" |
| 1298 | if summary_path.exists(): |
| 1299 | actions.append(("DELETE", str(summary_path.relative_to(kb_dir)))) |
| 1300 | |
| 1301 | source_md = wiki_dir / "sources" / f"{doc_name}.md" |
| 1302 | source_json = wiki_dir / "sources" / f"{doc_name}.json" |
nothing calls this directly
no test coverage detected