You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(#213): name conversions by the original's full filename to end stem collisions
ingest derived the conversion path from the original's stem (src.stem +
out_suffix), so same-folder/same-stem/different-extension originals
(report.hwpx, report.pptx) collided on one runs/sources/report.md: the first
converted won, the second was skipped as 'up to date', and its content was
silently lost while sources/coverage mispaired both originals to the one file.
Name each conversion by the original's *full* filename plus the out-suffix
(report.hwpx -> runs/sources/report.hwpx.md, report.pptx ->
runs/sources/report.pptx.md), and key source_rel_key() to match: an original
keeps its extension, a conversion drops only its out-suffix, so each original
pairs 1:1 with exactly its own conversion. Subdir mirroring is preserved
(sources/sub/x.pdf -> runs/sources/sub/x.pdf.md).
Cross-cutting mapping sites updated for the new key, all with a legacy stem-key
fallback so pre-#213 conversions keep pairing until a KB is re-ingested:
- factlog/common.py: source_rel_key() (full-name/last-suffix rule) +
new source_stem_key() legacy fallback helper
- cli.py cmd_ingest: dst = original full name + out_suffix
- cli.py cmd_sources, cmd_status, tools/coverage.py: pair on full-name key,
fall back to legacy stem key
- cli.py eject matches()/--orphans: provenance header stays the primary
signal; stem/relkey fallbacks made naming-consistent and a legacy stem-key
set added to orphan pairing so legacy mirrored conversions are not
false-flagged (preserves #103 cross-subtree orphan detection)
Tests: new #213 stem-collision case in test_ingest.sh (both preserved, correct
content, idempotent re-run, sources pairing); updated conversion output names in
test_ingest/pptx/hwpx and unit test_source_paths for the new key semantics.
Docs (README, SKILL.md) updated with the new naming + migration note.
Copy file name to clipboardExpand all lines: skills/factlog/SKILL.md
+5-4Lines changed: 5 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -43,7 +43,7 @@ produced by `factlog ingest`).
43
43
Examples:
44
44
-`"sources/my-doc.md"`
45
45
-`"sources/subdir/notes.md#section-heading"`
46
-
-`"runs/sources/report.md"` (a converted `.docx`/`.pdf` original)
46
+
-`"runs/sources/report.pdf.md"` (a converted `.docx`/`.pdf` original — the conversion keeps the original's full name, extension included, so same-stem originals never collide; #213)
47
47
48
48
Bare filenames (e.g. `"my-doc.md"`) are NOT valid and will be silently dropped
49
49
by `merge_candidates.py`. Always include the `sources/` or `runs/sources/` prefix.
@@ -216,8 +216,9 @@ first:
216
216
217
217
`--scan` auto-discovers every binary file under `sources/` and writes a text
218
218
conversion (with a provenance header) into `runs/sources/` — never into
219
-
`sources/`, mirroring the original's subdirectory (`sources/sub/x.pdf` →
220
-
`runs/sources/sub/x.md`). It is idempotent (unchanged files are skipped).
219
+
`sources/`, mirroring the original's subdirectory and keeping the original's
220
+
full name so same-stem originals never collide (`sources/sub/x.pdf` →
221
+
`runs/sources/sub/x.pdf.md`; #213). It is idempotent (unchanged files are skipped).
221
222
Sources matching `policy/sync-ignore.md` are skipped. Then extract from **both**
222
223
`sources/` (native text) and `runs/sources/` (conversions).
223
224
@@ -439,7 +440,7 @@ it is correctly reported as a gap, not "covered":
439
440
`/factlog sync` (or investigate why nothing was extracted).
440
441
-**binary gap** — a binary source under `sources/` with **no conversion** at
441
442
all: it needs conversion first via `factlog ingest`. A binary that already has
442
-
a `runs/sources/<stem>` conversion is **not** a gap — facts attach to the
443
+
a `runs/sources/<original-name>.md` conversion is **not** a gap — facts attach to the
443
444
conversion, so the original is reported as *covered via conversion* (it counts
444
445
toward "covered", with a `(N via conversion)` note in the summary). A binary
445
446
under `runs/sources/` is instead flagged as an anomaly (that directory holds
set +e; out="$("$PYTHON" -m factlog ingest --target "$KB""$KB/sources/sample.hwpx"2>&1)"; rc=$?;set -e
59
-
md="$KB/runs/sources/sample.md"
60
-
[ "$rc"-eq 0 ] && [ -f"$md" ] && ok "hwpx ingests to runs/sources/<stem>.md (rc 0)"|| bad "hwpx ingest failed (rc=$rc)"
59
+
md="$KB/runs/sources/sample.hwpx.md"# #213: conversion keeps the original's full name
60
+
[ "$rc"-eq 0 ] && [ -f"$md" ] && ok "hwpx ingests to runs/sources/<name>.md (rc 0)"|| bad "hwpx ingest failed (rc=$rc)"
61
61
head -1 "$md"| grep -qF "ingested-by-factlog"&& head -1 "$md"| grep -qF "factlog-hwpx"&& ok "provenance header written"|| bad "provenance header missing"
62
62
grep -qx "첫째 문단""$md"&& ok "multiple <hp:t> runs join into one line"|| bad "runs not joined"
63
63
grep -qx "태그제거""$md"&& ok "inline tags stripped"|| bad "inline tag not stripped"
@@ -70,7 +70,7 @@ grep -qx "둘째 섹션" "$md" && ok "text from a second section included" || ba
[ "$rc"-eq 0 ] && [ -f"$KB/runs/sources/scanned.md" ] && ok "--scan auto-discovers and converts hwpx"|| bad "--scan did not convert hwpx (rc=$rc)"
73
+
[ "$rc"-eq 0 ] && [ -f"$KB/runs/sources/scanned.hwpx.md" ] && ok "--scan auto-discovers and converts hwpx"|| bad "--scan did not convert hwpx (rc=$rc)"
74
74
75
75
# --- corrupt hwpx: explicit fails, --scan reports but does not fail ------------
0 commit comments