Skip to content

feat: doc-to-code linkage manifest for deterministic selection - #69

Open
Benkapner wants to merge 3 commits into
mainfrom
feat/doc-code-linkage
Open

feat: doc-to-code linkage manifest for deterministic selection#69
Benkapner wants to merge 3 commits into
mainfrom
feat/doc-code-linkage

Conversation

@Benkapner

Copy link
Copy Markdown
Collaborator

Summary

Adds front-matter declarations so doc files can declare which source files they cover. Declared docs are selected deterministically with no LLM call.

  • Parser (src/linkage.py): reads YAML front-matter (.md), directive comments (.rst), and line comments (.adoc)
  • Selection: declared docs whose covers paths intersect the diff are merged into the selection results. The LLM pass still runs for undeclared docs.
  • README: syntax examples for all three formats, positioned as an incremental adoption path

Test plan

  • uv run pytest -v passes (428 tests)
  • Lint clean
  • A doc with matching front-matter is selected when its covered source file is in the diff

File selection is currently an LLM guess on every run. Where a
maintainer declares which source files a doc covers, selection should
be deterministic, free, and correct. Add a parser for YAML
front-matter in .md, comment directives in .rst, and comment
directives in .adoc. Parser only; no selection changes yet.
Any doc whose declared covers paths intersect the diff is included
with no LLM call for that decision. The LLM selection pass still runs
for undeclared docs. This is additive, not a replacement.
Add a README section showing front-matter syntax for all three formats
and positioning linkage as an incremental adoption path.
@Benkapner
Benkapner requested a review from csoceanu August 17, 2026 05:48
@Benkapner Benkapner self-assigned this Aug 17, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:49 AM UTC · Completed 6:08 AM UTC

Commit: 9b08e5c · View workflow run →

@fullsend-ai-review

Copy link
Copy Markdown

Review

Findings

High

  • [missing-dependency] src/linkage.py:6linkage.py imports yaml (PyYAML) but pyyaml is not declared in pyproject.toml production dependencies. PyYAML is only available as a transitive dependency of pre-commit (dev-only). The Dockerfile installs only main dependencies (pip install /app), so this will cause an ImportError at runtime when find_declared_docs is called for .md files.
    Remediation: Add pyyaml to the dependencies list in pyproject.toml.

Medium

  • [logic-error] src/suggest_docs.py:520 — The declared docs merge runs unconditionally after file discovery, including when relevant_files was set from a previous interactive review's accepted files. If a user ran [review-docs], rejected a doc file with a covers front-matter declaration, then ran [update-docs], the merge re-adds the rejected file — overriding the user's explicit rejection and breaking the [update-docs] contract documented in CLAUDE.md.
    Remediation: Filter rejected files from declared_paths before merging, or skip linkage merge when honoring a previous review.

  • [symlink-following] src/linkage.py:86find_declared_docs uses Path(doc_root).rglob('*') which follows symlinks by default. In the GitHub Actions context where untrusted PRs are processed, a PR could introduce a symlink pointing outside the doc root; file contents would be read by parse_doc_frontmatter.
    Remediation: Add if doc.is_symlink(): continue before processing each path.

  • [path-traversal] src/linkage.py:12parse_doc_frontmatter reads files via Path(file_path).read_text() without calling validate_file_path() from security_utils.py, which is used for all file I/O in generation.py. While paths come from rglob (not direct user input), the inconsistency with the codebase's existing security pattern is a gap.
    Remediation: Add validate_file_path(file_path, base_dir=doc_root) before reading, consistent with generation.py.

  • [missing-module-in-catalog] CLAUDE.md:13 — The Source modules table exhaustively catalogs every src/ module but does not include the new linkage.py. This omission makes the architecture documentation incomplete.
    Remediation: Add a row: | linkage.py | Doc-to-code linkage — deterministic file selection from front-matter declarations |

Low

  • [missing-authorization] — This PR introduces a non-trivial new feature but has no linked issue.

  • [path-traversal] src/linkage.py:96covers paths in front-matter are attacker-controlled values with no validation for .., absolute paths, or null bytes. Currently only used in string matching, but lacks defense-in-depth.

  • [import-placement] src/suggest_docs.py:517 — Function-level from linkage import find_declared_docs inside main() is inconsistent with the codebase convention of module-level imports.

  • [error-handling-idiom] src/linkage.py:20 — Bare except Exception silently swallows errors without logging, inconsistent with the logging pattern in discovery.py and generation.py.

  • [test-coverage-gap] tests/test_linkage.py:1 — Tests do not cover directory prefix matching (c.startswith(covered_path + '/')), malformed YAML, or integration with suggest_docs.py.

  • [return-type-consistency] src/linkage.py:85find_declared_docs returns tagged tuples (str, 'declared') but the call site immediately discards the tag. No other discovery function uses this pattern.

  • [module-docstring] src/linkage.py:1 — Single-line module docstring where most source modules use multi-line format.

  • [function-docstring] src/linkage.py:75extract_changed_paths and find_declared_docs lack Args: and Returns: sections.

  • [import-style] src/linkage.py:5 — Missing blank line between stdlib and third-party import groups.

  • [incomplete-pipeline-description] CLAUDE.md:83 — Command flows pipeline description doesn't mention the new front-matter linkage step.

  • [edge-case] src/linkage.py:40 — Split-based front-matter delimiter parsing has standard limitations with consecutive --- sequences. No data corruption; declarations silently ignored in edge cases.


Labels: PR adds a new feature (doc-to-code linkage) with Python implementation


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

@fullsend-ai-review fullsend-ai-review 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.

See the review comment for full details.

Comment thread src/linkage.py
import re
from pathlib import Path

import yaml

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] missing-dependency

linkage.py imports yaml (PyYAML) but pyyaml is not declared in pyproject.toml production dependencies. PyYAML is only available as a transitive dependency of pre-commit (dev-only). The Dockerfile installs only main dependencies, so this will cause an ImportError at runtime when find_declared_docs is called for .md files.

Suggested fix: Add pyyaml to the dependencies list in pyproject.toml.

Comment thread src/suggest_docs.py
# Merge in declared docs (front-matter linkage). These skip the LLM call.
from linkage import find_declared_docs

declared_docs = find_declared_docs(diff)

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] logic-error

The declared docs merge runs unconditionally after file discovery, including when relevant_files was set from a previous interactive review's accepted files. If a user ran [review-docs], rejected a doc file with a covers front-matter declaration, then ran [update-docs], the merge re-adds the rejected file — overriding the user's explicit rejection and breaking the [update-docs] contract documented in CLAUDE.md.

Suggested fix: Filter rejected files from declared_paths before merging, or skip linkage merge when honoring a previous review.

Comment thread src/linkage.py
Returns a list of (doc_path, "declared") tuples for docs that match,
and scans all doc files in doc_root.
"""
changed = extract_changed_paths(diff_text)

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] symlink-following

find_declared_docs uses Path(doc_root).rglob('*') which follows symlinks by default. In the GitHub Actions context where untrusted PRs are processed, a PR could introduce a symlink pointing outside the doc root; file contents would be read by parse_doc_frontmatter.

Suggested fix: Add if doc.is_symlink(): continue before processing each path.

Comment thread src/linkage.py
def parse_doc_frontmatter(file_path):
"""Extract code-to-docs front-matter from a documentation file.

Supports YAML front-matter (--- delimiters) for .md files, and

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] path-traversal

parse_doc_frontmatter reads files via Path(file_path).read_text() without calling validate_file_path() from security_utils.py, which is used for all file I/O in generation.py. While paths come from rglob (not direct user input), the inconsistency with the codebase's existing security pattern is a gap.

Suggested fix: Add validate_file_path(file_path, base_dir=doc_root) before reading, consistent with generation.py.

Comment thread src/linkage.py
for doc in Path(doc_root).rglob("*"):
if not doc.is_file() or doc.suffix not in doc_extensions:
continue
if ".doc-index" in str(doc):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] path-traversal

covers paths in front-matter are attacker-controlled values with no validation for .., absolute paths, or null bytes. Currently only used in string matching (no I/O), but lacks defense-in-depth.

Comment thread src/linkage.py

Returns a list of (doc_path, "declared") tuples for docs that match,
and scans all doc files in doc_root.
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] return-type-consistency

find_declared_docs returns tagged tuples (str, 'declared') but the call site immediately discards the tag. No other discovery function uses this pattern.

Comment thread src/linkage.py
@@ -0,0 +1,107 @@
"""Doc-to-code linkage: deterministic file selection from front-matter declarations."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] module-docstring

Single-line module docstring where most source modules use multi-line format with an elaborating paragraph.

Comment thread src/linkage.py
def extract_changed_paths(diff_text):
"""Extract all file paths changed in a unified diff."""
paths = set()
for match in re.finditer(r"^diff --git a/(.+?) b/", diff_text, re.MULTILINE):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] function-docstring

extract_changed_paths and find_declared_docs lack Args: and Returns: sections used by other public functions in the codebase.

Comment thread src/linkage.py

import re
from pathlib import Path

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] import-style

Missing blank line between stdlib and third-party import groups.

Comment thread src/linkage.py
return {}
parts = content.split("---", 2)
if len(parts) < 3:
return {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] edge-case

Split-based front-matter delimiter parsing has standard limitations with consecutive --- sequences. No data corruption; declarations silently ignored in edge cases.

@fullsend-ai-review fullsend-ai-review Bot added feature python Pull requests that update python code labels Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant