Skip to content

docs: README rewrite, inclusive messaging, and v0.7.x housekeeping#56

Merged
cemililik merged 8 commits into
mainfrom
development
May 15, 2026
Merged

docs: README rewrite, inclusive messaging, and v0.7.x housekeeping#56
cemililik merged 8 commits into
mainfrom
development

Conversation

@cemililik

Copy link
Copy Markdown
Collaborator

Summary

  • README rewrite (326 → 182 lines). Drop version/phase annotations, the 66-line Project Structure ASCII tree, the duplicated CLI section, and the Pro CLI preview. Condense Features into 4 groups, give Compliance & Safety a dedicated callout, link out to architecture / configuration reference, and reduce Notebooks to 3 featured + folder link.
  • Inclusive multi-entry-point messaging. Remove the "pipelines, not notebooks" framing across README tagline, site index.html + quickstart.html heroes (with EN / TR / DE / FR / ES / ZH translation parity), user-manual CI/CD page, product_strategy{,-tr}.md, pipeline non-goals, and the CLAUDE.md + AGENTS.md one-line description so future contributions don't reinforce the framing.
  • Doc self-contained link discipline (95f7a84) — new tools/check_usermanual_self_contained.py guard + 130+ usermanual link fixes for the static SPA viewer.
  • Roadmap reshape (8f59fde) — archive Phase 14, introduce Phase 14.5 v0.7.x hardening track.
  • Post-release housekeeping (bc4c4a0, a97d873) — bump to 0.7.1rc1, skip the atomic-write race regression test on Windows.

Test plan

  • ruff check . && pytest tests/
  • python3 tools/check_bilingual_parity.py --strict (verified locally: 48 pairs OK)
  • python3 tools/check_anchor_resolution.py --strict (verified locally: 275 files OK)
  • python3 tools/check_usermanual_self_contained.py --strict (verified locally: 132 pages OK)
  • python3 tools/update_site_version.py --check (verified locally: site literals match CHANGELOG 0.7.0)
  • python3 tools/check_no_analysis_refs.py (verified locally)
  • Visual: README renders cleanly on GitHub
  • Visual: site/quickstart.html hero shows "One YAML." (EN) / "Tek YAML." (TR)
  • Visual: site/index.html beginner hero lead reads cleanly in all 6 languages

🤖 Generated with Claude Code

cemililik and others added 5 commits May 15, 2026 10:53
publish.yml run 25906380797 (v0.7.0 tag push) failed on all four
Windows matrix cells (3.10/3.11/3.12/3.13) of cross-os-tests with:

  AssertionError: Concurrent writers produced 12 exception(s):
    [PermissionError(13, 'Access is denied'), ...]
  tests/test_pipeline_orchestrator.py:246

The test
(``TestAtomicWriteJson.test_per_writer_temp_path_does_not_race_on_shared_tmp``)
pins the POSIX ``FileNotFoundError`` race that the F-S-1 fix on PR #53
addressed: pre-fix the helper used a shared ``<target>.tmp`` and
``os.replace`` from writer B could fail because writer A had just
unlinked the same tmp via its own rename.  Per-writer-unique tmp
filenames eliminate that bug on POSIX.

On Windows, ``os.replace`` to the same destination path raises a
DIFFERENT error (``PermissionError: Access is denied``) when
concurrent writers briefly hold a handle on the destination — that's
a file-lock race intrinsic to the Win32 rename API, not the
tmp-rename race the F-S-1 fix is about.  The pipeline orchestrator's
atomic-write contract is POSIX-shaped (sequential writers; the
test's 16-thread stress is purely a regression probe against the
original race shape), and Windows's concurrent-replace semantics
are not in scope.

Fix: marker ``@pytest.mark.skipif(sys.platform == "win32", ...)``
with a docstring explaining the platform-specific reason.  The
POSIX-side regression coverage is preserved on the ubuntu-latest and
macos-latest matrix cells.

PyPI was NOT published in run 25906380797 — the ``pypi`` job is
gated on ``needs: cross-os-tests`` which never went green.  The
v0.7.0 GitHub tag was deleted (local + remote) so this commit can be
re-tagged onto main HEAD and publish.yml re-fired cleanly.

Verification:
- ruff format --check + ruff check clean
- TestAtomicWriteJson passes locally (2/2; macOS)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.7.0 published to PyPI (publish.yml run 25906894834, all 12 cross-OS
matrix cells green, OIDC trusted publish success).  Smoke test:
``pip install forgelm==0.7.0`` in a fresh venv reports
``forgelm --version → ForgeLM 0.7.0``.  Post-release housekeeping:

1. ``pyproject.toml`` → ``0.7.1rc1`` (next dev cycle marker)
2. ``docs/roadmap.md`` + ``-tr.md``:
   - Phase 14 row: "🟡 Merged" → "✅ Done" with v0.7.0 release
     date + completed-phases anchor
   - "Latest release on PyPI" / "PyPI'deki son sürüm" paragraph:
     v0.6.0 → v0.7.0 with the Phase 14 + SSRF hardening summary;
     v0.6.0 demoted to "Previous release" / "Önceki sürüm"
   - "Released:" / "Yayınlandı:" line: v0.6.0 → v0.7.0
   - "Current state" line: 20 phases → 21 phases (Phase 14 added),
     PyPI through v0.7.0
   - Mermaid: ``v0.7.0`` node → ``v0.7.0 ✅ Released`` /
     ``✅ Yayınlandı``
   - Tree-comment for ``phase-14-pipeline-chains.md``: "Merged …
     v0.7.0 tag pending" → "Done — shipped v0.7.0 (PyPI 2026-05-15)"
   - ``releases.md`` comment: ``v0.3.0 → v0.6.0`` → ``v0.3.0 →
     v0.7.0``
3. ``docs/roadmap/completed-phases.md``: appended a comprehensive
   Phase 14 entry (driver, what shipped, security fold-in,
   backward-compat invariant, full review-absorption history with
   PR #53 + #54 round summaries, cross-references to operator
   guides + schema + user manual pages).  Slug
   ``#phase-14-multi-stage-pipeline-chains-v070`` matches the
   target anchor used from ``roadmap.md`` and ``roadmap-tr.md``.

Verification:
- ruff format --check + ruff check clean
- 48 / 48 bilingual doc pair parity green
- 274 markdown files: all anchors + relative links resolve
  (including the new ``completed-phases.md#phase-14-...`` anchors)
- site/ version literals still pin v0.7.0 (no change needed; the
  bump to 0.7.1rc1 is dev-cycle only)

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

v0.7.0 shipped Phase 14 (Multi-Stage Pipeline Chains) on 2026-05-15.
Post-release housekeeping:

1. Phase 14.5 — Pipeline Hardening (new planning doc)
   ``docs/roadmap/phase-14-5-pipeline-hardening.md`` is the canonical
   home for the 4 review-deferred items from PR #54:
   - F-PR54-H6: canonical pipeline manifest hash + non-chain-field
     tamper detection
   - F-PR54-H7: per-stage ``training_manifest.json`` deep-parse
     validation (pairs with H6 as "pipeline manifest verification
     rewrite")
   - F-PR54-M10: webhook ``pipeline.*`` event vocabulary
     documentation (optional ``tools/check_webhook_event_vocabulary
     .py`` ``--strict`` guard)
   - F-PR54-M11: ``WebhookNotifier._send(**extra)`` explicit
     allowlist
   Wave 2 carry-overs from the original Phase 14 spec (intra-stage
   HF Trainer resume, DAG pipelines, parallel exec, wizard pipeline
   path) tracked at the bottom as future-phase backlog, NOT in-flight
   Phase 14.5 work.  Targets v0.7.x patch cycle.

2. ``docs/roadmap/phase-14-pipeline-chains.md`` deleted
   Phase 14 shipped in v0.7.0; the canonical history is in
   ``completed-phases.md#phase-14-multi-stage-pipeline-chains-v070``
   (the entry I appended in commit bc4c4a0).  Phase 15 followed the
   same pattern (3399c0e archived its standalone planning doc once
   shipped).  Six cross-references redirected to either the
   completed-phases anchor or the new Phase 14.5 doc:
   - ``docs/guides/pipeline{,-tr}.md`` Cross-references section
   - ``docs/usermanuals/{en,tr}/deployment/model-merging.md``
     merge-sweep helper aside
   - ``docs/roadmap/completed-phases.md`` historical reslotting note
     (L915) + cross-references list (L1535)

3. ``docs/roadmap.md`` + ``docs/roadmap-tr.md``
   - New Phase 14.5 row in the Planned section (📋 Planned / Planlandı).
   - Mermaid diagram: new ``P145`` node + ``V275[v0.7.x]`` dotted edge.
   - Tree-structure comment: ``phase-14-pipeline-chains.md`` →
     ``phase-14-5-pipeline-hardening.md`` (the design doc no longer
     exists; the new planning doc takes its slot).

4. ``docs/roadmap/releases.md``
   - "v0.7.0 — Pipeline Chains (Planned)" rewritten as a Released
     entry with full Summary / Highlights / Public surface changes /
     Review-absorption history / Full changelog sections (mirrors
     the v0.6.0 entry's depth).
   - New "v0.7.x — Pipeline Hardening (Planned)" placeholder
     between v0.7.0 and v0.6.0-pro pointing at the Phase 14.5 spec.

5. ``docs/roadmap/risks-and-decisions.md``
   - New "2026-05-15 — v0.7.0 release-review deferrals → Phase 14.5"
     deferred-findings table with the 4 F-PR54 rows + bundled-landing
     note.
   - Three new decision-log entries: 2026-05-11 (v0.6.0 / Phase 15),
     2026-05-15 (v0.7.0 / Phase 14 + retag), 2026-05-15 (Phase 14.5
     introduction).

Verification:
- bilingual parity: 48/48 pairs (the new Phase 14.5 doc is EN-only;
  TR mirror not added since this is a planning doc, not user-facing
  reference — same pattern as phase-13-pro-cli.md)
- anchor resolution: 274 markdown files green (every Phase 14.5
  cross-reference resolves, the completed-phases anchor links resolve)
- CLI help consistency: 457 forgelm invocations green
- no-analysis-refs: green

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The static-site SPA viewer in site/guide.html only intercepts
``#/<section>/<page>`` hash-router routes and external HTTPS URLs;
relative ``.md`` href clicks (``../../../guides/foo.md``, even
intra-manual ``../section/page.md``) fire plain browser navigation
and 404. Replace every such reference across docs/usermanuals/
{en,tr} (55 file pairs) with either an in-manual SPA route or an
absolute github.com/cemililik/ForgeLM/blob/main/... URL, depending on
whether the topic has an in-manual page. Also drops broken SPA
routes that never had a backing manual page (``#/standards/release``,
``#/roadmap/phase-13``, ``#/reference/audit-event-catalog``).

New guard: tools/check_usermanual_self_contained.py walks every
usermanual page and fails on any link the SPA can't resolve, with
14-test regression coverage. Wired into the local self-review
gauntlet (CLAUDE.md + AGENTS.md), CI workflow (.github/workflows/ci.yml),
and a new "User-manual link discipline" section in
docs/standards/documentation.md. sync-bilingual-docs skill (both
.claude and .agents mirrors) updated with the rule. Fenced code
blocks (sample JSON / shell output mentioning docs/... paths as
data) are skipped so the guard does not flag literal CLI envelopes.

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

- Rewrite README.md (326 → 182 lines): drop version/phase tags, the
  Project Structure ASCII tree, CLI section duplication, and the Pro
  CLI preview. Condense Features into 4 groups, give Compliance &
  Safety its own callout, and reduce Notebooks to 3 featured + folder
  link.

- Reframe notebook-vs-pipeline copy as inclusive of terminal, notebook,
  and CI/CD entry points across README, site/index + quickstart heroes
  (with EN/TR/DE/FR/ES/ZH translation parity), user-manual CI/CD page,
  product_strategy, pipeline non-goals, and CLAUDE/AGENTS one-liners.

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 →

@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

@codacy-production

codacy-production Bot commented May 15, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 6 minor

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

Results:
6 new issues

Category Results
CodeStyle 6 minor

View in Codacy

🟢 Metrics 65 complexity · 1 duplication

Metric Results
Complexity 65
Duplication 1

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.

@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@cemililik has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 2 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: f543c898-4bf1-4266-ab8a-69de89c30749

📥 Commits

Reviewing files that changed from the base of the PR and between 24de388 and f6f43a6.

📒 Files selected for processing (77)
  • .agents/skills/sync-bilingual-docs/SKILL.md
  • .claude/skills/sync-bilingual-docs/SKILL.md
  • .github/workflows/ci.yml
  • .markdownlint.json
  • AGENTS.md
  • CLAUDE.md
  • README.md
  • docs/guides/pipeline-tr.md
  • docs/guides/pipeline.md
  • docs/product_strategy-tr.md
  • docs/product_strategy.md
  • docs/roadmap-tr.md
  • docs/roadmap.md
  • docs/roadmap/completed-phases.md
  • docs/roadmap/phase-14-5-pipeline-hardening.md
  • docs/roadmap/phase-14-pipeline-chains.md
  • docs/roadmap/releases.md
  • docs/roadmap/risks-and-decisions.md
  • docs/standards/documentation.md
  • docs/standards/localization.md
  • docs/usermanuals/en/compliance/annex-iv.md
  • docs/usermanuals/en/compliance/audit-log.md
  • docs/usermanuals/en/compliance/gdpr-erasure.md
  • docs/usermanuals/en/compliance/gdpr.md
  • docs/usermanuals/en/compliance/human-approval-gate.md
  • docs/usermanuals/en/compliance/overview.md
  • docs/usermanuals/en/compliance/verify-audit.md
  • docs/usermanuals/en/data/deduplication.md
  • docs/usermanuals/en/data/pii-masking.md
  • docs/usermanuals/en/deployment/chat.md
  • docs/usermanuals/en/deployment/model-merging.md
  • docs/usermanuals/en/deployment/verify-gguf.md
  • docs/usermanuals/en/evaluation/trend-tracking.md
  • docs/usermanuals/en/operations/cicd.md
  • docs/usermanuals/en/operations/experiment-tracking.md
  • docs/usermanuals/en/operations/gpu-cost.md
  • docs/usermanuals/en/operations/iso-soc2-deployer.md
  • docs/usermanuals/en/operations/supply-chain.md
  • docs/usermanuals/en/operations/webhooks.md
  • docs/usermanuals/en/reference/configuration.md
  • docs/usermanuals/en/reference/exit-codes.md
  • docs/usermanuals/en/reference/json-output.md
  • docs/usermanuals/en/reference/library-api.md
  • docs/usermanuals/en/reference/performance.md
  • docs/usermanuals/en/training/pipelines.md
  • docs/usermanuals/tr/compliance/annex-iv.md
  • docs/usermanuals/tr/compliance/audit-log.md
  • docs/usermanuals/tr/compliance/gdpr-erasure.md
  • docs/usermanuals/tr/compliance/gdpr.md
  • docs/usermanuals/tr/compliance/human-approval-gate.md
  • docs/usermanuals/tr/compliance/overview.md
  • docs/usermanuals/tr/compliance/verify-audit.md
  • docs/usermanuals/tr/data/deduplication.md
  • docs/usermanuals/tr/data/pii-masking.md
  • docs/usermanuals/tr/deployment/chat.md
  • docs/usermanuals/tr/deployment/model-merging.md
  • docs/usermanuals/tr/deployment/verify-gguf.md
  • docs/usermanuals/tr/evaluation/trend-tracking.md
  • docs/usermanuals/tr/operations/cicd.md
  • docs/usermanuals/tr/operations/experiment-tracking.md
  • docs/usermanuals/tr/operations/gpu-cost.md
  • docs/usermanuals/tr/operations/iso-soc2-deployer.md
  • docs/usermanuals/tr/operations/supply-chain.md
  • docs/usermanuals/tr/operations/webhooks.md
  • docs/usermanuals/tr/reference/configuration.md
  • docs/usermanuals/tr/reference/exit-codes.md
  • docs/usermanuals/tr/reference/json-output.md
  • docs/usermanuals/tr/reference/library-api.md
  • docs/usermanuals/tr/reference/performance.md
  • docs/usermanuals/tr/training/pipelines.md
  • pyproject.toml
  • site/index.html
  • site/js/translations.js
  • site/quickstart.html
  • tests/test_check_usermanual_self_contained.py
  • tests/test_pipeline_orchestrator.py
  • tools/check_usermanual_self_contained.py
✨ 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 a new CI guard, check_usermanual_self_contained.py, to enforce link isolation within the user manual SPA viewer, alongside a comprehensive update to documentation and the project roadmap following the release of Phase 14. Feedback on the new tool identifies several logic gaps, including a restrictive regex that lacks support for anchors and arbitrary path depths, incomplete link extraction for complex Markdown syntax, and a need for more robust code block detection to avoid false positives in indented or inline blocks.

# SPA hash-router form recognised by site/js/guide.js — ``#/<section>/<page>``.
# Section/page slugs are kebab-case alphanumerics matching the
# directory naming under docs/usermanuals/<lang>/.
_SPA_ROUTE_RE = re.compile(r"^#/(?P<section>[a-z0-9][a-z0-9-]*)/(?P<page>[a-z0-9][a-z0-9-]*)$")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The _SPA_ROUTE_RE regex is too restrictive. It currently only allows exactly two path components (section and page) and does not support anchors (e.g., #/section/page#heading). Since _walk_manual_files uses rglob to find files at any depth, the validation logic should support arbitrary path depth. Additionally, forbidding anchors prevents linking to specific sections within a manual page, which is a common documentation requirement.

References
  1. Ensure that invalid inputs or states are safely handled in all cases.

Comment on lines +140 to +157
return BrokenLink(
link,
f"hash-router route {href!r} is not in the canonical "
"``#/<section>/<page>`` form recognised by the SPA viewer",
)
section = match.group("section")
page = match.group("page")
lang_root = _lang_root_of(link.source, usermanuals_root)
if lang_root is None:
# Shouldn't happen — we only call this for files under
# docs/usermanuals/ — but degrade gracefully.
return BrokenLink(link, "source file is not under docs/usermanuals/")
target = lang_root / section / f"{page}.md"
if not target.is_file():
return BrokenLink(
link,
f"SPA route {href!r} has no backing file at docs/usermanuals/{lang_root.name}/{section}/{page}.md",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

This validation logic depends on the regex capturing exactly two groups (section and page). If the regex is updated to support arbitrary depth and anchors, this function needs to be updated to use the full path and ignore the anchor during the file existence check.

References
  1. Verify code functionality, handle edge cases, and ensure alignment between function descriptions and implementations.

Comment on lines +113 to +118
for match in _LINK_RE.finditer(line):
text_part = match.group(1)
href_part = match.group(2).strip()
if not href_part:
continue
yield Link(source=source, line=line_no, text=text_part, href=href_part)

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 link extraction logic has several limitations: it does not handle Markdown links with titles (e.g., [text](url "title")), it fails on URLs containing nested parentheses, and it misses links that span multiple lines. When a title is present, href_part will contain both the URL and the title, causing resolution failures in _validate_relative_path.

References
  1. Verify code functionality, handle edge cases, and ensure alignment between function descriptions and implementations.

Comment on lines +103 to +112
for line_no, line in enumerate(text.splitlines(), start=1):
# Skip fenced code blocks — JSON / shell / yaml examples
# legitimately mention ``docs/...`` paths as literal data,
# not as clickable links.
stripped = line.lstrip()
if stripped.startswith("```") or stripped.startswith("~~~"):
in_code_block = not in_code_block
continue
if in_code_block:
continue

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 logic to skip code blocks only handles fenced blocks (``` or ~~~). It does not handle indented code blocks (4 spaces) or inline code blocks (`code`). Links appearing inside inline code or indented blocks will be incorrectly extracted and validated, which may lead to false positives if they are intended as literal examples.

References
  1. Ensure that invalid inputs or states are safely handled in all cases.

@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 a new CI guard, check_usermanual_self_contained.py, to enforce link isolation in the user manual directory for SPA viewer compatibility. It includes updates to documentation standards, roadmap files, and a mass migration of links across manual pages. Feedback identifies several logic errors in the new tool, including a restrictive SPA route regex that lacks anchor support, the absence of disk existence checks for relative paths, and a failure to correctly parse Markdown links with optional titles.

# SPA hash-router form recognised by site/js/guide.js — ``#/<section>/<page>``.
# Section/page slugs are kebab-case alphanumerics matching the
# directory naming under docs/usermanuals/<lang>/.
_SPA_ROUTE_RE = re.compile(r"^#/(?P<section>[a-z0-9][a-z0-9-]*)/(?P<page>[a-z0-9][a-z0-9-]*)$")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The current regex for SPA routes is too restrictive. It does not support optional anchors (e.g., #/section/page#anchor) which are valid in the SPA viewer, and it enforces a strict two-level directory structure. This will cause false positives for valid links and prevent linking to specific sections of a page. Additionally, the tool's own suggestion for intra-manual links (which can be deeper than two levels) would fail this validation.

References
  1. Ensure regex patterns for URL validation are robust enough to handle valid edge cases like anchors and varying path depths.

Comment on lines +138 to +157
match = _SPA_ROUTE_RE.match(href)
if match is None:
return BrokenLink(
link,
f"hash-router route {href!r} is not in the canonical "
"``#/<section>/<page>`` form recognised by the SPA viewer",
)
section = match.group("section")
page = match.group("page")
lang_root = _lang_root_of(link.source, usermanuals_root)
if lang_root is None:
# Shouldn't happen — we only call this for files under
# docs/usermanuals/ — but degrade gracefully.
return BrokenLink(link, "source file is not under docs/usermanuals/")
target = lang_root / section / f"{page}.md"
if not target.is_file():
return BrokenLink(
link,
f"SPA route {href!r} has no backing file at docs/usermanuals/{lang_root.name}/{section}/{page}.md",
)

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

This function should be updated to work with a more flexible SPA route regex, allowing for arbitrary path depth and optional anchors while still verifying the existence of the backing .md file. The error message should also be updated to show the full expected path for better debugging.

References
  1. Validation logic should be consistent with the supported directory structure and URL formats.

Comment on lines +195 to +198
target = (link.source.parent / path_part).resolve()
except OSError as exc:
return BrokenLink(link, f"path resolve failed: {exc}")
lang_root_resolved = lang_root.resolve()

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 tool currently checks if a relative path escapes the language root, but it doesn't verify if the target path actually exists on disk. Adding an existence check would make the guard more effective at catching broken links to assets like images or other non-Markdown files that are allowed to stay as relative paths.

References
  1. A link checker should verify the existence of the target resource on disk when using relative paths.

Comment on lines +114 to +115
text_part = match.group(1)
href_part = match.group(2).strip()

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 link extraction logic doesn't handle Markdown links with optional titles (e.g., [text](url "title")). The title part will be included in the href_part, causing validation to fail. Splitting the href part on whitespace will correctly extract the URL and ignore the title.

References
  1. Markdown parsing logic should account for standard syntax features like link titles.

cemililik and others added 3 commits May 15, 2026 19:03
- tools/check_usermanual_self_contained.py:108 — collapse chained
  `startswith("```") or startswith("~~~")` into a single tuple-argument
  call (pythonic; equivalent semantics).
- .markdownlint.json — set MD024 to `siblings_only: true`. The release
  notes file (docs/roadmap/releases.md) repeats the same H3 names
  (`Summary`, `Highlights`, `Public surface changes`, `Full changelog`)
  under each release's H2 by design; treating duplicates under the
  same parent as the only hard error is the correct policy for this
  changelog-style document.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Validated review feedback by reading the SPA viewer (site/js/guide.js)
and grepping the actual usermanual corpus.  Applying only the changes
backed by real behaviour or in-tree evidence.

Real issues fixed:

- **SPA route anchor support** (Gemini high-priority).  Verified
  site/js/guide.js:343 appends ``#<heading>`` to the route via
  ``history.replaceState`` when the reader clicks a TOC entry.  The
  prior regex rejected this canonical form.  ``_SPA_ROUTE_RE`` now
  accepts an optional ``#<heading>`` suffix; the backing-file check
  still uses the section/page portion only.
- **Markdown link-title stripping** (Gemini medium-priority).
  ``[text](url "title")`` and ``[text](url 'title')`` are valid
  CommonMark; the title was being appended to the href and would have
  failed validation if anyone added one.  No false positives in the
  current corpus, but the fix is one line and defensive.

Codacy minors (6 → 0 on pylint C/W rules):

- Added docstrings to ``Link``, ``BrokenLink``, and ``main``.
- Shortened three f-string lines that exceeded the 100-char Codacy cap
  (project ruff cap is 120, but Codacy uses pylint default).

Regression tests (4 new, brings file to 18 passing):

- SPA route with anchor passes when the backing page exists.
- SPA route with anchor still fails when the backing page is missing.
- Double-quoted link title is stripped before SPA validation.
- Single-quoted link title is stripped on an external URL.

Review feedback investigated and *not* applied — kept the changeset
focused on items with real evidence:

- "Restrictive 2-level depth": SPA is 2-level by design per
  site/js/guide.js (#/<section>/<page> hash-router); deeper paths
  would not render.  Existing regex shape is correct.
- "Nested-paren URLs / multi-line links / indented code blocks /
  inline-backtick code": grepped the corpus — zero matches.  Not
  applying speculative changes.
- "No on-disk existence check for relative paths": tools/
  check_anchor_resolution.py already owns the on-disk side, and the
  file docstring explicitly pairs the two guards.

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

CI lint failed after the previous commit because the two formatters
disagreed:

- ``[tool.ruff] line-length = 120`` is the project's declared policy and
  ``ruff format`` reflows long f-strings onto single lines accordingly.
- Codacy runs pylint with its default ``max-line-length = 100``, which
  flagged the same lines.  Splitting them to please pylint then made
  ruff format reflow them back, breaking ``ruff format --check`` in CI.

Resolved systemically by adding ``[tool.pylint.format] max-line-length
= 120`` to pyproject.toml so pylint honours the same cap as ruff.
Pylint stays at 10.00/10 locally; ruff format --check is now clean.

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

Copy link
Copy Markdown

@cemililik cemililik merged commit a8e3988 into main May 15, 2026
10 of 11 checks passed
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