llmwiki rm undoes an ingest: it deletes a source file, deletes the concept pages that source exclusively owns, and leaves any page another live source also contributed to untouched. There is no confirmation flag - a bare rm applies immediately - so --dry-run is the only pre-flight check available.
Basic usage
No LLM provider credentials are required.
rm never calls the chat model, and it does not need a working embeddings backend either - see Embeddings after a removal.Flags
What gets deleted
A kept page’s body is a merge of contributions from every source that produced it, including the one just removed - the file itself carries no record of that. So
rm records each kept slug in state.json’s frozenSlugs, the same marker compile already sets when it notices a deleted source.
The mark tells the next compile to rebuild that page cleanly from the sources that survive, dropping the removed source’s contribution and its citations rather than leaving them in place. A page no live source owns any more is orphaned instead.
The mark is cleared only once the replacement page is actually written. If extraction or page validation fails during that rebuild, the mark and the surviving sources’ ownership are both preserved, so a later compile tries again rather than leaving the page stale with nothing to say so.
Limitations
Naming the source
Pass the filename undersources/, with or without the .md suffix:
sources/ directory of your project.
rm matches on filename only. You cannot pass the original URL or local path you ingested. If <source> matches neither a file under sources/ nor an interrupted removal, rm changes nothing and exits 1.
Example
What rm reports but doesn’t fix
A removal can damage three things elsewhere in the project. rm reports all three without editing anything for you:
- A broken wikilink. Deleting a page can leave a surviving page’s
[[wikilink]]pointing at something that no longer exists. Reported as the surviving page’s path and the doomed target. Runllmwiki lintfor the full detail. - A pending review candidate that references the removed source. Reported by count. Run
llmwiki review listto see it. - A stale citation. A kept page’s body is a merge of every contributing source, so it can still carry a
^[<source>.md]marker pointing at the file just deleted. The page is correctly preserved, butllmwiki lintwill raisebroken-citation(severityerror) on it. Whenever any page is kept,rmsays so:
rm doesn’t scan the citations itself - llmwiki lint already reports exactly which markers broke, and re-implementing that parsing on a destructive path would be the wrong place for it.
Safety boundaries
A symlinked entry insources/ is never treated as a source - rm resolves it the same as a missing file.
A concept slug that would escape wiki/concepts/ fails a filename-safety floor: rather than deleting through it, rm leaves the page on disk, warns Not deleted: <slug> (floor:unsafe-slug), and exits 1.
That warning is unreachable by construction, and is kept only as defense in depth. Every slug
rm can reach comes from .llmwiki/state.json, and reading that file already rejects an unsafe slug - the whole state is classified corrupt, and rm refuses before a plan exists. The floor exists so the deletion primitive is safe for any caller, not because this one can trip it.rm, the next compile, or llmwiki recover restores any pages from that batch that had already been removed - see resuming an interrupted removal.
rm runs under the project lock (.llmwiki/lock). If another llmwiki process already holds it, rm refuses cleanly and exits 1 rather than waiting or forcing. --dry-run never takes the lock at all.
Within a single rm, the plan is computed before the project lock is acquired - deliberately, so --dry-run never has to take it. That leaves a window in which a concurrent compile, watch or review approve can change which pages this source owns. (llmwiki watch recompiles automatically on any change under sources/, so this is an ordinary workflow, not a rare schedule.)
rm recomputes that ownership from fresh state under the lock. If it differs from the plan in any way - a page became shared, became exclusive, moved to another source, or was added - the removal refuses outright, before deleting anything:
state.json are exactly as they were, and re-running plans against current state. rm refuses rather than silently re-planning because the plan you were shown is the only set the command is authorized to delete - quietly deleting a different one is the failure a command with no confirmation prompt cannot afford.
Resuming an interrupted removal
rm deletes the source file first, then the pages. If the page batch fails - a page survives its unlink, the process is killed - the source file is already gone while its .llmwiki/state.json entry remains.
Re-running rm with the same name finishes the job. That pairing of “file gone, state entry present” is what identifies an interrupted removal, and it’s how rm tells the case apart from a name that was never there:
Embeddings after a removal
Removal uses the same storage selection as compile. An existing binary index stays authoritative; an older JSON backup is not updated or used as a fallback. The removal itself has no new text to embed, but the refresh that runs after it is the same full refresh every other command uses, not a pruning-only variant. It always prunes the deleted pages’ vectors, and it also embeds any other eligible page that doesn’t already have a stored vector - sorm can call your embeddings provider even for pages it didn’t touch.
This step is attempted even when no embeddings backend is configured - it is not skipped. A missing or invalid backend surfaces as a warning instead:
rm still exits 0. If you’ve set LLMWIKI_EMBED_STRICT, the same failure makes rm exit 1 too, matching every other llmwiki command.
Either way, the delete has already happened by the time embeddings run: the source file, the deleted pages, and
.llmwiki/state.json are all written before the embeddings step starts. A non-zero exit here means your embedding index is stale, not that the removal was rolled back or partially applied.