@@ -116,6 +116,15 @@ def build_parser() -> argparse.ArgumentParser:
116116 "without them, so a whole-corpus re-read (one agent session per source) can never "
117117 "happen by accident." ,
118118 )
119+ p_ingest .add_argument (
120+ "--reingest" ,
121+ action = "store_true" ,
122+ help = "Re-import the given already-ingested sources FRESH: first a delete cleanup session "
123+ "strips each source's previous facts from the wiki (its manifest entry is dropped), then "
124+ "the same run ingests it as a brand-new source under the current model + rules — the "
125+ "full re-think --force's reconcile deliberately avoids (reconcile keeps the existing "
126+ "treatment). Costs two sessions per source. Requires explicit paths, like --force." ,
127+ )
119128 p_ingest .add_argument (
120129 "--retry" ,
121130 action = "store_true" ,
@@ -422,11 +431,17 @@ def cmd_ingest(args: argparse.Namespace) -> int:
422431 agent session per source, so forcing the ENTIRE corpus must never happen by accident — the
423432 flag alone is refused with exit 2, before ``ingest.ingest`` is ever called.
424433
434+ ``--reingest`` is force's bigger sibling — strip the named sources' previous facts (a delete
435+ cleanup session each), then re-import them fresh in the same run. Same explicit-paths rule as
436+ ``--force`` (exit 2 without them), and the two flags refuse each other: reconcile-in-place
437+ and strip-and-re-import are different intents, and combining them would blur which one wins.
438+
425439 ``--retry`` is the no-typing complement: it computes its own bounded set —
426440 ``ingest.retry_candidates()``, the failed sources still on disk plus the ingested ones no
427441 wiki page cites — prints it, and runs those as a FORCED re-read. It therefore refuses
428- explicit paths and ``--force`` (exit 2): the point of the flag is that citadel picks the
429- set, and combining the two would blur which one wins. Nothing to retry is a clean exit 0.
442+ explicit paths, ``--force`` and ``--reingest`` (exit 2): the point of the flag is that
443+ citadel picks the set, and combining them would blur which one wins. Nothing to retry is a
444+ clean exit 0.
430445
431446 ``--jobs N`` is a usage error below 1 (exit 2, like ``--force`` without paths) rather than an
432447 exception out of the API layer; omitted, the run takes ``CITADEL_JOBS`` (default 1, serial)."""
@@ -436,11 +451,21 @@ def cmd_ingest(args: argparse.Namespace) -> int:
436451 print (f"error: --jobs must be at least 1 (got { args .jobs } ); 1 means the serial default." , file = sys .stderr )
437452 return 2
438453
439- if args .retry and (args .paths or args .force ):
454+ if args .retry and (args .paths or args .force or args . reingest ):
440455 print (
441456 "error: --retry computes its own retry set (every failed source plus every ingested "
442- "source no wiki page cites) — combine no paths and no --force with it; use "
443- "`citadel ingest --force <paths>` for a hand-picked re-read instead." ,
457+ "source no wiki page cites) and cannot be combined with explicit paths, --force, or "
458+ "--reingest; use `citadel ingest --force <paths>` (or `--reingest <paths>`) for a "
459+ "hand-picked re-read instead." ,
460+ file = sys .stderr ,
461+ )
462+ return 2
463+
464+ if args .force and args .reingest :
465+ print (
466+ "error: --force and --reingest are different intents — --force reconciles the "
467+ "source's facts in place (keeping its existing treatment), --reingest strips them "
468+ "and re-imports the source fresh. Pick one." ,
444469 file = sys .stderr ,
445470 )
446471 return 2
@@ -453,6 +478,15 @@ def cmd_ingest(args: argparse.Namespace) -> int:
453478 )
454479 return 2
455480
481+ if args .reingest and not args .paths :
482+ print (
483+ "error: --reingest requires explicit paths (each source runs a delete cleanup "
484+ "session plus a fresh ingest session; name the files or directories to re-import, "
485+ "e.g. `citadel ingest --reingest raw/notes.md`)." ,
486+ file = sys .stderr ,
487+ )
488+ return 2
489+
456490 paths = args .paths or None
457491 force = args .force
458492 if args .retry :
@@ -486,7 +520,9 @@ def cmd_ingest(args: argparse.Namespace) -> int:
486520 # CITADEL_LLM_VERBOSE — not just the --verbose flag — also drops the spinner that would
487521 # otherwise clobber the streamed transcript.
488522 progress = ConsoleProgress (spinner = not config .LLM_VERBOSE )
489- report = ingest .ingest (paths , progress = progress , full_rescan = args .full_rescan , force = force , jobs = args .jobs )
523+ report = ingest .ingest (
524+ paths , progress = progress , full_rescan = args .full_rescan , force = force , jobs = args .jobs , reingest = args .reingest
525+ )
490526 print (report .render ())
491527 # Non-zero on a per-source error OR a structural problem left behind (a broken
492528 # cross-link the agent introduced) — so ingest gates the wiki's integrity in CI.
0 commit comments