Skip to content

release: development cycle — PR #50 bug-fix batch + agent skills + housekeeping - #51

Merged
cemililik merged 13 commits into
mainfrom
development
May 14, 2026
Merged

cemililik merged 13 commits into
mainfrom
development

Conversation

@cemililik

@cemililik cemililik commented May 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Bringing development (7 commits ahead of main, 25 files, +2141/-43) into the trunk. Three independent themes that all landed in one cycle and have already been merged + reviewed inside development:

  1. PR fix: review-batch P1-P3 — 8 code review findings (1 bundled commit) #50 review-fix batch (commits a4ed221 → 83ee102): 13 code-review findings, 8 confirmed bugs fixed across multiple rounds — see #50 for the full review trail.
  2. Agent skills mirror (commit 8d678fa): .agents/ tree + AGENTS.md providing agent-agnostic guidance (Codex / Cursor / generic AI surfaces), mirroring the existing .claude/skills/ + CLAUDE.md content.
  3. Post-merge housekeeping (commit fcbb926): _EXEMPT entries for the new agent docs and a stabilisation refactor for the test_unsloth_backend_raises_without_package order-sensitive flake.

Bug fixes from the review batch

Severity Issue Fix locator
P1 --benchmark-only evaluated fresh-init LoRA, not the trained checkpoint _no_train_modes.py::_run_benchmark_only now routes through inference.load_model() with PEFT auto-detection + fail-loud on malformed adapter_config.json
P1 _ensure_validation_split crashed on <2-row datasets data.py guard returns the dataset unchanged with a warning
P1 Trainer set eval_strategy="steps" with eval_dataset=None when validation absent trainer.py::_get_common_training_kwargs downshifts to eval_strategy="no" + disables load_best_model_at_end
P1 Live HF Hub call in test suite tests/test_compliance.py mocks _fingerprint_hf_metadata + _fingerprint_hf_revision
P2 Anonymous approver silently bypassed AuditLogger policy _approve.py::_resolve_approver_identity exits with EXIT_CONFIG_ERROR unless FORGELM_ALLOW_ANONYMOUS_OPERATOR=1
P2 judge_results.json / safety_results.json leaked raw prompts, responses, and judge reasoning Save layer strips PII by default; opt-in via JudgeConfig.include_eval_samples / SafetyConfig.include_eval_samples
P2 Raw model output in JSON-parse warning + reason _parse_judge_json logs only length and a fixed reason string
P2 12 numeric TrainingConfig fields had no bounds Pydantic ge / gt / le constraints added
P3 --data-audit breadcrumb caught only OSError Now catches (OSError, ConfigError) so anonymous-operator failures don't abort the deprecation log
Hardening PEFT tokenizer caveat — base tokenizer dropped fine-tuning special tokens inference.load_model prefers adapter/tokenizer_config.json when present

What's now in the public tree

  • .agents/skills/{add-config-field, add-test, add-trainer-feature, cut-release, review-pr, sync-bilingual-docs}/SKILL.md (6 skills)
  • AGENTS.md (CLAUDE.md mirror for non-Claude AI surfaces)
  • _PII_REDACT_FIELDS module-level constants in judge.py + safety.py
  • _CategoryTelemetry dataclass bundling Phase 9 telemetry args (Sonar S107 fix)
  • _resolve_benchmark_load_target + _pick_tokenizer_source helpers (Sonar S3776 fixes)
  • 21 new regression tests across 6 test modules

Verification

  • ruff format + ruff check: clean (107 / 168 files)
  • All 11 self-review gauntlet guards: green
    • forgelm --config config_template.yaml --dry-run
    • bilingual parity (46 doc pairs)
    • anchor resolution (271 markdown files)
    • CLI help consistency (447 invocations)
    • wizard defaults sync
    • no-analysis-refs (now passes with the new _EXEMPT entries)
    • no unguarded sys.modules pop
    • site/CHANGELOG version literal match (v0.6.0)
  • pytest tests/test_inference.py: 18/18 green (flake fixed in fcbb926)
  • New-behaviour test coverage: 21 tests across test_no_train_modes.py, test_data_edge_cases.py, test_trainer.py, test_human_approval_gate.py, test_judge_functions.py, test_safety_advanced.py, test_inference.py

Out of scope (deferred to separate PRs)

Test plan

  • CI cross-OS matrix (Linux / macOS / Windows × Python 3.10-13) green on the dev tip
  • Manual: forgelm --benchmark-only <peft-checkpoint> against a real PEFT adapter
  • Manual: forgelm --benchmark-only <merged-model> against a merged full-weight model
  • Manual: forgelm approve <run_id> under FORGELM_OPERATOR=ci-bot and again with neither env var
  • Eyeball: a judge-eval run with include_eval_samples=false (default) produces judge_results.json with no prompt/response/reason fields

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Config flags to opt into persisting raw safety/judge samples.
    • Benchmark-only mode detects and supports adapter-style checkpoints.
    • DNS-rebinding (SSRF) hardening for outbound HTTP (pinning + Host/SNI preservation).
  • Bug Fixes

    • Skip/avoid invalid auto-splits for very small datasets.
    • Tokenizer load now respects adapter-provided tokenizer when present.
  • Improvements

    • Stricter training hyperparameter validation.
    • Training args safely disable eval when no validation data.
    • Anonymous operator identity now requires explicit opt-in.
  • Documentation

    • Added project-wide agent guidance and multiple skill guides; updated changelog.
  • Tests

    • Expanded hermetic tests for HTTP pinning, redaction, trainer/no-train flows, judge/safety persistence.

Review Change Stack

cemililik and others added 7 commits May 14, 2026 02:30
…I, audit policy

8 findings from the comprehensive code review fixed in one bundled commit
(per the "fix scope policy" — confirmed bugs ship together, no leftovers).

P1 (production blockers)
- forgelm/cli/_no_train_modes.py: --benchmark-only now uses
  inference.load_model() with PEFT auto-detection (adapter_config.json
  → base_model_name_or_path + adapter). Previously wrapped a fresh
  untrained LoRA on top of the supplied checkpoint via the training
  loader, silently evaluating the wrong weights.
- forgelm/data.py: _ensure_validation_split() now skips the split for
  datasets with <2 rows instead of crashing on train_test_split.
- tests/test_compliance.py: test_hub_dataset now mocks the HF Hub
  helpers; the test no longer requires network connectivity.

P2 (compliance / privacy / config-validation)
- forgelm/cli/subcommands/_approve.py: _resolve_approver_identity()
  mirrors AuditLogger's anonymous-operator policy — refuses to silently
  return "anonymous" without FORGELM_ALLOW_ANONYMOUS_OPERATOR=1 opt-in
  (Article 12 record-keeping consistency).
- forgelm/judge.py, forgelm/safety.py: judge_results.json and
  safety_results.json no longer persist raw prompt/response snippets.
  In-memory JudgeResult / SafetyResult details are unchanged so
  webhooks and trainer logs still have full context.
- forgelm/judge.py: _parse_judge_json() logs only the response length
  and a fixed reason string on parse failure, not the raw model output.
- forgelm/config.py: 12 TrainingConfig numeric fields now carry
  ge/gt/le constraints (max_steps, num_train_epochs, batch sizes,
  learning_rate, warmup_ratio, weight_decay, eval/save/limit steps,
  early_stopping_patience, GRPO generations & completion length).

P3 (resilience)
- forgelm/cli/_dispatch.py: --data-audit deprecation breadcrumb now
  catches Exception (was OSError-only); ConfigError from AuditLogger
  in a regulated env should not abort the audit run.

Skipped (with rationale in the review prompt):
- P2-4 (FORGELM_GGUF_CONVERTER): already validated to .py + warning.
- P3-2 (FORGELM_AUDIT_TEST_WORKER_RAISES): documented test hook,
  fail-closed on exact split-name match.
- P3-3 (ingestion.py 2600+ lines): behavior-neutral split deferred to
  its own PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… PEFT tokenizer, tests

Addresses the three blocking review findings on PR #50 plus the two
non-blocking ones, and lands regression tests for every new behaviour
the previous commit introduced.

Blocking (3)

- P1-2 downstream: forgelm/trainer.py now downshifts eval_strategy
  to "no" and disables load_best_model_at_end / metric_for_best_model /
  greater_is_better when no validation split exists. Previously the
  builder always emitted eval_strategy="steps" while _build_trainer
  passed eval_dataset=None — HF Trainer raises ValueError on that
  combination. Test: tests/test_trainer.py::TestTrainingArgsValidationGuard
  (validation-present keeps "steps"; absent or empty downshifts to "no").

- P2-2 reason redaction: forgelm/judge.py and forgelm/safety.py now
  strip prompt + response + (for judge) the natural-language reason
  field from on-disk artifacts unless the opt-in flag is set. New
  Pydantic fields JudgeConfig.include_eval_samples and
  SafetyConfig.include_eval_samples default to False (privacy by
  default for GDPR / EU AI Act Art. 10). Trainer + run_*_evaluation
  thread the flag through. In-memory JudgeResult / SafetyResult
  details unchanged. Tests: TestJudgeResultRedaction +
  TestSafetyResultRedaction (default redacts, opt-in keeps all fields).

- Tests for the previous batch's behaviour changes:
  - P1-1 benchmark loader: tests/test_no_train_modes.py covers
    PEFT detection (adapter_config.json present → load_model(base,
    adapter=path); absent → load_model(path)) and asserts
    get_model_and_tokenizer is NOT called.
  - P1-2 data: tests/test_data_edge_cases.py::TestEnsureValidationSplit
    asserts single-row / empty datasets skip the split with a warning.
  - P2-1 approver identity: tests/test_human_approval_gate.py::
    TestApproverIdentityPolicy covers FORGELM_OPERATOR priority,
    getpass fallback, EXIT_CONFIG_ERROR without opt-in, and
    anonymous@<hostname> with opt-in.
  - P2-3 warning log sanitisation: TestJudgeResultRedaction's
    test_parse_judge_json_warning_strips_raw_text asserts the
    JSON-parse warning + reason no longer echo the raw model output.

Non-blocking (2)

- P2 PEFT tokenizer caveat: forgelm/inference.py now prefers the
  adapter directory's tokenizer when adapter/tokenizer_config.json
  exists — fine-tuning may add special tokens or a custom chat
  template that the base tokenizer would silently drop. Falls back
  to base path otherwise. Tests:
  test_tokenizer_loaded_from_adapter_when_tokenizer_config_present
  and test_tokenizer_falls_back_to_base_when_adapter_has_no_tokenizer_config
  in tests/test_inference.py.

- P3 stale docstring: forgelm/cli/subcommands/_approve.py's
  _resolve_approver_identity docstring now describes the opt-in /
  exit policy instead of the obsolete "anonymous if both fail" line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…umentation sync

- Introduced `cut-release` skill for managing ForgeLM releases, including version bumping, changelog finalization, tagging, and publishing.
- Added `review-pr` skill to facilitate thorough code reviews for pull requests, ensuring adherence to coding standards and best practices.
- Created `sync-bilingual-docs` skill to maintain structural alignment between English and Turkish documentation, ensuring consistency across bilingual resources.
- Added `AGENTS.md` to provide guidance for AI agents working on ForgeLM, outlining project principles, skills, and repository structure.
…uard, hoist redact constants

Addresses the four bot-review findings on PR #50 plus the two
cosmetic tradeoffs left from the previous review-response round.

Bot findings (gemini-code-assist + sourcery-ai)
- forgelm/cli/_no_train_modes.py:38 (gemini): explicit encoding="utf-8"
  on adapter_config.json read.
- forgelm/cli/_no_train_modes.py:40 (gemini + sourcery overlap): when
  adapter_config.json is missing base_model_name_or_path or is malformed
  JSON, exit with EXIT_CONFIG_ERROR and a clear log message instead of
  silently falling back to model_path (which would crash deep inside
  PeftModel.from_pretrained with a confusing "config.json not found").
  Two new regression tests cover this (corrupt JSON + missing field).
- forgelm/cli/subcommands/_approve.py:96 (gemini): wrap socket.gethostname()
  in try/except OSError; restricted containers / sandboxes can fail
  hostname resolution and the whole point of FORGELM_ALLOW_ANONYMOUS_OPERATOR=1
  is to keep running there. Falls back to "unknown-host" narrowly.
- forgelm/judge.py + forgelm/safety.py (sourcery): hoist the redaction
  field set to module-level frozenset constants (_PII_REDACT_FIELDS).
  Documents the GDPR / EU AI Act Art. 10 motivation in-line and avoids
  reallocating the set on every save call.

Cosmetic tradeoffs deferred from PR #50 review-response round
- forgelm/cli/_no_train_modes.py: flatten `import json as _json` /
  `import os as _os` to the module-level `json` / `os` imports. The
  underscore-prefix variants added isolation noise without isolation
  benefit; behaviour is identical.
- forgelm/cli/_dispatch.py: narrow `except Exception` to
  `(OSError, ConfigError)` on the --data-audit deprecation breadcrumb.
  Keeps the best-effort contract without silently swallowing unrelated
  runtime errors.

Skipped (with rationale in the PR comment)
- sourcery's TrainingConfig 0/None disable semantics: HF's
  TrainingArguments uses None default (never 0) for save_total_limit;
  ge=1 is consistent with the schema's int typing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mells

Three SonarQube findings introduced by the review-response rounds —
each fixed by extracting helpers, no behaviour change.

- forgelm/cli/_no_train_modes.py: extract _resolve_benchmark_load_target()
  helper that owns PEFT detection + adapter_config.json read +
  base_model_name_or_path validation. _run_benchmark_only now has a
  single load_model() call, dropping cognitive complexity from 19 to
  under the 15 limit. (S3776, Critical)

- forgelm/inference.py: extract _pick_tokenizer_source() helper for
  the "prefer adapter tokenizer when tokenizer_config.json is present"
  decision. load_model cognitive complexity 17 -> under 15. (S3776,
  Critical)

- forgelm/safety.py: bundle the three Phase 9 category-telemetry
  arguments (track_categories, category_dist, severity_dist) into a
  new _CategoryTelemetry dataclass.  _save_safety_results parameter
  count drops from 14 to 12 (the 13-parameter limit) without forcing
  callers to flatten on the call site. (S107, Major)

Existing test_safety_advanced.py::TestSafetyResultRedaction tests
updated to use the new dataclass; tests/test_no_train_modes.py PEFT
detection tests still pass against the refactored shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix: review-batch P1-P3 — 8 code review findings (1 bundled commit)
…st stabiliser

Two cleanup items left over from the PR #50 merge that should land
before the next phase opens:

1) tools/check_no_analysis_refs.py: register the agent-agnostic
   mirror files (AGENTS.md and .agents/skills/sync-bilingual-docs/
   SKILL.md) in _EXEMPT.  They are functional path filters / agent-
   guidance documents that legitimately name the gitignored
   docs/marketing/ + docs/analysis/ directories, mirroring the
   existing CLAUDE.md and .claude/skills/sync-bilingual-docs/SKILL.md
   exemptions.  Without this, the local 11-guard self-review
   gauntlet fails on development after PR #50 introduced these
   files in commit 8d678fa.

2) tests/test_inference.py::test_unsloth_backend_raises_without_package:
   refactored to call _load_unsloth directly with a sys.modules['unsloth']
   = None monkeypatch, instead of going through load_model() with
   nested MagicMock stubs for torch / transformers.  The previous
   pattern was order-sensitive: after the tokenizer-from-adapter
   tests populated sys.modules with stubs and torn them down, a
   follow-up real `import transformers` would hit ValueError on
   torch.__spec__ or AttributeError on torch.__version__ via the
   safetensors transitive import.  Direct call exercises the same
   failure surface (`from unsloth import ...` raises ImportError)
   without the dispatcher's import zinciri.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@codacy-production

codacy-production Bot commented May 14, 2026 •

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 1 high · 14 minor

Alerts:
⚠ 15 issues (≤ 0 issues of at least minor severity)

Results:
15 new issues

Category Results
Security 1 high
CodeStyle 14 minor

View in Codacy

🟢 Metrics 107 complexity · 19 duplication

Metric Results
Complexity 107
Duplication 19

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.

@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, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented May 14, 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 29 minutes and 44 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ 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: 6d3075dd-8339-4da5-9bcb-728ce4f518c5

📥 Commits

Reviewing files that changed from the base of the PR and between 188fc8a and c83e0ad.

📒 Files selected for processing (2)
  • AGENTS.md
  • CLAUDE.md
📝 Walkthrough

Walkthrough

Adds AGENTS.md and multiple skill docs; implements DNS-rebinding-safe HTTP pinning and Host/SNI preservation; introduces opt-in flags to avoid persisting raw safety/judge samples (default: redact); tightens training config validation; adjusts inference/CLI flows; and adds wide test coverage aligning to these behaviors.

Changes

Agent workflow standards, documentation, and PII redaction for evaluations

Layer / File(s) Summary
Core AGENTS.md guide and project standards
AGENTS.md
Foundational AGENTS.md document codifies non-negotiable engineering principles, repository structure, common pitfalls, mandatory self-review workflow, and user communication etiquette.
Reusable skill documents
.agents/skills/*/SKILL.md
Adds multiple skill guides for adding YAML config fields, writing tests with project conventions, trainer-level feature work, cutting releases, reviewing PRs, and syncing bilingual docs.
SSRF-safe HTTP core
forgelm/_http.py, pyproject.toml
Adds _resolve_safe_destination, _build_pinned_url, _pinned_session, shared _is_blocked_ip, Host header handling and secret-masking updates; pins outbound requests to resolved public IP literals and preserves hostname via Host header and HostHeaderSSLAdapter; requires requests-toolbelt.
HTTP DNS-rebinding tests
tests/test_http_dns_rebinding.py
Comprehensive tests covering resolver behavior, single-resolve pinning, Host header formatting (ports, IPv6, userinfo), SSL adapter mounting on HTTPS, and allow_private bypass.
Judge and safety PII redaction
forgelm/judge.py, forgelm/safety.py, tests/test_judge_functions.py, tests/test_safety_advanced.py
Adds _PII_REDACT_FIELDS and opt-in include_samples/include_eval_samples flags; _parse_judge_json no longer echoes invalid model output; _save_*_results now redact or preserve raw prompt/response/reason based on opt-in; tests validate default redaction and opt-in preservation and that parse errors don’t leak content.
Config and trainer wiring
forgelm/config.py, forgelm/trainer.py, tests/test_trainer.py
Tightens Pydantic bounds on training hyperparameters; adds include_eval_samples to SafetyConfig and JudgeConfig; trainer disables eval/best-model selection when validation missing and forwards include_samples flags to evaluations; tests validate eval-strategy downshift.
Inference and data handling
forgelm/inference.py, forgelm/data.py, tests/test_inference.py, tests/test_data_edge_cases.py
Adapter-aware tokenizer selection prefers adapter tokenizer_config.json when present; _ensure_validation_split skips splitting tiny datasets (<2) with a warning; tests added for tokenizer source and validation-split edge cases.
CLI benchmark and approver behavior
forgelm/cli/_no_train_modes.py, forgelm/cli/subcommands/_approve.py, forgelm/cli/_dispatch.py, tests/test_no_train_modes.py, tests/test_human_approval_gate.py
Adds _resolve_benchmark_load_target to detect PEFT adapters and forward adapter path to load_model; approver identity resolution now errors when unknown unless FORGELM_ALLOW_ANONYMOUS_OPERATOR=1, which returns anonymous@<hostname>; legacy audit logging catch extended to include ConfigError; tests added.
Webhook and synthetic test hermetic changes
tests/test_webhook.py, tests/test_synthetic.py
Adds autouse fixtures to stub _resolve_safe_destination, switches many tests to patch requests.Session.post (matching safe_post usage), and updates assertions to validate Host header behavior where applicable.
Tooling updates and test isolation
tools/check_no_analysis_refs.py, tests/test_compliance.py
Includes .agents/**/*.md in analysis scan and adds exemptions for AGENTS.md/bilingual-skill; isolates dataset fingerprint test internals with mocking.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant _resolve_safe_destination
  participant Session as requests.Session
  participant HostAdapter as HostHeaderSSLAdapter

  Caller->>_resolve_safe_destination: resolve(hostname)
  _resolve_safe_destination->>Caller: public_ip_literal
  Caller->>Session: send request to http(s)://<public_ip_literal>/... with Host header=original hostname
  Session->>HostAdapter: use SNI/Host for TLS (HTTPS)
  Session->>Caller: response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • cemililik/ForgeLM#19: Related changes touching outbound-HTTP SSRF hardening and webhook/judge test alignments.
  • cemililik/ForgeLM#52: Overlapping DNS-rebinding SSRF hardening that added _resolve_safe_destination, pinned-URL + Host behavior, and corresponding tests/dependency updates.
  • cemililik/ForgeLM#12: Adjacent work on legacy audit/dispatch behavior related to the _dispatch.py audit-path adjustments.

Poem

"A rabbit hopped through docs and tests today,
Pinning hosts so mischief can't get away.
Samples hidden unless you give a nod,
Trainers check splits and adapters applaud.
Hooray for skills—let the changelog say!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.72% 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 clearly summarizes the main theme: merging a development cycle with bug fixes, agent skills, and housekeeping. It is specific and directly related to the changeset.
Description check ✅ Passed The description is comprehensive and well-structured, covering summary, bug fixes table, public items, verification, scope limitations, and test plan. It aligns well with the template structure and provides substantial detail about changes.
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.

@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 AGENTS.md and a comprehensive set of skill definitions to standardize AI agent interactions with the repository. Significant functional updates include the implementation of PII redaction for safety and judge evaluation results (defaulting to privacy-first), stricter operator identity resolution for audit logs, and enhanced robustness for benchmark-only runs and small datasets. Review feedback correctly identified a violation of the English-only comment standard in forgelm/judge.py, where a Turkish term was used.

Comment thread forgelm/judge.py

# GDPR / EU AI Act Art. 10 — fields stripped from on-disk judge_results.json
# unless the operator opts in via JudgeConfig.include_eval_samples=True.
# ``reason`` is included because the judge's natural-language gerekçesi may

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 comment contains the Turkish word "gerekçesi" (reasoning/justification). According to the project's engineering standards (as stated in AGENTS.md), code comments must be in English only to maintain codebase consistency.

Suggested change
# ``reason`` is included because the judge's natural-language gerekçesi may
# ``reason`` is included because the judge's natural-language reasoning may
References
  1. Single-language comment drift — mixing Turkish + Spanish + English in code comments. English only in code. (link)

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
tests/test_trainer.py (1)

158-213: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add the torch-availability skip guard to this new test class.

TestTrainingArgsValidationGuard exercises _get_common_training_kwargs(), which imports torch at line 285 of forgelm/trainer.py and uses it throughout (lines 318–322). Unlike other torch-dependent test classes in this file, this one is not guarded, so it will fail in no-torch environments.

Suggested patch
+@pytest.mark.skipif(not torch_available, reason="torch not installed")
 class TestTrainingArgsValidationGuard:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_trainer.py` around lines 158 - 213, The new
TestTrainingArgsValidationGuard class exercises
ForgeTrainer._get_common_training_kwargs which imports and uses torch, so add a
torch-availability skip guard to the class (e.g., apply
pytest.importorskip("torch") at top of the class file or decorate the class with
pytest.mark.skipif(importlib.util.find_spec("torch") is None, reason="requires
torch")); ensure the guard is placed so the entire
TestTrainingArgsValidationGuard class is skipped when torch is not installed to
match other torch-dependent test classes in this file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@AGENTS.md`:
- Around line 115-116: Remove the gitignored docs/marketing/* link(s) in
AGENTS.md (the "Reinforced in [...]" markdown link) and replace them with
public, versioned document links such as docs/roadmap.md or
docs/product_strategy.md; for any internal-only guidance keep the text but do
not linkify it (mark it plain text like "internal-only guidance" or similar) and
apply the same replacement to the other occurrences of docs/marketing/* in this
file.

In `@forgelm/cli/_no_train_modes.py`:
- Around line 47-49: adapter_meta may not be a mapping (json.load can return a
list) so calling adapter_meta.get() will raise AttributeError; update the code
before accessing base_path to validate adapter_meta is a dict/Mapping (e.g.,
isinstance(adapter_meta, dict) or isinstance(adapter_meta, Mapping)), and if it
isn't, call logger.error with a clear message and exit with EXIT_CONFIG_ERROR
(same behavior as the existing error path), then safely read base_path from
adapter_meta.get("base_model_name_or_path").

In `@forgelm/judge.py`:
- Around line 45-49: The code comment above the _PII_REDACT_FIELDS definition
contains a non-English word ("gerekçesi"); update the comment to use
English-only wording (e.g., replace "gerekçesi" with "reasoning") so it complies
with the repository's English-only comment rule and still explains that "reason"
may include verbatim PII from eval prompts/responses (refer to
_PII_REDACT_FIELDS and JudgeConfig.include_eval_samples).

---

Outside diff comments:
In `@tests/test_trainer.py`:
- Around line 158-213: The new TestTrainingArgsValidationGuard class exercises
ForgeTrainer._get_common_training_kwargs which imports and uses torch, so add a
torch-availability skip guard to the class (e.g., apply
pytest.importorskip("torch") at top of the class file or decorate the class with
pytest.mark.skipif(importlib.util.find_spec("torch") is None, reason="requires
torch")); ensure the guard is placed so the entire
TestTrainingArgsValidationGuard class is skipped when torch is not installed to
match other torch-dependent test classes in this file.
🪄 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: a76bc69d-8177-44f9-9b00-041ff4b48002

📥 Commits

Reviewing files that changed from the base of the PR and between 75009b9 and fcbb926.

📒 Files selected for processing (25)
  • .agents/skills/add-config-field/SKILL.md
  • .agents/skills/add-test/SKILL.md
  • .agents/skills/add-trainer-feature/SKILL.md
  • .agents/skills/cut-release/SKILL.md
  • .agents/skills/review-pr/SKILL.md
  • .agents/skills/sync-bilingual-docs/SKILL.md
  • AGENTS.md
  • forgelm/cli/_dispatch.py
  • forgelm/cli/_no_train_modes.py
  • forgelm/cli/subcommands/_approve.py
  • forgelm/config.py
  • forgelm/data.py
  • forgelm/inference.py
  • forgelm/judge.py
  • forgelm/safety.py
  • forgelm/trainer.py
  • tests/test_compliance.py
  • tests/test_data_edge_cases.py
  • tests/test_human_approval_gate.py
  • tests/test_inference.py
  • tests/test_judge_functions.py
  • tests/test_no_train_modes.py
  • tests/test_safety_advanced.py
  • tests/test_trainer.py
  • tools/check_no_analysis_refs.py

Comment thread AGENTS.md
Comment on lines +115 to +116
Reinforced in [docs/marketing/strategy/05-yapmayacaklarimiz.md](docs/marketing/strategy/05-yapmayacaklarimiz.md). Do not propose or implement:

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 | ⚡ Quick win

Remove references to gitignored docs/marketing/* from this public guide.

These links point to local-only paths and conflict with the project’s own “don’t reference working-memory directories from public docs” rule. Replace them with public, versioned docs (for example docs/roadmap.md / docs/product_strategy.md) and keep the “internal-only” guidance non-linkified.

Based on learnings: "Never reference docs/analysis/ or docs/marketing/ contents from production code, public docs, CHANGELOG entries, commit messages, or PR descriptions."

Also applies to: 199-201

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` around lines 115 - 116, Remove the gitignored docs/marketing/*
link(s) in AGENTS.md (the "Reinforced in [...]" markdown link) and replace them
with public, versioned document links such as docs/roadmap.md or
docs/product_strategy.md; for any internal-only guidance keep the text but do
not linkify it (mark it plain text like "internal-only guidance" or similar) and
apply the same replacement to the other occurrences of docs/marketing/* in this
file.

Comment on lines +47 to +49
base_path = adapter_meta.get("base_model_name_or_path")
if not base_path:
logger.error(

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 | ⚡ Quick win

Handle non-object adapter metadata before accessing .get()

Line 47 assumes json.load() returns a mapping. If adapter_config.json is valid JSON but not an object (e.g., []), this raises AttributeError and bypasses the intended EXIT_CONFIG_ERROR path.

💡 Suggested fix
     try:
         with open(adapter_cfg_path, encoding="utf-8") as f:
             adapter_meta = json.load(f)
     except (OSError, json.JSONDecodeError) as e:
         logger.error("Failed to read PEFT adapter_config.json at %s: %s", adapter_cfg_path, e)
         sys.exit(EXIT_CONFIG_ERROR)

+    if not isinstance(adapter_meta, dict):
+        logger.error(
+            "PEFT checkpoint at %s has invalid adapter_config.json root type (%s); expected a JSON object.",
+            model_path,
+            type(adapter_meta).__name__,
+        )
+        sys.exit(EXIT_CONFIG_ERROR)
+
     base_path = adapter_meta.get("base_model_name_or_path")
-    if not base_path:
+    if not isinstance(base_path, str) or not base_path.strip():
         logger.error(
             "PEFT checkpoint at %s is missing 'base_model_name_or_path' in adapter_config.json — "
             "cannot reconstruct the base model + adapter combination.",
             model_path,
         )
         sys.exit(EXIT_CONFIG_ERROR)

-    return base_path, model_path
+    return base_path.strip(), model_path
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@forgelm/cli/_no_train_modes.py` around lines 47 - 49, adapter_meta may not be
a mapping (json.load can return a list) so calling adapter_meta.get() will raise
AttributeError; update the code before accessing base_path to validate
adapter_meta is a dict/Mapping (e.g., isinstance(adapter_meta, dict) or
isinstance(adapter_meta, Mapping)), and if it isn't, call logger.error with a
clear message and exit with EXIT_CONFIG_ERROR (same behavior as the existing
error path), then safely read base_path from
adapter_meta.get("base_model_name_or_path").

Comment thread forgelm/judge.py
Comment on lines +45 to +49
# GDPR / EU AI Act Art. 10 — fields stripped from on-disk judge_results.json
# unless the operator opts in via JudgeConfig.include_eval_samples=True.
# ``reason`` is included because the judge's natural-language gerekçesi may
# quote PII from the eval prompts/responses verbatim.
_PII_REDACT_FIELDS: frozenset[str] = frozenset({"prompt", "response", "reason"})

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 | 🟡 Minor | ⚡ Quick win

Use English-only wording in this code comment.

Line 47 includes gerekçesi, which breaks the repository’s English-only comment rule. Replace it with an English term (e.g., “reasoning”).

As per coding guidelines, **/*.{py,js,ts,tsx}: "Keep code comments in English only; do not mix Turkish, Spanish, or other languages in code".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@forgelm/judge.py` around lines 45 - 49, The code comment above the
_PII_REDACT_FIELDS definition contains a non-English word ("gerekçesi"); update
the comment to use English-only wording (e.g., replace "gerekçesi" with
"reasoning") so it complies with the repository's English-only comment rule and
still explains that "reason" may include verbatim PII from eval
prompts/responses (refer to _PII_REDACT_FIELDS and
JudgeConfig.include_eval_samples).

cemililik and others added 5 commits May 14, 2026 04:42
Pre-fix flow:
  1. _is_private_destination(host) calls socket.getaddrinfo → public IP
  2. requests.post(host_url, ...) → connect-time DNS lookup
  3. If the attacker's DNS server flipped the answer between steps 1
     and 2 (TTL=0 + rebinding), the connect lands on 127.0.0.1 /
     169.254.169.254 / RFC1918 — payload + bearer token leak.

Hardening:
  - New helper `_resolve_safe_destination(host)` resolves exactly once
    and returns either a public IP literal or a block reason.
  - safe_post / safe_get rebuild the outbound URL with that IP literal
    so requests never re-resolves.
  - Original hostname is propagated via the `Host` header (mandatory
    for virtual-hosted endpoints like Slack / Teams / Discord) and via
    SNI through requests_toolbelt's HostHeaderSSLAdapter, mounted on a
    per-call Session for the https:// scheme.
  - allow_private=True callers keep the legacy requests.post flow so
    in-cluster / split-horizon DNS still works for operator-blessed
    internal destinations.
  - IPv6 IP literals are bracketed per RFC 3986 ([2001:db8::1]:443).

Threat model: attacker needs control of the DNS server for the
operator-configured webhook domain (or a downstream resolver they can
poison). allow_private_destinations=false default already forced the
operator to opt in to private destinations, so DNS rebinding was the
last remaining bypass; this fix closes it for the default-secure path.

Dependency: requests-toolbelt>=1.0.0,<2.0.0 added as a hard dep.
Small (~30KB), same ecosystem as requests, security-critical so an
opt-in extra would weaken the default-secure posture.

Tests:
  - tests/test_http_dns_rebinding.py (new, 13 tests):
    - _resolve_safe_destination helper coverage (6 unit tests:
      public hostname, mixed-result blocking, DNS failure, empty
      host, public IP literal, private IP literal).
    - DNS rebinding behavioural test asserting getaddrinfo is called
      exactly once and the URL is rebuilt with the public IP.
    - Host header preservation with auth header round-trip.
    - HostHeaderSSLAdapter mounted on https:// scheme.
    - HTTP path does not need the SSL adapter.
    - allow_private=True bypass exercises the legacy path.
    - IPv6 literal bracketed in the rebuilt URL.
    - safe_get mirrors safe_post for the same hardening.
  - Updated existing webhook / judge / synthetic tests for the
    Session.post mock pattern (16 occurrences across 3 files).
    Two tests (test_url_env_resolution, test_custom_api_base,
    test_api_base_reaches_http_call) reframed to assert on the
    `Host` header rather than the URL because the URL now carries
    the IP literal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…4 tests

Addresses all actionable findings from PR #52's review bots (gemini,
sourcery) and the SonarQube quality-gate fail (1 hotspot + duplication
+ maintainability).

Real bugs (gemini "high" + sourcery "security")

- forgelm/_http.py safe_post + safe_get: the auto-set ``Host`` header
  now uses ``parsed.netloc.rsplit("@", 1)[-1]`` instead of
  ``parsed.hostname``.  Pre-fix the header silently dropped the port
  (``Host: example.com`` for ``https://example.com:8443/``) and the
  bracket-wrap for IPv6 literals, violating RFC 7230 § 5.4 — virtual-
  hosted endpoints that switch on the authority-form would have
  routed wrong.  Userinfo is stripped (``user:pass@host`` → ``host``)
  so credentials never echo into the request header.

- forgelm/_http.py safe_post + safe_get exception handlers: log
  masking now operates on the outbound ``request_headers`` (which
  includes the auto-set ``Host`` and any caller secrets) rather than
  the raw ``headers`` parameter (which may be ``None`` or stale).
  Pre-fix, if the caller passed ``headers=None``, a secret added via
  the auto-Host path would not have been masked from the warning log
  on transport failure.

Refactor for maintainability + Sonar duplication

- Extracted ``_is_blocked_ip(ip)`` shared helper that owns the
  RFC1918 / loopback / link-local / IETF-reserved / multicast policy.
  Both ``_is_private_destination`` (legacy yes/no) and
  ``_resolve_safe_destination`` (DNS-rebinding-safe resolver) now
  delegate, so the policy cannot drift between call sites.  Closes
  Sonar's 4.9% new-code duplication finding on this PR.

Test gaps (sourcery)

- ``TestRfc7230HostHeader`` (4 new tests): non-standard port stays in
  Host, IPv6 literal stays bracketed, userinfo stripped, explicit
  caller-supplied Host header takes precedence over the auto-set value.

- ``TestNoPublicIpResolvedBranch`` (1 new test): covers the
  ``"no public IP resolved"`` branch of ``_resolve_safe_destination``
  by mocking ``getaddrinfo`` to return only malformed IP strings.

- ``TestSafeGetPinning.test_safe_get_allow_private_bypasses_pinning``:
  mirrors the existing safe_post allow_private bypass test for the
  read-side helper.

Sonar S5332 false-positive

- ``tests/test_http_dns_rebinding.py::test_http_session_does_not_mount_ssl_adapter``:
  the ``http://`` literal is a fixture for the SSL-adapter dispatch
  branch — no outbound call, no insecure egress.  ``# NOSONAR python:S5332``
  with justification on the two flagged lines.

Skipped (with rationale)

- sourcery overall #2 ("distinguish IP-literal-private vs mixed-result
  reasons"): minor message-specificity, deferred to a separate PR to
  keep this review-response focused.
- sourcery overall #3 ("per-process Session cache to amortise mount
  cost"): premature optimisation — webhook / judge calls are low-
  frequency, the per-call ``Session()`` is microsecond-cheap, and a
  shared session would complicate the SNI-per-host model.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eDict

Issue #14 review feedback (codex): the previous round used a plain
``dict(headers or {})`` to materialise the outbound header set, then
``setdefault("Host", ...)`` to fall back to the auto-derived value.
Python's ``dict`` is case-sensitive, so a caller passing
``headers={"host": "operator-supplied.example.com"}`` (lowercase) would
trigger:

  request_headers = {"host": "operator-supplied.example.com"}
  request_headers.setdefault("Host", "<auto>")
  # → {"host": "operator-supplied.example.com", "Host": "<auto>"}

Two Host entries on the wire — invalid per RFC 7230 § 5.4 and a
silent break of the "caller override wins" contract this PR's
``test_explicit_host_header_not_overridden`` tried to lock down.

Fix: switch to ``requests.structures.CaseInsensitiveDict``, which
collapses every casing of the same header to one logical slot.
``"Host" not in request_headers`` (replacing the ``setdefault``) is
also case-insensitive, so an explicit caller-supplied
``host`` / ``HOST`` / ``Host`` all suppress the auto-Host injection
correctly.  Applied to both ``safe_post`` and ``safe_get`` (the
identical pattern).  ``_mask_secrets_in_text`` annotation widened to
``Optional[MutableMapping[str, str]]`` so the new outbound type
flows through the masking helper without a mypy false-positive.

Test: new ``test_caller_host_header_with_different_casing_does_not_duplicate``
in ``TestRfc7230HostHeader`` passes a lowercase ``host`` header and
asserts (a) exactly one Host entry on the wire (case-insensitive
count), and (b) the caller's value wins.  20 / 20 DNS-rebinding
tests + 170 / 170 HTTP-related tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
security: close DNS-rebinding TOCTOU in SSRF guard (closes #14)
Closes the four blocking findings + one P3 from the PR #51 review.

Blocking #1 — full suite kırmızı (26 fail in offline/sandbox CI):
- forgelm/_http.py::safe_post + safe_get: moved cheap local-policy
  checks (timeout floor, method allowlist) BEFORE the SSRF DNS
  resolve.  A misconfigured caller (timeout=0, sub-floor value,
  unsupported method) is now rejected without a DNS round-trip — and
  unit tests for those branches no longer accidentally depend on
  ``socket.getaddrinfo`` reaching the network.
- tests/test_webhook.py, tests/test_judge_functions.py,
  tests/test_synthetic.py: module-level autouse fixture
  ``_stub_ssrf_resolver`` makes ``forgelm._http._resolve_safe_destination``
  hermetic.  IP literals still flow through the real ``_is_blocked_ip``
  policy (so the SSRF-block parametric tests keep their teeth) and
  ``localhost`` is canonically treated as loopback; every other
  hostname resolves to the public sentinel ``8.8.8.8``.  Dedicated
  resolver coverage in tests/test_http_dns_rebinding.py is untouched
  because it lives in its own module and patches the same seam at
  finer granularity.

Blocking #2 — AGENTS.md skill links pointed at .Codex/skills/, a
  directory that does not exist.  Rewrote all 8 links to .agents/skills/
  so the agent-mirror tema's primary use path actually works.

Blocking #3 — tools/check_no_analysis_refs.py added .agents/**/*.md
  to ``_PUBLIC_GLOBS``.  Previously the .agents/ tree was never scanned,
  so the ``_EXEMPT`` entry added in commit fcbb926 was silently
  ineffective and the guard's green result was misleading.  The exempt
  entry is still required (and now actually exercised) because
  .agents/skills/sync-bilingual-docs/SKILL.md legitimately names
  the gitignored docs/marketing/ and docs/analysis/ directories.

P3 — forgelm/cli/_no_train_modes.py::_resolve_benchmark_load_target
  return type: PEP 604 ``tuple[str, str | None]`` → ``Tuple[str, Optional[str]]``
  to match the project's existing typing style (consistent with
  every other typed helper in this module).

Verification
- ruff format + check: clean (192 / 192 files)
- 11 / 11 self-review gauntlet guards: green (forgelm --dry-run,
  bilingual parity 46 pairs, anchor resolution 271 md, CLI help
  consistency 447 invocations, wizard defaults, no-analysis-refs
  with new glob, no unguarded sys.modules pop, site version).
- HTTP-touching modules (webhook + judge + synthetic + dns_rebinding):
  96 / 96 green.
- Full pytest tests/ (--ignore=test_phase12_review_fixes.py, the
  documented perf-flake that the previous review marked non-blocking):
  **1779 passed, 13 skipped, 0 failed in 954.74s (~16 min).**

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cemililik

Copy link
Copy Markdown
Collaborator Author

Review response — 188fc8a (pushed to development, auto-included in this PR)

All four blocking findings + the P3 fixed. Full suite is green now.

# Bulgu Fix
1 Blocking — DNS resolve cheap-validation öncesi koşuyor; offline CI'da 26 test fail forgelm/_http.py: timeout check (+ safe_get method check) artık SSRF resolve'tan önce. tests/test_webhook.py + test_judge_functions.py + test_synthetic.py üç modüle module-level autouse fixture _stub_ssrf_resolver eklendi: IP literal'leri gerçek _is_blocked_ip policy'sine yönlendirir, hostname'leri 8.8.8.8 public sentinel'e stub'lar, localhost'u loopback olarak özel-case'ler. test_http_dns_rebinding.py dokunulmadı (kendi finer-granularity mock'larıyla yaşıyor).
2 Blocking — AGENTS.md skill linkleri .Codex/skills/ (var olmayan dizin) 8 link .agents/skills/'e güncellendi (sed transform + 8/8 doğrulandı).
3 Blocking — check_no_analysis_refs.py .agents/ dizinini taramıyordu (false-green) _PUBLIC_GLOBS'a .agents/**/*.md eklendi. Mevcut _EXEMPT entry'si (.agents/skills/sync-bilingual-docs/SKILL.md) artık gerçekten devreye giriyor.
P3 — tuple[str, str | None] PEP 604 syntax Tuple[str, Optional[str]] + uygun from typing import (proje stiliyle hizalı).

Verification

  • ruff format + check: clean (192 / 192 files)
  • 11 / 11 self-review gauntlet guards: all green
  • HTTP-touching modules (test_webhook, test_judge_functions, test_synthetic, test_http_dns_rebinding): 96 / 96 ✓
  • Full pytest tests/ (with --ignore=test_phase12_review_fixes.py — the perf-flake the previous review explicitly marked non-blocking): 1779 passed, 13 skipped, 0 failed in 954.74s (~16 min)

Onaylanmış noktalar

  • requests-toolbelt hard dep — kabul ✓
  • P2-5 alignment beta sınırsız — kabul ✓
  • CHANGELOG [Unreleased]::Security v0.6.1 release'e girer — kabul ✓
  • Manual test plan'daki 2 şart (gerçek Slack POST + allow_private=True legacy path) merge öncesi koşacak — sende ✓

Nitpick (blocking değil, follow-up'a uygun)

_http.py safe_get'in http:// rejection mesajındaki string-split trick'i ile safe_post'taki # NOSONAR yaklaşımının tutarsızlığı — reviewer haklı, ayrı bir cosmetic PR'a layık.

Trunk-merge gate açık.

@cemililik

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 14, 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: 2

♻️ Duplicate comments (1)
AGENTS.md (1)

115-116: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove gitignored working-memory path references from this public doc.

Line 115 and Line 201 still reference docs/marketing/*, and the same section also names docs/analysis/*. This keeps a prohibited public-doc citation pattern alive and creates documentation-policy drift.

Suggested minimal rewrite
-Reinforced in [docs/marketing/strategy/05-yapmayacaklarimiz.md](docs/marketing/strategy/05-yapmayacaklarimiz.md). Do not propose or implement:
+Reinforced in internal-only strategy guidance. Do not propose or implement:
@@
-- The `docs/marketing/` directory is gitignored (internal strategy). Content there is real; treat it as a source of truth for direction but don't reference it in public-facing code or docs.
-- The `docs/analysis/` directory is gitignored research / audit working memory (PR-cycle review notes, external-repo comparisons, drafts). **Never reference its contents from production code, public docs, CHANGELOG entries, commit messages, or PR descriptions.** Decisions distilled from those notes live in `docs/standards/`, `docs/roadmap/`, the CHANGELOG, and inline code comments — those are the citations reviewers see.
-- The roadmap ([docs/roadmap.md](docs/roadmap.md)) is what ships. The marketing strategy roadmap ([docs/marketing/marketing_strategy_roadmap.md](docs/marketing/marketing_strategy_roadmap.md)) is what gets announced. Don't conflate the two.
+- Internal strategy and working-memory directories are gitignored and must not be cited from public docs or production artifacts.
+- Use public sources for citations: `docs/standards/`, `docs/roadmap/`, `CHANGELOG.md`, and code comments.
+- The roadmap ([docs/roadmap.md](docs/roadmap.md)) is the public shipping plan. Keep internal planning artifacts out of public references.

As per coding guidelines: “Never reference docs/analysis/ … from … public docs” and “Never reference docs/marketing/ in public-facing code or docs”.

Also applies to: 199-201

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` around lines 115 - 116, Remove all gitignored working-memory path
references in AGENTS.md: search for the literal strings "docs/marketing/" and
"docs/analysis/" (they appear near the referenced lines, e.g., the occurrences
that mention docs/marketing/* and docs/analysis/*) and either delete those path
mentions or replace them with appropriate public-safe placeholders (e.g.,
"internal/marketing" or "internal/analysis" or a generic "internal docs") so the
public document no longer references private working-memory paths; ensure any
cross-reference text and surrounding sentences remain grammatically correct
after the replacement.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@AGENTS.md`:
- Line 47: The fenced tree code block containing the "ForgeLM/" listing in
AGENTS.md is missing a language tag; update that fenced block (the
triple-backtick before the tree output) to use a language tag such as "text" so
the fence becomes ```text, ensuring markdownlint rule MD040 is satisfied.

In `@tools/check_no_analysis_refs.py`:
- Around line 112-114: The PR adds an exemption entry "AGENTS.md":
frozenset({"docs/marketing/", "docs/analysis/"}) to the _EXEMPT set which
undermines the guard by allowing prohibited docs/marketing/ and docs/analysis/
references to bypass checks; remove the "AGENTS.md" exemption (and any duplicate
exemptions at the other occurrences around the same block) so .agents/**/*.md
stays in scan scope, and instead update the AGENTS.md (and mirrored skill docs)
to remove direct path references to docs/marketing/ and docs/analysis/ so the
policy guard can enforce the rule.

---

Duplicate comments:
In `@AGENTS.md`:
- Around line 115-116: Remove all gitignored working-memory path references in
AGENTS.md: search for the literal strings "docs/marketing/" and "docs/analysis/"
(they appear near the referenced lines, e.g., the occurrences that mention
docs/marketing/* and docs/analysis/*) and either delete those path mentions or
replace them with appropriate public-safe placeholders (e.g.,
"internal/marketing" or "internal/analysis" or a generic "internal docs") so the
public document no longer references private working-memory paths; ensure any
cross-reference text and surrounding sentences remain grammatically correct
after the replacement.
🪄 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: 36f2a214-68f1-4749-9b1e-9eddf7563d83

📥 Commits

Reviewing files that changed from the base of the PR and between fcbb926 and 188fc8a.

📒 Files selected for processing (10)
  • AGENTS.md
  • CHANGELOG.md
  • forgelm/_http.py
  • forgelm/cli/_no_train_modes.py
  • pyproject.toml
  • tests/test_http_dns_rebinding.py
  • tests/test_judge_functions.py
  • tests/test_synthetic.py
  • tests/test_webhook.py
  • tools/check_no_analysis_refs.py
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • forgelm/cli/_no_train_modes.py

Comment thread AGENTS.md Outdated
Comment on lines +112 to +114
# AGENTS.md is the agent-agnostic mirror of CLAUDE.md (Codex / Cursor /
# generic AI-agent surface); it restates the same gitignored-dir policy.
"AGENTS.md": frozenset({"docs/marketing/", "docs/analysis/"}),

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 | ⚡ Quick win

These exemptions undermine the guard’s policy objective.

Adding AGENTS.md (and mirrored skill docs) to _EXEMPT lets prohibited docs/marketing/ / docs/analysis/ references pass in public policy docs instead of fixing the source text. The safer pattern is: keep .agents/**/*.md in scan scope, remove these exemptions, and rewrite those docs to avoid direct path references.

As per coding guidelines: “Never reference docs/analysis/ … in public docs” and “Never reference docs/marketing/ in public-facing code or docs”.

Also applies to: 122-124

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/check_no_analysis_refs.py` around lines 112 - 114, The PR adds an
exemption entry "AGENTS.md": frozenset({"docs/marketing/", "docs/analysis/"}) to
the _EXEMPT set which undermines the guard by allowing prohibited
docs/marketing/ and docs/analysis/ references to bypass checks; remove the
"AGENTS.md" exemption (and any duplicate exemptions at the other occurrences
around the same block) so .agents/**/*.md stays in scan scope, and instead
update the AGENTS.md (and mirrored skill docs) to remove direct path references
to docs/marketing/ and docs/analysis/ so the policy guard can enforce the rule.

Reviewer (codex) flagged the dilsiz triple-backtick at line 47 of
AGENTS.md.  markdownlint MD040 ("fenced code block without language")
applies to opening fences; the fence at line 98 is the closing fence
of the same block and stays bare.  Same fix mirrored to CLAUDE.md so
the two agent-guidance trees stay structurally identical.

Skipped (reviewer's other two findings — same theme):

* "Remove the AGENTS.md entry from _EXEMPT and rewrite the
  docs/marketing/ / docs/analysis/ references to placeholders."

  Rationale: AGENTS.md is the agent-agnostic mirror of CLAUDE.md
  (see memory entry ``project-agents-md-mirror``).  The 5 path
  references in AGENTS.md (lines 115, 169, 199-201) are byte-for-byte
  copies of the same references in CLAUDE.md, which has been on the
  _EXEMPT list for years.  These are policy-statement documents —
  they cite the gitignored directories by their real names so the
  agent knows not to write into them.  Replacing the names with
  placeholders ("internal/marketing", etc.) hides the actual
  directory name from the very agent the rule is trying to instruct,
  defeating the policy.  Applying the change only to AGENTS.md would
  break mirror parity with CLAUDE.md; applying it to both is a
  whole-design pivot that is out of scope for PR #51.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cemililik

Copy link
Copy Markdown
Collaborator Author

Review response — c83e0ad

Fix applied

  • AGENTS.md:47 + CLAUDE.md:47 fenced block dil tagı: ``` → ```text (markdownlint MD040). Line 98'deki fence kapanış fence olduğundan dil tagı gerekmiyor (MD040 sadece açılışa uygulanır). CLAUDE.md'de de aynı düzeltme — iki agent-guidance dosyası birbirinin mirror'ı, structural parity korunur.

Skipped (with rationale)

"Remove the AGENTS.md entry from _EXEMPT and rewrite the docs/marketing/ / docs/analysis/ references to placeholders."

AGENTS.md, CLAUDE.md'nin agent-agnostic mirror'ı — 5 path referansının (lines 115, 169, 199-201) birebir CLAUDE.md karşılığı var (grep doğruladı, aynı satır numaraları). CLAUDE.md yıllardır _EXEMPT'ta; yeni karar değil, PR #51 scope'u dışı bir tasarım kararı.

İki doküman da policy-statement niteliğinde — gitignored dizinleri gerçek adlarıyla zikretmek bilinçli: agent'a "buraya yazma" derken hangi path'in kastedildiğini saklamak (örn. "internal/marketing") agent'a yararlı bilgiyi vermemek olur. Policy guard'ın amacı production code / public docs'ta bu path'leri citation olarak kullanmayı engellemek; agent guidance dokümanı bu kuralın kendisini ifade etmek için path'leri zikretmek zorunda.

Sadece AGENTS.md'yi değiştirmek mirror parity'yi kırardı (sonraki review döngüsünde "AGENTS.md drift'le" gelir). CLAUDE.md + AGENTS.md ikisini birden placeholder'a çevirmek ise whole-design pivotu — PR #51 scope'u dışı.

Verification

  • check_bilingual_parity.py --strict: ✓ 46 pair
  • check_anchor_resolution.py --strict: ✓ 271 markdown
  • check_no_analysis_refs.py: ✓ no public-tree references

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@cemililik
cemililik merged commit ccb7d84 into main May 14, 2026
8 of 10 checks passed
cemililik added a commit that referenced this pull request May 15, 2026
Two unrelated work items landed together because they were spotted in
the same review pass.

──────────────────────────────────────────────────────────────────
1. Nightly CI: requests-toolbelt missing on --no-deps install
──────────────────────────────────────────────────────────────────
Nightly compatibility workflow (.github/workflows/nightly.yml) ran
on ``main`` HEAD ccb7d84 (PR #51 merge) and failed:

  E   ModuleNotFoundError: No module named 'requests_toolbelt'

Across three jobs:
- Test with minimum supported versions (3.10)
- Test with latest dependencies (3.11)
- Test with latest dependencies (3.12)

Root cause: the v0.6.0 SSRF DNS-rebinding hardening (issue #14, PR
#52) added ``requests-toolbelt>=1.0.0,<2.0.0`` as a hard runtime
dependency in pyproject.toml.  Both failing nightly jobs install
ForgeLM via ``pip install -e . --no-deps`` (after a manual pip
install of the dependency list) — so the manual list MUST mirror
the pyproject dependency declarations, but ``requests-toolbelt``
was never added.  The 2026-05-15 nightly is the first run with this
test surface on main, hence the first failure.

Fix:
- ``test-latest-deps`` matrix: add ``requests-toolbelt`` to the
  unconstrained latest-deps pin line.  Added a comment block that
  spells out the contract (this list MUST mirror pyproject because
  the next step is --no-deps) so a future dependency addition
  doesn't recreate this gap.
- ``test-min-deps``: pin ``requests-toolbelt==1.0.0`` (the lower
  bound from pyproject's ``>=1.0.0,<2.0.0`` range) and added the
  same contract comment.
- The fix lands on development and propagates to main via PR #54.

──────────────────────────────────────────────────────────────────
2. PR #54 review findings — 3 valid (fix) + 1 skipped (reasoned)
──────────────────────────────────────────────────────────────────
SKIPPED — docs/reference/configuration-tr.md L313 "en az 1 zorunlu"
   Reviewer wanted "minimum 2 stages" but the schema enforces
   ``min_length=1`` (verified in ``forgelm/config.py:1351``).  The
   v0.7.0 cut CHANGELOG explicitly unified the wording around "at
   least 1 stage" with the note that single-stage ergonomics are
   better served by omitting the ``pipeline:`` block, but the
   schema accepts a 1-element pipeline so an operator can iterate
   up to a multi-stage chain without re-shaping the YAML.  Reviewer
   appears to be reading a prior unmerged variant; current code +
   docs are internally consistent.

FIXED — docs/roadmap{,-tr}.md "Latest release on PyPI" / "PyPI'deki
son sürüm" still said v0.5.7
   v0.6.0 shipped 2026-05-11; both EN and TR roadmaps still pointed
   "Latest release on PyPI" at v0.5.7, contradicting the same
   page's ``Current state: ... v0.6.0`` line just below.  Rewrote
   both paragraphs to lead with v0.6.0 (Phase 15 Ingestion Pipeline
   Reliability, 2026-05-11) and the full ingest-time-guard
   enumeration, with a roadmap link to ``completed-phases.md``.

FIXED — forgelm/cli/_dispatch.py L277 stale "v0.7.0" comment
   The dispatch comment for the ``--data-audit`` deprecation said
   "slated for removal in v0.7.0" but the matching DeprecationWarning
   message + audit-log ``version`` field were already updated to
   v0.8.0 in the v0.7.0 cut cycle (per the one-minor warning window
   policy).  Updated the inline comment to match, with a reference
   to ``docs/standards/release.md#deprecation-cadence``.

FIXED — tests/test_pipeline_cli.py:140 IndexError defensive guard
   (Sonar python:S1244 / S6395 Critical)
   ``test_empty_input_model_normalised_to_none`` accessed
   ``configs_seen[0]`` without first asserting the list was
   non-empty.  If a future dispatch-order change short-circuits the
   run before the fake trainer is instantiated, the test would
   fail with an opaque ``IndexError: list index out of range``
   instead of a "no stage ran" message.  Added an ``assert
   configs_seen, "..."`` guard with a descriptive failure message
   before the index access.

Verification:
- ruff format + check clean
- 196 / 196 targeted tests pass
- All 11 self-review guards green

Co-Authored-By: Claude Opus 4.7 (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