Skip to content

feat(phase-11.5): Ingestion / audit polish (v0.5.1) - #12

Merged
cemililik merged 7 commits into
mainfrom
development
Apr 28, 2026
Merged

cemililik merged 7 commits into
mainfrom
development

Conversation

@cemililik

@cemililik cemililik commented Apr 27, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Phase 11.5 — operational polish on top of v0.5.0's ingestion + audit surface. No new training capabilities; behaviour at default flags is identical. The wins are large-corpus throughput, ergonomics, and a cleaner CLI shape.

All 12 follow-ups carved out of Phase 11's review backlog (phase-11-5-backlog.md) landed in one focused pass:

  • forgelm audit PATH is now a first-class subcommand (--verbose, --near-dup-threshold, dedicated --output default). The legacy forgelm --data-audit FLAG is preserved as a deprecation alias and logs a one-line notice — existing CI pipelines need zero changes.
  • LSH-banded near-duplicate detection drops within-split + cross-split scans from O(n²) to ~O(n × k) using pigeonhole banding (bands = threshold + 1). Recall stays exact at the default Hamming threshold; brute-force fallback retained for edge thresholds where bands shrink below 4 bits. Same banded shape powers _count_leaked_rows.
  • Streaming _read_jsonl_split — the audit's JSONL reader is now a generator yielding (row, parse_err, decode_err); _audit_split consumes it row-by-row via a _StreamingAggregator. RAM stays bounded on multi-million-row splits.
  • Token-aware --chunk-tokens + --tokenizer on forgelm ingest sizes chunks against an HF AutoTokenizer.encode instead of raw character counts. trust_remote_code=False hard-pinned for safety.
  • PDF page-level header / footer dedup strips lines that recur as the first or last non-empty line on ≥ 70 % of pages — kills audit near_duplicate_pairs noise on long policy / book PDFs.
  • PII severity tiers — pii_severity block (total, by_tier, by_type, worst_tier) alongside the flat pii_summary. Tier table follows PCI-DSS / GDPR Art. 9 weighting (credit_card / iban → critical, national IDs → high, email → medium, phone → low).
  • summarize_report truncation policy — default verbose=False folds zero-finding splits into a single tail line; forgelm audit --verbose reverses it.
  • Structured ingestion notes — IngestionResult.notes_structured: {key: value} for CI/CD consumers; extra_notes keeps the prose. JSON output exposes both.
  • Wizard "ingest first" entry point — BYOD quickstart and the full 8-step wizard now offer to run forgelm ingest inline when the typed dataset path is a directory of raw documents.
  • xxhash backend + token-level memo — optional xxhash.xxh3_64 digest path (added to forgelm[ingestion]) is several times faster than BLAKE2b on short keys; BLAKE2b stays as the bare-install fallback. Module-scope lru_cache(maxsize=10_000) memoises per-token digest (Zipfian token frequency makes this very effective).
  • Atomic audit-report write — data_audit_report.json written via tempfile.NamedTemporaryFile + os.replace so a crashed audit can never leave a half-written report on disk. newline="\n" pinned for byte-exact reproducibility.

Module deltas

  • forgelm/data_audit.py: PII_SEVERITY table, _token_digest (cached + xxhash optional), LSH-banded find_near_duplicates, _count_leaked_rows rebuilt on the same banded index, _read_jsonl_split promoted to a generator, _audit_split(split_name, path, ...) rewritten as a streaming aggregator, _build_pii_severity, _atomic_write_json, summarize_report(verbose=...), _split_has_findings, _render_split_block, _render_pii_severity.
  • forgelm/ingestion.py: _strip_repeating_page_lines, _extract_pdf threads dedup state, _chunk_sliding_tokens, _chunk_paragraph_tokens, _strategy_dispatch_tokens, _load_tokenizer, IngestionResult.notes_structured + pdf_header_footer_lines_stripped, _process_one_file extended for token-aware dispatch.
  • forgelm/cli.py: _add_audit_subcommand, _run_audit_cmd, dispatcher hooks audit; --data-audit reflows through _run_data_audit with invoked_via_legacy_flag=True.
  • forgelm/wizard.py: _offer_ingest_for_directory + _prompt_dataset_path_with_ingest_offer; BYOD path and full-wizard Step 5 dataset prompt both use them.
  • pyproject.toml: version bumped to 0.5.1rc1; xxhash added to the [ingestion] extra (optional — fallback path keeps bare installs OK).

Behavioural / API deltas (additive unless noted)

  • AuditReport gains a pii_severity: Dict[str, Any] field. JSON consumers reading only pii_summary continue to work.
  • find_near_duplicates(fingerprints, *, threshold, bits=64) accepts a bits keyword for adaptive banding.
  • _read_jsonl_split is now a generator — old buffered tuple return is gone. Callers that were materialising rows can wrap with list(...).
  • _audit_split(split_name, path, ...) now takes a path instead of an in-memory list. Returns (info, fingerprints, pii_split, parse_errors, decode_errors) so OSError handling stays in the orchestrator.

Documentation

  • Roadmap (roadmap.md + roadmap-tr.md): Phase 11.5 marked done; v0.5.1 set as merged-pending-publish; v0.5.2 reserved for Phase 14.
  • Backlog (phase-11-5-backlog.md): rewritten as a "what landed" record with module pointers.
  • Releases (releases.md): v0.5.1 ticked across all 11 features; v0.5.2 reslotted as Pipeline Chains.
  • Guides (ingestion.md, data_audit.md, TR mirrors): token-aware mode, PDF dedup, audit subcommand, PII severity, verbose policy.
  • Reference (usage.md, TR mirror), README, CHANGELOG: detailed Unreleased entry.

Test plan

  • ruff format . && ruff check . — all checks pass
  • pytest tests/ — 754 passed, 8 skipped (extras-skip for optional deps)
  • New tests cover each Phase 11.5 surface:
    • tests/test_data_audit.py: TestLshBandedNearDuplicates (LSH parity vs. brute force + high-threshold fallback), TestPiiSeverity (critical-tier verdict + neutral case), TestSummarizeVerbosePolicy, TestAtomicWrite, TestStreamingReader, TestTokenCachePerformance.
    • tests/test_ingestion.py: TestPdfHeaderFooterDedup (collapse + short-doc skip + no-repeats pass-through), TestStructuredIngestionNotes, TestTokenAwareCli.
    • tests/test_cli_subcommands.py: TestAuditSubcommand (happy path, JSON envelope, legacy --data-audit alias parity).
    • tests/test_wizard_byod.py: refreshed for ingest-first wording.
  • forgelm --config config_template.yaml --dry-run → DRY RUN COMPLETE
  • forgelm audit <jsonl> --output <dir> → writes data_audit_report.json, summary shows PII severity / worst tier = critical for credit-card seed
  • forgelm --data-audit <jsonl> --output <dir> (legacy) → identical on-disk product + deprecation notice in INFO log
  • forgelm audit … --output-format json → envelope contains pii_severity + report_path
  • forgelm ingest <txt> --pii-mask --output-format json → notes_structured populated, pdf_header_footer_lines_stripped: 0 for non-PDF
  • PyPI release engineering (tag v0.5.1, build, publish) — handled separately after merge

Notes for reviewer

  • This PR is additive on top of v0.5.0, not a rewrite. The legacy --data-audit flag and the pii_summary field stay around exactly as they were; new fields (pii_severity) and new subcommand are extras alongside them.
  • The streaming _read_jsonl_split rewrite is the largest behavioural change in scope. It preserves the same per-line tolerance semantics (parse errors, decode errors, non-dict rows) but yields tuples instead of returning a list — see the migration notes in the CHANGELOG and the TestStreamingReader cases for exact contract.
  • LSH banding default (bands = threshold + 1) keeps recall exact at the default threshold=3 by pigeonhole. This is not an approximation; the brute-force fallback engages only when bits // bands < 4 (i.e. very wide thresholds where the index would degenerate anyway).
  • --chunk-tokens deliberately requires --tokenizer. We refuse to pick a default vocab because the resulting chunk count would silently differ per-model; surfacing the dependency keeps the operator in control.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Promoted forgelm audit subcommand (legacy --data-audit kept as deprecation alias); token-aware ingestion flags and an inline "ingest first" wizard flow; structured ingestion metadata and PDF header/footer dedup.
  • Enhancements

    • LSH-banded near-duplicate detection, streaming JSONL processing, PII severity tiers with worst-tier guidance, optional faster digest backend, and atomic audit report writing.
  • Documentation & Tests

    • Updated CLI examples, changelog/roadmap, and expanded test coverage for audit/ingest behavior.

Operational follow-up to v0.5.0's ingestion + audit surface — the 12 items
carved out of Phase 11's review backlog, all landed in one focused pass.
No new training capabilities; behaviour at default flags is identical, the
gains are large-corpus throughput, ergonomics, and a cleaner CLI shape.

Highlights:

* `forgelm audit PATH` is now a first-class subcommand with `--verbose`,
  `--near-dup-threshold`, and its own `--output` default. The legacy
  `forgelm --data-audit FLAG` is preserved as a deprecation alias and
  logs a one-line notice; existing pipelines need zero changes.
* Near-duplicate detection swapped from O(n²) brute force to LSH banding
  (`bands = threshold + 1` by pigeonhole). Recall stays exact at the
  default threshold; brute-force fallback retained when bands shrink
  below 4 bits. Both within-split and cross-split helpers benefit.
* `_read_jsonl_split` is now a generator and `_audit_split` consumes it
  row-by-row via a `_StreamingAggregator` — RAM use no longer scales
  with split size.
* `--chunk-tokens N --tokenizer MODEL` on `forgelm ingest` sizes chunks
  against an HF AutoTokenizer instead of raw character counts.
  `trust_remote_code=False` hard-pinned for safety.
* PDF page header/footer dedup (`_strip_repeating_page_lines`) strips
  lines that recur on >=70% of pages — kills audit near-duplicate noise
  on long policy / book PDFs.
* `pii_severity` block alongside `pii_summary`: `total`, `by_tier`,
  `by_type`, `worst_tier`. Tier table follows PCI-DSS / GDPR Art. 9
  weighting. Notes line leads with the worst tier.
* `summarize_report(verbose=False)` (default) folds zero-finding splits
  into a single tail line. `forgelm audit --verbose` reverses it.
* `IngestionResult.notes_structured` carries machine-readable
  `{key: value}` for CI/CD consumers; `extra_notes` keeps the prose.
  JSON output exposes both.
* Wizard's BYOD + full-flow dataset prompts now offer to run
  `forgelm ingest` inline when the typed path is a directory of raw
  documents — closes the onboarding loop end-to-end.
* Optional `xxhash.xxh3_64` digest + module-scope token-level
  `lru_cache(maxsize=10_000)` accelerate the simhash hot path; BLAKE2b
  remains as the bare-install fallback.
* `data_audit_report.json` is written via tempfile + os.replace so a
  crashed audit can never leave a half-written report on disk.
  newline="\n" pinned for byte-exact reproducibility.

Module deltas:

* `forgelm/data_audit.py`: PII_SEVERITY table, `_token_digest` (cached
  + xxhash optional), LSH-banded `find_near_duplicates`,
  `_count_leaked_rows` rebuilt on the same banded index, `_read_jsonl_split`
  promoted to a generator, `_audit_split(split_name, path, ...)` rewritten
  as a streaming aggregator, `_build_pii_severity`, `_atomic_write_json`,
  `summarize_report(verbose=...)`, `_split_has_findings`,
  `_render_split_block`, `_render_pii_severity`.
* `forgelm/ingestion.py`: `_strip_repeating_page_lines`, `_extract_pdf`
  threads dedup state, `_chunk_sliding_tokens`, `_chunk_paragraph_tokens`,
  `_strategy_dispatch_tokens`, `_load_tokenizer`,
  `IngestionResult.notes_structured` + `pdf_header_footer_lines_stripped`,
  `_process_one_file` extended for token-aware dispatch.
* `forgelm/cli.py`: `_add_audit_subcommand`, `_run_audit_cmd`, dispatcher
  hooks "audit", `--data-audit` reflows through `_run_data_audit` with
  `invoked_via_legacy_flag=True`.
* `forgelm/wizard.py`: `_offer_ingest_for_directory` +
  `_prompt_dataset_path_with_ingest_offer`; BYOD path and Step 5 dataset
  prompt both use them.
* `pyproject.toml`: version bumped to 0.5.1rc1; `xxhash` added to the
  `[ingestion]` extra (optional — fallback path keeps bare installs OK).

Tests:

* `tests/test_data_audit.py`: TestLshBandedNearDuplicates,
  TestPiiSeverity, TestSummarizeVerbosePolicy, TestAtomicWrite,
  TestStreamingReader, TestTokenCachePerformance.
* `tests/test_ingestion.py`: TestPdfHeaderFooterDedup,
  TestStructuredIngestionNotes, TestTokenAwareCli; TestSummary updated
  for the new "Next: forgelm audit ..." hint.
* `tests/test_cli_subcommands.py`: TestAuditSubcommand (subcommand happy
  path, JSON envelope, legacy `--data-audit` alias parity).
* `tests/test_wizard_byod.py`: refreshed for the ingest-first wording
  (empty-directory rejection + decline-the-offer path).

Result: 754 passed, 8 skipped (extras-skip). Lint clean.

Docs:

* docs/roadmap.md + roadmap-tr.md: Phase 11.5 marked done; v0.5.1 set as
  the merged-pending-publish milestone; v0.5.2 reserved for Phase 14.
* docs/roadmap/phase-11-5-backlog.md: rewritten as a "what landed" record
  with module pointers per item.
* docs/roadmap/releases.md: v0.5.1 ticked across 11 features; v0.5.2
  reslotted as Pipeline Chains.
* docs/guides/{ingestion,data_audit}{,-tr}.md: token-aware mode, PDF
  header/footer dedup, audit subcommand, PII severity, verbose policy
  documented; legacy --data-audit retained as deprecation alias.
* docs/reference/usage{,-tr}.md: CLI examples updated for the new
  subcommand + token-aware ingestion.
* README.md: feature blurb refreshed; quick-start hint promoted to
  `forgelm audit`.
* CHANGELOG.md: detailed Unreleased entry covering all 12 items + tests
  + behavioural deltas.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @cemililik, your pull request is larger than the review limit of 150000 diff characters

@coderabbitai

coderabbitai Bot commented Apr 27, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@cemililik has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 48 minutes and 43 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3fa706be-c370-49a5-ab9a-dd504e904232

📥 Commits

Reviewing files that changed from the base of the PR and between 9a15af3 and b4db7fb.

📒 Files selected for processing (2)
  • docs/roadmap/phase-12-data-curation-maturity.md
  • forgelm/cli.py
📝 Walkthrough

Walkthrough

Phase 11.5 (v0.5.1) ships ingestion and audit polish: adds forgelm audit subcommand (legacy --data-audit alias), LSH-banded near-duplicate detection with brute-force fallback, streaming JSONL auditing, token-aware chunking with tokenizer plumbing, PDF header/footer deduplication, PII severity tiers, structured ingestion notes, wizard “ingest first” for directories, optional xxhash digest backend with token-level lru_cache, and atomic audit report writes.

Changes

Cohort / File(s) Summary
Changelog & Roadmap
CHANGELOG.md, docs/roadmap.md, docs/roadmap-tr.md, docs/roadmap/.../releases.md, docs/roadmap/.../phase-11-5-backlog.md, docs/roadmap/.../phase-11-data-ingestion.md, docs/roadmap/phase-10-5-quickstart.md
Record Phase 11.5/v0.5.1 as shipped, add Phase 11.5 release notes and roadmap node, retarget subsequent phases and fix quickstart doc link.
Feature Documentation
README.md, docs/guides/data_audit.md, docs/guides/data_audit-tr.md, docs/guides/ingestion.md, docs/guides/ingestion-tr.md, docs/reference/usage.md, docs/reference/usage-tr.md
Promote forgelm audit (keep legacy alias), add --verbose, --near-dup-threshold, --chunk-tokens, --tokenizer, --overlap-tokens examples; document LSH banding, simhash backend choice/xxhash, token memoization, PDF header/footer dedup, pii_severity, streaming JSONL semantics, and atomic report writes.
Core Audit & Near-Duplicate Detection
forgelm/data_audit.py
Implement LSH-banded candidate generation with exact Hamming verification and brute-force fallback; add optional xxhash backend and _token_digest lru_cache; convert split reader to streaming generator; add pii_severity and atomic JSON report writes; expose bits/threshold plumbing and verbose summary control.
CLI & Ingestion Integration
forgelm/cli.py, forgelm/ingestion.py, forgelm/compliance.py
Add audit subcommand and maintain --data-audit deprecation alias; forward --near-dup-threshold/--verbose; add token-aware ingest flags (--chunk-tokens, --tokenizer, --overlap-tokens), warnings/validation for mixed args, and update compliance/help hints to forgelm audit.
Ingestion internals & Models
forgelm/ingestion.py
Add notes_structured and pdf_header_footer_lines_stripped to IngestionResult; implement PDF repeating header/footer stripping (≥70% rule), tokenizer lazy-load, token-based chunking and overlap semantics, validation (require --tokenizer with --chunk-tokens), and refactor per-file processing helpers.
Wizard Directory-Aware Ingest
forgelm/wizard.py
Enable inline “ingest first” flow for directory inputs: detect supported files, prompt to ingest, collect JSONL path and PII masking choice, run ingestion, and return produced JSONL or None to re-prompt.
Project Config & Extras
pyproject.toml
Bump package version to 0.5.1rc1; add optional xxhash to ingestion extras (fallback to blake2b when absent).
Tests & Bench
tests/test_cli_subcommands.py, tests/test_data_audit.py, tests/test_ingestion.py, tests/test_wizard_byod.py, tests/test_wizard_phase11_5.py, tests/bench_simhash.py, tests/test_compliance.py
Add/extend tests for forgelm audit (and legacy alias), LSH vs brute-force parity and fallback, atomic report writes, streaming JSONL parsing, token-digest caching, pii_severity aggregation, PDF header/footer dedup, ingestion structured notes, wizard ingest flow, and a simhash microbenchmark.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLIAudit as "forgelm audit"
    participant AuditLogic as AuditRunner
    participant Reader as _read_jsonl_split
    participant Simhash as compute_simhash
    participant Detector as find_near_duplicates
    participant PIIGrader as _build_pii_severity
    participant FileOps as TempFile/os.replace
    participant Output as data_audit_report.json

    User->>CLIAudit: run audit <path> --output <dir> --near-dup-threshold N
    CLIAudit->>AuditLogic: start audit(path, threshold, verbose)
    AuditLogic->>Reader: iterate rows (streaming generator)
    Reader-->>AuditLogic: yield row/meta
    AuditLogic->>Simhash: compute_simhash(tokens) (uses _token_digest cache / xxhash if available)
    Simhash-->>AuditLogic: fingerprint
    AuditLogic->>Detector: find_near_duplicates(fingerprints, threshold, bits=64)
    Detector-->>AuditLogic: candidate pairs + distances
    AuditLogic->>PIIGrader: detect PII & compute pii_severity
    PIIGrader-->>AuditLogic: pii_severity + worst_tier
    AuditLogic->>FileOps: write report to temp file
    FileOps->>FileOps: os.replace(temp -> data_audit_report.json)
    FileOps->>Output: final report persisted
    Output-->>User: summary printed (summarize_report, verbose controls folding)
Loading
sequenceDiagram
    participant User
    participant CLIIngest as "forgelm ingest"
    participant IngestPath as ingest_path()
    participant Tokenizer as _load_tokenizer
    participant PDFDedup as _strip_repeating_page_lines
    participant Chunker as token-aware chunker
    participant Aggregator as _emit_chunk/_StreamingAggregator
    participant Output as IngestionResult (notes_structured)

    User->>CLIIngest: run ingest <path> --chunk-tokens N --tokenizer name
    CLIIngest->>IngestPath: call ingest_path(..., chunk_tokens, tokenizer)
    IngestPath->>Tokenizer: load tokenizer (validate present)
    IngestPath->>PDFDedup: detect & strip repeating page header/footer lines
    PDFDedup-->>IngestPath: lines_stripped count
    IngestPath->>Chunker: produce token-based chunks (respect overlap_tokens)
    Chunker-->>Aggregator: emit chunks
    Aggregator-->>IngestPath: accumulate metrics
    IngestPath->>Output: populate notes_structured and pdf_header_footer_lines_stripped
    Output-->>User: JSONL written and returned for downstream (wizard/next steps)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I nibbled bytes beneath the moonlit log,
Bands of simhash chased the noisy fog,
PDF headers trimmed, token chunks aligned,
Reports wrote atomically — tidy and kind,
Wizard whispers "ingest first" — hop, hop, big win!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 59.62% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(phase-11.5): Ingestion / audit polish (v0.5.1)' directly and concisely describes the main change: Phase 11.5 feature delivery focused on ingestion and audit polish targeting v0.5.1.
Description check ✅ Passed The PR description comprehensively covers all required template sections: clear summary of the change, detailed bullet list of changes across modules, appropriate 'New feature' type checked, thorough testing section with multiple verification steps, and complete checklist with all items verified.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch development

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codacy-production

codacy-production Bot commented Apr 27, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 168 complexity · 0 duplication

Metric Results
Complexity 168
Duplication 0

View in Codacy

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces Phase 11.5, focusing on operational polish for the ingestion and audit features. Key additions include a first-class forgelm audit subcommand, LSH-banded near-duplicate detection for improved performance on large corpora, streaming JSONL reading to bound memory usage, and token-aware chunking for ingestion. The audit report now includes PII severity tiers and is written atomically to disk. Feedback suggests optimizing the LSH implementation to handle large clusters of identical fingerprints more efficiently by grouping them before processing. Additionally, the PDF header/footer stripping logic could be improved to iteratively remove multi-line headers and footers.

Comment thread forgelm/data_audit.py
Comment on lines +474 to +488
for indices in buckets.values():
if len(indices) < 2:
continue
for left_pos in range(len(indices)):
i = indices[left_pos]
fp_i = fingerprints[i]
for right_pos in range(left_pos + 1, len(indices)):
j = indices[right_pos]
if (i, j) in seen:
continue
seen.add((i, j))
distance = hamming_distance(fp_i, fingerprints[j])
if distance <= threshold:
pairs.append((i, j, distance))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current LSH implementation remains vulnerable to $O(N^2)$ complexity and high memory usage when the dataset contains large clusters of identical or nearly identical fingerprints. In such cases, the indices list for a bucket can be large, leading to a nested loop that generates a massive number of pairs, and the seen set can grow excessively.

Since the auditor only requires the count of near-duplicate pairs (and not the full list), this can be optimized by grouping identical fingerprints first and then running LSH on the unique fingerprints. For exact duplicates, the pair count is $\sum \frac{n_i(n_i-1)}{2}$. For near-duplicates between different fingerprints $f_1$ and $f_2$, the pair count is $count(f_1) \times count(f_2)$. This would significantly improve performance and memory efficiency on large corpora with boilerplate or repeated content.

Comment thread forgelm/ingestion.py Outdated
Comment on lines +107 to +128
first_counts: Counter = Counter(lines[0] for lines in page_lines if lines)
last_counts: Counter = Counter(lines[-1] for lines in page_lines if lines)

cutoff = max(2, int(_PDF_REPEAT_THRESHOLD * len(pages)))
repeating_firsts = {ln for ln, n in first_counts.items() if n >= cutoff}
repeating_lasts = {ln for ln, n in last_counts.items() if n >= cutoff}

if not repeating_firsts and not repeating_lasts:
return pages, 0

cleaned: List[str] = []
stripped = 0
for lines in page_lines:
kept = list(lines)
while kept and kept[0] in repeating_firsts:
kept.pop(0)
stripped += 1
while kept and kept[-1] in repeating_lasts:
kept.pop()
stripped += 1
if kept:
cleaned.append("\n".join(kept))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of header/footer stripping only considers lines that were originally the first or last non-empty lines of a page. If a document has a multi-line header (e.g., Line 1: Company Name, Line 2: Confidential), stripping Line 1 will make Line 2 the new first line, but it won't be stripped because it wasn't in the initial repeating_firsts set.

Consider refactoring this to iteratively strip common first/last lines until no more lines meet the threshold.

    stripped = 0
    while True:
        stripped_this_pass = 0
        first_counts = Counter(lines[0] for lines in page_lines if lines)
        repeating_firsts = {ln for ln, n in first_counts.items() if n >= cutoff}
        if repeating_firsts:
            for lines in page_lines:
                if lines and lines[0] in repeating_firsts:
                    lines.pop(0)
                    stripped_this_pass += 1
        
        last_counts = Counter(lines[-1] for lines in page_lines if lines)
        repeating_lasts = {ln for ln, n in last_counts.items() if n >= cutoff}
        if repeating_lasts:
            for lines in page_lines:
                if lines and lines[-1] in repeating_lasts:
                    lines.pop(-1)
                    stripped_this_pass += 1
        
        if stripped_this_pass == 0:
            break
        stripped += stripped_this_pass

    if stripped == 0:
        return pages, 0
    cleaned = ["\n".join(lines) for lines in page_lines if lines]

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
docs/guides/data_audit-tr.md (1)

135-140: ⚠️ Potential issue | 🟠 Major

Stale "v0.5.0 dışı" / "karesel" paragraphs contradict Phase 11.5 LSH banding shipping in this PR.

The doc's intro now advertises LSH-banded near-duplicate detection as part of v0.5.1, but this paragraph still tells operators near-dup is quadratic and that an LSH band index is out of scope for v0.5.0. Same stale wording reappears in the troubleshooting table at line 221.

Both should be rewritten to reflect the new ~O(n × k) banded path and the brute-force fallback for high-threshold edge cases (the actual Phase 11.5 behavior tested in tests/test_data_audit.py::TestLshBandedNearDuplicates).

📝 Suggested rewrite
-Satır sayısında karesel; ~50K satıra kadar olan veri setlerinde
-audit-zamanı kullanım için uygun. Daha büyük külliyatlar için LSH band
-indeksi gerekecek — `v0.5.0` kapsamı dışı.
+Faz 11.5 itibarıyla yakın-tekrar tespiti LSH band indeksi
+kullanır (eşik = `b - 1` band, fingerprint başına ~`O(k)`); aday
+çiftler ardından Hamming mesafesiyle doğrulanır. Çok yüksek
+eşiklerde (band genişliği < ~4 bit) kod otomatik olarak brute-force
+fallback'e döner.

And line 221:

-| Büyük veri setinde `near_duplicate_pairs` çok büyük | Simhash karesel on-binlerce satırda koştu | Önce örnekleyin; LSH index desteği takip edecek |
+| Büyük veri setinde `near_duplicate_pairs` çok büyük | Eşik gerçekten benzer çift sayısının üstünde | `--near-dup-threshold`'u düşürün veya veri setini örnekleyin |

As per coding guidelines: "Documentation claims in README and marketing must point to real code; every feature mentioned must be backed by implementation" — and the inverse holds: docs must not advertise limitations the code no longer has.

🤖 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 135 - 140, The paragraph claiming
"karesel" (quadratic) behavior and that LSH band indexing is "v0.5.0 kapsamı
dışı" is stale; update the intro paragraph and the troubleshooting table entry
(around the second occurrence) to describe the Phase 11.5 LSH-banded
near-duplicate path: explain that the default path uses an O(n × k) LSH banded
approach with a brute-force fallback for high-threshold edge cases, and
reference the implemented behavior exercised by
tests/test_data_audit.py::TestLshBandedNearDuplicates; ensure wording removes
the "out of scope" claim and replaces "quadratic" with a note about when the
brute-force fallback may apply.
docs/guides/ingestion-tr.md (1)

155-155: ⚠️ Potential issue | 🟡 Minor

Eski forgelm --data-audit örneklerini de subcommand'a güncelleyin (TR mirror).

Bu dosyada line 101 forgelm audit'e geçirilmiş ama line 155 (uçtan uca örnek) ve line 309 (PII içeren formlar) hâlâ legacy forgelm --data-audit formunu öneri olarak gösteriyor. Doc, subcommand'ı yeni varsayılan olarak konumlandırdığı için tutarlılık açısından bu iki örneği de yeni forma çevirin (legacy alias'tan ayrıca tek bir yerde bahsedilmesi yeterli).

📝 Önerilen değişiklik
-forgelm --data-audit data/policies.jsonl --output ./audit/
+forgelm audit data/policies.jsonl --output ./audit/

(line 309 için aynı düzenlemeyi data/medical.jsonl'a uygulayın.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/guides/ingestion-tr.md` at line 155, Replace the legacy flag-style
examples that use "forgelm --data-audit ..." with the new subcommand form
"forgelm audit ..." in the end-to-end example and the PII/forms example;
specifically, change instances like "forgelm --data-audit data/policies.jsonl
--output ./audit/" to "forgelm audit data/policies.jsonl --output ./audit/" and
likewise update the example that references the medical/PII JSONL to use
"forgelm audit <file> --output <dir>"; keep a single note elsewhere mentioning
the legacy alias if desired.
docs/guides/ingestion.md (1)

96-96: ⚠️ Potential issue | 🟡 Minor

Update remaining forgelm --data-audit examples to the subcommand for consistency.

Lines 96 (PII section), 151 (end-to-end example), and 306 (PII forms / OCR section) still recommend the legacy forgelm --data-audit ... form, even though the rest of the guide (line 9, intro) now points at forgelm audit. Since the legacy flag is a deprecation alias going forward, switch these examples too — keeping legacy mentioned only in the "compatibility" callout in usage.md/README is enough.

The TR mirror (docs/guides/ingestion-tr.md) has the same partial migration at lines 155 and 309.

📝 Suggested fix
-`forgelm --data-audit` to verify.
+`forgelm audit` to verify.
-forgelm --data-audit data/policies.jsonl --output ./audit/
+forgelm audit data/policies.jsonl --output ./audit/
-forgelm --data-audit data/medical.jsonl --output ./audit/
+forgelm audit data/medical.jsonl --output ./audit/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/guides/ingestion.md` at line 96, Replace the remaining legacy CLI
examples that use the deprecated flag form `forgelm --data-audit` with the new
subcommand `forgelm audit` in the docs; specifically update the occurrences in
docs/guides/ingestion.md (PII section, end-to-end example, PII forms/OCR
example) and the TR mirror docs/guides/ingestion-tr.md, and ensure the old
`--data-audit` form is only mentioned in the dedicated "compatibility" /
usage/README callout rather than in examples.
docs/guides/data_audit.md (3)

261-261: ⚠️ Potential issue | 🟡 Minor

Stale troubleshooting row: LSH is no longer a follow-up.

This entry pre-dates Phase 11.5 — the diagnosis ("Simhash quadratic ran") and the fix ("LSH index support is a follow-up") contradict the LSH-banded near-duplicate detection just documented at lines 162-169 of this same file. Either drop the row or rewrite it for the post-LSH world (e.g., suggest tuning --near-dup-threshold if pair counts are still high).

🤖 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 261, The table row about
"`near_duplicate_pairs` enormous on a large dataset" is stale because LSH-banded
near-duplicate detection is now implemented; update that row to reflect the
post-LSH behavior by removing the "LSH index support is a follow-up" text and
either drop the row or rewrite it to recommend post-LSH actions (e.g., mention
tuning the `--near-dup-threshold` parameter, sampling strategies, or LSH
band/threshold settings) and change the diagnosis from "Simhash quadratic ran"
to guidance that explains large pair counts can still occur and how to mitigate
them.

211-219: ⚠️ Potential issue | 🟡 Minor

Recommended-workflow snippet still uses the deprecated flag.

The rest of the guide (lines 3, 20-29, 226, 248-250) tells operators the new shape is forgelm audit PATH, but the "Recommended workflow" example here still invokes the deprecation alias forgelm --data-audit. New readers copy-pasting from this section will eat the deprecation notice on every CI run.

📝 Proposed fix
 # Audit first — surfaces issues before you commit to a long training run
-forgelm --data-audit data/policies.jsonl --output ./checkpoints/policy-run/
+forgelm audit data/policies.jsonl --output ./checkpoints/policy-run/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/guides/data_audit.md` around lines 211 - 219, The "Recommended workflow"
snippet still uses the deprecated flag invocation "forgelm --data-audit"; update
it to use the new subcommand form "forgelm audit PATH" (e.g., replace "forgelm
--data-audit data/policies.jsonl --output ./checkpoints/policy-run/" with the
equivalent "forgelm audit data/policies.jsonl --output
./checkpoints/policy-run/") so the example aligns with the rest of the doc and
avoids CI deprecation noise; look for the snippet around the "Recommended
workflow" text and update the command invocation accordingly.

1-282: ⚠️ Potential issue | 🟠 Major

Update the Turkish mirror (docs/guides/data_audit-tr.md) to include Phase 11.5 documentation in full.

The Turkish version is missing two substantial Phase 11.5 sections present in the English guide:

  • PII severity tiers block — the JSON example (lines ~100–120 in English) with tier structure, regulatory references (PCI-DSS, GDPR Art. 9, ENISA), and worst_tier guidance for pipeline gatekeeping.
  • LSH banding explanation — the detailed technical section (lines ~180–200 in English) covering algorithm (bands = threshold + 1), complexity (O(n²) → O(n × k)), fallback logic, and the pluggable simhash backend (xxhash.xxh3_64 vs BLAKE2b with lru_cache).

The Turkish file currently mentions Phase 11.5 in the introduction but does not document these key features. Sync the mirror to match the English version's complete feature set.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/guides/data_audit.md` around lines 1 - 282, The Turkish guide is missing
the Phase 11.5 PII severity tiers and LSH banding sections; update the Turkish
mirror to mirror the English content by adding the full "pii_severity" JSON
block (including total, by_tier, by_type, worst_tier and regulatory references
PCI‑DSS/GDPR Art.9/ENISA and the gatekeeping guidance), and add the LSH banding
explanation describing "bands = threshold + 1", the O(n²)→O(n×k) complexity
improvement, the brute-force fallback behavior of find_near_duplicates, and the
simhash backend details (xxhash.xxh3_64 vs BLAKE2b and the
lru_cache(maxsize=10_000) token memoisation) so the Turkish doc contains the
same Phase 11.5 feature set and examples as the English guide.
🧹 Nitpick comments (5)
tests/test_data_audit.py (1)

520-642: LGTM — strong Phase 11.5 coverage; one optional brittleness note.

The new test classes (LSH parity vs brute, severity tiers, atomic write hygiene, streaming-reader 3-tuple contract, _token_digest cache observability) lock in the most error-prone surfaces from this PR.

Optional: TestSummarizeVerbosePolicy.test_clean_split_folded_in_default_mode asserts the literal "clean split" string in rendered output (line 586). If the renderer's copy ever changes, this test becomes a tripwire on wording rather than behavior. A regex like re.search(r"clean.*split", ...) or asserting the validation header is absent in default mode (and present in verbose) would be more robust. Not blocking.

🤖 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 520 - 642, The test
TestSummarizeVerbosePolicy.test_clean_split_folded_in_default_mode is brittle
because it asserts the exact substring "clean split" in rendered_default; update
the assertion to check behavior rather than exact wording by either using a
regex match (e.g., re.search(r"clean.*split", rendered_default)) or by asserting
the validation split header is absent in rendered_default and present in
rendered_verbose; modify the assertion lines that reference
rendered_default/rendered_verbose in that test to use the more robust check
against summarize_report's output.
forgelm/wizard.py (2)

389-392: Default ingest output path is CWD-relative; consider anchoring it deterministically.

Path("data").resolve() resolves against the current working directory — if the wizard is launched from ~/work/projects/foo/, the default lands at ~/work/projects/foo/data/<dir>_ingested.jsonl, which is usually fine. But if the user just cdd into a system directory or scratch path, the default may surprise.

Lower-priority polish: derive the default from the input directory itself (e.g. resolved.parent / "data" / f"{resolved.name}_ingested.jsonl") so the path lives next to the source corpus regardless of CWD. The user can still override at the prompt.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@forgelm/wizard.py` around lines 389 - 392, The default output directory
currently uses out_dir = Path("data").resolve() which anchors the JSONL file to
the current working directory; change out_dir to be derived from the input path
(e.g., out_dir = resolved.parent / "data") so default_out = out_dir /
f"{resolved.name}_ingested.jsonl" lives next to the source corpus; ensure you
still expand and resolve the user-provided out_path_raw via out_path =
Path(out_path_raw).expanduser().resolve() and keep using the same _prompt call
to let users override.

338-343: Drift risk: _INGEST_SUPPORTED_EXTENSIONS duplicates forgelm.ingestion.SUPPORTED_EXTENSIONS.

The comment correctly identifies the trade-off (avoid the extras-module import cost on the JSONL fast path), but if forgelm/ingestion.py ever gains a new supported format the wizard will silently miss it — _directory_has_ingestible_files will return False and the user will hit the "doesn't contain any" message even though ingestion would have handled the file.

A unit test assertion that the two sets agree prevents this drift:

♻️ Lock the two lists to drift via a test
# tests/test_wizard.py (new file or add to existing)
def test_wizard_supported_extensions_match_ingestion():
    from forgelm.ingestion import SUPPORTED_EXTENSIONS
    from forgelm.wizard import _INGEST_SUPPORTED_EXTENSIONS

    assert set(_INGEST_SUPPORTED_EXTENSIONS) == set(SUPPORTED_EXTENSIONS), (
        "wizard's _INGEST_SUPPORTED_EXTENSIONS drifted from forgelm.ingestion.SUPPORTED_EXTENSIONS"
    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@forgelm/wizard.py` around lines 338 - 343, The wizard hard-codes
_INGEST_SUPPORTED_EXTENSIONS which can drift from
forgelm.ingestion.SUPPORTED_EXTENSIONS; add a unit test (e.g.,
tests/test_wizard.py) that imports forgelm.ingestion.SUPPORTED_EXTENSIONS and
forgelm.wizard._INGEST_SUPPORTED_EXTENSIONS and asserts their sets are equal so
any future change in SUPPORTED_EXTENSIONS fails the test, ensuring
_directory_has_ingestible_files behavior stays in sync with ingestion.
forgelm/ingestion.py (1)

622-634: chunk_size != 2048 heuristic silently swallows the warning when the user passed the default value.

The warning is supposed to flag stale CLI invocations that mix --chunk-size with --chunk-tokens, but it can only fire when chunk_size is not equal to the in-file default of 2048. A user who explicitly passes --chunk-size 2048 --chunk-tokens N --tokenizer M gets no warning, and any future change to the chunk_size default would silently invert the behaviour for everyone.

A sentinel default (or routing the explicit-vs-default determination through the CLI layer) makes the intent crash-proof.

♻️ Sketch — sentinel default
-def ingest_path(
+_DEFAULT_CHUNK_SIZE = 2048
+
+def ingest_path(
     input_path: str,
     *,
     output_path: str,
-    chunk_size: int = 2048,
+    chunk_size: Optional[int] = None,
     overlap: int = 200,
     ...
 ) -> IngestionResult:
     ...
+    effective_chunk_size = _DEFAULT_CHUNK_SIZE if chunk_size is None else chunk_size
     if chunk_tokens is not None:
         if not tokenizer:
             raise ValueError(...)
-        if chunk_size != 2048:
+        if chunk_size is not None:
             logger.warning(
                 "Token-aware mode active (--chunk-tokens=%d): --chunk-size=%d is ignored.",
                 chunk_tokens,
-                chunk_size,
+                chunk_size,
             )

(Then thread effective_chunk_size through _process_one_file.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@forgelm/ingestion.py` around lines 622 - 634, The current check uses
chunk_size != 2048 to detect whether the user explicitly provided a chunk size,
which wrongly treats an explicit "--chunk-size 2048" as the default; change the
API so chunk_size is a sentinel (e.g., None or a special value) when not
provided by the CLI and compute an effective_chunk_size later, then update this
block (references: chunk_tokens, chunk_size, tokenizer_obj, _load_tokenizer and
the caller like _process_one_file) to check the sentinel instead of 2048 and log
the warning whenever the user explicitly supplied a chunk-size while also using
--chunk-tokens; thread effective_chunk_size through the processing call so
downstream code uses the resolved value.
forgelm/data_audit.py (1)

1057-1067: Optional: dict.fromkeys for the zero-initialised tier maps.

SonarCloud flags both occurrences. Cosmetic, but the fromkeys form is a touch shorter and signals "all values share the same initial value":

♻️ Proposed cleanup
-    if not pii_summary:
-        return {
-            "total": 0,
-            "by_tier": {tier: 0 for tier in PII_SEVERITY_ORDER},
-            "by_type": {},
-            "worst_tier": None,
-        }
-
-    by_tier: Dict[str, int] = {tier: 0 for tier in PII_SEVERITY_ORDER}
+    if not pii_summary:
+        return {
+            "total": 0,
+            "by_tier": dict.fromkeys(PII_SEVERITY_ORDER, 0),
+            "by_type": {},
+            "worst_tier": None,
+        }
+
+    by_tier: Dict[str, int] = dict.fromkeys(PII_SEVERITY_ORDER, 0)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@forgelm/data_audit.py` around lines 1057 - 1067, Replace the explicit
comprehension used to zero-initialise tier maps with dict.fromkeys to be more
concise and signal a shared initial value: update the inline return "by_tier":
{tier: 0 for tier in PII_SEVERITY_ORDER} and the later by_tier = {tier: 0 for
tier in PII_SEVERITY_ORDER} to use dict.fromkeys(PII_SEVERITY_ORDER, 0); leave
by_type and other structures unchanged.
🤖 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/ingestion.md`:
- Around line 4-7: The intra-doc anchor `#phase-115-deltas` is missing; add a
new heading "## Phase 11.5 deltas" immediately after the intro paragraph that
briefly summarizes the two changes (token-aware chunking and PDF page-level
header/footer dedup) and includes links to the existing sections titled
"Token-aware chunking — `--chunk-tokens` (Phase 11.5)" and "PDF page-level
header/footer dedup (Phase 11.5)"; alternatively, if you prefer minimal change,
remove the `[Phase 11.5 deltas](`#phase-115-deltas`)` link from the intro.

---

Outside diff comments:
In `@docs/guides/data_audit-tr.md`:
- Around line 135-140: The paragraph claiming "karesel" (quadratic) behavior and
that LSH band indexing is "v0.5.0 kapsamı dışı" is stale; update the intro
paragraph and the troubleshooting table entry (around the second occurrence) to
describe the Phase 11.5 LSH-banded near-duplicate path: explain that the default
path uses an O(n × k) LSH banded approach with a brute-force fallback for
high-threshold edge cases, and reference the implemented behavior exercised by
tests/test_data_audit.py::TestLshBandedNearDuplicates; ensure wording removes
the "out of scope" claim and replaces "quadratic" with a note about when the
brute-force fallback may apply.

In `@docs/guides/data_audit.md`:
- Line 261: The table row about "`near_duplicate_pairs` enormous on a large
dataset" is stale because LSH-banded near-duplicate detection is now
implemented; update that row to reflect the post-LSH behavior by removing the
"LSH index support is a follow-up" text and either drop the row or rewrite it to
recommend post-LSH actions (e.g., mention tuning the `--near-dup-threshold`
parameter, sampling strategies, or LSH band/threshold settings) and change the
diagnosis from "Simhash quadratic ran" to guidance that explains large pair
counts can still occur and how to mitigate them.
- Around line 211-219: The "Recommended workflow" snippet still uses the
deprecated flag invocation "forgelm --data-audit"; update it to use the new
subcommand form "forgelm audit PATH" (e.g., replace "forgelm --data-audit
data/policies.jsonl --output ./checkpoints/policy-run/" with the equivalent
"forgelm audit data/policies.jsonl --output ./checkpoints/policy-run/") so the
example aligns with the rest of the doc and avoids CI deprecation noise; look
for the snippet around the "Recommended workflow" text and update the command
invocation accordingly.
- Around line 1-282: The Turkish guide is missing the Phase 11.5 PII severity
tiers and LSH banding sections; update the Turkish mirror to mirror the English
content by adding the full "pii_severity" JSON block (including total, by_tier,
by_type, worst_tier and regulatory references PCI‑DSS/GDPR Art.9/ENISA and the
gatekeeping guidance), and add the LSH banding explanation describing "bands =
threshold + 1", the O(n²)→O(n×k) complexity improvement, the brute-force
fallback behavior of find_near_duplicates, and the simhash backend details
(xxhash.xxh3_64 vs BLAKE2b and the lru_cache(maxsize=10_000) token memoisation)
so the Turkish doc contains the same Phase 11.5 feature set and examples as the
English guide.

In `@docs/guides/ingestion-tr.md`:
- Line 155: Replace the legacy flag-style examples that use "forgelm
--data-audit ..." with the new subcommand form "forgelm audit ..." in the
end-to-end example and the PII/forms example; specifically, change instances
like "forgelm --data-audit data/policies.jsonl --output ./audit/" to "forgelm
audit data/policies.jsonl --output ./audit/" and likewise update the example
that references the medical/PII JSONL to use "forgelm audit <file> --output
<dir>"; keep a single note elsewhere mentioning the legacy alias if desired.

In `@docs/guides/ingestion.md`:
- Line 96: Replace the remaining legacy CLI examples that use the deprecated
flag form `forgelm --data-audit` with the new subcommand `forgelm audit` in the
docs; specifically update the occurrences in docs/guides/ingestion.md (PII
section, end-to-end example, PII forms/OCR example) and the TR mirror
docs/guides/ingestion-tr.md, and ensure the old `--data-audit` form is only
mentioned in the dedicated "compatibility" / usage/README callout rather than in
examples.

---

Nitpick comments:
In `@forgelm/data_audit.py`:
- Around line 1057-1067: Replace the explicit comprehension used to
zero-initialise tier maps with dict.fromkeys to be more concise and signal a
shared initial value: update the inline return "by_tier": {tier: 0 for tier in
PII_SEVERITY_ORDER} and the later by_tier = {tier: 0 for tier in
PII_SEVERITY_ORDER} to use dict.fromkeys(PII_SEVERITY_ORDER, 0); leave by_type
and other structures unchanged.

In `@forgelm/ingestion.py`:
- Around line 622-634: The current check uses chunk_size != 2048 to detect
whether the user explicitly provided a chunk size, which wrongly treats an
explicit "--chunk-size 2048" as the default; change the API so chunk_size is a
sentinel (e.g., None or a special value) when not provided by the CLI and
compute an effective_chunk_size later, then update this block (references:
chunk_tokens, chunk_size, tokenizer_obj, _load_tokenizer and the caller like
_process_one_file) to check the sentinel instead of 2048 and log the warning
whenever the user explicitly supplied a chunk-size while also using
--chunk-tokens; thread effective_chunk_size through the processing call so
downstream code uses the resolved value.

In `@forgelm/wizard.py`:
- Around line 389-392: The default output directory currently uses out_dir =
Path("data").resolve() which anchors the JSONL file to the current working
directory; change out_dir to be derived from the input path (e.g., out_dir =
resolved.parent / "data") so default_out = out_dir /
f"{resolved.name}_ingested.jsonl" lives next to the source corpus; ensure you
still expand and resolve the user-provided out_path_raw via out_path =
Path(out_path_raw).expanduser().resolve() and keep using the same _prompt call
to let users override.
- Around line 338-343: The wizard hard-codes _INGEST_SUPPORTED_EXTENSIONS which
can drift from forgelm.ingestion.SUPPORTED_EXTENSIONS; add a unit test (e.g.,
tests/test_wizard.py) that imports forgelm.ingestion.SUPPORTED_EXTENSIONS and
forgelm.wizard._INGEST_SUPPORTED_EXTENSIONS and asserts their sets are equal so
any future change in SUPPORTED_EXTENSIONS fails the test, ensuring
_directory_has_ingestible_files behavior stays in sync with ingestion.

In `@tests/test_data_audit.py`:
- Around line 520-642: The test
TestSummarizeVerbosePolicy.test_clean_split_folded_in_default_mode is brittle
because it asserts the exact substring "clean split" in rendered_default; update
the assertion to check behavior rather than exact wording by either using a
regex match (e.g., re.search(r"clean.*split", rendered_default)) or by asserting
the validation split header is absent in rendered_default and present in
rendered_verbose; modify the assertion lines that reference
rendered_default/rendered_verbose in that test to use the more robust check
against summarize_report's output.
🪄 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: b07a2e81-7b58-475a-b0fb-73080666678e

📥 Commits

Reviewing files that changed from the base of the PR and between cee5b1f and e5cf9b5.

📒 Files selected for processing (22)
  • CHANGELOG.md
  • README.md
  • docs/guides/data_audit-tr.md
  • docs/guides/data_audit.md
  • docs/guides/ingestion-tr.md
  • docs/guides/ingestion.md
  • docs/reference/usage-tr.md
  • docs/reference/usage.md
  • docs/roadmap-tr.md
  • docs/roadmap.md
  • docs/roadmap/phase-11-5-backlog.md
  • docs/roadmap/phase-11-data-ingestion.md
  • docs/roadmap/releases.md
  • forgelm/cli.py
  • forgelm/data_audit.py
  • forgelm/ingestion.py
  • forgelm/wizard.py
  • pyproject.toml
  • tests/test_cli_subcommands.py
  • tests/test_data_audit.py
  • tests/test_ingestion.py
  • tests/test_wizard_byod.py

Comment thread docs/guides/ingestion.md Outdated
…/ 8 nits)

Verified each finding against the current code; applied 17 of the 20 raised,
declined 1 (LSH cluster-grouping optimisation — defer to follow-up; current
LSH already covers common cases and the win only kicks in on pathological
identical-fingerprint clusters), folded 2 into others.

Critical
--------
* xxhash perf claim toned to match measurement. Reviewer benched
  1.05× end-to-end / 1.3× raw on a Python-wrapped path; my repeat
  produced 1.06× / 1.31-1.40×. The "4-10×" figure in xxhash's
  README is C-level pure-hash, not Python. Edited the inline comment
  in `forgelm/data_audit.py:46-56`, the `_token_digest` docstring,
  `docs/guides/data_audit.md`, and the CHANGELOG. Added
  `tests/bench_simhash.py` so future reviewers can re-measure on
  their own hardware (run manually; not in CI). Recorded the
  measured numbers in `docs/roadmap/phase-11-5-backlog.md`.

High
----
* `_StreamingAggregator.keysets: List[frozenset]` → `keyset_counts:
  Counter[frozenset]`. Stable schema = 1 entry instead of N. Updated
  `_aggregator_to_info` to use `most_common(1)`. Tightened the
  `_audit_split` docstring: memory is dominated by per-row
  fingerprints (~30 MB / 1 M rows), not "bounded" — accurate now.
* `_chunk_paragraph_tokens` now budgets the `"\n\n"` separator
  token cost. Compute `sep_tokens` once per call, add when a join
  would happen. Prevents the small-but-real per-chunk overflow
  the docstring already promised against.
* `forgelm/compliance.py:150` hint string now says
  `forgelm audit <dataset>` instead of the deprecated
  `forgelm --data-audit`. Updated the matching test in
  `tests/test_compliance.py` to accept either form during the
  deprecation window.

Medium
------
* Restored the round-3 archaeology in the non-dict row branch of
  `_ingest_row`: the double-counting (`non_object_rows += 1`
  AND `null_or_empty += 1`) is intentional — without the
  `null_or_empty` bump, `null_or_empty_rate` would understate
  corruption while length stats / fingerprints excluded the row.
  Comment now says so, with a pointer to the test that pins the
  contract.
* `_chunk_paragraph_tokens` paragraph-mode `overlap_tokens`
  silent ignoring is now documented in the docstring AND surfaces
  as a one-line `INFO` log in `ingest_path` so an operator who
  mixes `--strategy paragraph --overlap-tokens N` sees what
  happened.
* New direct unit tests:
  - `TestCountLeakedRowsLshParity`: `_count_leaked_rows` LSH path
    matches the brute-force formula; high-threshold fallback still
    counts identical rows.
  - `TestAtomicWriteFailure::test_failure_during_replace_leaves_no_tmp`:
    monkey-patches `os.replace` to raise; verifies the `.tmp`
    cleanup actually runs and the canonical file is not created.
  - `TestPiiSeveritySnapshot`: pins the per-call snapshot contract
    so a future "let me deep-freeze the table" change is loud.
* `_build_pii_severity` snapshots `PII_SEVERITY` at call time
  (`severity_table = dict(PII_SEVERITY)`) so a test mutating the
  module-level dict cannot corrupt audit output mid-run.

Nits
----
* `dict.fromkeys(PII_SEVERITY_ORDER, 0)` instead of dict
  comprehensions in `_build_pii_severity`. Same shape, signals
  shared initial value.
* `chunk_size` in `ingest_path` is now `Optional[int] = None`
  with a public `DEFAULT_CHUNK_SIZE = 2048` sentinel. The "did the
  operator pass it explicitly?" check no longer compares against
  `2048` (which would falsely flag an explicit `--chunk-size 2048`
  alongside `--chunk-tokens`). CLI default is now `None`, library
  resolves to `DEFAULT_CHUNK_SIZE`.
* Wizard ingest-first hook anchors the default JSONL path to the
  source corpus's parent (`resolved.parent / "data"`) instead of
  `cwd / "data"`. Pairs with the operator's existing layout instead
  of dumping next to wherever they happened to invoke `forgelm`.
* New `tests/test_wizard_phase11_5.py::test_wizard_extensions_match_ingestion`
  fails loudly if `wizard._INGEST_SUPPORTED_EXTENSIONS` drifts from
  `ingestion.SUPPORTED_EXTENSIONS`.
* `TestSummarizeVerbosePolicy` now asserts behaviour, not the
  fold-line wording: "└─ validation" header absent in default mode,
  present in verbose. Substring-tied to fold-line wording was
  brittle.
* `TestSummary::test_summary_contains_next_step_hint` tightened:
  must contain `forgelm audit`; explicitly forbids `forgelm
  --data-audit` so a future regression to legacy wording is loud.
* PR-agent inline (PDF multi-line headers): `_strip_repeating_page_lines`
  now iterates — pop a repeating first/last line, recompute the
  count maps, repeat until no line crosses the threshold. New test
  `test_multiline_header_stripped_iteratively` covers the
  ACME / CONFIDENTIAL pattern that the single-pass version missed.

Doc fixes
---------
* `docs/guides/ingestion.md`: removed dangling `#phase-115-deltas`
  anchor (the per-feature sections already document what landed);
  replaced 3 `forgelm --data-audit` examples with `forgelm audit`.
* `docs/guides/ingestion-tr.md`: same `--data-audit` → `audit`
  replacements (2 examples).
* `docs/guides/data_audit.md`: workflow snippet uses `forgelm
  audit`; troubleshooting "near_duplicate_pairs enormous" row
  rewritten — "LSH index support is a follow-up" is no longer
  true; new wording explains genuine high near-dup is signal,
  not algorithmic noise, and recommends `--near-dup-threshold`
  tuning + PDF dedup; xxhash blurb tones the speedup claim and
  cites the bench script.
* `docs/guides/data_audit-tr.md`: added the missing "PII şiddet
  katmanları (Faz 11.5)" section mirroring the EN one (full
  `pii_severity` JSON shape + tier table provenance + gatekeeping
  guidance); rewrote "Near-duplicate tespiti" — replaced the
  outdated "karesel; LSH band indeksi v0.5.0 kapsamı dışı"
  paragraph with the implemented LSH banding semantics, brute
  fallback note, xxhash backend description, and the lru_cache
  story; troubleshooting row updated to match the EN one.
* `docs/roadmap/phase-14-pipeline-chains.md`: stale `v0.5.1`
  release target → `v0.5.2` (Phase 11.5 took the v0.5.1 slot).

Result: 760 passed (+6 vs. previous 754), 8 skipped. Lint clean.
Smoke verified: `forgelm audit ...`, `forgelm --data-audit ...`
(legacy with deprecation log), `forgelm ingest --strategy
paragraph --overlap-tokens 50` (logs the ignore note),
`forgelm ingest --output-format json` (`notes_structured` +
`pdf_header_footer_lines_stripped` shapes intact).

Declined / deferred
-------------------
* PR-agent inline on `forgelm/data_audit.py:474-488` (LSH cluster
  optimisation): real but only matters on pathological
  identical-fingerprint clusters. Adds non-trivial complexity
  (group → unique fingerprints → arithmetic combination) for an
  edge case the LSH already handles fine on real corpora. Moved
  to follow-up backlog rather than landing in this fix pass.
…ssions

5 functions exceeded the 15-cog cap; broken into single-concern helpers
without changing observable behaviour. 760 passed / 8 skipped, lint clean.

Refactors
---------
* `forgelm/data_audit.py:find_near_duplicates` (22 → ≤15) — extracted
  `_bucket_pairs_within_threshold` for the per-bucket Hamming
  verification pass; reused the existing `_build_band_index` helper
  instead of inlining the bucket build. The orchestrator is now a
  flat band → bucket → verify pipeline.
* `forgelm/data_audit.py:_count_leaked_rows` (28 → ≤15) — split into
  `_count_leaked_brute` (the linear-scan fallback) and
  `_source_row_leaks` (single-row LSH walk with early exit). Keeps
  per-source-row `seen` set scoping intact.
* `forgelm/ingestion.py:_strip_repeating_page_lines` (17 → ≤15) —
  pulled `_repeating_edge_lines` (count → set) and
  `_pop_repeating_edges` (mutation pass) out of the iteration loop.
  Multi-line header handling is unchanged (still iterative until no
  edge crosses the threshold).
* `forgelm/ingestion.py:_extract_pdf` (16 → ≤15) — encryption /
  decrypt-attempt logic moved to `_try_pdf_decrypt`; per-page
  text extraction loop moved to `_read_pdf_pages`.
* `forgelm/ingestion.py:_process_one_file` (18 → ≤15) — three
  concerns now have their own helpers:
  - `_extract_text_for_ingest`: extractor dispatch + tolerant
    skip-on-failure (ImportError still propagates).
  - `_select_chunks_iter`: token-aware vs character chunker pick.
  - `_emit_chunk`: mask + serialise + outcome counter update.

bench_simhash.py
----------------
* L78 (`int.from_bytes` return value not used): assigned to `_` for
  both backends so the two timed loops stay symmetrical and the
  intentional discard is explicit. Bench numbers unchanged
  (1.32-1.34× raw, 1.10× e2e on this run).
* 7× SonarCloud `python:S2245` PRNG warning: added per-call-site
  `# NOSONAR(python:S2245)` annotations on every `random.choices` /
  `random.randint` / `random.seed` site, plus a module-docstring
  paragraph documenting that this is microbenchmark-only test data
  and never security-sensitive. The same comment also covers the
  ruff/bandit S311 check (the ruff config already excludes the `S`
  group, but the inline annotations document intent regardless).
…tspots

Line-level ``# NOSONAR(python:S2245)`` annotations didn't suppress the
hotspots; the rule continues to flag every ``random.choices`` /
``random.randint`` / ``random.seed`` call site even with the
suppression comment attached.

Switching to deterministic generators removes the issue at the source
without falling back to ``secrets.SystemRandom`` (overkill for a
microbenchmark) or littering the file with rule-specific opt-outs.

Changes
-------
* ``import random`` removed entirely. The module is ``random``-free.
* ``_gen_short`` and ``_gen_long`` now build tokens via a tiny
  ``_det_token(idx, length)`` helper that walks
  ``string.ascii_lowercase`` with prime-step (7, 13) permutations —
  varied-looking output, no PRNG.
* ``_gen_zipfian_english`` cycles deterministically through a
  20-token stop-word list. The Zipfian effect comes from the small
  vocab being hashed against a 50K-row corpus, which is what the
  ``lru_cache`` is meant to absorb anyway.
* ``_bench_compute_simhash`` token-count cycles deterministically
  through 50-150 via ``50 + (i * 17 % 101)`` and packs each text via
  a strided walk through the base vocab.
* ``random.seed(42)`` in ``main()`` removed — there is nothing left
  to seed. Reproducibility is now structural (same code → same
  inputs) rather than seeded.

Verification
------------
* ``ruff check`` clean (after auto-import-sort).
* ``python tests/bench_simhash.py`` produces numbers in the same
  ballpark as the seeded version: 1.32-1.37× raw, ~1.0-1.1× e2e
  (run-to-run wall-clock variance dominates either way).
* ``pytest tests/`` — 760 passed, 8 skipped (unchanged).
* ``grep -E '^[^#]*random\.' tests/bench_simhash.py`` returns
  nothing — only docstring/comment mentions remain, and those are
  background context for the change.
@cemililik

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cemililik

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
forgelm/cli.py (1)

1177-1205: ⚠️ Potential issue | 🟠 Major

Map ingest I/O failures to the CLI contract.

This handler misses PermissionError, IsADirectoryError, ENOSPC, and other OSErrors from opening the output file or walking the input tree. Those currently fall out as tracebacks instead of returning EXIT_CONFIG_ERROR. As per coding guidelines, forgelm/cli.py: Exit codes are a public contract: 0/1/2/3/4 as per error-handling.md, especially for CLI.

Suggested fix
-    except (FileNotFoundError, ValueError) as exc:
+    except (OSError, ValueError) as exc:
         if output_format == "json":
             print(json.dumps({"success": False, "error": str(exc)}))
         else:
             logger.error("Ingest 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 1177 - 1205, The current except block around the
ingest_path call only catches FileNotFoundError and ValueError and misses
OS-level I/O errors; update the first except to also catch PermissionError,
IsADirectoryError and OSError (e.g., change "except (FileNotFoundError,
ValueError) as exc:" to include these exceptions) so any file/open/walk errors
are handled the same way (print JSON on json output_format or logger.error
otherwise) and then sys.exit(EXIT_CONFIG_ERROR); keep the existing ImportError
handler for EXIT_TRAINING_ERROR unchanged and reference the same symbols
(ingest_path, output_format, logger, EXIT_CONFIG_ERROR, EXIT_TRAINING_ERROR)
when making the change.
🤖 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/ingestion-tr.md`:
- Around line 21-26: Update the English mirror docs/guides/ingestion.md to match
the Turkish change by adding the optional `xxhash` backend to the listed
optional packages (`pypdf`, `python-docx`, `ebooklib`, `beautifulsoup4`,
`langdetect`) and note that `xxhash` is available from version 11.5; ensure the
wording about these being optional for plain text/Markdown and that import of
the module does not load extractors is preserved so both guides stay in sync.

In `@forgelm/cli.py`:
- Around line 345-350: The audit subparser's "--output" currently sets
default="./audit" and thus overwrites a top-level --output; change the audit
subparser's add_argument for "--output" to use default=argparse.SUPPRESS so the
subparser only provides the value when explicitly passed, and ensure the
existing fallback logic in _run_data_audit() is used to set "./audit" when no
audit-specific output is present (i.e., check for the presence of the attribute
on the parsed args and apply the "./audit" default inside _run_data_audit()).
- Around line 361-368: The CLI currently accepts any integer for
--near-dup-threshold; add validation so negative values are rejected at
parse-time. Replace the current type=int on the add_argument call for
"--near-dup-threshold" (the add_argument call in the parser setup in cli.py)
with a validator function (e.g., non_negative_int) that raises
argparse.ArgumentTypeError when value < 0, or alternatively perform an immediate
check after parsing and call parser.error("...") for negative values so the
process exits via the standard CLI config-error path (parser.error -> exit code
2).

In `@forgelm/data_audit.py`:
- Around line 1355-1378: _render_split_block currently omits showing
parse_errors, decode_errors, and non_object_rows even though
_split_has_findings() promotes splits with those problems; update
_render_split_block to detect these keys on the info dict and append
human-readable lines (e.g. "parse errors: X", "decode errors: X", "non-object
rows: X" or short details) similar to how languages_top3/pii_counts are rendered
so malformed JSONL splits are clearly flagged in the CLI output; keep the
existing block prefix/indenting and ordering so these error lines appear after
the sample_count line and before other metrics.

In `@forgelm/ingestion.py`:
- Around line 136-138: The cutoff calculation currently floors the threshold by
using int(_PDF_REPEAT_THRESHOLD * len(pages)), which can make a 70% rule behave
as <70%; change the computation to use math.ceil to enforce "≥70%" (e.g. cutoff
= max(2, math.ceil(_PDF_REPEAT_THRESHOLD * len(pages)))) and add the required
import for math if not present; update the reference in the dedup logic that
uses cutoff (variable cutoff and related use of
page_lines/_PDF_REPEAT_THRESHOLD) accordingly.
- Around line 177-188: The current _read_pdf_pages silently swallows
page.extract_text() failures; change it to stop hiding these errors by (a)
catching only the PDF/text-extraction-specific exception your PDF library throws
(or Exception if there is no narrower type) and logging a warning with path and
page index, and then (b) propagate a file-level failure (raise a
FileIngestionError or IOError) instead of returning a partial page list so the
caller can mark the entire file failed; update any caller of _read_pdf_pages to
handle the propagated exception or the new error type.
- Around line 167-174: The post-decrypt check currently uses reader.is_encrypted
which remains True in pypdf>=4.0 even after a successful decrypt; change the
logic to capture the return value of reader.decrypt(path) (the
PasswordType/boolean result) and gate the subsequent validation on that return
instead of is_encrypted: call result = reader.decrypt("") and if result
indicates failure (not PasswordType.SUCCESS / falsy) then raise the ValueError,
otherwise proceed; update the exception handling block around reader.decrypt
and, if needed, import or reference the PasswordType enum used by pypdf to
compare the return value (symbols: reader.decrypt, reader.is_encrypted,
FileNotDecryptedError).

In `@forgelm/wizard.py`:
- Around line 405-424: The try/except around calling ingest_path currently only
catches FileNotFoundError, ValueError and ImportError, so
PermissionError/OSError from writing JSONL or traversing the tree slip through;
update the ingest_path call's exception handling (the try block that invokes
ingest_path) to also catch PermissionError and broader OSError, and in that
handler print a concise, retryable message including the exception details
(e.g., "Ingest failed due to filesystem error: {exc} — check permissions or
output path") and return None so the wizard can re-prompt; keep the existing
ImportError handling separate.

In `@tests/test_wizard_phase11_5.py`:
- Around line 19-23: The test currently compares extensions via set equality
which hides ordering drift and duplicates; change the assertion in
tests/test_wizard_phase11_5.py to compare the sequences verbatim (i.e., assert
wizard._INGEST_SUPPORTED_EXTENSIONS == ingestion.SUPPORTED_EXTENSIONS) so
ordering and duplicates are detected — reference the symbols
wizard._INGEST_SUPPORTED_EXTENSIONS and ingestion.SUPPORTED_EXTENSIONS and keep
the existing failure message.

---

Outside diff comments:
In `@forgelm/cli.py`:
- Around line 1177-1205: The current except block around the ingest_path call
only catches FileNotFoundError and ValueError and misses OS-level I/O errors;
update the first except to also catch PermissionError, IsADirectoryError and
OSError (e.g., change "except (FileNotFoundError, ValueError) as exc:" to
include these exceptions) so any file/open/walk errors are handled the same way
(print JSON on json output_format or logger.error otherwise) and then
sys.exit(EXIT_CONFIG_ERROR); keep the existing ImportError handler for
EXIT_TRAINING_ERROR unchanged and reference the same symbols (ingest_path,
output_format, logger, EXIT_CONFIG_ERROR, EXIT_TRAINING_ERROR) when making the
change.
🪄 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: dcbd19ba-f137-4005-b3b9-161612610196

📥 Commits

Reviewing files that changed from the base of the PR and between e5cf9b5 and 5c805dc.

📒 Files selected for processing (17)
  • CHANGELOG.md
  • docs/guides/data_audit-tr.md
  • docs/guides/data_audit.md
  • docs/guides/ingestion-tr.md
  • docs/guides/ingestion.md
  • docs/roadmap/phase-11-5-backlog.md
  • docs/roadmap/phase-14-pipeline-chains.md
  • forgelm/cli.py
  • forgelm/compliance.py
  • forgelm/data_audit.py
  • forgelm/ingestion.py
  • forgelm/wizard.py
  • tests/bench_simhash.py
  • tests/test_compliance.py
  • tests/test_data_audit.py
  • tests/test_ingestion.py
  • tests/test_wizard_phase11_5.py
✅ Files skipped from review due to trivial changes (2)
  • docs/roadmap/phase-14-pipeline-chains.md
  • forgelm/compliance.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_data_audit.py

Comment thread docs/guides/ingestion-tr.md
Comment thread forgelm/cli.py
Comment thread forgelm/cli.py
Comment thread forgelm/data_audit.py
Comment thread forgelm/ingestion.py
Comment thread forgelm/ingestion.py
Comment thread forgelm/ingestion.py
Comment on lines +177 to +188
def _read_pdf_pages(reader: Any, path: Path) -> List[str]:
"""Extract per-page text; tolerate page-level failures with a debug log."""
pages: List[str] = []
for page in reader.pages:
try:
text = page.extract_text() or ""
except Exception as exc:
logger.debug("PDF page extraction failed for %s: %s", path, exc)
text = ""
if text.strip():
pages.append(text)
return pages

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't silently drop unreadable PDF pages.

A page-level extract_text() failure currently turns into a debug log plus an empty page, so the default ingest path can emit a partial document with no operator-visible signal. This should be surfaced as a warning/structured note or treated as a file failure.

As per coding guidelines "No silent failures: no bare except:, no except Exception: pass, no logging-and-swallowing for non-fatal paths, no || true in CI".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@forgelm/ingestion.py` around lines 177 - 188, The current _read_pdf_pages
silently swallows page.extract_text() failures; change it to stop hiding these
errors by (a) catching only the PDF/text-extraction-specific exception your PDF
library throws (or Exception if there is no narrower type) and logging a warning
with path and page index, and then (b) propagate a file-level failure (raise a
FileIngestionError or IOError) instead of returning a partial page list so the
caller can mark the entire file failed; update any caller of _read_pdf_pages to
handle the propagated exception or the new error type.

Comment thread forgelm/wizard.py
Comment thread tests/test_wizard_phase11_5.py Outdated
Phase 12's slot was never planned — `docs/roadmap/phase-12-quickstart.md`
was a leftover filename from early ordering when Quickstart held that
slot. Renamed it to `phase-10-5-quickstart.md` (matches the current
phase number) and used the freed Phase 12 slot for the natural
continuation of the Phase 11 + 11.5 ingestion / audit lineage.

What landed in this commit
--------------------------
- `docs/roadmap/phase-12-data-curation-maturity.md` (new) — full
  4-6 week plan with three Tiers (Must-have / Should-have / Could-have),
  five "Won't-do" lines, kill criteria, module-touchpoint table, and
  a week-by-week sequencing.
- `docs/roadmap/phase-12-quickstart.md` → `phase-10-5-quickstart.md`
  rename; the file's "Filename history" note now records the rename
  rationale so the link in releases.md / roadmap.md / roadmap-tr.md
  stays self-explanatory.
- Phase 14 (Multi-Stage Pipeline Chains) reslotted from `v0.5.2` to
  `v0.5.3`. Phase 12 takes `v0.5.2` so the ingestion lineage
  (11 → 11.5 → 12) finishes uninterrupted before the trainer
  orchestration surface gets reshaped. Phase 14's own delivery
  section + releases.md updated; rationale documented in both files.
- `docs/roadmap.md` + `docs/roadmap-tr.md` status table, current /
  next milestone paragraphs, mermaid diagram, and documentation map
  all updated for the new sequence (P115 → P12 → P14 → P13).
- `docs/roadmap/phase-11-5-backlog.md` "What's next" section now
  links forward to Phase 12 with the competitive-review provenance.
- `docs/roadmap/releases.md` v0.5.2 entry rewritten for Phase 12
  features; v0.5.3 entry added for Phase 14.
- `CHANGELOG.md` automatically picks up the new Phase 12 link
  through the doc-rename pass; no behavioural delta in this commit.

Phase 12 thesis (one paragraph)
-------------------------------
Mature `forgelm ingest` + `forgelm audit` along three axes — scale
(MinHash LSH dedup for >50K-row corpora via the optional `datasketch`
dependency), security (code/secret leakage scanning + optional
ML-based PII via Presidio), quality (markdown-aware splitter,
Gopher/C4-style heuristic filter, DOCX/Markdown table preservation).
Closes the four concrete gaps the post-`v0.5.1` competitive review
surfaced against LLaMA-Factory / Axolotl / Unsloth / NeMo Curator /
Dolma / RedPajama / LlamaIndex / LangChain / Marker / Docling.
Layout-aware PDF parsing (Marker / Docling delegation) and
embedding-based semantic dedup are deliberately deferred to
Phase 13+ — both would add runtime ML dependencies that conflict
with `docs/marketing/strategy/05-yapmayacaklarimiz.md` and Annex IV
reproducibility guarantees.

Verification
------------
- `forgelm --config config_template.yaml --dry-run` clean.
- `pytest tests/` — 760 passed, 8 skipped (no code changes; doc-only
  commit).
- `grep -rn phase-12-quickstart docs/ --include='*.md'` returns only
  the historical-filename note in phase-10-5-quickstart.md (intended).
- Cross-references between roadmap.md, roadmap-tr.md, releases.md,
  phase-11-5-backlog.md, phase-14-pipeline-chains.md, and the new
  phase-12-data-curation-maturity.md all resolve.
…, docs)

All 10 review items verified against current code; every one applied.
Lint clean, 760 passed / 8 skipped, smoke verified for the three
behavioural changes (negative threshold rejection, --output SUPPRESS
fallback, deprecated --data-audit + top-level --output).

CLI (forgelm/cli.py)
--------------------
* `audit` subparser ``--output`` now uses ``argparse.SUPPRESS``. The
  top-level ``--output`` (default=None) wins when the operator doesn't
  pass it on the subcommand; ``_run_data_audit`` then falls back to
  ``./audit`` as before. Removes the silent-overwrite footgun where a
  ``forgelm --output ./tmp audit X`` invocation could be ignored by
  the subparser default. ``_run_audit_cmd`` now reads via
  ``getattr(args, "output", None)``.
* ``--near-dup-threshold`` gains a `_non_negative_int` argparse type
  helper that raises ``ArgumentTypeError`` on negative values; the
  resulting "argument --near-dup-threshold: value must be ≥ 0" error
  surfaces through argparse's standard exit path (code 2). Also
  rejects non-numeric input cleanly.
* ``_run_ingest_cmd``'s except tuple now covers PermissionError,
  IsADirectoryError, and OSError alongside FileNotFoundError /
  ValueError. ImportError handler stays separate (EXIT_TRAINING_ERROR).
  Comment documents that the OSError subclasses are listed explicitly
  for reader visibility even though OSError catches them.

data_audit (forgelm/data_audit.py)
----------------------------------
* `_render_split_block` now surfaces JSONL hygiene counters
  (``parse_errors``, ``decode_errors``, ``non_object_rows``) ahead of
  body metrics. ``_split_has_findings`` already promoted splits with
  these issues, but the renderer dropped them on the floor — operators
  scrolling the multi-split summary now see the malformed-JSONL signal
  at the top of the offending block instead of having to open the
  on-disk JSON.

ingestion (forgelm/ingestion.py)
--------------------------------
* `_strip_repeating_page_lines` cutoff: ``int(_PDF_REPEAT_THRESHOLD *
  N)`` → ``math.ceil(...)``. The 70 % rule was firing as ≤ 70 % on
  page counts where the multiplication landed mid-integer (e.g., 5
  pages × 0.7 = 3.5 floored to 3 = 60 %). math.ceil enforces the
  documented "≥ 70 %" promise. Added ``import math``.
* `_try_pdf_decrypt` now decides on ``reader.decrypt("")``'s return
  value (PasswordType IntEnum; NOT_DECRYPTED == 0 is falsy), not on
  ``reader.is_encrypted``. pypdf ≥ 4.0 keeps ``is_encrypted`` true
  even after a successful decrypt, so the old check would falsely
  reject correctly-decrypted owner-encrypted PDFs.
* `_read_pdf_pages` upgraded the swallowed page-extract error from
  ``logger.debug`` to ``logger.warning`` and now includes the page
  index in the message. Decision: still tolerate (don't raise) page-
  level failure — a 500-page PDF with one malformed page should
  produce 499 good pages, not 0. The new warning makes the failure
  visible in CI logs; rationale documented inline.

wizard (forgelm/wizard.py)
--------------------------
* `_offer_ingest_for_directory` now catches PermissionError,
  IsADirectoryError, and OSError around the ingest_path call (in
  addition to FileNotFoundError / ValueError / ImportError). Filesystem
  errors (output path not writable, source tree locked, ENOSPC, broken
  symlink during walk) were leaking through and crashing the wizard.
  New handler prints a retry-friendly message and returns None so the
  caller can re-prompt.

tests (tests/test_wizard_phase11_5.py)
--------------------------------------
* Drift assertion changed from ``set(...) == set(...)`` to verbatim
  sequence equality (``wizard._INGEST_SUPPORTED_EXTENSIONS ==
  ingestion.SUPPORTED_EXTENSIONS``). Now catches ordering drift and
  accidental duplicates that set-comparison would silently accept.

docs (docs/guides/ingestion.md)
-------------------------------
* EN guide's "Install" paragraph now lists ``xxhash`` alongside the
  other optional packages and notes it landed in v0.5.1. Restores
  parity with the TR mirror that already had this; the wording about
  the import not pulling extras until exercised is preserved.

Verification
------------
* ``ruff check`` clean (one auto-format on touched files).
* ``pytest tests/`` — 760 passed, 8 skipped (no test count change;
  drift test now sequence-strict but tuple equality still holds).
* Smoke:
  - ``forgelm audit --near-dup-threshold -5 path`` → argparse error,
    exits 2.
  - ``forgelm --data-audit path --output /tmp/x/`` (legacy) → writes
    to /tmp/x/data_audit_report.json (top-level --output wins).
  - ``forgelm audit path`` (no --output) → writes to ./audit/
    (SUPPRESS-driven fallback through _run_data_audit).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
forgelm/cli.py (1)

313-333: Validate token-based numeric flags at parse-time as well.

--chunk-tokens and --overlap-tokens currently allow negatives and rely on downstream ingest_path rejection. Parsing them with _non_negative_int would fail earlier and stay consistent with --near-dup-threshold.

Suggested refactor
     p.add_argument(
         "--chunk-tokens",
-        type=int,
+        type=_non_negative_int,
         default=None,
         metavar="N",
         help=(
@@
     p.add_argument(
         "--overlap-tokens",
-        type=int,
+        type=_non_negative_int,
         default=0,
         metavar="N",
         help=(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@forgelm/cli.py` around lines 313 - 333, The --chunk-tokens and
--overlap-tokens argparse entries currently accept negative values and defer
validation to ingest_path; update the parser to validate at parse-time by using
the existing _non_negative_int type (the same validator used for
--near-dup-threshold) for both p.add_argument calls for "--chunk-tokens" and
"--overlap-tokens" so negatives are rejected early; keep the same defaults and
metavar but ensure help text still notes that --overlap-tokens is ignored when
--chunk-tokens is not set and adjust any references to ingest_path validation
accordingly.
🤖 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/roadmap/phase-12-data-curation-maturity.md`:
- Around line 62-66: The fenced code block starting with ``` in the
docs/roadmap/phase-12-data-curation-maturity.md should include a language tag to
satisfy markdownlint MD040; update that opening backtick fence (the ``` block
that contains the table) to include a language such as "text" (e.g., change ```
to ```text) so the table example is explicitly tagged.

---

Nitpick comments:
In `@forgelm/cli.py`:
- Around line 313-333: The --chunk-tokens and --overlap-tokens argparse entries
currently accept negative values and defer validation to ingest_path; update the
parser to validate at parse-time by using the existing _non_negative_int type
(the same validator used for --near-dup-threshold) for both p.add_argument calls
for "--chunk-tokens" and "--overlap-tokens" so negatives are rejected early;
keep the same defaults and metavar but ensure help text still notes that
--overlap-tokens is ignored when --chunk-tokens is not set and adjust any
references to ingest_path validation accordingly.
🪄 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: 8cde5d5d-197b-44ca-a087-fd337454e0a8

📥 Commits

Reviewing files that changed from the base of the PR and between 5c805dc and 9a15af3.

📒 Files selected for processing (14)
  • CHANGELOG.md
  • docs/guides/ingestion.md
  • docs/roadmap-tr.md
  • docs/roadmap.md
  • docs/roadmap/phase-10-5-quickstart.md
  • docs/roadmap/phase-11-5-backlog.md
  • docs/roadmap/phase-12-data-curation-maturity.md
  • docs/roadmap/phase-14-pipeline-chains.md
  • docs/roadmap/releases.md
  • forgelm/cli.py
  • forgelm/data_audit.py
  • forgelm/ingestion.py
  • forgelm/wizard.py
  • tests/test_wizard_phase11_5.py
✅ Files skipped from review due to trivial changes (2)
  • docs/roadmap/phase-10-5-quickstart.md
  • docs/roadmap/phase-14-pipeline-chains.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/test_wizard_phase11_5.py
  • forgelm/data_audit.py

Comment thread docs/roadmap/phase-12-data-curation-maturity.md Outdated
…t validation)

* `docs/roadmap/phase-12-data-curation-maturity.md`: the table-syntax
  example fence under task #5 ("DOCX / Markdown table preservation")
  was a bare ``` block — markdownlint MD040 flag. Added the ``text``
  language tag so the markdown table illustration stays as plain text
  instead of being interpreted as a different language.

* `forgelm/cli.py`: ``--chunk-tokens`` and ``--overlap-tokens`` now
  use the existing ``_non_negative_int`` argparse type (previously
  ``type=int`` accepted negatives and deferred the check to
  ``ingest_path``'s validation, which surfaced as a confusing
  EXIT_CONFIG_ERROR much later in the pipeline). Negatives now exit
  at parse-time with the same standard "argument --X: value must be
  ≥ 0" error already used by ``--near-dup-threshold``. Help text
  updated to document the new bound and to keep the existing
  paragraph-mode note about ``--overlap-tokens`` being ignored.
  Note: ``--chunk-tokens 0`` still passes argparse (non-negative is
  the cheap CLI check) and is rejected by ``ingest_path`` itself
  ("chunk_tokens must be positive") — keeping that validator there
  avoids two parallel definitions of "what counts as valid".

Verification
------------
* ``forgelm ingest --chunk-tokens -10 ...`` → argparse error, exit 2.
* ``forgelm ingest --overlap-tokens -5 ...`` → argparse error, exit 2.
* ``ruff check`` clean.
* ``pytest tests/`` — 760 passed, 8 skipped (unchanged).
@sonarqubecloud

Copy link
Copy Markdown

@cemililik
cemililik merged commit ae82d7e into main Apr 28, 2026
10 checks passed
cemililik added a commit that referenced this pull request Apr 28, 2026
…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>
cemililik added a commit that referenced this pull request Apr 28, 2026
…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>
cemililik added a commit that referenced this pull request Apr 29, 2026
…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>
cemililik added a commit that referenced this pull request May 6, 2026
…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>
cemililik added a commit that referenced this pull request Jun 15, 2026
…view)

merge_pipeline_stage_config dumped stage model/lora/training/data/evaluation with exclude_none only; an override omitting staging_ttl_days re-materialised the default 7 as explicitly-set and falsely raised ConfigError against a canonical retention block. Match the root dump (exclude_unset=True). CodeRabbit #12.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant