Skip to content

feat: scheduled full-repo documentation drift audit - #72

Open
Benkapner wants to merge 1 commit into
mainfrom
feat/scheduled-audit
Open

feat: scheduled full-repo documentation drift audit#72
Benkapner wants to merge 1 commit into
mainfrom
feat/scheduled-audit

Conversation

@Benkapner

Copy link
Copy Markdown
Collaborator

Summary

Adds a scheduled audit mode that walks the entire docs tree and identifies stale documentation.

  • New mode: audit for schedule triggers
  • Walks docs tree using existing folder indexes, asks the LLM to rate each doc's staleness (fresh/stale/very-stale)
  • Reports findings as a single GitHub Issue (create-or-update, never a new issue per run)
  • audit-budget input bounds cost on large repos (default 20 files); reports what was skipped
  • New src/audit.py module

Test plan

  • uv run pytest -v passes (420 tests)
  • Lint clean
  • Audit report format groups by severity correctly

A PR diff is a delta; documentation describes end state. Per-PR
triggering catches incremental drift but misses accumulated
staleness. Add an audit mode for schedule triggers that walks the
docs tree using folder indexes, assesses each doc's staleness via
LLM, and reports findings as a single updated GitHub Issue (never
a new issue per run). Budget cap bounds cost on large repos and
reports what was skipped.
@Benkapner
Benkapner requested a review from csoceanu August 17, 2026 05:54
@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:55 AM UTC · Completed 6:16 AM UTC

Commit: 38c58bd · View workflow run →

@fullsend-ai-review

Copy link
Copy Markdown

Review

Findings

Medium

  • [logic-error] src/audit.py:92 — Severity parsing uses substring matching (severity in text.upper()), causing false positives. If the LLM responds with "FRESH: docs are not stale", the check for STALE matches the word "stale" in the reasoning text before FRESH is checked (iteration order: VERY-STALE → STALE → FRESH), misclassifying a fresh document as stale.
    Remediation: Use text.upper().startswith(severity) or a regex like re.match(r'^(VERY-STALE|STALE|FRESH)\b', text.upper()) to match only the leading severity token.

  • [api-contract] src/audit.py:123post_audit_issue() accepts a repo parameter and falls back to GITHUB_REPOSITORY, but never passes --repo to the gh CLI commands. After setup_docs_environment() changes the working directory to a cloned docs repo, gh infers the repo from the current directory’s git remote, creating issues in the wrong repository in separate-docs-repo mode.
    Remediation: Pass --repo, repo to each gh command (gh label create, gh issue list, gh issue edit, gh issue create).

  • [scope-coherence] action.yml:76 — The mode input description advertises three values (comment, detect-only, audit) but detect-only is completely unimplemented. A user setting mode: detect-only gets silent fallthrough to comment behavior, which may fail in a scheduled workflow context (no PR or comment body).
    Remediation: Remove detect-only from the description until implemented, or add validation for unrecognized mode values.

  • [error-handling-pattern] src/audit.py:97 — The _assess_doc() except block does not call check_context_error(e). Every other LLM API call handler in the codebase (discovery.py, doc_index.py, generation.py, jira_integration.py) calls this function to surface actionable context-window errors.
    Remediation: Import check_context_error from config and call it before the print statement in the except block.

  • [missing-doc] README.md — The "Optional Action Inputs" table does not list the new mode and audit-budget inputs. Users have no way to discover these inputs or learn how to configure the audit feature.
    Remediation: Add mode and audit-budget rows to the table and add a section explaining the audit mode workflow.

  • [missing-doc] CLAUDE.md — The source modules table, environment variables table, and command flows section do not reflect the new audit.py module, MODE/AUDIT_BUDGET env vars, or the audit execution flow.
    Remediation: Add audit.py to the modules table, MODE and AUDIT_BUDGET to the env vars table, and an "Audit mode" section to command flows.

  • [incomplete-doc] README.md — The "How It Works" section says "Triggered by PR Comments" but the new audit mode runs on schedule/workflow_dispatch, not PR comments.
    Remediation: Add mention of audit mode and its trigger mechanism.

Low

  • [error-handling-gap] src/suggest_docs.py:270int(os.environ.get("AUDIT_BUDGET", "20")) lacks error handling. A non-numeric value causes an unhandled ValueError, inconsistent with how MAX_CONTEXT_CHARS is handled in config.py.

  • [test-inadequate] tests/test_audit.py — Tests only cover format_audit_report(). Core functions run_audit(), _assess_doc(), and post_audit_issue() are untested. 194 lines of new code with 26 lines of tests likely falls below the 60% coverage minimum.

  • [input-validation] src/suggest_docs.py:262MODE env var read without validation against an allowlist. Unrecognized values (e.g., typo auditt) silently fall through to comment mode.

  • [input-validation] src/audit.py:15max_files parameter has no bounds validation. Zero or negative values silently skip all files; very large values could cause excessive LLM API calls.

  • [missing-authorization] — This non-trivial feature addition (new module, new action inputs, new execution mode) has no linked issue. Consider creating a tracking issue.

  • [module-docstring-format] src/audit.py:1 — Module docstring does not follow the codebase pattern of structured "This module handles:" inventory used by peer modules.

  • [import-organization] src/audit.py:9 — Imports lack section comments (# Import configuration, etc.) used in peer modules like discovery.py and generation.py.

  • [stale-doc] CLAUDE.md:8 — Trigger description says "triggered by issue_comment events" but audit mode uses schedule/workflow_dispatch triggers.


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/audit.py
text = (response.choices[0].message.content or "").strip()

for severity in ("VERY-STALE", "STALE", "FRESH"):
if severity in text.upper():

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

Severity parsing uses substring matching (severity in text.upper()), causing false positives. An LLM response like 'FRESH: docs are not stale' matches STALE before FRESH due to iteration order (VERY-STALE, STALE, FRESH), misclassifying fresh documents as stale.

Suggested fix: Use text.upper().startswith(severity) or a regex to match only the leading severity token.

Comment thread src/audit.py
return "\n".join(lines)


def post_audit_issue(findings, repo=None):

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] api-contract

post_audit_issue() accepts a repo parameter but never passes --repo to gh CLI commands. After setup_docs_environment() changes the working directory to a cloned docs repo, gh infers the repo from the current directory's git remote, creating issues in the wrong repository in separate-docs-repo mode.

Suggested fix: Pass --repo repo to each gh command (gh label create, gh issue list, gh issue edit, gh issue create).

Comment thread action.yml
description: 'Path to a Markdown style configuration file (.md) containing documentation style guidelines. If not set, auto-detects .code-to-docs/style.md in the repository root.'
required: false
default: ''
mode:

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] scope-coherence

The mode input description advertises three values (comment, detect-only, audit) but detect-only is completely unimplemented. A user setting mode: detect-only gets silent fallthrough to comment behavior, which may fail in a scheduled workflow context.

Suggested fix: Remove detect-only from the description until implemented, or add validation for unrecognized mode values.

Comment thread src/audit.py
return severity.lower(), reason

return "stale", text[:200]
except Exception as e:

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] error-handling-pattern

The _assess_doc() except block does not call check_context_error(e). Every other LLM API call handler in the codebase (discovery.py, doc_index.py, generation.py, jira_integration.py) calls this function to surface actionable context-window errors.

Suggested fix: Import check_context_error from config and call it before the print statement in the except block.

Comment thread src/suggest_docs.py
if not setup_docs_environment():
print("Failed to set up docs environment")
return
budget = int(os.environ.get("AUDIT_BUDGET", "20"))

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] error-handling-gap

AUDIT_BUDGET env var parsed with int() without error handling. A non-numeric value causes an unhandled ValueError, inconsistent with how MAX_CONTEXT_CHARS is handled in config.py.

Suggested fix: Wrap in try/except ValueError with fallback to 20.

Comment thread src/suggest_docs.py
return

# Handle audit mode (scheduled full-repo drift check)
mode = os.environ.get("MODE", "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.

[low] input-validation

MODE env var read without validation against an allowlist. Unrecognized values silently fall through to comment mode.

Suggested fix: Validate MODE is one of the expected values and warn on unrecognized input.

Comment thread src/audit.py
from security_utils import run_command_safe, sanitize_output


def run_audit(max_files=20):

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] input-validation

max_files parameter has no bounds validation. Zero or negative values silently skip all files; very large values could cause excessive LLM API calls.

Suggested fix: Clamp max_files to a reasonable range (e.g. 1-500).

Comment thread src/audit.py
@@ -0,0 +1,194 @@
"""Scheduled full-repo documentation drift audit.

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-format

Module docstring does not follow the codebase pattern of structured 'This module handles:' inventory used by peer modules.

Comment thread src/audit.py

import json
import os

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-organization

Imports lack section comments (# Import configuration, etc.) used in peer modules like discovery.py and generation.py.

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