Repository navigation
feat: Phase 11 + post-v0.4.5 sync (v0.5.0) - #11
Conversation
v0.4.5 published to PyPI on 2026-04-26 (https://github.com/cemililik/ForgeLM/releases/tag/v0.4.5). Doc state now reflects that: - roadmap.md / roadmap-tr.md: 'Latest release on PyPI: v0.4.5' (was v0.4.0); v0.4.0 demoted to 'Previous'. - phase-12-quickstart.md: status banner 'shipped as v0.4.5 on 2026-04-26 (PyPI)' instead of 'merged to main; tag + PyPI pending'. - releases.md: v0.4.5 status 'Released' with link to GitHub Release. - doc map line: 'shipped as v0.4.5' (was 'tag + PyPI publish pending'). Next dev cycle: pyproject.toml + forgelm/__init__.py bumped to 0.5.0rc1 (Phase 11 Document Ingestion target). The CHANGELOG `[Unreleased]` header is already in place and stays empty until the first 0.5.0 work lands.
The README's Notebooks section listed only 5 of the 9 .ipynb files in notebooks/. The four missing ones cover real, shippable features that deserve discoverability: - post_training_workflow.ipynb — end-to-end Phase 10 toolchain (fit-check → chat → export → deploy). - safety_evaluation.ipynb — 140-prompt × 6-category red-team harness. - synthetic_data_training.ipynb — teacher-to-student distillation. - galore_memory_optimization.ipynb — full-parameter training as an alternative to LoRA. Reorganised the section into four logical groups (Getting started / Alignment / Advanced training / Post-training & safety) with a one-line description for each non-obvious notebook so a reader can pick the right one without opening it. Also: - Added a PyPI version badge alongside the License/Python/CI badges now that v0.4.5 is published. - Mirrored the notebook list at the bottom of docs/guides/quickstart.md so the user-facing guide hands off to the runnable artifacts.
Brings raw enterprise corpora into ForgeLM's training format and surfaces
governance signals before training starts. Bridges ingestion → training →
EU AI Act Article 10 in one tool.
forgelm/ingestion.py + `forgelm ingest` subcommand
- Multi-format extractors: PDF (pypdf), DOCX (python-docx), EPUB
(ebooklib + beautifulsoup4), TXT, Markdown.
- Two chunking strategies: paragraph (default; greedy, never splits
mid-paragraph) and sliding (fixed window with --overlap). Semantic
raises NotImplementedError; reserved for a follow-up phase.
- Output is `{"text": "..."}` JSONL — recognized by data.py as
pre-formatted SFT input without further preprocessing.
- --recursive walks directory trees; unsupported extensions skip
silently, supported files with no extractable text skip with a
warning. OCR is intentionally out of scope.
- --pii-mask redacts emails / phones / Luhn-validated credit cards /
IBAN / national IDs (TR / DE / FR / US-SSN) before writing.
forgelm/data_audit.py + `forgelm --data-audit` top-level flag
- Per-split metrics: sample count, column schema, length distribution
(min/max/mean/p50/p95), null/empty rate, top-3 language detection
(best-effort via langdetect).
- 64-bit simhash near-duplicate detection within each split; default
Hamming threshold 3 ≈ 95% similarity (canonical web-page-dedup
setting). Cross-split overlap report catches train-test leakage.
- PII regex set with credit-card Luhn validation and TC Kimlik No
checksum validation. Per-split + aggregate counts.
- Layout: single .jsonl → treated as `train`; directory containing
train.jsonl / validation.jsonl / test.jsonl auto-discovered.
- Writes data_audit_report.json under --output (default ./audit/);
--output-format json mirrors the report on stdout for CI/CD. CPU-only.
EU AI Act Article 10 integration (compliance.py)
- generate_data_governance_report inlines data_audit_report.json under
the `data_audit` key when present in the trainer's output_dir.
Compliance bundle becomes a single self-contained document.
pyproject.toml [ingestion] extra
- pypdf, python-docx, ebooklib, beautifulsoup4, langdetect.
Cross-platform; no native compilation. Plain TXT / Markdown +
audit module are pure stdlib and work without the extra.
CLI wiring (cli.py)
- New `ingest` subcommand alongside chat / export / deploy / quickstart.
- --data-audit + --output top-level flags; runs before the config-required
check so operators can audit raw data without writing a YAML.
- Epilog and help text updated with the new subcommand line.
Compatibility
- wizard.py: BYOD dataset prompt now detects directories and hints at
`forgelm ingest` (was silently re-prompting "not a regular file").
- nightly.yml: added an ingest+audit smoke test (TXT-only path so the
[ingestion] extra isn't required in CI).
Tests
- tests/test_ingestion.py — TXT path + chunking strategies + PII mask;
PDF round-trip skips when pypdf missing.
- tests/test_data_audit.py — PII regex + Luhn / TC Kimlik validators,
simhash properties, end-to-end audit on file + split-keyed directory
layouts, governance integration.
- tests/test_wizard_byod.py: directory-rejection assertion updated to
match the new ingest hint.
- 704/704 pass locally (9 GPU/TRL-env skips unchanged).
Documentation
- New: docs/guides/ingestion.md, docs/guides/data_audit.md.
- Updated: README (feature section, install matrix, quickstart hand-off,
guide table), docs/reference/usage.md (CLI sections), docs/qms/
sop_data_management.md (audit pipeline integration), docs/standards/
architecture.md (module table + extras matrix), CHANGELOG.md
([Unreleased]), docs/roadmap.md + roadmap-tr.md (Phase 11 → Done),
docs/roadmap/phase-11-data-ingestion.md (status banner + tasks
marked [x]), docs/roadmap/releases.md (v0.5.0 entry expanded),
docs/guides/quickstart.md (Option C: raw docs path).
… backlog
19 findings applied as real fixes; 14 explicitly deferred to a new
docs/roadmap/phase-11-5-backlog.md (LSH index, streaming reader, OCR
docs, etc.); 1 already correct (langdetect seed).
Bugs (functional / safety)
- 1: `semantic` strategy removed from `forgelm ingest --strategy` choices.
The function still raises NotImplementedError; the CLI surface no longer
exposes a runtime-crashing option. Help text now points at the
follow-up phase.
- 2: `_chunk_sliding` rejects `overlap > chunk_size // 2` up front. A typo
like `--chunk-size 200 --overlap 199` previously emitted ~one chunk
per character (multi-million-line JSONL) silently.
- 3: `_audit_split` now records the union of keys across rows under
`columns`, plus a `schema_drift_columns` list when later rows add
fields not present in row 0. Heterogeneous BYOD JSONL no longer
loses optional columns silently.
- 6: phone PII regex narrowed. Was matching any 6-12 digit run with
separators (timestamps, line numbers, ISO dates, IBAN fragments).
Now anchored to `+CC` country prefix or `(area)` parenthesised area
code — bare digit runs do not flag.
- 7: `de_id` regex narrowed from `[A-Z0-9]{9,10}` to
`[A-Z]\d{7,8}[A-Z0-9]?` so IATA codes / UUID fragments / API key
fragments do not trip Personalausweis detection.
- 8: PDF happy-path test added (hand-built minimal text-bearing PDF; no
reportlab dep). Encrypted PDF rejection test added.
- 9: `_extract_pdf` distinguishes encrypted PDFs from "extraction failed"
errors. Owner-encrypted PDFs are auto-decrypted with an empty
password; user-encrypted PDFs raise ValueError with an actionable
hint (qpdf / pdftk).
- 10: `_extract_text` warns when more than 1% of a `.txt` file decodes
as Unicode replacement chars (binary masquerading as text).
- 14: split alias map (`dev`, `val`, `valid`, `eval`, `holdout`)
collapses onto canonical `validation` / `test`. Cross-split leakage
works without forcing operators to rename their files.
- 24: `--data-audit --output-format json` now emits a *summary* on
stdout (top-level metrics + report path), not the full JSON.
Multi-split audits used to dump tens of KB to CI logs.
- 26: pseudo-split fallback emits a clear warning + report note
("cross-split leakage analysis is meaningless without a real
partition") instead of silently treating arbitrary `*.jsonl` files
as splits.
- 27: `AuditReport` now carries both `source_path` (absolute, traceable)
and `source_input` (literal user input, portable across machines).
- 30: `IngestionResult.pii_redaction_counts` records per-category
redaction counts when `--pii-mask` runs, plus a notes-line summary.
Compliance evidence ("X emails + Y phones removed") is now visible
without diffing the masked output.
- 31: `audit_dataset` tolerates per-split read failures (OSError) —
the bad split lands under `splits[name]["error"]`, the audit
continues, and a notes line surfaces the skip.
- 34: audit notes now actionable. PII note suggests
`forgelm ingest --pii-mask` / `mask_pii`; cross-split leakage note
recommends re-shuffling; near-duplicate note explains the cost.
Doc-only clarifications
- 11: `mask_pii` docstring documents the first-match-wins precedence
(most-specific patterns scanned first; phone last).
- 15: ingestion guide explains char vs token: `--chunk-size 2048` ≈
500-700 tokens; size against `model.max_length`.
- 16: `_PII_PATTERNS` order rationale now in module-level comment.
- 18: ingestion guide notes lexicographic file ordering for
reproducibility.
- 25: `--recursive` help text spells out that the default is shallow.
Tests
- TestSlidingChunking: `test_overlap_above_half_chunk_rejected`
- TestPdfExtractor: text-bearing happy path + encrypted-PDF rejection
- TestPiiDetection: phone narrowing (with-prefix + bare-digits-not-phone),
de_id narrowing
- TestAuditDirectoryLayout: alias parametrize (dev/val/eval/holdout),
pseudo-split warning
- TestSchemaDrift, TestPartialFailureTolerance, TestActionableNotes,
TestReproducibility — new test classes covering Bugs 3 / 31 / 34 / 27.
- 718/718 passing locally (11 skips: 8 pre-existing TRL/torch, 3
optional-dep PDF roundtrips when pypdf is missing).
Backlog
- New docs/roadmap/phase-11-5-backlog.md captures the 14 deferred
follow-ups (LSH banding, streaming reader, token-aware chunk size,
PDF header/footer dedup, simhash token cache, xxhash backend,
audit progress bar, audit-as-subcommand, PII severity tiers,
truncation policy, structured ingestion notes, wizard "ingest first"
entry, OCR handoff docs, ebooklib deprecation).
… progress, OCR docs) Three small Phase 11.5 backlog items pulled forward — none introduce breaking changes, none require new dependencies. ebooklib deprecation (#4 in backlog) - _extract_epub now passes options={"ignore_ncx": True, "ignore_missing_css": True} to epub.read_epub. Silences the deprecation / NCX / CSS warnings that newer ebooklib (>=0.18) emits when the keyword is omitted; behavior is unchanged for downstream. Audit progress logging (#7 in backlog) - _audit_split now emits a "computing simhashes for N rows" notice and a "X / N rows fingerprinted" line every 5000 rows when the split crosses that threshold. Quickstart-sized audits stay quiet. - audit_dataset main loop logs each split's open ("scanning split 'X' (N rows from path.jsonl)") and a "scanning for near-duplicates (O(n²); N rows)" notice when the within-split pairing step is the expensive piece. Closes the 3-5 minute silent stretch on 30K+ row audits without pulling tqdm in. OCR handoff documentation (#13 in backlog) - docs/guides/ingestion.md: new "Working with scanned PDFs" section with two worked recipes — Tesseract (local, free, via ocrmypdf or raw pdftoppm + tesseract) and AWS Textract (cloud, paid, better on multi-column / forms). Plus a brief PII-redaction-after-OCR flow. Removes the dead-end where "OCR is out of scope" left operators with nowhere to go. Backlog - docs/roadmap/phase-11-5-backlog.md updated: the three landed items removed from the table, header gains a "Recently landed" callout pointing at the relevant docs path. Twelve items remain in the follow-up queue. Verified: ruff clean, 718 tests / 11 skips unchanged, manual smoke on 6000-row audit confirms progress lines fire at the expected intervals.
There was a problem hiding this comment.
Sorry @cemililik, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 57 minutes and 28 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThis PR delivers Phase 11 "Document Ingestion & Data Audit" for v0.5.0: adds Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI as forgelm ingest<br/>(CLI)
participant Extractor as Format Extractor<br/>(PDF/DOCX/EPUB/TXT)
participant Chunker as Chunker<br/>(paragraph/sliding)
participant PII as PII Masker
participant Writer as JSONL Writer
participant Output as /tmp/out.jsonl
User->>CLI: forgelm ingest input/ --output /tmp/out.jsonl
CLI->>Extractor: enumerate files (recursive?)
Extractor->>Extractor: parse per-format text
Extractor->>Chunker: send extracted text
Chunker->>Chunker: split according to strategy
Chunker->>PII: deliver chunks
alt --pii-mask enabled
PII->>PII: detect & redact sensitive matches
end
PII->>Writer: write {"text": "..."} lines
Writer->>Output: persist JSONL
Output-->>CLI: return IngestionResult
CLI-->>User: summary + next-step hint
sequenceDiagram
participant User
participant CLI as forgelm --data-audit<br/>(CLI)
participant Resolver as Split Resolver
participant Reader as JSONL Reader
participant Metrics as Metrics Engine
participant Overlap as Cross-Split Detector
participant Report as Report Writer
participant OutputDir as output_dir/
User->>CLI: forgelm --data-audit --input dataset.jsonl --output output_dir
CLI->>Resolver: map input -> splits (train/validation/test)
Resolver->>Reader: read JSONL lines per split
Reader->>Metrics: extract text fields & compute stats
Metrics->>Metrics: detect PII, compute simhash, near-duplicates, language
Metrics->>Overlap: compare fingerprints across splits
Overlap->>Report: record leakage & leaked counts
Report->>OutputDir: write data_audit_report.json
OutputDir-->>CLI: return AuditReport summary & path
CLI-->>User: summary + report path
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 415 |
| Duplication | 2 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Code Review
This pull request implements Phase 11 of the roadmap, introducing a document ingestion pipeline and a dataset audit tool. The ingestion module supports PDF, DOCX, EPUB, TXT, and Markdown formats with paragraph and sliding window chunking strategies, including optional PII masking. The audit tool provides metrics on sample counts, length distributions, language detection, near-duplicate detection via simhash, and cross-split leakage checks. Feedback focused on performance bottlenecks in the auditing logic due to quadratic complexity in duplicate detection and a discrepancy in DOCX extraction where table content was being ignored despite documentation claims.
| def find_near_duplicates( | ||
| fingerprints: List[int], | ||
| *, | ||
| threshold: int = DEFAULT_NEAR_DUP_HAMMING, | ||
| ) -> List[Tuple[int, int, int]]: | ||
| """Pair-find rows whose simhash Hamming distance ≤ ``threshold``. | ||
|
|
||
| Returns ``[(i, j, distance), ...]`` with ``i < j``. Quadratic in the | ||
| number of fingerprints; intended for audit-time use on datasets up to | ||
| ~50 K rows. For larger datasets, an LSH band index would be required — | ||
| out of scope for v0.5.0. | ||
| """ | ||
| pairs: List[Tuple[int, int, int]] = [] | ||
| for i, fp_i in enumerate(fingerprints): | ||
| if fp_i == 0: | ||
| continue | ||
| for j in range(i + 1, len(fingerprints)): | ||
| fp_j = fingerprints[j] | ||
| if fp_j == 0: | ||
| continue | ||
| distance = hamming_distance(fp_i, fp_j) | ||
| if distance <= threshold: | ||
| pairs.append((i, j, distance)) | ||
| return pairs |
There was a problem hiding this comment.
The find_near_duplicates function implements a nested loop with O(n^2) complexity. For the documented target scale of ~50,000 rows, this results in approximately 1.25 billion iterations. In pure Python, this will be extremely slow and may take several minutes or even hours to complete. Additionally, if the dataset contains many near-duplicates, the pairs list can grow very large, potentially leading to high memory consumption (e.g., 10k identical rows would generate ~50 million tuples). Since the audit report only requires the count of near-duplicate pairs, consider using a more efficient approach like LSH-based bucketing (pigeonhole principle with blocks) and only returning the count to save memory.
| fp_b_set = {fp for fp in fingerprints_by_split[b] if fp != 0} | ||
| if not fp_b_set: | ||
| continue | ||
| shared = sum(1 for fp in fp_a if any(hamming_distance(fp, other) <= threshold for other in fp_b_set)) |
There was a problem hiding this comment.
| def _extract_docx(path: Path) -> str: | ||
| try: | ||
| from docx import Document | ||
| except ImportError as exc: # pragma: no cover | ||
| raise ImportError( | ||
| "DOCX ingestion requires the 'ingestion' extra. Install with: pip install 'forgelm[ingestion]'" | ||
| ) from exc | ||
|
|
||
| doc = Document(str(path)) | ||
| paragraphs = [p.text for p in doc.paragraphs if p.text and p.text.strip()] | ||
| return "\n\n".join(paragraphs) |
There was a problem hiding this comment.
The _extract_docx function currently only extracts text from doc.paragraphs, which excludes content within tables. This contradicts the documentation in docs/guides/ingestion.md (line 183) which states that DOCX tables are flattened to plain text. To align with the documentation and ensure comprehensive data ingestion, table content should be extracted as well.
def _extract_docx(path: Path) -> str:
try:
from docx import Document
except ImportError as exc: # pragma: no cover
raise ImportError(
"DOCX ingestion requires the 'ingestion' extra. Install with: pip install 'forgelm[ingestion]'"
) from exc
doc = Document(str(path))
text_parts = [p.text for p in doc.paragraphs if p.text and p.text.strip()]
# Also extract text from tables to align with documentation
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
if cell.text and cell.text.strip():
text_parts.append(cell.text)
return "\n\n".join(text_parts)There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
docs/standards/architecture.md (1)
71-71:⚠️ Potential issue | 🟡 MinorStale module count.
The "Seventeen single-file modules" claim is now off — the table at lines 79-96 has 18 rows after
ingestion.pyanddata_audit.pywere added (and arguablyinference.pyfrom Phase 10 is also unaccounted for). Bump the prose so it stays in step with the topology table.📝 Suggested fix
-Seventeen single-file modules. **No sub-packages inside `forgelm/`.** If a module grows past ~1000 lines and has cohesive subsections, split into `module_name/` package, but keep the public API at `forgelm.module_name.X` so imports don't break. +Eighteen single-file modules. **No sub-packages inside `forgelm/`.** If a module grows past ~1000 lines and has cohesive subsections, split into `module_name/` package, but keep the public API at `forgelm.module_name.X` so imports don't break.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/standards/architecture.md` at line 71, Update the stale sentence "Seventeen single-file modules." to accurately reflect the current module count (at least 18) or, better, remove the hard-coded number and replace it with a dynamic phrasing like "multiple single-file modules" or "N single-file modules" that is maintained alongside the topology table; change the exact text token "Seventeen single-file modules." in the architecture prose so it matches the modules listed (including ingestion.py, data_audit.py, and inference.py) and ensure the guidance about splitting modules into module_name/ packages (and keeping public API at forgelm.module_name.X) remains unchanged.README.md (1)
241-241:⚠️ Potential issue | 🟡 MinorStale test count in Project Structure block.
The PR description states
718 passed, 11 skipped, but this line still advertises# 430 passed (+34 skipped) across 30 test files. Either drop the count (it goes stale every PR) or update it to match v0.5.0rc1.📝 Proposed fix
-tests/ # 430 passed (+34 skipped) across 30 test files +tests/ # Unit + integration suite (run `pytest tests/`)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` at line 241, The "tests/" line in README.md contains a stale test summary ("# 430 passed (+34 skipped) across 30 test files") that doesn't match the PR/test run (718 passed, 11 skipped) — update or remove this count to avoid future rot: either replace the existing summary text next to the "tests/" entry with the current v0.5.0rc1 results (718 passed, 11 skipped) or remove the numeric summary entirely so the "tests/" bullet remains without hard-coded counts; edit the line that begins with "tests/" in README.md to apply the change.
♻️ Duplicate comments (1)
docs/roadmap-tr.md (1)
20-20:⚠️ Potential issue | 🟡 MinorSame v0.4.0 date drift as
roadmap.md.CHANGELOG.md line 105 has v0.4.0 = 2026-04-25, not 2026-04-26. Fix here to keep the EN/TR mirror in sync once
docs/roadmap.mdis corrected.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/roadmap-tr.md` at line 20, The v0.4.0 entry in docs/roadmap-tr.md currently shows the date 2026-04-26 which drifts from CHANGELOG.md and the EN roadmap; update the date string for the `v0.4.0` entry to 2026-04-25 so the Turkish mirror (`v0.4.0` — Post-Training Tamamlama) matches the canonical sources.
🧹 Nitpick comments (13)
forgelm/compliance.py (1)
139-151: Article 10 inlining is well-scoped.The defensive
getattrchain, file existence check, and explicit catch ofJSONDecodeError/OSErrorkeep this best-effort and non-fatal. One small consideration: a corrupt UTF-8 file would raiseUnicodeDecodeError(not in the catch tuple) and bubble up. If you want full robustness, broaden to(json.JSONDecodeError, OSError, UnicodeDecodeError)so a damaged audit artifact never crashes the governance report.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@forgelm/compliance.py` around lines 139 - 151, The current inlining of data_audit_report.json uses a try/except that catches json.JSONDecodeError and OSError but will still raise on a corrupt UTF-8 file (UnicodeDecodeError); update the exception tuple in the except block that surrounds the open+json.load (the block that assigns report["data_audit"]) to include UnicodeDecodeError so the handler (logger.warning(..., audit_path, exc)) swallows encoding errors as well and keeps the process non‑fatal for output_dir/audit_path.forgelm/cli.py (2)
1106-1117: Exit-code choice for missing extras differs from sibling commands.
_run_chat_cmd(line 789–791) mapsImportErrortoEXIT_TRAINING_ERROR(2), but here the missing-extras case maps toEXIT_CONFIG_ERROR(1). Both are valid public-contract codes, but the inconsistency makes it harder for CI scripts to branch on "missing optional dep". Pick one convention across subcommands and document it indocs/standards/error-handling.mdif it isn't already.As per coding guidelines: "Exit codes are a public contract: 0/1/2/3/4 as per error-handling.md, especially for CLI".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@forgelm/cli.py` around lines 1106 - 1117, The ImportError handler in the ingest command currently exits with EXIT_CONFIG_ERROR while _run_chat_cmd maps ImportError to EXIT_TRAINING_ERROR; make the exit-code convention consistent by changing the ImportError except block (the "except ImportError as exc:" handler that uses output_format and logger) to exit with EXIT_TRAINING_ERROR instead of EXIT_CONFIG_ERROR, and update docs/standards/error-handling.md to document that missing optional dependency (ImportError) should use EXIT_TRAINING_ERROR across subcommands.
1139-1153:_run_data_auditonly catchesFileNotFoundError.
audit_datasetcallsout_dir.mkdir(...)andopen(out_path, "w", ...), both of which can raiseOSError(read-only FS, permission denied, ENOSPC)._resolve_inputand_read_jsonl_splitcan also surface non-FileNotFoundErrorOSErrorcases. Today those will produce a Python traceback instead of a clean JSON/text error envelope.♻️ Suggested broaden
- try: - report = audit_dataset(audit_input, output_dir=target) - except FileNotFoundError as exc: + try: + report = audit_dataset(audit_input, output_dir=target) + except (FileNotFoundError, OSError, ValueError) as exc: if output_format == "json": print(json.dumps({"success": False, "error": str(exc)})) else: logger.error("Audit failed: %s", exc) sys.exit(EXIT_CONFIG_ERROR)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@forgelm/cli.py` around lines 1139 - 1153, The current _run_data_audit function only catches FileNotFoundError from audit_dataset, leaving other filesystem-related OSError subclasses (permission errors, ENOSPC, etc.) unhandled; update the except clause to catch OSError (or add an additional except OSError as exc) so any filesystem/IO errors from audit_dataset, _resolve_input, or _read_jsonl_split are handled the same way: when output_format == "json" print a JSON error envelope with the stringified exc, otherwise call logger.error("Audit failed: %s", exc), and then sys.exit(EXIT_CONFIG_ERROR) — reference _run_data_audit, audit_dataset, output_format, logger, json and EXIT_CONFIG_ERROR to locate the change.forgelm/data_audit.py (4)
100-100: Use a greedy quantifier for the credit-card pattern.
\b(?:\d[ -]*?){13,19}\bworks (regex backtracking will eventually consume separators when the surrounding\bforces it), but the reluctant*?adds avoidable backtracking on long digit-rich text and is what SonarCloud is flagging. A greedy*is functionally equivalent here and faster.♻️ Proposed change
- "credit_card": re.compile(r"\b(?:\d[ -]*?){13,19}\b"), + "credit_card": re.compile(r"\b(?:\d[ -]*){13,19}\b"),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@forgelm/data_audit.py` at line 100, The credit card regex under "credit_card" uses a reluctant quantifier (\d[ -]*?) which causes unnecessary backtracking; update the pattern in forgelm/data_audit.py to use a greedy quantifier (\d[ -]*) so the entry "credit_card" becomes \b(?:\d[ -]*){13,19}\b (keep the surrounding word boundaries and same repetition range) to improve performance while preserving behavior.
459-514:_resolve_inputcomplexity warning is fixable by extracting the directory branch.SonarCloud flags 19 vs. 15. Splitting the directory-scanning logic into
_resolve_directory_splits(src) -> (layouts, notes)(returning({}, [...])when no canonical files match so the pseudo-split fallback can take over) would cut the nesting and let each branch be tested in isolation.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@forgelm/data_audit.py` around lines 459 - 514, The _resolve_input function is too complex; extract the directory-scanning branch into a new helper _resolve_directory_splits(src) that encapsulates the logic currently inside the if src.is_dir() block (use _SPLIT_ALIASES, discovery of canonical/alias files, building layouts, notes, the pseudo-split fallback and logger.warning), and have _resolve_input call this helper and return its (layouts, notes) result or continue to raise FileNotFoundError for missing input; this reduces nesting and allows unit testing of directory logic in _resolve_directory_splits separately.
435-438: Mixed.format()inside a dict literal is easy to misread.
"leaked_rows_in_{}".format(a)works but the surrounding code uses f-strings everywhere; this is the only positional.formatcall in the file. Also, theif fp_a else 0.0branch on line 437 is unreachable — the functioncontinues onif not fp_aat line 428.♻️ Proposed cleanup
report["pairs"][f"{a}__{b}"] = { - "leaked_rows_in_{}".format(a): shared, - "leak_rate": round(shared / len(fp_a), 4) if fp_a else 0.0, + f"leaked_rows_in_{a}": shared, + "leak_rate": round(shared / len(fp_a), 4), }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@forgelm/data_audit.py` around lines 435 - 438, The dict entry creation in report["pairs"][f"{a}__{b}"] mixes a .format() call with f-strings and includes an unreachable ternary because fp_a is already guarded by an earlier continue; change the key name to use an f-string (e.g., f"leaked_rows_in_{a}") for consistency and remove the unnecessary "if fp_a else 0.0" branch so leak_rate is computed as round(shared / len(fp_a), 4) (relying on the prior if not fp_a: continue), updating the code that builds report["pairs"][f"{a}__{b}"] accordingly.
352-415:_audit_splitis doing five things; pulling out the per-feature passes would tame the complexity.SonarCloud flags cognitive complexity 25 vs. 15 budget. The function currently mixes schema/drift, text payload extraction, length stats, language sampling, simhash fingerprinting, and PII counting. Each pass is independent and easy to extract:
_compute_schema(rows) -> (columns, drift_columns)_compute_payloads(rows) -> (payloads, null_or_empty_count)_compute_languages(payloads, sample_size=200)_compute_fingerprints(split_name, payloads)_compute_pii(payloads)That keeps the public per-split orchestrator readable and gives each helper an independently testable surface.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@forgelm/data_audit.py` around lines 352 - 415, _audit_split currently exceeds cognitive complexity by mixing schema/drift discovery, payload extraction, length stats, language sampling, simhashing and PII counting into one large function; refactor by extracting those independent passes into small helpers and call them from _audit_split: implement _compute_schema(rows) -> (columns, drift_columns) to build seen_columns and detect drift vs base_columns, _compute_payloads(rows) -> (text_payloads, null_or_empty) to run _extract_text_payload and compute null counts, _compute_languages(text_payloads, sample_size=200) to sample and return top language counts, _compute_fingerprints(split_name, text_payloads) to compute simhashes with progress logging and return fingerprints and simhash distinct count, and _compute_pii(text_payloads) to aggregate detect_pii results; update _audit_split to orchestrate these helpers and assemble info, fingerprints and pii_counts_split accordingly so behavior and returned tuple remain unchanged.forgelm/ingestion.py (1)
291-414: Consider extracting the per-file processing loop.SonarCloud flags cognitive complexity 30 (vs. 15 budget) for
ingest_path. The body is dominated by the per-file loop (extract → handle errors → chunk → optional PII mask → write). Pulling that into a_process_file(...)helper that returns chunk-count / chars / pii-counts would bring the public function back under budget and make the test surface narrower.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@forgelm/ingestion.py` around lines 291 - 414, ingest_path is over-complex due to a large per-file loop; extract that logic into a new helper (e.g. _process_file) that takes (fpath, strategy, chunk_size, overlap, mask_pii, detect_pii, out_fh) and returns a small result struct/tuple (chunks_written, chars_written, per_file_pii_counts, skipped_flag, format_ext) so the main ingest_path just iterates files, calls _select_extractor then _process_file, and accumulates chunk_count, total_chars, format_counts and pii_redaction_counts; move error handling for extractor calls, empty-text checks, chunking via _strategy_dispatch, optional PII detect_pii/mask_pii logic, and the out_fh.write(json.dumps(...)) into _process_file to reduce ingest_path cognitive complexity while preserving existing symbols (ingest_path, _select_extractor, _strategy_dispatch, detect_pii, mask_pii, IngestionResult).pyproject.toml (1)
86-92: Pin upper bounds on the newingestionextras.The other optional groups consistently cap major versions (e.g.
bitsandbytes>=0.42.0,<1.0.0,lm-eval>=0.4.4,<1.0.0). The new group uses open-ended>=only, which means a future pypdf 6.x or python-docx 2.x with breaking API changes would be silently pulled in bypip install 'forgelm[ingestion]'and break the format extractors at runtime. (pypdf 6.x is already publicly available as of April 2026.)♻️ Proposed bounds
ingestion = [ - "pypdf>=4.0.0", - "python-docx>=1.0.0", - "ebooklib>=0.18", - "beautifulsoup4>=4.12.0", - "langdetect>=1.0.9", + "pypdf>=4.0.0,<6.0.0", + "python-docx>=1.0.0,<2.0.0", + "ebooklib>=0.18,<1.0.0", + "beautifulsoup4>=4.12.0,<5.0.0", + "langdetect>=1.0.9,<2.0.0", ]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pyproject.toml` around lines 86 - 92, The ingestion extras in pyproject.toml currently use open-ended >= pins for pypdf, python-docx, ebooklib, beautifulsoup4, and langdetect; update the ingestion array to add conservative upper bounds that cap the next major (e.g., pypdf>=4.0.0,<6.0.0, python-docx>=1.0.0,<2.0.0, ebooklib>=0.18,<1.0.0, beautifulsoup4>=4.12.0,<5.0.0, langdetect>=1.0.9,<2.0.0) so future breaking major releases won’t be pulled in—modify the ingestion extras entry in pyproject.toml accordingly.docs/roadmap/phase-11-data-ingestion.md (1)
3-3: Status banner is fine but the static-analysis hint is worth a glance.
markdownlintflags MD028 (blank line inside the blockquote that follows on line 5) and MD001 (heading jumps from#to### Tasks:at line 13). Neither blocks anything, but if you're already touching the file you can collapse the blank line and demote### Tasks:to## Tasks:to keep the linter quiet.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/roadmap/phase-11-data-ingestion.md` at line 3, The markdown linter warnings come from a blank line inside the blockquote after the Status banner and an incorrectly leveled heading; remove the empty line inside the blockquote (collapse the blockquote so there is no blank line after the Status line) and change the heading "### Tasks:" to "## Tasks:" so the heading levels do not jump and MD028/MD001 are resolved.tests/test_data_audit.py (3)
76-78: Silence SonarCloud type-arg warnings consistently with line 123.These two calls intentionally exercise the non-string guard in
detect_pii, but SonarCloud flags them (failures at L77 and L78). Line 123 already uses# type: ignore[arg-type]for the same scenario onmask_pii— apply the same suppression here for consistency and a clean CI run.♻️ Proposed change
def test_returns_empty_for_non_string(self): - assert detect_pii(None) == {} - assert detect_pii(42) == {} + assert detect_pii(None) == {} # type: ignore[arg-type] + assert detect_pii(42) == {} # type: ignore[arg-type]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/test_data_audit.py` around lines 76 - 78, Add the same SonarCloud type-arg suppression used for mask_pii to these tests: in test_returns_empty_for_non_string update the two assertions that call detect_pii(None) and detect_pii(42) to append a "# type: ignore[arg-type]" comment to each call so the non-string guard is still tested but static type checkers/CI warnings are suppressed; locate the assertions inside the test_returns_empty_for_non_string function around detect_pii and add the comments there.
109-116: Drop the redundant local import of_is_credit_card.
_is_credit_cardis already imported at the top of the file (line 17), so the inlinefrom forgelm.data_audit import _is_credit_cardhere is dead. The explanatory docstring above is fine to keep.♻️ Proposed change
def test_luhn_helper_distinguishes_valid_from_invalid(self): # The masker may also redact long digit runs as candidate phone # numbers (false positives are intentional per the module docstring). # We assert at the helper level instead, which is unambiguous. - from forgelm.data_audit import _is_credit_card - assert _is_credit_card("4111111111111111") is True assert _is_credit_card("4111111111111112") is False🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/test_data_audit.py` around lines 109 - 116, Remove the redundant inline import in the test method test_luhn_helper_distinguishes_valid_from_invalid: delete the line "from forgelm.data_audit import _is_credit_card" and rely on the existing module-level import of _is_credit_card; leave the surrounding docstring and the two assert lines unchanged so the test still calls the helper _is_credit_card("4111111111111111") and _is_credit_card("4111111111111112").
257-266:caplog.at_level("WARNING")context is set up but never asserted on.The PR comment for Bug 26 says the operator must be warned about pseudo-splits, but this test only verifies the
report.notesblob — nothing checks that a WARNING-level log record was actually emitted. Either assert oncaplog.records(the stronger guarantee, since notes and logs can drift apart) or drop thecaplogfixture entirely to keep the test honest.♻️ Proposed change (assert the warning was logged)
- def test_pseudo_split_fallback_warns(self, tmp_path, caplog): + def test_pseudo_split_fallback_warns(self, tmp_path, caplog): # Bug 26: when no canonical split files exist, every .jsonl becomes # its own pseudo-split BUT the operator must be warned that # cross-split leakage analysis is meaningless in that case. _write_jsonl(tmp_path / "alpha.jsonl", [{"text": "x"}]) _write_jsonl(tmp_path / "beta.jsonl", [{"text": "y"}]) with caplog.at_level("WARNING"): report = audit_dataset(str(tmp_path)) assert "alpha" in report.splits and "beta" in report.splits assert any("pseudo-split" in n for n in report.notes) + assert any( + r.levelname == "WARNING" and "pseudo-split" in r.getMessage() + for r in caplog.records + )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/test_data_audit.py` around lines 257 - 266, The test test_pseudo_split_fallback_warns sets up caplog but never asserts a log was emitted; update the test to assert that a WARNING-level log record about pseudo-splits was emitted when calling audit_dataset (e.g., inspect caplog.records or caplog.text for a message containing "pseudo-split" or similar) in addition to the existing report.notes checks, or remove the caplog usage if you prefer not to assert logs; focus your change inside test_pseudo_split_fallback_warns and reference caplog, audit_dataset, and report.notes when adding the assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/guides/data_audit.md`:
- Line 58: The fenced code block starting before the JSON object that contains
"cross_split_overlap" is missing a language identifier; update the opening
triple-backtick fence to include "json" so markdownlint MD040 is satisfied
(i.e., change ``` to ```json immediately before the object that begins with
"cross_split_overlap"). Ensure the rest of the block remains unchanged and that
the closing ``` remains present.
- Around line 21-24: The documentation currently contradicts itself about what
`--output-format json` prints; reconcile both descriptions so they match: decide
whether `--output-format json` writes the full JSON report to stdout or only a
summary (while always writing the full `data_audit_report.json` to `--output`),
then update the earlier paragraph (mentions `--output`,
`data_audit_report.json`, and `--output-format json`) and the later Note (the
explicit Note that references `--output-format json`, top-level metrics + report
path, and `data_audit_report.json`) to state the chosen behavior consistently
and clearly for CI users.
- Around line 1-7: Create a Turkish mirror of the user-facing guide by adding a
new file named data_audit-tr.md that is a full translation of data_audit.md:
translate the title and body into Turkish but preserve and not translate
code/CLI examples such as `forgelm --data-audit` and artifact names like
`data_audit_report.json`, keep references to trainer concepts like `output_dir`,
and ensure frontmatter/metadata (if any) matches the original; finally, register
the new Turkish page in the docs navigation/sidebar so it appears alongside the
English guide.
In `@docs/guides/ingestion.md`:
- Around line 116-122: The docs currently state encrypted PDFs surface a
ValueError to the operator, but the actual behavior in PdfExtractor and tests
(see TestPdfExtractor::test_encrypted_pdf_raises_clear_error) is to catch the
failure per-file, log a warning, and append the file to files_skipped rather
than exiting; update the wording in docs/guides/ingestion.md to describe this
per-file handling (warning + files_skipped) and include the suggested
external-decrypt command as remediation for skipped files.
- Around line 1-10: Add a Turkish mirror of the user-facing guide by creating a
new file named docs/guides/ingestion-tr.md that is a bilingual mirror of
docs/guides/ingestion.md: copy the content and translate all user-facing text
into Turkish, preserve any Markdown structure, links (adjust localized link
targets if needed), headings, code blocks and metadata, and ensure the new file
matches the same frontmatter/formatting conventions as other -tr guides so it
appears alongside ingestion.md in the docs build.
In `@docs/reference/usage.md`:
- Around line 165-168: The closing trailer sentence "This uses the `synthetic`
config section to generate training data from a teacher model before training
begins. See the [Configuration Guide](configuration.md) for all synthetic data
options." is orphaned and must be moved so it immediately follows the Synthetic
Data Generation section (the paragraph that begins with the forgelm --config
example), not after the Document Ingestion / Dataset Audit blocks; relocate that
exact sentence to sit directly after the synthetic example/trailer in the
Synthetic Data Generation section (or hoist it up before inserting the Document
Ingestion and Dataset Audit sections) so the Document Ingestion (v0.5.0+) and
Dataset Audit paragraphs appear after that trailer and read correctly.
- Around line 130-166: Add Turkish translations of the two missing sections
titled "Document Ingestion (v0.5.0+)" and "Dataset Audit (v0.5.0+)" into the
Turkish mirror (usage-tr) so it matches the English usage reference; include the
same command examples (single file, recursive, sliding-window, PII mask)
translated text for descriptions, keep the version note "(v0.5.0+)", preserve
links to the corresponding guides (ingestion and data_audit) and the note about
the trainer inlining data_audit findings under the data_audit key so the
bilingual docs remain in sync.
In `@docs/roadmap.md`:
- Line 20: Update the date for the v0.4.0 roadmap entry where the string "v0.4.0
— Post-Training Completion (2026-04-26)" appears so it reads "v0.4.0 —
Post-Training Completion (2026-04-25)" to match CHANGELOG.md; make the identical
replacement in the translated roadmap entry that contains the same v0.4.0
string.
In `@docs/roadmap/phase-11-5-backlog.md`:
- Around line 11-12: The count in the table header (| # | Item | Why deferred |
Effort | Impact |) and the PR summary disagree: update the table rows or the PR
prose so they match; either add the two missing rows to the table to reach "14
deferred follow-ups" or change the PR summary to "12 deferred follow-ups", and
if two items indeed landed mid-cycle, list them under the "Recently landed"
section (mention their titles/IDs) so the document and PR description are
consistent.
In `@docs/roadmap/phase-11-data-ingestion.md`:
- Around line 15-16: Update the roadmap entry to match the shipped ingestion
output: replace the SFT-style {"messages":[...]} schema with the actual JSONL
record shape emitted by forgelm.ingestion (records of the form {"text":
"<chunk>"}), keep the same mention of supported input formats and chunking
strategies (sliding/paragraph/semantic), and note the v0.5.0 behavior and
optional dependency group (forgelm[ingestion]); also update related docs
(releases and reference/usage) to use the same {"text": ...} schema so loaders
aren’t written against a nonexistent messages format.
In `@forgelm/ingestion.py`:
- Around line 374-380: The current code increments pii_redaction_counts by
running detect_pii(payload) which independently applies all _PII_PATTERNS and
can double-count spans that match multiple patterns while mask_pii mutates the
text in a single pass, causing counts to drift; change mask_pii to perform the
single-pass redaction and also return a per-type count (e.g., return
(masked_text, counts_dict)), then replace the detect_pii(payload) usage in this
block with the counts returned from mask_pii so pii_redaction_counts is
incremented from the actual redactions, and update any call sites of mask_pii to
accept the new return shape (or alternatively make mask_pii accept a flag to
return counts) while preserving existing behavior for other callers.
In `@tests/test_ingestion.py`:
- Around line 257-274: Update the docs text in guides/ingestion.md so it matches
the actual behavior asserted by test_encrypted_pdf_raises_clear_error: instead
of saying a ValueError is raised, state that encrypted PDFs are logged as a
warning and skipped during batch ingestion (files_skipped increments) and ensure
the warning message recommends the external decrypt path to the operator;
reference the ingest_path flow and the test name to locate the behavior to
document.
---
Outside diff comments:
In `@docs/standards/architecture.md`:
- Line 71: Update the stale sentence "Seventeen single-file modules." to
accurately reflect the current module count (at least 18) or, better, remove the
hard-coded number and replace it with a dynamic phrasing like "multiple
single-file modules" or "N single-file modules" that is maintained alongside the
topology table; change the exact text token "Seventeen single-file modules." in
the architecture prose so it matches the modules listed (including ingestion.py,
data_audit.py, and inference.py) and ensure the guidance about splitting modules
into module_name/ packages (and keeping public API at forgelm.module_name.X)
remains unchanged.
In `@README.md`:
- Line 241: The "tests/" line in README.md contains a stale test summary ("# 430
passed (+34 skipped) across 30 test files") that doesn't match the PR/test run
(718 passed, 11 skipped) — update or remove this count to avoid future rot:
either replace the existing summary text next to the "tests/" entry with the
current v0.5.0rc1 results (718 passed, 11 skipped) or remove the numeric summary
entirely so the "tests/" bullet remains without hard-coded counts; edit the line
that begins with "tests/" in README.md to apply the change.
---
Duplicate comments:
In `@docs/roadmap-tr.md`:
- Line 20: The v0.4.0 entry in docs/roadmap-tr.md currently shows the date
2026-04-26 which drifts from CHANGELOG.md and the EN roadmap; update the date
string for the `v0.4.0` entry to 2026-04-25 so the Turkish mirror (`v0.4.0` —
Post-Training Tamamlama) matches the canonical sources.
---
Nitpick comments:
In `@docs/roadmap/phase-11-data-ingestion.md`:
- Line 3: The markdown linter warnings come from a blank line inside the
blockquote after the Status banner and an incorrectly leveled heading; remove
the empty line inside the blockquote (collapse the blockquote so there is no
blank line after the Status line) and change the heading "### Tasks:" to "##
Tasks:" so the heading levels do not jump and MD028/MD001 are resolved.
In `@forgelm/cli.py`:
- Around line 1106-1117: The ImportError handler in the ingest command currently
exits with EXIT_CONFIG_ERROR while _run_chat_cmd maps ImportError to
EXIT_TRAINING_ERROR; make the exit-code convention consistent by changing the
ImportError except block (the "except ImportError as exc:" handler that uses
output_format and logger) to exit with EXIT_TRAINING_ERROR instead of
EXIT_CONFIG_ERROR, and update docs/standards/error-handling.md to document that
missing optional dependency (ImportError) should use EXIT_TRAINING_ERROR across
subcommands.
- Around line 1139-1153: The current _run_data_audit function only catches
FileNotFoundError from audit_dataset, leaving other filesystem-related OSError
subclasses (permission errors, ENOSPC, etc.) unhandled; update the except clause
to catch OSError (or add an additional except OSError as exc) so any
filesystem/IO errors from audit_dataset, _resolve_input, or _read_jsonl_split
are handled the same way: when output_format == "json" print a JSON error
envelope with the stringified exc, otherwise call logger.error("Audit failed:
%s", exc), and then sys.exit(EXIT_CONFIG_ERROR) — reference _run_data_audit,
audit_dataset, output_format, logger, json and EXIT_CONFIG_ERROR to locate the
change.
In `@forgelm/compliance.py`:
- Around line 139-151: The current inlining of data_audit_report.json uses a
try/except that catches json.JSONDecodeError and OSError but will still raise on
a corrupt UTF-8 file (UnicodeDecodeError); update the exception tuple in the
except block that surrounds the open+json.load (the block that assigns
report["data_audit"]) to include UnicodeDecodeError so the handler
(logger.warning(..., audit_path, exc)) swallows encoding errors as well and
keeps the process non‑fatal for output_dir/audit_path.
In `@forgelm/data_audit.py`:
- Line 100: The credit card regex under "credit_card" uses a reluctant
quantifier (\d[ -]*?) which causes unnecessary backtracking; update the pattern
in forgelm/data_audit.py to use a greedy quantifier (\d[ -]*) so the entry
"credit_card" becomes \b(?:\d[ -]*){13,19}\b (keep the surrounding word
boundaries and same repetition range) to improve performance while preserving
behavior.
- Around line 459-514: The _resolve_input function is too complex; extract the
directory-scanning branch into a new helper _resolve_directory_splits(src) that
encapsulates the logic currently inside the if src.is_dir() block (use
_SPLIT_ALIASES, discovery of canonical/alias files, building layouts, notes, the
pseudo-split fallback and logger.warning), and have _resolve_input call this
helper and return its (layouts, notes) result or continue to raise
FileNotFoundError for missing input; this reduces nesting and allows unit
testing of directory logic in _resolve_directory_splits separately.
- Around line 435-438: The dict entry creation in report["pairs"][f"{a}__{b}"]
mixes a .format() call with f-strings and includes an unreachable ternary
because fp_a is already guarded by an earlier continue; change the key name to
use an f-string (e.g., f"leaked_rows_in_{a}") for consistency and remove the
unnecessary "if fp_a else 0.0" branch so leak_rate is computed as round(shared /
len(fp_a), 4) (relying on the prior if not fp_a: continue), updating the code
that builds report["pairs"][f"{a}__{b}"] accordingly.
- Around line 352-415: _audit_split currently exceeds cognitive complexity by
mixing schema/drift discovery, payload extraction, length stats, language
sampling, simhashing and PII counting into one large function; refactor by
extracting those independent passes into small helpers and call them from
_audit_split: implement _compute_schema(rows) -> (columns, drift_columns) to
build seen_columns and detect drift vs base_columns, _compute_payloads(rows) ->
(text_payloads, null_or_empty) to run _extract_text_payload and compute null
counts, _compute_languages(text_payloads, sample_size=200) to sample and return
top language counts, _compute_fingerprints(split_name, text_payloads) to compute
simhashes with progress logging and return fingerprints and simhash distinct
count, and _compute_pii(text_payloads) to aggregate detect_pii results; update
_audit_split to orchestrate these helpers and assemble info, fingerprints and
pii_counts_split accordingly so behavior and returned tuple remain unchanged.
In `@forgelm/ingestion.py`:
- Around line 291-414: ingest_path is over-complex due to a large per-file loop;
extract that logic into a new helper (e.g. _process_file) that takes (fpath,
strategy, chunk_size, overlap, mask_pii, detect_pii, out_fh) and returns a small
result struct/tuple (chunks_written, chars_written, per_file_pii_counts,
skipped_flag, format_ext) so the main ingest_path just iterates files, calls
_select_extractor then _process_file, and accumulates chunk_count, total_chars,
format_counts and pii_redaction_counts; move error handling for extractor calls,
empty-text checks, chunking via _strategy_dispatch, optional PII
detect_pii/mask_pii logic, and the out_fh.write(json.dumps(...)) into
_process_file to reduce ingest_path cognitive complexity while preserving
existing symbols (ingest_path, _select_extractor, _strategy_dispatch,
detect_pii, mask_pii, IngestionResult).
In `@pyproject.toml`:
- Around line 86-92: The ingestion extras in pyproject.toml currently use
open-ended >= pins for pypdf, python-docx, ebooklib, beautifulsoup4, and
langdetect; update the ingestion array to add conservative upper bounds that cap
the next major (e.g., pypdf>=4.0.0,<6.0.0, python-docx>=1.0.0,<2.0.0,
ebooklib>=0.18,<1.0.0, beautifulsoup4>=4.12.0,<5.0.0, langdetect>=1.0.9,<2.0.0)
so future breaking major releases won’t be pulled in—modify the ingestion extras
entry in pyproject.toml accordingly.
In `@tests/test_data_audit.py`:
- Around line 76-78: Add the same SonarCloud type-arg suppression used for
mask_pii to these tests: in test_returns_empty_for_non_string update the two
assertions that call detect_pii(None) and detect_pii(42) to append a "# type:
ignore[arg-type]" comment to each call so the non-string guard is still tested
but static type checkers/CI warnings are suppressed; locate the assertions
inside the test_returns_empty_for_non_string function around detect_pii and add
the comments there.
- Around line 109-116: Remove the redundant inline import in the test method
test_luhn_helper_distinguishes_valid_from_invalid: delete the line "from
forgelm.data_audit import _is_credit_card" and rely on the existing module-level
import of _is_credit_card; leave the surrounding docstring and the two assert
lines unchanged so the test still calls the helper
_is_credit_card("4111111111111111") and _is_credit_card("4111111111111112").
- Around line 257-266: The test test_pseudo_split_fallback_warns sets up caplog
but never asserts a log was emitted; update the test to assert that a
WARNING-level log record about pseudo-splits was emitted when calling
audit_dataset (e.g., inspect caplog.records or caplog.text for a message
containing "pseudo-split" or similar) in addition to the existing report.notes
checks, or remove the caplog usage if you prefer not to assert logs; focus your
change inside test_pseudo_split_fallback_warns and reference caplog,
audit_dataset, and report.notes when adding the assertion.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 51515584-5e3c-4707-8459-61905d832f97
📒 Files selected for processing (25)
.github/workflows/nightly.ymlCHANGELOG.mdREADME.mddocs/guides/data_audit.mddocs/guides/ingestion.mddocs/guides/quickstart.mddocs/qms/sop_data_management.mddocs/reference/usage.mddocs/roadmap-tr.mddocs/roadmap.mddocs/roadmap/phase-11-5-backlog.mddocs/roadmap/phase-11-data-ingestion.mddocs/roadmap/phase-12-quickstart.mddocs/roadmap/releases.mddocs/standards/architecture.mdforgelm/__init__.pyforgelm/cli.pyforgelm/compliance.pyforgelm/data_audit.pyforgelm/ingestion.pyforgelm/wizard.pypyproject.tomltests/test_data_audit.pytests/test_ingestion.pytests/test_wizard_byod.py
|
|
||
| ### Cross-split overlap | ||
|
|
||
| ```json |
There was a problem hiding this comment.
Tag fenced block language for markdownlint MD040.
The fenced block at line 58 has no language identifier — markdownlint flags it. Use json to match the rest of the file.
📝 Proposed fix
-```
+```json
{
"cross_split_overlap": {🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 58-58: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/guides/data_audit.md` at line 58, The fenced code block starting before
the JSON object that contains "cross_split_overlap" is missing a language
identifier; update the opening triple-backtick fence to include "json" so
markdownlint MD040 is satisfied (i.e., change ``` to ```json immediately before
the object that begins with "cross_split_overlap"). Ensure the rest of the block
remains unchanged and that the closing ``` remains present.
| # Document Ingestion Guide | ||
|
|
||
| Convert raw enterprise corpora (PDF / DOCX / EPUB / TXT / Markdown) into the | ||
| SFT-ready JSONL ForgeLM trains on. Phase 11; introduced in `v0.5.0`. | ||
|
|
||
| > Pair with [`forgelm --data-audit`](data_audit.md) afterwards to surface | ||
| > length-distribution / language / near-duplicate / PII metrics, and with | ||
| > [`forgelm --generate-data`](../reference/usage.md) if you want the chunks | ||
| > expanded into Q&A `messages` form via a teacher model. | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
fd -t f 'ingestion.*\.md$' docs/Repository: cemililik/ForgeLM
Length of output: 126
🏁 Script executed:
#!/bin/bash
# List all markdown files in docs/guides/
echo "=== All markdown files in docs/guides/ ==="
fd -t f '\.md$' docs/guides/
echo ""
echo "=== Check for Turkish mirror patterns (-tr.md) ==="
fd -t f '\-tr\.md$' docs/guides/
echo ""
echo "=== Check entire docs directory for Turkish mirrors ==="
fd -t f '\-tr\.md$' docs/Repository: cemililik/ForgeLM
Length of output: 668
Missing Turkish mirror for user-facing guide.
docs/guides/ingestion.md is missing a docs/guides/ingestion-tr.md companion. Per the project guideline, user-facing docs under docs/guides/ must ship as bilingual mirror documents.
Note: All files in docs/guides/ currently lack Turkish mirrors (not just this file), while docs/reference/ guides have implemented them. Ensure consistency across the documentation structure.
🧰 Tools
🪛 LanguageTool
[locale-violation] ~6-~6: In American English, ‘afterward’ is the preferred variant. ‘Afterwards’ is more commonly used in British English and other dialects.
Context: ...forgelm --data-audit afterwards to surface > length-distribution / lang...
(AFTERWARDS_US)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/guides/ingestion.md` around lines 1 - 10, Add a Turkish mirror of the
user-facing guide by creating a new file named docs/guides/ingestion-tr.md that
is a bilingual mirror of docs/guides/ingestion.md: copy the content and
translate all user-facing text into Turkish, preserve any Markdown structure,
links (adjust localized link targets if needed), headings, code blocks and
metadata, and ensure the new file matches the same frontmatter/formatting
conventions as other -tr guides so it appears alongside ingestion.md in the docs
build.
| **Merged on `development`, tag + PyPI publish pending:** `v0.5.0` — Document Ingestion & Data Audit (Phase 11). `forgelm ingest` turns raw PDF/DOCX/EPUB/TXT into SFT-ready JSONL; `forgelm --data-audit` produces a CPU-only dataset quality report (length distribution, near-duplicate detection, cross-split leakage, PII flags) that feeds the EU AI Act Article 10 governance artifact automatically. | ||
|
|
||
| **Current milestone:** `v0.5.0` — Document Ingestion & Data Audit (Phase 11). PDF/DOCX/EPUB → JSONL, PII detection, near-duplicate audit. | ||
| **Previous:** `v0.4.0` — Post-Training Completion (2026-04-26). Inference primitives, interactive chat REPL, GGUF export, VRAM fit advisor, and deployment config generation. |
There was a problem hiding this comment.
Date for v0.4.0 disagrees with CHANGELOG.
This line says v0.4.0 — Post-Training Completion (2026-04-26), but CHANGELOG.md line 105 records v0.4.0 as 2026-04-25. v0.4.5 is the 2026-04-26 release. Same fix needed in docs/roadmap-tr.md line 20.
📝 Proposed fix
-**Previous:** `v0.4.0` — Post-Training Completion (2026-04-26). Inference primitives, interactive chat REPL, GGUF export, VRAM fit advisor, and deployment config generation.
+**Previous:** `v0.4.0` — Post-Training Completion (2026-04-25). Inference primitives, interactive chat REPL, GGUF export, VRAM fit advisor, and deployment config generation.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| **Previous:** `v0.4.0` — Post-Training Completion (2026-04-26). Inference primitives, interactive chat REPL, GGUF export, VRAM fit advisor, and deployment config generation. | |
| **Previous:** `v0.4.0` — Post-Training Completion (2026-04-25). Inference primitives, interactive chat REPL, GGUF export, VRAM fit advisor, and deployment config generation. |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/roadmap.md` at line 20, Update the date for the v0.4.0 roadmap entry
where the string "v0.4.0 — Post-Training Completion (2026-04-26)" appears so it
reads "v0.4.0 — Post-Training Completion (2026-04-25)" to match CHANGELOG.md;
make the identical replacement in the translated roadmap entry that contains the
same v0.4.0 string.
Verified each of 30+ findings against current code. 17 real issues
fixed in this commit; 6 declined as cosmetic/refactor nits already
covered by docs/roadmap/phase-11-5-backlog.md; 1 finding (data_audit.md
fence missing language) was a false positive — fence already had
'json'. The reviewer's v0.4.0 date direction was inverted: tag + PyPI
upload both stamp 2026-04-26, so CHANGELOG entry [0.4.0] — 2026-04-25
was the outlier and gets aligned upward, not roadmap pulled down.
Code
- ingestion.py: mask_pii now optionally returns (masked_text,
counts_dict) via return_counts=True; ingest_path uses that for
pii_redaction_counts so a span matched by multiple patterns isn't
double-counted (Bug 11). detect_pii() pre-pass removed.
- ingestion.py _extract_docx: walk doc.tables and flatten cell text
in row-major order; docs already promised this behavior (Bug 30).
- data_audit.py credit_card: \d[ -]*? → \d[ -]* (greedy); same set
match, no needless backtracking (Bug 19).
- data_audit.py _cross_split_overlap: f-string for the per-pair key
+ drop dead `if fp_a else 0.0` ternary that was guarded above.
- compliance.py: UnicodeDecodeError now caught when inlining
data_audit_report.json so a corrupt UTF-8 audit file doesn't abort
governance report generation (Bug 18).
- cli.py _run_ingest_cmd: ImportError now exits with
EXIT_TRAINING_ERROR to match chat / export / etc. — missing extras
are a runtime failure, not config validation (Bug 16).
- cli.py _run_data_audit: catch (FileNotFoundError, OSError) so
PermissionError / ENOSPC / IsADirectoryError that bubble up before
the per-split tolerance loop are reported, not opaque crashes (Bug 17).
- cli.py _run_ingest_cmd JSON output: structured pii_redaction_counts
field surfaced (was only in notes string).
Docs
- docs/guides/data_audit.md: reconciled --output-format json behaviour
description (top + later Note now agree: stdout = summary, disk =
full report) (Bug 2). Encrypted-PDF wording in
docs/guides/ingestion.md updated to match per-file skip-with-warning
behavior the test asserts (Bug 4).
- docs/reference/usage.md: orphaned synthetic-config trailer moved
back into the Synthetic Data Generation section (Bug 6).
- docs/reference/usage-tr.md: new "Doküman Yutma" + "Veri Seti
Denetimi" sections mirror the EN additions (Bug 7).
- docs/guides/ingestion-tr.md, data_audit-tr.md: new TR mirrors of the
Phase 11 user-facing guides (Bugs 3, 5).
- README.md: replace stale "430 passed (+34 skipped) across 30 test
files" with version-stable wording; add TR mirror links to the new
Phase 11 guides (Bug 13).
- docs/standards/architecture.md: drop hard-coded "Seventeen single-file
modules" — count tracks the table (Bug 12).
- docs/roadmap/phase-11-data-ingestion.md: spec output schema
{"messages":[…]} → {"text": …} matching shipped behavior; collapse
duplicate blockquote; lift "### Tasks:" / "### Requirements:" /
"### Delivery:" to ## level so heading levels don't jump (Bugs 10, 15).
- docs/roadmap/phase-11-5-backlog.md: "Recently landed" callout now
enumerates the three pulled-forward items + states explicit count
(12 deferred / 19 fixed / 3 landed = 34 original) for PR-summary
consistency (Bug 9).
- CHANGELOG.md: v0.4.0 date 2026-04-25 → 2026-04-26 to align with the
authoritative tag commit + PyPI upload timestamps (Bug 8).
Build
- pyproject.toml [ingestion] extra: add upper bounds (pypdf<6,
python-docx<2, ebooklib<1, beautifulsoup4<5, langdetect<2) so a
future major bump on any of those doesn't pull breaking changes
into a `pip install --upgrade` flow.
Tests
- test_data_audit.py: type-ignore[arg-type] on detect_pii(None) /
detect_pii(42) calls; remove duplicate _is_credit_card import
(already imported at module scope); test_pseudo_split_fallback_warns
now asserts the warning actually reached the logger (caplog),
in addition to the existing report.notes check.
Declined (Phase 11.5 backlog scope, already documented):
- _resolve_input / _audit_split / ingest_path refactors for cognitive
complexity (cosmetic; module-level concerns stay grouped per
architecture.md's "one concern per file" guidance).
- O(n²) near-duplicate detection (already item 1 in backlog —
LSH banding).
- All tests/test_*.py refactors not described above.
Verified locally: 718 passing tests (11 skipped pre-existing TRL+pypdf
extra paths), ruff clean, end-to-end CLI smoke confirms structured
pii_redaction_counts in --output-format json output.
All 12 findings verified by reproducing against current code (Findings
1, 2, 3, 4, 5, 8, 10 with concrete repro scripts; 6, 7, 9, 11, 12 by
direct code inspection). Fix shape chosen per finding rather than
mechanically applied — see per-bug rationale below.
Real bugs (high → medium severity)
- Finding 1: non-dict JSONL row crashed audit at row.get(). Fixed by
treating array / scalar rows as `non_object_rows` in a SEPARATE
counter (not conflated with null_or_empty per the reviewer's
suggestion — operator visibility is sharper this way). Surfaces in
splits[name]["non_object_rows"]; downstream length / fingerprint
passes get an empty payload for those rows so stats stay honest.
- Finding 2: cross-split leak rate reported in the asymmetric
direction (5/100=5% hides 5/10=50% test contamination). Fixed by
reporting BOTH directions: leak_rate_<a> + leak_rate_<b> with
matching leaked_rows_in_<split> counts. Notes line now picks the
WORST direction explicitly ("Worst leak rate: 50.00% (train__test)").
Single-pass implementation — no extra perf cost over the prior
asymmetric scan.
- Finding 3: UnicodeDecodeError used to abort the whole audit.
_read_jsonl_split now opens with errors="replace" + tracks U+FFFD
occurrences as decode_errors so non-UTF-8 bytes don't kill the run
but the operator still gets a structured signal. Mirrors the
pattern ingestion._extract_text already uses (round 2's Finding 10).
- Finding 4: malformed JSONL lines were log-only — parseable subset
was reported with no count of dropped lines. _read_jsonl_split now
returns (rows, parse_errors, decode_errors) and audit_dataset
surfaces both as splits[name]["parse_errors"] /
["decode_errors"] + an aggregate "Data integrity" note when any
split has either. Article 10 audits now distinguish "this dataset
has 1240 rows" from "this dataset had 1330 lines but 90 were
silently dropped."
- Finding 5: sliding chunking emitted runt trailing fragments
(text=205, chunk=200, overlap=100 → 3 chunks of [200, 105, 5]).
Early-exit guard returns once the current window already covers
end-of-text; example above now yields 2 chunks of [200, 105].
The runt was a pure prefix of the prior chunk's overlap region —
zero new context, but it inflated near-duplicate counts and
skewed length stats.
- Finding 6: Article 10 governance auto-inlining looks for
data_audit_report.json in training.output_dir, but the audit CLI
defaults to ./audit/. When operators use defaults the inlining
silently no-ops. Fixed by emitting a clear INFO log naming the
expected path AND the exact command to populate it
("Run `forgelm --data-audit <dataset> --output <output_dir>`
before training to populate it"). Smart-default coupling
(auto-deriving --output from training.output_dir) was rejected as
it'd violate the "audit module is independent of training config"
separation; loud log is cheaper and just as effective.
- Finding 9: schema_drift_columns used rows[0] as the "base shape" —
fragile when row 0 is the outlier (header row, single missing
field, the non-dict case from Finding 1). Replaced with modal
keyset via collections.Counter; the most common shape across rows
becomes the base. False-drift on outlier-row-0 datasets eliminated.
- Finding 11: open(dst, "w", encoding=...) gave Windows CRLF line
endings via universal newline translation. JSONL Files spec
mandates LF, and CRLF breaks byte-exact reproducibility checksums
between Linux/macOS and Windows runs of the same ingest. Both
audit JSON write and ingest JSONL write now pass newline="\n".
- Finding 12: audit_dataset back-filled near_duplicate_pairs into
the info dict that _audit_split returned — split between two
functions, hidden coupling. Moved find_near_duplicates inside
_audit_split so info is fully owned by one function. Keeps the
contract obvious: _audit_split returns a complete info dict + the
fingerprints + per-split PII counts.
Test coverage gaps
- Finding 7: DOCX and EPUB extractors had ZERO automated tests.
Added TestDocxExtractor (paragraphs + row-major-flattened tables
per Bug 30) and TestEpubExtractor (chapter content). Both fixtures
built by the dep itself (python-docx, ebooklib) — no third-party
fixture files. skipif(not _has(...)) so the test suite still runs
cleanly without the [ingestion] extra installed.
- Finding 8: mask_pii(return_counts=True) had no direct tests.
TestMaskPiiReturnCounts adds 5 tests: aggregation per pattern,
first-match-wins (no double-count), invalid-Luhn-not-counted,
non-string returns empty counts, back-compat one-arg form still
returns a plain string. Locks down the compliance-evidence path.
- Finding 6 (cont): TestGovernanceAuditInlining adds 3 tests against
forgelm.compliance.generate_data_governance_report — present /
corrupt / missing audit JSON paths.
Doc fix
- Finding 10: docs/guides/data_audit.md (+ -tr) example imported
asdict from forgelm.data_audit — works only because the module
re-exports the symbol unintentionally. Fixed to
`from dataclasses import asdict` so a future cleanup of the
module's import surface doesn't rot the docs.
Verified: ruff clean, 737 passing tests (+19 vs. round 2), end-to-end
smoke confirms a single 5-line JSONL with array / scalar / parse-error
/ mojibake rows and a valid row produces a complete report (no crash)
with non_object_rows=2, parse_errors=1, decode_errors=1, sample_count=4.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
docs/guides/data_audit-tr.md (1)
61-70:⚠️ Potential issue | 🟡 MinorSame
cross_split_overlapschema mismatch as the EN guide.The TR mirror inherits the same incorrect example shape. Apply the matching fix here so the bilingual pair stays in sync (see the EN comment for the corrected JSON).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/guides/data_audit-tr.md` around lines 61 - 70, The TR guide's JSON example for "cross_split_overlap" uses the incorrect schema for "pairs" — update the snippet so it matches the corrected schema used in the EN guide: keep "hamming_threshold" and the same semantic values but replace the current "pairs" shape with the corrected structure from the EN guide (apply the exact corrected JSON for "pairs" as in the EN comment) so "cross_split_overlap", "hamming_threshold" and "pairs" are consistent between languages.
🧹 Nitpick comments (5)
forgelm/cli.py (1)
1151-1151: Tiny cleanup:FileNotFoundErroris a subclass ofOSError.SonarCloud's hint here is valid — listing both in the
excepttuple is redundant sinceFileNotFoundErroris already anOSError. Drop the subclass for clarity:- except (FileNotFoundError, OSError) as exc: + except OSError as exc:Behavior is unchanged; the comment about
PermissionError / ENOSPC / IsADirectoryErroralready covers this correctly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@forgelm/cli.py` at line 1151, The except clause in the error handling block currently catches both FileNotFoundError and OSError redundantly; update the handler in the relevant try/except (the line reading "except (FileNotFoundError, OSError) as exc") to only catch OSError (i.e., remove FileNotFoundError from the tuple) so the behavior remains the same but the code is clearer; leave the existing comment about PermissionError/ENOSPC/IsADirectoryError unchanged.forgelm/data_audit.py (1)
521-545:matched_bis built but never read — drop it (or use it to halve the second pass).Inside
_cross_split_overlap,matched_baccumulates everyfp_bfingerprint that triggered a hit on the first sweep, butleaked_in_bis then computed by an independentany(hamming_distance(...) <= threshold for other in fp_a_set)overfp_b. The accumulator is dead code — the inline comment even acknowledges "matched_b as a starting point but a single pass over b is needed", but nothing actually consumes it.Also note:
fp_a_set = set(fp_a)doesn't accelerate the inner loop becausehamming_distancestill requires iterating elements (no O(1) lookup), so building the set is pure overhead.♻️ Suggested simplification
- fp_a_set = set(fp_a) - fp_b_set = set(fp_b) - # Single pass over fp_a captures both directions: - # rows in `a` whose nearest neighbour in `b` is within threshold, - # and the per-row matched fingerprints lift contributes to the - # `b`-side count. - leaked_in_a = 0 - matched_b: set = set() - for fp in fp_a: - for other in fp_b_set: - if hamming_distance(fp, other) <= threshold: - leaked_in_a += 1 - matched_b.add(other) - break - # Now count distinct b-rows whose nearest neighbour in `a` is - # close. We have `matched_b` as a starting point but a single - # pass over `b` is needed to find rows that match any `a`-row, - # not just the first `a`-row that triggered a match. - leaked_in_b = sum(1 for fp in fp_b if any(hamming_distance(fp, other) <= threshold for other in fp_a_set)) + leaked_in_a = sum( + 1 for fp in fp_a if any(hamming_distance(fp, other) <= threshold for other in fp_b) + ) + leaked_in_b = sum( + 1 for fp in fp_b if any(hamming_distance(fp, other) <= threshold for other in fp_a) + )This drops the unused accumulator and the no-op
set(...)calls without changing the reported metric.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@forgelm/data_audit.py` around lines 521 - 545, The matched_b accumulator and the fp_a_set/fp_b_set set conversions in _cross_split_overlap are dead/inefficient: remove matched_b and the set(...) allocations, compute leaked_in_a by iterating fp_a and checking any(hamming_distance(fp, other) <= threshold for other in fp_b) and compute leaked_in_b symmetrically by iterating fp_b and checking against fp_a; keep using leaked_in_a, leaked_in_b, threshold, hamming_distance and the existing report["pairs"][f"{a}__{b}"] population but drop matched_b, fp_a_set and fp_b_set to simplify and avoid unnecessary work.docs/guides/ingestion.md (1)
99-110: Tag the two unfenced code blocks for markdownlint MD040.The directory-tree fence at line 99 and the CLI-usage fence at line 161 lack a language identifier — markdownlint flags both, and the rest of the file uses explicit fences (e.g. line 161 in
data_audit-tr.mdusestext).📝 Proposed fixes
-``` +```text ./policies/ ├── 2024_q1.pdf-``` +```text forgelm ingest INPUT_PATH \ --output FILE \Also applies to: 159-171
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/guides/ingestion.md` around lines 99 - 110, Two code fences lack language identifiers: the directory-tree block that begins with "./policies/" and the CLI example that starts with "forgelm ingest ./policies/ --recursive --output data/all_policies.jsonl"; add a language tag (use "text") to both opening ``` fences so markdownlint MD040 is satisfied, and mirror the existing style used elsewhere (e.g., the fenced example in data_audit-tr.md) to keep consistency.forgelm/ingestion.py (2)
308-434: Optional: extract the per-file processing block to tame cognitive complexity.
ingest_pathpacks file iteration, extractor selection, exception triage, chunking, PII masking, JSONL writing, and counters into one body — SonarCloud reports cognitive complexity 30. Pulling the inner loop into a small helper (e.g._process_file(fpath, out_fh, …)returning chunks/chars/pii_counts) would let the entry point read top-down without changing behavior. Not blocking; flagging since the function is the primary touchpoint for future ingestion features (semantic chunking, additional formats).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@forgelm/ingestion.py` around lines 308 - 434, The ingest_path function is too cognitively complex; extract the inner per-file work into a helper like _process_file(fpath, out_fh, strategy, chunk_size, overlap, mask_pii) that performs extractor selection, exception handling, skipping logic, chunk generation, PII masking and JSONL writes, and returns counts (chunks_written, chars_written, file_processed_flag, file_skipped_flag, per_file_format, per_file_pii_counts); then simplify ingest_path to iterate files, call _process_file for each, and aggregate returned counters (chunk_count, total_chars, files_processed/skipped, format_counts, pii_redaction_counts, notes) so the high-level flow reads top-to-bottom without changing behavior or side effects.
371-378: Confirmed: ImportError re-raise ordering is correct.Worth a one-line comment here since
ImportErroris a subclass ofExceptionand a future reader could be tempted to consolidate the twoexceptclauses. Keepingexcept ImportError: raiseahead of the broadexcept Exceptionis what guarantees missing optional extras surface loudly instead of being silently downgraded to "skipped file". A short comment ("ImportError must propagate — missing extras are not a per-file skip") would prevent regressions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@forgelm/ingestion.py` around lines 371 - 378, Add a one-line comment above the ImportError except branch in the try/except around extractor(fpath) explaining that ImportError must propagate so missing optional extras surface (i.e., "ImportError must propagate — missing extras are not a per-file skip"), and keep the existing ordering with except ImportError: raise before the broader except Exception to prevent swallowing optional-dependency errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/guides/data_audit.md`:
- Around line 61-70: Update the example JSON for the "cross_split_overlap"
payload so each pair contains the four directional fields the tests expect:
leaked_rows_in_train, leaked_rows_in_test, leak_rate_train, and leak_rate_test
(instead of the single "leak_rate"); specifically replace the current pair
object under "pairs" for "train__test" with entries for both directions and
their respective leak counts/rates, and apply the same change to the TR mirror
of the guide.
In `@forgelm/cli.py`:
- Line 238: The ingest subparser help string and the top-level epilog need to
include Markdown (.md) to match SUPPORTED_EXTENSIONS; update the help= value
that currently reads "Convert raw documents (PDF / DOCX / EPUB / TXT) into
SFT-ready JSONL." to include "Markdown (.md)" (or ".md") and likewise edit the
epilog text so Markdown is listed among supported input types; reference the
existing SUPPORTED_EXTENSIONS constant to ensure wording exactly matches the
supported types.
---
Duplicate comments:
In `@docs/guides/data_audit-tr.md`:
- Around line 61-70: The TR guide's JSON example for "cross_split_overlap" uses
the incorrect schema for "pairs" — update the snippet so it matches the
corrected schema used in the EN guide: keep "hamming_threshold" and the same
semantic values but replace the current "pairs" shape with the corrected
structure from the EN guide (apply the exact corrected JSON for "pairs" as in
the EN comment) so "cross_split_overlap", "hamming_threshold" and "pairs" are
consistent between languages.
---
Nitpick comments:
In `@docs/guides/ingestion.md`:
- Around line 99-110: Two code fences lack language identifiers: the
directory-tree block that begins with "./policies/" and the CLI example that
starts with "forgelm ingest ./policies/ --recursive --output
data/all_policies.jsonl"; add a language tag (use "text") to both opening ```
fences so markdownlint MD040 is satisfied, and mirror the existing style used
elsewhere (e.g., the fenced example in data_audit-tr.md) to keep consistency.
In `@forgelm/cli.py`:
- Line 1151: The except clause in the error handling block currently catches
both FileNotFoundError and OSError redundantly; update the handler in the
relevant try/except (the line reading "except (FileNotFoundError, OSError) as
exc") to only catch OSError (i.e., remove FileNotFoundError from the tuple) so
the behavior remains the same but the code is clearer; leave the existing
comment about PermissionError/ENOSPC/IsADirectoryError unchanged.
In `@forgelm/data_audit.py`:
- Around line 521-545: The matched_b accumulator and the fp_a_set/fp_b_set set
conversions in _cross_split_overlap are dead/inefficient: remove matched_b and
the set(...) allocations, compute leaked_in_a by iterating fp_a and checking
any(hamming_distance(fp, other) <= threshold for other in fp_b) and compute
leaked_in_b symmetrically by iterating fp_b and checking against fp_a; keep
using leaked_in_a, leaked_in_b, threshold, hamming_distance and the existing
report["pairs"][f"{a}__{b}"] population but drop matched_b, fp_a_set and
fp_b_set to simplify and avoid unnecessary work.
In `@forgelm/ingestion.py`:
- Around line 308-434: The ingest_path function is too cognitively complex;
extract the inner per-file work into a helper like _process_file(fpath, out_fh,
strategy, chunk_size, overlap, mask_pii) that performs extractor selection,
exception handling, skipping logic, chunk generation, PII masking and JSONL
writes, and returns counts (chunks_written, chars_written, file_processed_flag,
file_skipped_flag, per_file_format, per_file_pii_counts); then simplify
ingest_path to iterate files, call _process_file for each, and aggregate
returned counters (chunk_count, total_chars, files_processed/skipped,
format_counts, pii_redaction_counts, notes) so the high-level flow reads
top-to-bottom without changing behavior or side effects.
- Around line 371-378: Add a one-line comment above the ImportError except
branch in the try/except around extractor(fpath) explaining that ImportError
must propagate so missing optional extras surface (i.e., "ImportError must
propagate — missing extras are not a per-file skip"), and keep the existing
ordering with except ImportError: raise before the broader except Exception to
prevent swallowing optional-dependency errors.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2a49dab8-70b0-4cf1-9a6c-70adaa461cdd
📒 Files selected for processing (19)
CHANGELOG.mdREADME.mddocs/guides/data_audit-tr.mddocs/guides/data_audit.mddocs/guides/ingestion-tr.mddocs/guides/ingestion.mddocs/reference/usage-tr.mddocs/reference/usage.mddocs/roadmap/phase-11-5-backlog.mddocs/roadmap/phase-11-data-ingestion.mddocs/standards/architecture.mdforgelm/cli.pyforgelm/compliance.pyforgelm/data_audit.pyforgelm/ingestion.pypyproject.tomltests/test_compliance.pytests/test_data_audit.pytests/test_ingestion.py
✅ Files skipped from review due to trivial changes (3)
- docs/roadmap/phase-11-5-backlog.md
- docs/reference/usage.md
- CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (3)
- docs/standards/architecture.md
- pyproject.toml
- docs/roadmap/phase-11-data-ingestion.md
…tors Round-4 reviewer surfaced 8 inline findings and SonarCloud independently flagged 5 cognitive-complexity violations + 4 type-mismatch warnings + 2 hardcoded-credential warnings + 1 weak-hash warning on the same surface. Three independent signals on the same code = signal, not noise. The ingest_path / _audit_split refactors I previously declined twice are applied this round. Inline-comment findings (small) - Finding 1+2 (data_audit.md / -tr.md L61-70): cross_split_overlap JSON example used the OLD `leak_rate` schema — updated to the dual-direction shape the code now emits (leaked_rows_in_<split>, leak_rate_<split>). Both EN and TR mirrors fixed. - Finding 3 (cli.py L238 + L302): `forgelm ingest --help` and the main parser epilog mentioned PDF/DOCX/EPUB/TXT but not Markdown, even though SUPPORTED_EXTENSIONS includes .md. Both strings now read "PDF / DOCX / EPUB / TXT / Markdown". - Finding 4 (ingestion.md L99-110): unfenced directory-tree code block got `text` language tag for markdownlint MD040. - Finding 5 (cli.py L1151): `except (FileNotFoundError, OSError)` → `except OSError` (FileNotFoundError is a subclass; the catch was redundant). Comment refreshed. - Finding 6 (data_audit.py L497-545): _cross_split_overlap had a dead `matched_b` accumulator + redundant set conversions. Extracted `_count_leaked_rows` + `_pair_leak_payload` helpers; the orchestrator is now a clean nested loop. Behavioural identity verified by tests. - Finding 7 (ingest_path refactor): previously declined twice. SonarCloud raised cog complexity 30 → 15 cap, third independent signal. Applied this round — _process_one_file helper extracts the per-file extract→chunk→mask→write block; ingest_path is now an aggregation loop over _FileOutcome dataclasses. Behaviour identical. - Finding 8 (ingestion.py L373): inline comment now states why ImportError must propagate (missing extras → EXIT_TRAINING_ERROR via the CLI wrapper, not a per-file skip). SonarCloud cognitive-complexity refactors - _audit_split (cog 31 → ~10): extracted four helpers — _compute_schema (modal-keyset drift detection + non_object_rows count), _compute_payloads (text + null/empty), _compute_top_languages, _compute_fingerprints (with progress logging), _aggregate_pii. - audit_dataset (cog 19 → ~10): extracted _process_split returning a _SplitOutcome dataclass + _pii_summary_notes + _cross_split_leak_notes. - _resolve_input (cog 21 → ~5): extracted _scan_canonical_split_files + _scan_pseudo_split_files + _resolve_directory_splits orchestrator. - _cross_split_overlap (cog 19 → ~6): see Finding 6 above. - generate_data_governance_report (cog 20 → ~5): extracted _build_text_length_stats + _build_split_info + _governance_section + _maybe_inline_audit_report. Each helper owns one slice. Each helper is private (underscore prefix) and individually unit- testable; existing tests pass unchanged. SonarCloud security/typing warnings - Weak hash (data_audit.py L258): MD5 → BLAKE2b. Use is non-crypto (simhash bit-mixing) — `usedforsecurity=False` was set but Sonar doesn't read that flag. BLAKE2b is on the modern allowlist, faster than SHA-256, and natively supports digest_size truncation (no slice needed). Fingerprint collisions identical in distribution; existing hamming-distance tests still pass. - Type mismatch (test_data_audit L77/78/121/435): detect_pii / mask_pii signatures changed from `str` → `Any`. The functions ALREADY guarded `if not text or not isinstance(text, str): return ...` defensively for arbitrary JSONL row payloads — the type signature now matches reality, so callers passing None / int / list don't trip mypy / Sonar typing checks. Test `# type: ignore[arg-type]` suppressions removed (no longer needed). - Hardcoded credentials (test_ingestion.py L274): renamed PDF fixture passwords from "secret" / "owner" to "fx-user" / "fx-owner" + added `# noqa: S105` comments + explanatory note that these are PDF authoring inputs to PdfWriter.encrypt, not real credentials. Verification - ruff format/check: clean - pytest tests/: 737 passed, 8 skipped (no regressions; refactors are behaviour-preserving) - End-to-end smoke: multi-split JSONL with parse error + non-dict row + cross-split leak produces a complete report with the new schema on cross_split_overlap.pairs.<a>__<b>; sliding chunker confirmed to emit no runt trailing chunks; mask_pii(int) defensively passes through unchanged.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
forgelm/ingestion.py (1)
386-393: Document the additionaloverlap <= chunk_size // 2bound.The docstring says only
overlapmust be< chunk_size, but_chunk_slidingalso rejectsoverlap > chunk_size // 2at lines 207-211 to prevent quadratic chunk explosion. Library users hitting that limit viaingest_path()get aValueErrorthat isn't reflected in the docstring contract.📝 Proposed docstring update
chunk_size: Soft size cap per chunk (characters). - overlap: Overlap window for the sliding strategy. Must be < ``chunk_size``. + overlap: Overlap window for the sliding strategy. Must satisfy + ``0 <= overlap <= chunk_size // 2`` (the upper half-cap prevents + pathological chunk explosion; e.g. ``overlap=199`` with + ``chunk_size=200`` would emit roughly one chunk per character).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@forgelm/ingestion.py` around lines 386 - 393, Update the ingest_path() docstring to include the additional constraint enforced by _chunk_sliding: besides overlap < chunk_size, document that overlap must also be <= chunk_size // 2 to avoid quadratic chunk explosion; mention that violating this will raise a ValueError from _chunk_sliding so callers should validate overlap accordingly and include the rationale (prevent quadratic growth) and the exact bound (overlap <= chunk_size // 2) alongside the existing description of overlap and chunk_size.tests/test_ingestion.py (1)
58-65: Stale comment — early-exit suppresses the 4th chunk.The comment claims 4 chunks (
"abcd", "cdef", "efgh", "gh") but_chunk_sliding's early-exit (lines 224-225 ofingestion.py) returns after"efgh"sincestart (4) + chunk_size (4) >= len(text) (8). The test still passes because only the first three are asserted, but the comment will mislead the next maintainer into expecting a runt"gh"tail.📝 Proposed comment fix
text = "abcdefgh" chunks = list(_chunk_sliding(text, chunk_size=4, overlap=2)) - # step = 2 → "abcd", "cdef", "efgh", "gh" + # step = 2 → "abcd", "cdef", "efgh". + # The early-exit in _chunk_sliding suppresses the runt "gh" tail + # that would otherwise be a pure prefix of the prior overlap window. assert chunks[0] == "abcd" assert chunks[1] == "cdef" assert chunks[2] == "efgh"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/test_ingestion.py` around lines 58 - 65, The test comment incorrectly states that _chunk_sliding will produce four chunks ("abcd", "cdef", "efgh", "gh") even though _chunk_sliding early-exits and only yields three; update the comment in test_overlap_preserves_context to reflect the actual output from _chunk_sliding (e.g., step = 2 → "abcd", "cdef", "efgh") or alternatively change the test inputs (chunk_size/overlap or text length) if you intend to assert a final short tail, and reference the _chunk_sliding function when making this change so the comment and assertions match the actual behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/guides/data_audit.md`:
- Around line 168-173: The CLI synopsis code fence in docs/guides/data_audit.md
is unlabeled (MD040); update the fenced block to use the text language tag so it
matches the Turkish mirror and satisfies markdownlint: replace the opening "```"
with "```text" for the forgelm --data-audit PATH block (the block currently
shown between lines 168–173) so the fence is explicitly labeled as text and
mirrors docs/guides/data_audit-tr.md.
In `@docs/guides/ingestion.md`:
- Around line 161-171: The CLI usage fenced block for the "forgelm ingest"
command is missing a Markdown language tag; update the code fence that contains
the forgelm ingest usage to use the text tag (i.e., ```text) so it matches the
labeled-fence convention used elsewhere (e.g., the data_audit-tr guide) and
resolves markdownlint MD040.
---
Nitpick comments:
In `@forgelm/ingestion.py`:
- Around line 386-393: Update the ingest_path() docstring to include the
additional constraint enforced by _chunk_sliding: besides overlap < chunk_size,
document that overlap must also be <= chunk_size // 2 to avoid quadratic chunk
explosion; mention that violating this will raise a ValueError from
_chunk_sliding so callers should validate overlap accordingly and include the
rationale (prevent quadratic growth) and the exact bound (overlap <= chunk_size
// 2) alongside the existing description of overlap and chunk_size.
In `@tests/test_ingestion.py`:
- Around line 58-65: The test comment incorrectly states that _chunk_sliding
will produce four chunks ("abcd", "cdef", "efgh", "gh") even though
_chunk_sliding early-exits and only yields three; update the comment in
test_overlap_preserves_context to reflect the actual output from _chunk_sliding
(e.g., step = 2 → "abcd", "cdef", "efgh") or alternatively change the test
inputs (chunk_size/overlap or text length) if you intend to assert a final short
tail, and reference the _chunk_sliding function when making this change so the
comment and assertions match the actual behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b4cf26ce-aa65-463f-a853-33393d0dbb12
📒 Files selected for processing (9)
docs/guides/data_audit-tr.mddocs/guides/data_audit.mddocs/guides/ingestion.mdforgelm/cli.pyforgelm/compliance.pyforgelm/data_audit.pyforgelm/ingestion.pytests/test_data_audit.pytests/test_ingestion.py
🚧 Files skipped from review as they are similar to previous changes (1)
- forgelm/data_audit.py
| ``` | ||
| forgelm --data-audit PATH \ | ||
| [--output DIR] \ | ||
| [--output-format {text,json}] \ | ||
| [--quiet | --log-level {DEBUG,INFO,WARNING,ERROR}] | ||
| ``` |
There was a problem hiding this comment.
Unlabeled fence — diverges from the Turkish mirror.
The CLI synopsis block at lines 168-173 has no language tag, which markdownlint flags (MD040). The Turkish mirror at docs/guides/data_audit-tr.md lines 170-175 already uses ```text for the same block, so there's a small bilingual divergence here. As per coding guidelines, "User-facing docs must be bilingual (English and Turkish) as mirror documents".
📝 Proposed fix
-```
+```text
forgelm --data-audit PATH \
[--output DIR] \
[--output-format {text,json}] \
[--quiet | --log-level {DEBUG,INFO,WARNING,ERROR}]</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
Verify each finding against the current code and only fix it if needed.
In @docs/guides/data_audit.md around lines 168 - 173, The CLI synopsis code
fence in docs/guides/data_audit.md is unlabeled (MD040); update the fenced block
to use the text language tag so it matches the Turkish mirror and satisfies
markdownlint: replace the opening "" with "text" for the forgelm
--data-audit PATH block (the block currently shown between lines 168–173) so the
fence is explicitly labeled as text and mirrors docs/guides/data_audit-tr.md.
</details>
<!-- fingerprinting:phantom:medusa:nectarine:608628d8-10bc-4ea6-9d79-3a4d3c8888d0 -->
<!-- d98c2f50 -->
<!-- This is an auto-generated comment by CodeRabbit -->
… comment)
All 4 inline findings verified against current code; 1 bonus stale CLI
flag found in the same scan and fixed alongside.
- docs/guides/data_audit.md L168-173: CLI synopsis fence got the `text`
language tag. Aligns with the labeled-fence convention used in the
TR mirror and silences markdownlint MD040.
- docs/guides/ingestion.md L161-171: same fix on the `forgelm ingest`
CLI synopsis fence. While reading the same block, noticed
`--strategy {sliding,paragraph,semantic}` was stale — `semantic` was
removed from the CLI choice list in round-1 (it raises
NotImplementedError; hiding it from the surface was deliberate).
Updated the synopsis to match: `{sliding,paragraph}`. TR mirror was
already correct.
- forgelm/ingestion.py ingest_path() docstring: now documents the
half-chunk overlap cap (overlap <= chunk_size // 2) alongside the
existing overlap < chunk_size rule, plus the rationale (prevents
quadratic chunk explosion from --overlap 199 + --chunk-size 200)
and the exact exception path (ValueError from _chunk_sliding).
- tests/test_ingestion.py test_overlap_preserves_context: comment said
step would yield 4 chunks ending in "gh"; the round-3 early-exit fix
in _chunk_sliding makes that runt impossible. Comment updated and
the assertion tightened from `chunks[0..2] == "abcd"/"cdef"/"efgh"`
to `chunks == ["abcd", "cdef", "efgh"]` so a future regression that
re-introduces the runt would be caught instead of hidden by an
incomplete index-only assertion.
Verified: ruff clean, 737 passing tests / 8 skipped (no behavioural
change), all four narrow doc/comment findings discharged.
|
…d-on-main state PR #13 (Phase 12 — v0.5.2) is merged on `main`. This sweep aligns the narrative-doc state with that fact and consolidates the post-merge review-cycle fixes under a single follow-up section. roadmap.md / roadmap-tr.md - Phase 11.5 + 12 status descriptions: "merged to development" → "merged to main (PR #11/#12 / #13)" with the actual merge date. - "Latest release on PyPI" line now correctly says v0.4.5 (PyPI state) instead of v0.5.0. Adds explicit clarification that v0.5.0 / v0.5.1 / v0.5.2 are merged to main but not yet tagged + published. - Phase-count matches v0.5.3 plan: "3 phases (12.5, 13, 14) planned". - TR mirror status icons updated to "🟡 Birleşti" matching the EN "🟡 Merged" — were previously prematurely marked "✅ Tamam". - Mermaid diagram in roadmap-tr.md gains the missing P125 node and bumps merged-but-not-published phases to a lighter green. - New cross-link to issue #14 (webhook SSRF DNS-rebinding TOCTOU) surfaces in both the v0.5.2 row and the v0.5.3 "Next" line as the tracked v0.5.3 hardening follow-up. roadmap/phase-14-pipeline-chains.md / roadmap/releases.md - "starts after Phase 12 lands" → concrete release event (`v0.5.2` PyPI tag is published). - v0.5.1 + v0.5.2 release-page status both moved off "Merged on development" to "Merged on main … remaining release-engineering steps" with the issue #14 hardening reference on v0.5.2. README.md - Always-on secrets-scan list updated: was 7 families with the wrong "private-key headers" wording; now lists all nine families per `forgelm.data_audit.SECRET_TYPES` (full BEGIN…END for OpenSSH / PGP). CHANGELOG.md - New "Fixed — post-PR-#13 review-cycle batches (rounds 8-12)" block in [Unreleased] consolidating the post-merge inline-comment batches: audit log hardening, deployer-instructions Markdown injection, quality-filter denominator, numpy fast-path bits guard, sliding-overlap clamp, batch-tokenizer narrow-except, webhook redirect/secret-fallback safety, trainer governance failure visibility, compliance-manifest exception narrowing, strict messages-format validation, wizard ASCII regex flag, GGUF converter case-insensitive validation, markdown-chunker complexity refactor, bidirectional MinHash extraction, streaming length digest, and the doc/test-count drift sweep. Pointers to issue #14 for the remaining DNS-rebinding follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d-on-main state PR #13 (Phase 12 — v0.5.2) is merged on `main`. This sweep aligns the narrative-doc state with that fact and consolidates the post-merge review-cycle fixes under a single follow-up section. roadmap.md / roadmap-tr.md - Phase 11.5 + 12 status descriptions: "merged to development" → "merged to main (PR #11/#12 / #13)" with the actual merge date. - "Latest release on PyPI" line now correctly says v0.4.5 (PyPI state) instead of v0.5.0. Adds explicit clarification that v0.5.0 / v0.5.1 / v0.5.2 are merged to main but not yet tagged + published. - Phase-count matches v0.5.3 plan: "3 phases (12.5, 13, 14) planned". - TR mirror status icons updated to "🟡 Birleşti" matching the EN "🟡 Merged" — were previously prematurely marked "✅ Tamam". - Mermaid diagram in roadmap-tr.md gains the missing P125 node and bumps merged-but-not-published phases to a lighter green. - New cross-link to issue #14 (webhook SSRF DNS-rebinding TOCTOU) surfaces in both the v0.5.2 row and the v0.5.3 "Next" line as the tracked v0.5.3 hardening follow-up. roadmap/phase-14-pipeline-chains.md / roadmap/releases.md - "starts after Phase 12 lands" → concrete release event (`v0.5.2` PyPI tag is published). - v0.5.1 + v0.5.2 release-page status both moved off "Merged on development" to "Merged on main … remaining release-engineering steps" with the issue #14 hardening reference on v0.5.2. README.md - Always-on secrets-scan list updated: was 7 families with the wrong "private-key headers" wording; now lists all nine families per `forgelm.data_audit.SECRET_TYPES` (full BEGIN…END for OpenSSH / PGP). CHANGELOG.md - New "Fixed — post-PR-#13 review-cycle batches (rounds 8-12)" block in [Unreleased] consolidating the post-merge inline-comment batches: audit log hardening, deployer-instructions Markdown injection, quality-filter denominator, numpy fast-path bits guard, sliding-overlap clamp, batch-tokenizer narrow-except, webhook redirect/secret-fallback safety, trainer governance failure visibility, compliance-manifest exception narrowing, strict messages-format validation, wizard ASCII regex flag, GGUF converter case-insensitive validation, markdown-chunker complexity refactor, bidirectional MinHash extraction, streaming length digest, and the doc/test-count drift sweep. Pointers to issue #14 for the remaining DNS-rebinding follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…prehensive v0.5.0 Originally planned as four sequential PyPI tags (v0.5.0 / v0.5.1 / v0.5.2 / v0.5.3). The four phases form one coherent surface (ingest → polish → mature → polish) that's hard to use in parts; consolidating into a single comprehensive v0.5.0 release keeps the user-facing release notes focused while git history retains the four phases as separate commit batches. pyproject.toml - version: 0.5.2rc1 → 0.5.0 CHANGELOG.md - Collapse [Unreleased] + [0.5.2] + [0.5.1] + [0.5.0] (summary stub) into a single [0.5.0] — 2026-04-29 entry. - Theme paragraph + consolidation note explaining the merger. - Internal phase boundaries preserved as section markers (### Phase 12.5 / Phase 12 / Phase 11.5 / Phase 11) so reviewers can map back to PR history (#11, #12, #13, current). - [Unreleased] is now empty (next minor picks up Phase 14). README.md - "Document Ingestion & Data Audit (v0.5.0 — polished in v0.5.1, matured in v0.5.2)" → "(v0.5.0 — Phases 11 + 11.5 + 12 + 12.5 consolidated)". - Drop "v0.5.2 added X" / "v0.5.1 added Y" / "Phase 12.5 polish (v0.5.3)" history phrasing — features now read as part of v0.5.0. docs/roadmap.md + roadmap-tr.md - Status table: four separate "🟡 Merged" rows for Phases 11 / 11.5 / 12 / 12.5 collapsed into one combined row pointing at releases.md#v050. Phase 14 row reslotted from v0.5.3 to v0.5.1. - "Latest PyPI: v0.4.5" wording trimmed (the v0.5.0/.1/.2 sentence about three pending tags is gone). - "Next: v0.5.3" → "Next: v0.5.1" (Phase 14). - Phase count: 16 complete + 3 planned → 17 complete + 2 planned (12.5 moves into the consolidated release; 13 + 14 remain planned). - Mermaid diagram: P11 / P115 / P12 / P125 all point at the same V2[v0.5.0 pending] node; styling bumped to merged-but-not-tagged light-green. docs/roadmap/releases.md - v0.5.0 / v0.5.1 / v0.5.2 sections collapsed into a single comprehensive v0.5.0 entry organised by phase (### Phase 11 / 11.5 / 12 / 12.5). - v0.5.3 (Phase 14) → v0.5.1 (Phase 14). docs/roadmap/phase-{11-5,12-5,12-data-curation,14-pipeline}.md - Consolidation note at the top of each historical phase plan ("originally targeted vX.Y.Z, ships as part of consolidated v0.5.0; see releases.md#v050"). Body kept intact as the historical planning trail. - phase-14: Target release v0.5.3 → v0.5.1; "starts after v0.5.2 PyPI tag" → "starts after v0.5.0 PyPI tag". - phase-12-5-backlog rows: "ships with v0.5.3" → "ships with v0.5.0". docs/guides/{ingestion,data_audit}{,-tr}.md + docs/reference/usage{,-tr}.md - Drop the "v0.5.2 added" / "v0.5.1 promoted" history phrasing inside feature descriptions. Features now read as v0.5.0 capabilities; phase labels (Phase 11 / 11.5 / 12 / 12.5) are kept as breadcrumbs for reviewers. - Section headings like "Document Ingestion (v0.5.0+; token-aware in v0.5.1; markdown + secrets-mask in v0.5.2)" → "(v0.5.0)". - Inline comments in CLI examples drop "v0.5.2:" / "v0.5.3 (Phase 12.5):" prefixes since the features all ship in the same release. notebooks/data_curation.ipynb - Install pin: 0.5.2 → 0.5.0 (cells 1 + 16). - "Phase 12.5 audit add-ons (v0.5.3)" markdown → "Other v0.5.0 audit add-ons". - Section 2 header drops the "Phase 12.5 shorthand (v0.5.3)" framing for --all-mask; the flag is just part of the v0.5.0 release. forgelm/cli.py - One internal comment tweaked from "v0.5.1 consumers" to "any pre-Phase-12 JSON consumer" (the legacy envelope key still has a real reason to exist; the framing just stops referencing a release that won't exist as a separate PyPI tag). - Phase labels in user-visible help text (Phase 11.5 / 12 / 12.5) kept intact — they're descriptive history, not version pins. Verification - ruff clean (78 files) - 840 tests pass (13 skipped) - 7/7 bilingual H2 parity (configuration / usage / distributed / data_preparation / architecture / ingestion / data_audit) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… drift guard Wave 5 / Faz 30 Task J: a CI guard that catches the bug class we've chased four times this wave (GH-8 verify-log, GH-11 benchmark, GH-16 --export-bundle, GH-18 --target kserve, GH-20 --pii-locale, F-W4-01 verify-audit --output-dir/--json, F-W4-PS-02 approve --run-id). Tool design (749 LOC, pure stdlib): - Discovers the live parser surface by spawning `python3 -m forgelm.cli --help` + `forgelm <sub> --help` for every shipped subcommand — resilient to internal `_parser.py` reorganisation (Option A in the task spec). - Walks docs/{guides,reference,usermanuals/{en,tr}/} + README.md for fenced bash/shell code blocks containing `forgelm <sub> ...` invocations. - Reports three drift classes: (1) subcommand not in parser surface; (2) flag not in parser surface for that subcommand; (3) flag value not in parser's `choices` list. - Skips `docs/analysis/` (gitignored research) + `docs/marketing/` (gitignored strategy) by default. - False-positive avoidance: skips synopsis lines (containing `[` or `(...|...)` choice indicators), and skips code blocks in "Wrong:"/"Don't:"/"Anti-pattern:"/"Legacy:"/"Yanlış:"-tagged context. Forward-reference framing (`planned`/`roadmap`/`future`/ `not in v0.5.5`/`(planned)`) within ±3 lines of the cited line is also tolerated as a legitimate ghost callout. - Exit codes follow `tools/` contract: 0 advisory / 1 strict. Tests (298 LOC, 15 cases — 3:23 wall-clock since each spawns the real forgelm parser surface): - Real shipping syntax (`verify-audit ./outputs/audit_log.jsonl --require-hmac`) → exit 0. - Each historical drift class regression-pinned (verify-audit --output-dir/--json, benchmark --model X, deploy --target kserve, chat --top-p). - Forward-reference framing + Wrong:-tagged blocks → tolerated. - --quiet suppression of the OK summary. - Parser-surface discovery sanity tests (synthetic empty repo, missing-subcommand short-circuit). CI wiring: - New advisory-mode step in `.github/workflows/ci.yml` runs the tool with `continue-on-error: true` after the bilingual-parity step. Matches the `check_anchor_resolution.py` Faz 26 precedent: gate lands first, baseline cleanup follows, then a later commit flips to `--strict` fail-the-build mode. Baseline drift on the live tree: 40 findings across 20 docs. Top hits cluster in `docs/usermanuals/{en,tr}/reference/cli.md` (legacy flag tables drifted from the audit/verify-audit/chat/deploy/ingest parsers as Wave 2-4 reshaped them) and a handful of phase-13/14 "planned" subcommands (`forgelm trend`, `forgelm compare-runs`) documented as if shipped. Cleanup is the next Wave 5 commit. Validation - ruff format / check — clean. - python3 -m pytest tests/test_check_cli_help_consistency.py --no-cov -q — 15/15 pass in 3:23. - python3 -m pytest --no-cov -q — full suite green (1428 tests now; +15 from this commit). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ve 5 Wave 5 / pre-merge review artifact, matching the Wave 3 + 4 cadence: each wave ships with a self-contained 4-agent review prompt that the maintainer can launch verbatim before merging the wave PR. Prompt covers Wave 5's full diff (122 files, ~9272 insertions across 7 commits e18baa0 → 6448c2d): - §A: 50 new doc-triplet files (Task A) — CLI surface accuracy, audit events, symbol citations, bilingual parity. - §B: tools/check_cli_help_consistency.py — tool correctness, cognitive complexity, the 15 pinned tests, strict CI flip. - §C: anchor checker --strict CI flip + 36→0 baseline cleanup. - §D: site/* v0.5.5 finalisation (5 pages + i18n keys). - §E: README + CONTRIBUTING + CLAUDE.md final pass. - §F: 10 standards files final pass. - §G: roadmap final state + new phase-12-6-closure-cycle.md. - §H: Tier 1 ghost-feature drift residuals (GH-11/016/018/020). - §I: pyproject.toml 0.5.1rc1 → 0.5.5 (brought forward from Faz 33 because check_site_claims.py --strict enforces site/code version match). - §J: 5 strict guards baseline + pytest 1428 + dry-run + closure plan honesty. Severity calibration unchanged from Wave 3 + 4: CRITICAL = data corruption / audit loss / public exit-code break / alignment-vs- certified mislead; HIGH = runtime crash on happy path or missing test for documented contract; MEDIUM/LOW/NIT calibrated per the prompt's own severity table. Deliverable shape per agent: single Markdown report at `docs/analysis/code_reviews/wave5-<agent-name>.md` with verdict + severity counts + per-finding entries (severity tag, ID, file:line, reasoning, suggested fix, regression test/guard). Launch: 4 parallel sub-agents with the same prompt; each picks up its focus list (§B/C/H/I/J for code-correctness, §A.1-3+§D for privacy-security, §A.4+§E/F/G+§J for standards, §B.1-2+§C.1+§J.2 for test-rigour). .gitignore updated to allow-list the new prompt under docs/analysis/code_reviews/ (matches the wave3-followup-review-prompt + wave4-review-prompt + wave4-followup-review-prompt allow-list pattern). Validation - python3 tools/check_anchor_resolution.py --strict — 264/264 OK (the new prompt's internal links all resolve). - python3 tools/check_bilingual_parity.py --strict — 39/39 OK. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each prior wave (1, 2a, 2b, 3, 4) has its own CHANGELOG section under [Unreleased] documenting the Faz that wave closed. Wave 5 was missed during the bulk commits; this commit adds the section above the existing Wave 4 entry, ordered newest-first per the project's Keep-a-Changelog convention. Section covers all 7 Wave 5 commits (e18baa0 through a93ec45): - Faz 30 Task A: 50 new doc-triplet files (11 reference docs + 5 guides + 9 usermanual pages × EN+TR) + parity registry +16 pairs. - Faz 30 Task J: tools/check_cli_help_consistency.py (749 LOC, 15 pinned tests) + advisory CI step + strict flip in fbb082d. - Faz 30 Task N: anchor resolver --strict CI flip + 36→0 baseline cleanup. - Faz 30 Task O: Tier 1 ghost-feature drift residuals (GH-11/016/018/020) cleared. - Faz 30 Tasks B + C: _meta.yaml +8 page entries + JS bundle rebuild (56 → 64 pages). - Faz 30 Tasks D + E + F + G: site v0.5.5 final state + README/CONTRIBUTING/CLAUDE.md + roadmap + 10 standards files final pass. - Faz 30 Tasks H + I + K + L + M: dispositions documented (incl. honest closure of Task K — `tools/regenerate_config_doc.py` was never built; the three strict guards lock the contract instead). Migration notes section calls out: - pyproject.toml version 0.5.1rc1 → 0.5.5 (brought forward from Faz 33 because check_site_claims.py --strict enforces site/code version match). - Two new strict CI gates (anchor resolution + cli-help consistency). - 8 new usermanual pages + 16 new bilingual doc pairs (no user-facing behaviour change). Faz 33 release commit will rename [Unreleased] → [0.5.5] — DATE and possibly fold the Wave 4 + Wave 5 sections into a single release-grouped entry; until then [Unreleased] holds the work that lands in v0.5.5. Validation: anchor strict 264/264 OK, parity strict 39/39 OK, cli-help strict 449/449 OK (the new section's Markdown is clean — no broken anchors, no flag drift in the migration-notes examples). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gent review findings - Wave 5 PR #34 round 3 absorption: full sweep of CodeRabbit's 6 OOD + 20 Minor inline review comments PLUS 24 multi-agent review findings (code-correctness / privacy-security / standards / test-rigor docs under docs/analysis/code_reviews/wave5-*.md). HIGH/Major absorptions: - 4 broken `forgelm.cli.subcommands.*` symbol citations across purge/approvals reference docs (en+tr): _run_row_erasure → _run_purge_row_id, _handle_pending → _run_approvals_list_pending, _handle_show → _run_approvals_show (F-W5-PRIV-02). - configuration.md (en+tr): drop `kserve | triton` ghost choices and the entire `## deployment:` ghost YAML block (no `DeploymentConfig` in ForgeConfig; extra="forbid" rejects it at load) — replace with a v0.6.0+ Phase 14 callout (F-W5-S1, F-W5-S2). - ingestion.md (en+tr): drop conflicting "instructions format default" claim; document raw-chunk JSONL with summary-only --output-format {text,json} (CR-OOD ingestion). - closure-plan PR #33 status: line 131 said "PR #33 pending" but line 119 said merged with `01e40ba` (CR-OOD closure-plan). - leakage.md (en+tr): remove `audit.leakage_check.remove_cross_split_overlap` knob reference — knob doesn't exist in code; replace with the documented jq-based removal flow (CR-OOD leakage). - TR + EN experiment-tracking: drop `$ ` prompt prefix in `text`-tagged preview blocks for `forgelm compare-runs` (CR-OOD experiment-tracking). - roadmap mermaid (en+tr): add Phase 12.6 (Closure Cycle) → v0.5.5 node + edge so visual flow matches table (CR-OOD roadmap). Minor absorptions: - phase-12-6-closure-cycle.md L123: stale "(currently 0.5.1rc1)" parenthetical → already-bumped during Wave 5 (F-W5-S4 / CR Minor). - gdpr-erasure.md (en+tr): rename duplicate `### What gets recorded` / `### Neyi kaydeder` H3 to disambiguate Article 17 vs 15 sections (CR Minor #2 + #20). - verify-gguf.md (en+tr) L147: revise misleading "still match SHA-256" hash explanation; clarify magic-header layer doesn't catch truncation independent of sidecar (CR Minor #3). - verify_audit link mismatch in 4 reference docs (en+tr × verify_gguf + verify_annex_iv): label text now matches href (CR Minor #4 + #8). - air_gap_deployment-tr.md L19: add `text` language tag to ASCII diagram fence (CR Minor #6). - site/index.html L374: drop internal "Phase 19" milestone leak; tag reads `embed` (user-facing label) (CR Minor #7). - air_gap_deployment.md L193: fix dangling sentence fragment in HF_TOKEN gated-models tip (CR Minor #9). - performance.md (en+tr) L184: drop ghost `forgelm audit --output-format parquet`; replace with pandas/pyarrow conversion step (CR Minor #10). - phase-11-5-backlog.md L10-12: link text → package paths matching href (forgelm/data_audit.py → forgelm/data_audit/, etc.) (CR Minor #11). - testing.md L140: stale `23/23` → `39/39` bilingual parity count (CR Minor #12). - testing.md L198: `python3` prefix consistency in checklist (CR Minor #13). - supply-chain.md (en+tr): bandit cadence aligned to standards doc (Nightly 03:00 UTC + release tag, not "every PR + main push") (CR Minor #14 + #15). - translations.js L1927: "Three values" → "Two values" (only forgelm-lang + forgelm-theme listed) (CR Minor #16). - approvals_subcommand.md (en+tr) L26: staging path → match L84 JSON sample (`final_model.staging.<run_id>` canonical, run-id-less form honoured as fallback) (CR Minor #17 + #19). - translations.js L1445/L1462/L1504/L1521 (en+tr): `<code>reject</code>` → `<code>forgelm reject</code>` for fully-qualified copy/paste parity (CR Minor #18). - _meta.yaml header: add formal DE/FR/ES/ZH deferred-policy banner per closure-plan Task L (F-W5-S6). - release.md L20 + L359: stale "0.3.1rc1 current" + "no __version__" claims; corrected to 0.5.5 + reflect that __version__ + __api_version__ ARE exported per Library API standard (F-W5-S3). - purge_subcommand.md (en+tr): add WARNING-on-fallback language for --output-dir asymmetry vs reverse-pii (F-W5-PRIV-05). - verify-audit.md (en+tr): replace overstated "escrow" wording with KMS/HSM-based separation (F-W5-PRIV-06). Tool tightening + new tests: - check_cli_help_consistency.py: tighten _FORWARD_REF_MARKERS to whole-phrase forms (drop bare "planned"/"future"/"roadmap" which matched "planned attacks" / "future work" / "roadmap-driven" in unrelated prose); add Turkish equivalents for bilingual docs (F-W5-01). - test_check_cli_help_consistency.py: 3 new regression tests: bare-substring negative (F-W5-01), >±3-line forward-ref window negative (F-W5-TR-01a), and Wrong:-block scope-to-one-block resumption (F-W5-TR-01b). Suite 17 → 20 tests, all green. User-manual orphan fix: - _meta.yaml: register `reference/json-output` page (existed in filesystem since Wave 2 but never declared in nav config — was invisible on the live site). Now declared = on-disk = 65 each. Skipped (with reason): - CR Minor #5 (library_api.md `forgelm.__version__` example): INVALID — verified `python3 -c "import forgelm; print(forgelm.__version__)"` prints 0.5.5 cleanly; both __version__ and __api_version__ ARE exported per Library API standard. CodeRabbit's web-search must have hit pre-Wave-5 PyPI. Doc is correct; code matches. - F-W5-S5 (parity tool autodiscover usermanuals): registry shape refactor (~50 new pairs) is scope creep for round 3; defer to v0.6. - F-W5-S8 (compliance_summary-tr.md): explicitly deferred to v0.6 by the finding itself. - F-W5-S9, F-W5-TR-11/12 (NIT cite/style): non-load-bearing. - F-W5-02/03/04 (cognitive complexity, unterminated fence, resume-after-Wrong unit test): refactor scope; covered by F-W5-TR-01 block-scope tests for the resume case. - F-W5-TR-02 (cleanup commit baseline regression test): the live CI --strict gate IS the guard; doubling up the integration-test layer is defensible but not blocking. - F-W5-TR-03/05/06/07/08/09/10 (extras): non-blocking coverage gaps. Self-review gauntlet (all green): ruff format/check ✅ bilingual_parity --strict 39/39 ✅ anchor_resolution --strict 264/264 ✅ cli_help_consistency --strict 425/425 ✅ (reduced from 449 due to intentional invocation removals: ghost YAML block, parquet ghost, 4 broken symbol-cite paths, etc.) site_claims --strict (artefacts/templates/GPU/version) ✅ field_descriptions --strict ✅ pytest 1433 passed / 14 skipped (+5 new tests vs round 2) ✅ forgelm --config config_template.yaml --dry-run ✅ build_usermanuals.py: en/tr 65/65 OK, JS bundles regenerate ✅ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>



Summary
This PR brings the
developmentbranch up to v0.5.0 state and lands Phase 11 — Document Ingestion & Data Audit plus post-release housekeeping from v0.4.5.forgelm ingest): Multi-format extractors (PDF / DOCX / EPUB / TXT / Markdown) with paragraph + sliding chunking, optional--pii-mask. Output is{"text": ...}JSONL the trainer accepts. OCR is out of scope; see new guide for Tesseract / AWS Textract handoff.forgelm --data-audit): CPU-only audit producingdata_audit_report.jsonwith per-split metrics, top-3 language detection, simhash near-duplicate detection, cross-split leakage check, PII regex (Luhn + TC Kimlik validated), schema-drift detection. Auto-inlines into EU AI Act Article 10 governance artifact when present at training time.pyproject.toml+forgelm/__init__.pybumped to0.5.0rc1for the next dev cycle.Phase 11 — what's new
New modules
forgelm/ingestion.py— single file or directory tree → SFT-ready JSONL. Sliding + paragraph chunking, recursive walk, encrypted-PDF detection, binary-content warning, deterministic file ordering.forgelm/data_audit.py— JSONL → audit JSON. Sample count, length distribution (p50/p95), language detection, simhash near-duplicate detection (within-split + cross-split leakage), PII regex with Luhn + TC Kimlik checksum validation, schema-drift detection.New CLI surface
forgelm ingest ./policies/ --recursive --output data/policies.jsonl forgelm ingest ./book.epub --strategy sliding --chunk-size 2048 --overlap 200 --output data/book.jsonl forgelm ingest ./customer_emails/ --pii-mask --output data/anon.jsonl forgelm --data-audit data/policies.jsonl --output ./audit/ forgelm --data-audit data/ # split-keyed directoryNew / updated guides
docs/guides/ingestion.md— full ingestion guide including OCR handoff recipes (Tesseract viaocrmypdf, AWS Textract).docs/guides/data_audit.md— full audit guide: split layouts, alias map, PII patterns, Article 10 integration.docs/qms/sop_data_management.md— audit pipeline added to the data-management SOP.docs/standards/architecture.md— module table + extras matrix updated.docs/roadmap.md,docs/roadmap-tr.md,docs/roadmap/phase-11-data-ingestion.md,docs/roadmap/releases.md— Phase 11 marked Done.docs/roadmap/phase-11-5-backlog.md— new backlog file capturing 12 deferred follow-ups (LSH banding, streaming reader, PII severity tiers, etc.).Optional dependency
Plain TXT / Markdown ingestion + the full audit module work without the extra (everything is pure stdlib in those paths).
Review findings — applied vs deferred
19 of 34 findings from the post-Phase-11 code review are applied in this PR. Highlights:
semanticstrategy crashed at runtime--strategychoices--overlap 199 --chunk-size 200→ quadratic chunk countoverlap > chunk_size // 2up frontschema_drift_columns+CCor(area)formats[A-Z]\d{7,8}[A-Z0-9]?ValueErrorwithqpdf/pdftkhintdev.jsonl/eval.jsonlnot recognised as splits--data-audit --output-format jsonflooded stdoutsource_inputfield--pii-maskredacted count not surfacedIngestionResult.pii_redaction_countsper categoryerrorkeyforgelm ingest --pii-masketc.Plus three "green tier" follow-ups landed in this PR:
options=deprecation silenced12 items remain in
phase-11-5-backlog.md— LSH banding, streaming reader, token-aware chunk sizing, PII severity tiers, etc.Other commits in this PR
47232fc— post-v0.4.5 release sync (roadmap / phase-12-quickstart / releases reflect PyPI publish; bump to 0.5.0rc1)ed81e09— README: PyPI badge + all 9 notebooks listed (was 5/9)Test plan
ruff format .+ruff check .— cleanpytest tests/— 718 passed, 11 skipped (8 pre-existing TRL/torch FSDP skips, 3 PDF roundtrip skips whenpypdfnot installed)forgelm ingest TXT_DIR+forgelm --data-audit JSONLend-to-end--helpregression — every subcommand still parsesforgelm ingest --pii-maskon a sample with mixed PII; verify counts inIngestionResult.pii_redaction_countspip install -e ".[ingestion]"in a fresh venv pulls all 5 deps (pypdf, python-docx, ebooklib, beautifulsoup4, langdetect)Notes for reviewer
0.5.0rc1is set inpyproject.tomlso a release-engineering pass after merge can droprc1and tag.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests
Chores
CI