Skip to content

Implement bundled skill file reads (1.3.4)#188

Open
leynos wants to merge 13 commits into
mainfrom
1-3-4-skill-read-file-interface
Open

Implement bundled skill file reads (1.3.4)#188
leynos wants to merge 13 commits into
mainfrom
1-3-4-skill-read-file-interface

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented May 20, 2026

Summary

This branch implements roadmap task (1.3.4) by adding a read-only skill_read_file interface for bundled skill resources. It gives the model a narrow, skill-scoped way to read SKILL.md, references/**, and allowed text assets from loaded bundles without exposing raw filesystem access.

Roadmap task: (1.3.4)
Execplan: docs/execplans/1-3-4-skill-read-file-interface.md

Review walkthrough

Validation

  • cargo test --features test-helpers --lib skills::file_read -- --nocapture: passed, 17 tests.
  • cargo test --features test-helpers --lib skill -- --nocapture: passed, 270 tests.
  • bunx markdownlint-cli2 docs/execplans/1-3-4-skill-read-file-interface.md docs/users-guide.md docs/agent-skills-support.md docs/developers-guide.md docs/axinite-architecture-overview.md docs/roadmap.md FEATURE_PARITY.md: passed with 0 errors.
  • git diff --check: passed.
  • make check-fmt: passed.
  • make lint: passed.
  • make test: passed, including 4091 nextest tests and 5 GitHub tool tests.
  • make all: passed after the final source and ExecPlan updates, including 4091 nextest tests and 5 GitHub tool tests.
  • coderabbit review --agent: actionable findings were addressed. The final confirmation retry is blocked by CodeRabbit service rate limits after the last non-Unix fallback comment; see the ExecPlan notes for the command logs.

Notes

The non-Unix file-open fallback documents its current mitigation explicitly: it relies on the pre-open symlink_metadata rejection and the post-open size revalidation, with a TODO for platform-specific atomic no-follow semantics if non-Unix deployments become first-class for this tool.

No Python e2e test was added because this branch changes a model-facing built-in tool contract, not a new HTTP route, CLI command, persistence workflow, UI flow, or network boundary. The Rust adapter and rstest-bdd scenarios cover the externally visible tool behaviour.

References

Summary by Sourcery

Add a read-only skill_read_file tool and underlying skill-scoped file read subsystem so models can read specific bundled skill resources via bundle-relative paths without exposing host filesystem details.

New Features:

  • Introduce the skill_read_file built-in tool that reads allowed text files from loaded skill bundles using a strict JSON schema.
  • Add a skills::file_read module that enforces bundle-relative path policy, scoped filesystem I/O, and structured success/error responses for skill file reads.

Enhancements:

  • Register skill_read_file in the tool registry and include it in the installed-skill read-only attenuation set.
  • Extend documentation and architecture notes to describe bundled file read behaviour, path policy, and developer extension points.
  • Update feature parity and roadmap docs to reflect completion of the bundled skill file read interface and scoped read capability.

Documentation:

  • Add an ExecPlan documenting the design, constraints, risks, and validation steps for the skill_read_file interface.
  • Update user and developer guides, architecture overview, agent skills support docs, and feature parity notes to describe the new skill-scoped file reading tool, its schema, and limitations.

Tests:

  • Add unit, property-based, and behavioural (rstest-bdd) tests covering path validation, inline text reads, non-inline assets, error codes, and symlink/traversal rejection for skill bundle file reads.
  • Extend skill tool tests to cover the new skill_read_file tool schema, execution behaviour, and BDD scenarios for allowed reads and denied filesystem traversal.

leynos added 2 commits May 19, 2026 01:40
Add the approval-gated ExecPlan for roadmap item `1.3.4`.
The plan captures the intended `skill_read_file` boundary, validation
strategy, testing expectations, documentation updates, and review gates
before implementation begins.
Update the pre-implementation ExecPlan with the current worktree path,
branch and pull request state, and the latest planning evidence.

Narrow behavioural test guidance to Rust `rstest-bdd` plus existing
Python `pytest` end-to-end patterns where system-level coverage is
actually needed.
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Sorry @leynos, you have reached your weekly rate limit of 2500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e1d02f4d-0006-4ff4-9317-687661215cb1

📥 Commits

Reviewing files that changed from the base of the PR and between 6d5bbce and d4fc942.

⛔ Files ignored due to path filters (1)
  • src/skills/file_read/snapshots/ironclaw__skills__file_read__tests__skill_read_file_error_path_not_readable.snap is excluded by !**/*.snap
📒 Files selected for processing (2)
  • src/skills/file_read/io.rs
  • src/skills/file_read/tests.rs
 ___________________________
< Goodbye, pre-merge panic. >
 ---------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).

Walkthrough

Implement the read-only skill_read_file interface (roadmap 1.3.4): add deterministic response types and inline-size cap, validate bundle-relative paths, perform Linux-safe scoped reads and UTF‑8 decoding, add a strict-schema tool adapter, comprehensive tests (unit/proptest/BDD), attenuation and registry wiring, and synced docs/execplan.

Changes

Scoped Skill File Read Feature

Layer / File(s) Summary
Docs, execplan and roadmap updates
FEATURE_PARITY.md, docs/agent-skills-support.md, docs/axinite-architecture-overview.md, docs/developers-guide.md, docs/execplans/1-3-4-skill-read-file-interface.md, docs/roadmap.md, docs/users-guide.md
Update feature parity and P0 checklist; document installed-skill allowlist (skill_read_file), lazy bundled-file fetch semantics and adapter seam; add ExecPlan 1.3.4 and mark task complete; expand architecture/developer/user guidance.
Domain contract and public types
src/skills/file_read.rs
Add MAX_SKILL_READ_FILE_BYTES, SkillReadFileResponse (Success/Error), SkillReadFileSuccess, SkillReadFileErrorResponse, SkillReadFileError, SkillReadFileErrorCode, SkillReadFileMetadata, and async entrypoint read_skill_file(...) that validates paths and delegates to I/O.
Bundle-relative path validation
src/skills/file_read/validation.rs
Normalise display path, reject empty/backslashes/traversal/root/prefix, allow only SKILL.md or references/**/assets/**, provide path_not_readable() and is_asset_path() helpers.
Scoped filesystem I/O, containment, and decoding
src/skills/file_read/io.rs
Implement read_validated_skill_file: open validated target under skill root (Linux: openat2 RESOLVE_BENEATH/NO_SYMLINKS), reject non-regular files, pre-check size metadata, enforce inline cap with two-stage read, infer MIME type, decode UTF‑8 (map failures to NonInlineAsset or InvalidUtf8), and build structured error/metadata payloads. Non-Linux emits fail-closed warning.
Domain unit and property tests
src/skills/file_read/tests.rs
Add temporary bundle fixture and tests for inline-text success (including at MAX bytes), entrypoint reading, disallowed path rejections, binary asset NonInlineAsset with mime metadata, FileTooLarge, missing file PathNotReadable, Unix symlink escape rejections, non-Linux fail-closed behaviour, and proptest coverage for path invariants plus insta snapshots.
SkillReadFileTool adapter and strict schema
src/tools/builtin/skill_tools.rs
Add SkillReadFileTool exposing skill_read_file with strict JSON schema (skill, path, additionalProperties=false); perform registry lookup, call read_skill_file, and serialise SkillReadFileResponse (handle unknown skills).
Tool adapter tests and BDD feature coverage
src/tools/builtin/skill_tools/tests.rs, src/tools/builtin/skill_tools/features/skill_read_file.feature
Add SkillReadFileWorld test fixtures, helper to insert test bundle, include skill_read_file in tool-schema tests, strict-schema assertion, async tests for success and unknown-skill error, and BDD scenarios asserting returned content and traversal denial semantics.
Module exposure and tool registration
src/skills/mod.rs, src/tools/builtin/mod.rs, src/tools/registry/builtins.rs
Expose file_read module publicly, re-export SkillReadFileTool, register SkillReadFileTool in ToolRegistry::register_skill_tools, and update debug count and doc comment.
Attenuation: read-only safe tool classification
src/skills/attenuation.rs
Add skill_read_file to READ_ONLY_TOOLS for Installed-trust attenuation; update all_tools() test helper and test_installed_only_read_only assertions.

Sequence Diagram(s)

sequenceDiagram
  participant Model
  participant SkillReadFileTool
  participant SkillRegistry
  participant read_skill_file
  participant Filesystem

  Model->>SkillReadFileTool: skill_read_file(skill, path)
  SkillReadFileTool->>SkillRegistry: lookup(skill_name)
  alt skill exists
    SkillRegistry-->>SkillReadFileTool: LoadedSkill
    SkillReadFileTool->>read_skill_file: read_skill_file(LoadedSkill, path)
    read_skill_file->>read_skill_file: validate_bundle_relative_path(path)
    alt validation fails
      read_skill_file-->>SkillReadFileTool: SkillReadFileResponse::Error
    else validation succeeds
      read_skill_file->>Filesystem: open & read scoped path
      Filesystem-->>read_skill_file: Success or Error metadata
      read_skill_file-->>SkillReadFileTool: SkillReadFileResponse
    end
    SkillReadFileTool-->>Model: JSON tool output
  else skill not found
    SkillReadFileTool-->>Model: unknown_skill JSON error
  end
Loading

Suggested labels

Roadmap

"Add a bounded read and answer true,
Skill bundles yield text but not a host path view,
Symlinks denied, byte-caps kept tight,
JSON replies clear, both success and blight,
Tests, tool, docs aligned — the feature ships in sight."


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (2 errors, 4 warnings)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error Snapshot test uses hardcoded fabricated error message instead of actual validation function output, creating a vacuous test. Replace hardcoded snapshot test with real read_skill_file call to ensure snapshot guards the actual runtime contract, not fabricated values.
Module-Level Documentation ❌ Error The file_read module in src/skills/mod.rs lacks a documentation comment, unlike other public modules. Module re-exports require doc comments to guide API consumers. Add a documentation comment above pub mod file_read; in src/skills/mod.rs explaining the module purpose and relationship to skill bundle file reads.
Testing (Unit And Behavioural) ⚠️ Warning Snapshot test hard-codes fabricated message instead of guarding runtime contract; duplicated error tests lack parameterisation despite review comment. Fix snapshot_error_path_not_readable to use actual runtime helper; collapse 6 duplicated snapshot tests into single @rstest parameterised case.
Testing (Property / Proof) ⚠️ Warning Critical security invariants (size bounds, symlink rejection) lack property tests; path validation proptest is weak (implication-based, not comprehensive coverage). Add proptest for MAX_SKILL_READ_FILE_BYTES; add proptest for symlink rejection; strengthen disallowed_generated_paths to directly test all failure cases.
Testing (Compile-Time / Ui) ⚠️ Warning Snapshot test hardcodes error message that differs from the actual runtime helper output, defeating the snapshot's purpose of guarding real contract. Rewrite snapshot_error_path_not_readable() to call read_skill_file with a traversal path and snapshot the actual runtime response, guaranteeing the snapshot documents real behaviour.
Observability ⚠️ Warning Missing observability for unknown skill lookup: when a skill does not exist, no tracing is emitted at tool or module level to diagnose the operational failure. Add tracing::debug! at SkillReadFileTool.execute when lookup_skill returns None, logging skill name and error code, before creating the unknown_skill response.
✅ Passed checks (14 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly references the roadmap item (1.3.4) and accurately summarises the primary feature being implemented: bundled skill file reads.
Description check ✅ Passed The description covers summary, change type (new feature), linked roadmap item, comprehensive validation steps, security impact (file access policy), blast radius, and review walkthrough. All template sections are completed with substantive content.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
User-Facing Documentation ✅ Passed User-facing documentation for skill_read_file is comprehensive in docs/users-guide.md, documenting schema, constraints, success/error responses, and security properties without exposing host paths.
Developer Documentation ✅ Passed Developers-guide documents skill_read_file API; ExecPlan includes decision log; roadmap 1.3.4 marked complete; architecture documents updated; single language used throughout.
Unit Architecture ✅ Passed Clean separation: pure validation (no I/O), isolated async I/O, explicit Result types, injectable dependencies, no global state, single responsibilities, boundary-respecting tests.
Domain Architecture ✅ Passed Domain logic properly segregated from infrastructure. Filesystem operations hidden in private adapter layer; OS errors sanitised before model exposure; clean tool adapter boundary.
Security And Privacy ✅ Passed Path validation prevents traversal/symlinks, errors hide host paths, read-only tool gating, openat2 secure flags, strict input validation. No secrets or credentials discovered.
Performance And Resource Use ✅ Passed File reads bounded by MAX_SKILL_READ_FILE_BYTES with Vec pre-sizing and take() limiting; single-file per call; path validation O(n); short-lived locks; one-time cloning.
Concurrency And State ✅ Passed Lock released before await; no locks in file_read module; LoadedSkill is immutable and Clone; all filesystem ops are non-blocking async without additional synchronisation.
Architectural Complexity And Maintainability ✅ Passed Abstractions are justified and proportional: file_read module isolates I/O and validation; tool adapter mirrors existing patterns; no circular deps, no unnecessary traits.
Rust Compiler Lint Integrity ✅ Passed No lint suppressions found. All pub(super) functions properly used; correct cfg guards on Linux-specific code; narrow module boundaries; no artificial lint-appeasement patterns detected.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 1-3-4-skill-read-file-interface

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

@github-actions github-actions Bot added scope: docs Documentation size: XS < 10 changed lines (excluding docs) risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels May 20, 2026
codescene-delta-analysis[bot]

This comment was marked as outdated.

Introduce `skill_read_file` as a built-in read-only tool backed by a
skill-owned path policy. Allow models to read `SKILL.md`, `references/**`,
and text assets from loaded skill bundles without exposing raw filesystem
access.

Return structured skill-scoped JSON errors for unknown skills, disallowed
paths, binary assets, oversized files, invalid UTF-8, and I/O failures. Add
unit, property, schema, and `rstest-bdd` coverage for the happy path and
policy denials.

Document the user-facing tool contract, internal boundary rules, feature
parity status, and roadmap completion for item `1.3.4`.
@github-actions github-actions Bot added size: XL 500+ changed lines scope: tool/builtin Built-in tools risk: low Changes to docs, tests, or low-risk modules risk: medium Business logic, config, or moderate-risk modules and removed size: XS < 10 changed lines (excluding docs) risk: low Changes to docs, tests, or low-risk modules labels May 20, 2026
@leynos leynos changed the title Add bundled skill file reads (1.3.4) Implement bundled skill file reads (1.3.4) May 20, 2026
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos marked this pull request as ready for review May 24, 2026 21:29
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented May 24, 2026

Reviewer's Guide

Adds a new read-only, skill-scoped skill_read_file interface that lets the model read specific text files from bundled skills via a dedicated domain policy module, a new builtin tool adapter, and updated registration/attenuation, along with comprehensive tests and documentation updates to mark roadmap item 1.3.4 complete.

Sequence diagram for the new skill_read_file tool execution

sequenceDiagram
    actor Model
    participant SkillReadFileTool
    participant SkillRegistry
    participant LoadedSkill
    participant FileReadPolicy as skills_file_read

    Model->>SkillReadFileTool: execute(params, ctx)
    SkillReadFileTool->>SkillReadFileTool: require_str(params, "skill") / require_str(params, "path")
    SkillReadFileTool->>SkillRegistry: lookup_skill(skill_name)
    SkillRegistry-->>SkillReadFileTool: Option<LoadedSkill>
    alt skill found
        SkillReadFileTool->>LoadedSkill: clone()
        SkillReadFileTool->>FileReadPolicy: read_skill_file(loaded_skill, path)
        FileReadPolicy->>FileReadPolicy: validate_bundle_relative_path(path)
        FileReadPolicy->>FileReadPolicy: read_validated_skill_file(skill, relative_path)
        FileReadPolicy-->>SkillReadFileTool: SkillReadFileResponse
    else unknown skill
        SkillReadFileTool->>FileReadPolicy: SkillReadFileResponse::unknown_skill(skill_name, path)
        FileReadPolicy-->>SkillReadFileTool: SkillReadFileResponse
    end
    SkillReadFileTool->>SkillReadFileTool: serde_json::to_value(response)
    SkillReadFileTool-->>Model: ToolOutput::success(result, elapsed)
Loading

File-Level Changes

Change Details Files
Introduce domain-owned skill_read_file policy for bundle-relative file reads with strict path validation and error modeling.
  • Add src/skills/file_read.rs defining SkillReadFileResponse/Success/Error types, error codes, metadata, max size, and read_skill_file entrypoint that validates bundle-relative paths and delegates to I/O helpers.
  • Implement src/skills/file_read/validation.rs to normalize and validate allowed paths (SKILL.md, references/, assets/), reject traversal/absolute/unsupported paths, and classify asset paths.
  • Implement src/skills/file_read/io.rs to resolve validated paths under LoadedSkill.skill_root(), enforce root containment and file-type checks, apply size limits, infer MIME type, read UTF‑8 content, and return structured errors for IO, non-inline assets, oversized files, invalid UTF‑8, and unreadable paths, including Unix O_NOFOLLOW plus a documented non-Unix fallback.
  • Export the new module from src/skills/mod.rs and reuse MAX_PROMPT_FILE_SIZE as MAX_SKILL_READ_FILE_BYTES.
src/skills/file_read.rs
src/skills/file_read/io.rs
src/skills/file_read/validation.rs
src/skills/mod.rs
Add a SkillReadFileTool builtin that exposes skill_read_file to the model, wired into the tool registry and skill attenuation as a read-only capability.
  • Extend src/tools/builtin/skill_tools.rs with strict_object_schema helper and a new SkillReadFileTool that validates parameters, looks up LoadedSkill in SkillRegistry, delegates to read_skill_file, and serializes responses as ToolOutput::success.
  • Export SkillReadFileTool from src/tools/builtin/mod.rs and register it in ToolRegistry::register_skill_tools(), updating the debug log to reflect five skill tools.
  • Include skill_read_file in the READ_ONLY_TOOLS attenuation allowlist and update attenuation tests to expect it to be preserved when skills are active.
src/tools/builtin/skill_tools.rs
src/tools/builtin/mod.rs
src/tools/registry/builtins.rs
src/skills/attenuation.rs
Add targeted unit, property, and BDD-style tests to cover path policy, I/O behaviour, schema strictness, and adapter behaviour for skill_read_file.
  • Create src/skills/file_read/tests.rs with fixtures that build a temporary bundle, then test successful reads, path rejections (absolute, traversal, unsupported, directories), non-inline binary asset handling, oversized files, missing files, symlink rejection, and proptest-based validation of allowed/disallowed bundle-relative paths.
  • Extend src/tools/builtin/skill_tools/tests.rs with SkillReadFileWorld test harness, helper to insert a deploy-docs bundle into the registry, schema tests (including additionalProperties=false), direct tool execution tests (success and unknown_skill), and rstest-bdd glue for scenarios described in a new skill_read_file.feature file.
  • Add src/tools/builtin/skill_tools/features/skill_read_file.feature with two scenarios: successful referenced text read and denial of raw filesystem traversal.
src/skills/file_read/tests.rs
src/tools/builtin/skill_tools/tests.rs
src/tools/builtin/skill_tools/features/skill_read_file.feature
Update user, architecture, and developer documentation plus roadmap and feature parity notes to describe the new skill_read_file capability and its constraints.
  • Document skill_read_file usage, input schema, success response, and error behaviour in docs/users-guide.md, clarifying allowed paths and non-inline handling of binary/oversized assets.
  • Update docs/agent-skills-support.md to describe attenuation of skill_read_file and its role as a lazy bundled-file accessor behind a thin adapter that defers to src/skills/file_read.rs.
  • Extend docs/developers-guide.md with a subsection on scoped skill file reads (1.3.4), detailing policy rules, error modeling, adapter responsibilities, and rstest-bdd usage for the new feature.
  • Adjust docs/axinite-architecture-overview.md, docs/roadmap.md (mark 1.3.4 done), docs/execplans/1-3-4-skill-read-file-interface.md, and FEATURE_PARITY.md to reflect the new scoped bundled-file read capability in the skills subsystem.
docs/users-guide.md
docs/agent-skills-support.md
docs/developers-guide.md
docs/axinite-architecture-overview.md
docs/roadmap.md
docs/execplans/1-3-4-skill-read-file-interface.md
FEATURE_PARITY.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

sourcery-ai[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fbfd0f30ba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/skills/file_read/io.rs Outdated
@coderabbitai coderabbitai Bot added the Roadmap label May 24, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@src/skills/file_read/io.rs`:
- Around line 60-66: The current sequence canonicalize + starts_with on
canonical_target followed by reopening by pathname (see canonical_target,
canonical_root and read_file_contents) creates a TOCTOU and allows symlink
escapes in intermediate path components; change the implementation to open files
atomically relative to the validated skill root handle instead of re-opening by
path: obtain a handle/dirfd for the validated root (the canonical_root result),
then open the requested target via an anchored open that forbids symlinks in
every component (on Unix use openat2 with RESOLVE_NO_SYMLINKS or equivalent; on
non-Unix use a directory-capability crate like cap_std to open relative to the
root handle), and have read_file_contents accept/read from the opened file
descriptor/handle rather than reopening by pathname. Ensure all path resolution
and symlink checks happen inside that single atomic open call so there is no
check-then-open window.

In `@src/tools/registry/builtins.rs`:
- Around line 244-250: Update the method doc comment to reflect the five
registered skill management tools by adding "read_file" to the summary;
specifically, adjust the docstring that documents the behavior of the function
which calls self.register_sync with SkillReadFileTool, SkillInstallTool,
SkillRemoveTool, etc., so it reads something like "Register skill management
tools (list, search, read_file, install, remove)" to match the actual
registrations (symbols: register_sync, SkillReadFileTool, SkillInstallTool,
SkillRemoveTool).
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1c7e8f5e-2a9a-475e-ac23-2850a9ee517c

📥 Commits

Reviewing files that changed from the base of the PR and between e4a001e and fbfd0f3.

📒 Files selected for processing (18)
  • FEATURE_PARITY.md
  • docs/agent-skills-support.md
  • docs/axinite-architecture-overview.md
  • docs/developers-guide.md
  • docs/execplans/1-3-4-skill-read-file-interface.md
  • docs/roadmap.md
  • docs/users-guide.md
  • src/skills/attenuation.rs
  • src/skills/file_read.rs
  • src/skills/file_read/io.rs
  • src/skills/file_read/tests.rs
  • src/skills/file_read/validation.rs
  • src/skills/mod.rs
  • src/tools/builtin/mod.rs
  • src/tools/builtin/skill_tools.rs
  • src/tools/builtin/skill_tools/features/skill_read_file.feature
  • src/tools/builtin/skill_tools/tests.rs
  • src/tools/registry/builtins.rs

Comment thread src/skills/file_read/io.rs Outdated
Comment thread src/tools/registry/builtins.rs
Open bundled skill files relative to the canonical skill root with Linux
`openat2`, rejecting symlinks in every path component before a file
descriptor is returned. Fail closed on non-Linux targets rather than using
the weaker path-based fallback.

Add regression coverage for exact maximum-size reads and intermediate
symlink escapes, and tidy review comments in the tool docs and ExecPlan.
@github-actions github-actions Bot removed the risk: low Changes to docs, tests, or low-risk modules label May 25, 2026
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot removed the Roadmap label May 25, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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)
src/skills/file_read/io.rs (1)

51-59: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Eliminate the remaining root-handle race.

tokio::fs::canonicalize(root) on Lines 55-58 is followed by
std::fs::File::open(root) on Line 119, so the bundle root is still
re-resolved by pathname. A concurrent rename or symlink swap of the root between
those steps can retarget the dirfd and make the later openat2 checks operate
inside a different directory tree. Anchor the read to a stable root handle
before any attacker-controlled re-resolution.

Also applies to: 109-163

🤖 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 `@src/skills/file_read/io.rs` around lines 51 - 59, The code currently
canonicalizes the path then later re-opens the root by pathname, leaving a
TOCTOU where a concurrent rename/symlink swap can change the directory target;
to fix, open a stable directory handle for the bundle root up-front (e.g., open
a std::fs::File with O_DIRECTORY/Read-only) inside open_validated_target and use
that handle (its fd) for subsequent validation and for calls into
open_relative_to_root/open_relative_to_root_by_fd instead of re-resolving the
path; replace the tokio::fs::canonicalize(root) + later
std::fs::File::open(root) sequence by opening the root dir first, perform
fstat/canonicalization from the opened handle (or use fd-based checks), and pass
the handle/fd into open_relative_to_root / the code that currently calls
std::fs::File::open so all checks and openat/openat2 operations are anchored to
the stable root handle (affecting open_validated_target, open_relative_to_root,
and the later code around the existing std::fs::File::open call).
🤖 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 `@src/skills/file_read/io.rs`:
- Around line 74-95: The current read_file_contents (and the caller flow) uses
file.read_to_end which can exhaust memory if the file grows after the initial
metadata check; change read_file_contents to read at most
MAX_SKILL_READ_FILE_BYTES + 1 bytes (set Vec capacity to that bound) and stop as
soon as that extra byte is read so you can detect growth early; if the buffer
length exceeds MAX_SKILL_READ_FILE_BYTES return the typed FileTooLarge error
(using SkillReadFileErrorCode::FileTooLarge and the observed actual_size)
instead of proceeding to parse_utf8_content; keep references to OpenedTarget and
the size field for capacity decisions and ensure parse_utf8_content is only
called when the size is within the allowed limit.

In `@src/skills/file_read/tests.rs`:
- Around line 66-87: The failing tests assume Linux-specific behavior from
open_relative_to_root in src/skills/file_read/io.rs (which returns
SkillReadFileErrorCode::IoError on non-linux), so update the tests: add
#[cfg(target_os = "linux")] to reads_bundle_reference_text_at_max_size and to
the symlink-related tests (intermediate_symlink_paths_are_rejected and
symlink_paths_are_rejected) so they only expect Success/PathNotReadable on
Linux, or alternatively add conditional branches in those tests that assert the
non-Linux branch returns SkillReadFileErrorCode::IoError (referencing the test
functions reads_bundle_reference_text_at_max_size,
intermediate_symlink_paths_are_rejected, symlink_paths_are_rejected and the
error enum SkillReadFileErrorCode/open_relative_to_root) to make platform
behavior explicit.

---

Duplicate comments:
In `@src/skills/file_read/io.rs`:
- Around line 51-59: The code currently canonicalizes the path then later
re-opens the root by pathname, leaving a TOCTOU where a concurrent
rename/symlink swap can change the directory target; to fix, open a stable
directory handle for the bundle root up-front (e.g., open a std::fs::File with
O_DIRECTORY/Read-only) inside open_validated_target and use that handle (its fd)
for subsequent validation and for calls into
open_relative_to_root/open_relative_to_root_by_fd instead of re-resolving the
path; replace the tokio::fs::canonicalize(root) + later
std::fs::File::open(root) sequence by opening the root dir first, perform
fstat/canonicalization from the opened handle (or use fd-based checks), and pass
the handle/fd into open_relative_to_root / the code that currently calls
std::fs::File::open so all checks and openat/openat2 operations are anchored to
the stable root handle (affecting open_validated_target, open_relative_to_root,
and the later code around the existing std::fs::File::open call).
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 555701a4-605f-4ebd-b58b-fd7c0184572e

📥 Commits

Reviewing files that changed from the base of the PR and between fbfd0f3 and d9ae7a4.

📒 Files selected for processing (5)
  • docs/execplans/1-3-4-skill-read-file-interface.md
  • src/skills/file_read.rs
  • src/skills/file_read/io.rs
  • src/skills/file_read/tests.rs
  • src/tools/registry/builtins.rs

Comment thread src/skills/file_read/io.rs
Comment thread src/skills/file_read/tests.rs
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 25, 2026

@coderabbitai Have the following now been resolved?

Annul any requirements that violate the en-GB-oxendict spelling (-ize / -yse / -our) conventions (for example a request to replace "normalize" with "normalise" or "artefact" with "artifact"), or where the requirement unnecessarily increases cyclomatic complexity.

Please address the comments from this code review:

## Overall Comments
- In `read_validated_skill_file` / `resolve_and_validate_target` you canonicalize the skill root on every read; if `skill_read_file` is used frequently this could add avoidable filesystem overhead, so consider storing a precomputed canonical root on `LoadedSkill` (or similar) and reusing it here.
- The non-Unix `open_readonly_no_follow` fallback currently has weaker guarantees than the Unix path (no O_NOFOLLOW); given the explicit TODO, it might be worth also emitting a debug or warn log when this branch is used so operators are aware that symlink protections are reduced on those platforms.

## Individual Comments

### Comment 1
<location path="src/skills/file_read/tests.rs" line_range="50-16" />
<code_context>
+    SkillReadFixture { _dir: dir, skill }
+}
+
+#[rstest]
+#[tokio::test]
+async fn reads_bundle_reference_text(skill_read_fixture: SkillReadFixture) {
+    let response = read_skill_file(&skill_read_fixture.skill, "references/usage.md").await;
+
+    assert_eq!(
+        response,
+        SkillReadFileResponse::Success(SkillReadFileSuccess {
+            skill: "deploy-docs".to_string(),
+            path: "references/usage.md".to_string(),
+            mime_type: "text/markdown".to_string(),
+            content: "# Usage\n".to_string(),
+        })
+    );
+}
+
+#[rstest]
</code_context>
<issue_to_address>
**suggestion (testing):** Add a boundary test for exactly MAX_SKILL_READ_FILE_BYTES-sized files

The existing `oversized_text_returns_file_too_large` test covers the `> MAX_SKILL_READ_FILE_BYTES` path. Please add a test where the file size is exactly `MAX_SKILL_READ_FILE_BYTES` and assert it succeeds, so the boundary behavior (including the `usize` cast in `read_file_contents`) is explicitly covered and less likely to regress.

Suggested implementation:

```rust
    SkillReadFixture { _dir: dir, skill }
}

#[rstest]
#[tokio::test]
async fn reads_bundle_reference_text(skill_read_fixture: SkillReadFixture) {
    let response = read_skill_file(&skill_read_fixture.skill, "references/usage.md").await;

    assert_eq!(
        response,
        SkillReadFileResponse::Success(SkillReadFileSuccess {
            skill: "deploy-docs".to_string(),
            path: "references/usage.md".to_string(),
            mime_type: "text/markdown".to_string(),
            content: "# Usage\n".to_string(),
        })
    );
}

#[rstest]
#[tokio::test]
async fn reads_bundle_reference_text_at_max_size(skill_read_fixture: SkillReadFixture) {
    let response = read_skill_file(&skill_read_fixture.skill, "references/max_size.md").await;

    match response {
        SkillReadFileResponse::Success(success) => {
            assert_eq!(success.skill, "deploy-docs".to_string());
            assert_eq!(success.path, "references/max_size.md".to_string());
            assert_eq!(success.mime_type, "text/markdown".to_string());
            assert_eq!(success.content.len(), MAX_SKILL_READ_FILE_BYTES as usize);
        }
        other => panic!(
            "expected Success for references/max_size.md at MAX_SKILL_READ_FILE_BYTES, got: {:?}",
            other
        ),
    }
}

```

1. Ensure `MAX_SKILL_READ_FILE_BYTES` is in scope in this test module. If it is not already imported (it likely is for the `oversized_text_returns_file_too_large` test), add something like:
   `use crate::skills::file_read::MAX_SKILL_READ_FILE_BYTES;`
   at the top of `src/skills/file_read/tests.rs`.
2. Add a `references/max_size.md` file to the `deploy-docs` test bundle used by `SkillReadFixture`. Its size **must be exactly** `MAX_SKILL_READ_FILE_BYTES` bytes. The content can be arbitrary markdown as long as the total byte length matches the constant.
3. If the test bundle layout is defined via helper code or fixtures elsewhere (e.g., in a builder for `deploy-docs`), update that code to include `references/max_size.md` with the required size instead of creating the file manually.
</issue_to_address>

### Comment 2
<location path="src/skills/file_read.rs" line_range="60" />
<code_context>
+pub struct SkillReadFileSuccess {
+    /// Canonical skill identifier used for the read.
+    pub skill: String,
+    /// Normalised bundle-relative path that was read.
+    pub path: String,
+    /// Best-effort media type inferred from the bundle-relative path.
</code_context>
<issue_to_address>
**issue (review_instructions):** "Normalised" should use -ize spelling per en-GB-oxendict, e.g. "Normalized".

The project style requires en-GB-oxendict spellings, which prefer -ize/-yse forms. Please change "Normalised" here to "Normalized".

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.rs`

**Instructions:**
Comments must use en-GB-oxendict (-ize / -yse / -our) spelling and grammar.

</details>
</issue_to_address>

### Comment 3
<location path="src/skills/file_read.rs" line_range="73" />
<code_context>
+pub struct SkillReadFileErrorResponse {
+    /// Canonical skill identifier, or the requested identifier for unknown skills.
+    pub skill: String,
+    /// Requested or normalised bundle-relative path.
+    pub path: String,
+    /// Stable, model-facing error payload.
</code_context>
<issue_to_address>
**issue (review_instructions):** "normalised" should use -ize spelling per en-GB-oxendict, e.g. "normalized".

Same as above: to comply with en-GB-oxendict, please update "normalised" to "normalized" in this comment.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.rs`

**Instructions:**
Comments must use en-GB-oxendict (-ize / -yse / -our) spelling and grammar.

</details>
</issue_to_address>

### Comment 4
<location path="docs/execplans/1-3-4-skill-read-file-interface.md" line_range="314" />
<code_context>
+
+## Surprises & discoveries
+
+- Observation: `leta workspace add` succeeded, but the first Rust LSP query
+  failed because `rust-analyzer` was not installed for the active toolchain.
+  Evidence: `leta grep ...` reported that the Rust language server failed to
</code_context>
<issue_to_address>
**suggestion (review_instructions):** The acronym “LSP” is used without being expanded on first use; please spell it out (for example, “Language Server Protocol (LSP)”).

Per the style guidance, uncommon acronyms should be defined the first time they appear. Here, “LSP” is introduced without expansion. Consider wording like “Rust Language Server Protocol (LSP) query” on first use.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Define uncommon acronyms on first use.

</details>
</issue_to_address>

### Comment 5
<location path="docs/execplans/1-3-4-skill-read-file-interface.md" line_range="240" />
<code_context>
+  Mitigation: add attenuation only after unit tests prove no writes, network
+  calls, generic filesystem paths, or state mutation are involved.
+
+- Risk: Python e2e coverage may need deterministic tool-call plumbing rather
+  than a direct unit-level invocation.
+  Severity: medium.
</code_context>
<issue_to_address>
**suggestion (review_instructions):** The acronym “e2e” is used without expansion; please define it on first use (e.g. “end-to-end (e2e)”).

The instructions require defining uncommon acronyms when they first appear. “e2e” is introduced here without explanation. Something like “Python end-to-end (e2e) coverage” would satisfy the requirement.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Define uncommon acronyms on first use.

</details>
</issue_to_address>

codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot added the Roadmap label May 25, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@src/skills/file_read/io.rs`:
- Around line 170-179: open_root_directory currently calls the blocking
std::fs::OpenOptions::open synchronously which can block the Tokio runtime; make
open_root_directory async and perform the
OpenOptions::new().read(true).custom_flags(...).open(root) inside
tokio::task::spawn_blocking (await the JoinHandle), then map the result to
SkillReadFileError via io_error like before. Update the function signature
(open_root_directory -> async fn open_root_directory(...)) and ensure errors
from the spawned task are propagated/mapped into io_error so callers of
open_root_directory receive the same SkillReadFileError on failure.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0268ea47-b032-4540-9264-213d34f7d431

📥 Commits

Reviewing files that changed from the base of the PR and between d9ae7a4 and 2699241.

📒 Files selected for processing (3)
  • docs/execplans/1-3-4-skill-read-file-interface.md
  • src/skills/file_read/io.rs
  • src/skills/file_read/tests.rs

Comment thread src/skills/file_read/io.rs
Emit structured tracing at `skill_read_file` validation and read failure
points. Include stable skill, path, error, size, limit, and OS error fields
without exposing host filesystem paths.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Add insta JSON snapshots for every `SkillReadFileResponse` payload shape.
Lock in the untagged success and error layouts, snake_case error codes, and
metadata omission for responses without inline metadata.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot removed the Roadmap label May 26, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@src/skills/file_read/tests.rs`:
- Around line 281-291: The test snapshot_error_path_not_readable currently
constructs a hard-coded SkillReadFileResponse/SkillReadFileErrorResponse with
message "Path is not readable within the skill bundle"; instead call or reuse
the real helper (read_skill_file or the validation helper in
src/skills/file_read/validation.rs) with the same input ("../secret") to
generate the actual SkillReadFileResponse payload and assert_json_snapshot!
against that result so the snapshot reflects the runtime message ("File is not
available for reading") and full metadata from the real implementation.
- Around line 274-278: Multiple near-duplicate tests in
src/skills/file_read/tests.rs (e.g., snapshot_error_unknown_skill using
SkillReadFileResponse::unknown_skill) should be collapsed into a single
parameterized test: replace the repeated individual #[test] functions with an
#[rstest] table (or a helper that yields (snapshot_name, response)) that
enumerates each case name and the corresponding SkillReadFileResponse variant,
then call assert_json_snapshot! inside the single test using the parameterized
snapshot_name and response; update imports to include rstest macros if missing
and remove the old duplicate test functions.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ebf7a742-38db-42c8-8869-367faf72f2ef

📥 Commits

Reviewing files that changed from the base of the PR and between 2699241 and 6d5bbce.

⛔ Files ignored due to path filters (7)
  • src/skills/file_read/snapshots/ironclaw__skills__file_read__tests__skill_read_file_error_file_too_large.snap is excluded by !**/*.snap
  • src/skills/file_read/snapshots/ironclaw__skills__file_read__tests__skill_read_file_error_invalid_utf8.snap is excluded by !**/*.snap
  • src/skills/file_read/snapshots/ironclaw__skills__file_read__tests__skill_read_file_error_io_error.snap is excluded by !**/*.snap
  • src/skills/file_read/snapshots/ironclaw__skills__file_read__tests__skill_read_file_error_non_inline_asset.snap is excluded by !**/*.snap
  • src/skills/file_read/snapshots/ironclaw__skills__file_read__tests__skill_read_file_error_path_not_readable.snap is excluded by !**/*.snap
  • src/skills/file_read/snapshots/ironclaw__skills__file_read__tests__skill_read_file_error_unknown_skill.snap is excluded by !**/*.snap
  • src/skills/file_read/snapshots/ironclaw__skills__file_read__tests__skill_read_file_success.snap is excluded by !**/*.snap
📒 Files selected for processing (3)
  • src/skills/file_read.rs
  • src/skills/file_read/io.rs
  • src/skills/file_read/tests.rs

Comment thread src/skills/file_read/tests.rs Outdated
Comment thread src/skills/file_read/tests.rs Outdated
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 26, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

src/skills/file_read/tests.rs

Comment on lines +345 to +356

fn snapshot_error_io_error() {
    let response = SkillReadFileResponse::Error(SkillReadFileErrorResponse {
        skill: "deploy-docs".to_string(),
        path: "references/usage.md".to_string(),
        error: SkillReadFileError {
            code: SkillReadFileErrorCode::IoError,
            message: "File is not available for reading".to_string(),
            metadata: None,
        },
    });
    assert_json_snapshot!("skill_read_file_error_io_error", &response);
}

❌ New issue: Code Duplication
The module contains 5 functions with similar structure: snapshot_error_file_too_large,snapshot_error_invalid_utf8,snapshot_error_io_error,snapshot_error_non_inline_asset and 1 more functions

@coderabbitai

This comment was marked as resolved.

Collapse the `SkillReadFileResponse` JSON shape snapshots into one rstest
case table. Build the path-not-readable payload from the real validation
helper so the snapshot follows the runtime error message.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Move the Linux skill root directory open into `spawn_blocking` so the
synchronous `OpenOptions::open` call does not run on the Tokio runtime.
Preserve the existing skill-root read error mapping for open and join failures.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 26, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

Code Duplication

src/skills/file_read/tests.rs:

What lead to degradation?

The module contains 4 functions with similar structure: snapshot_error_file_too_large,snapshot_error_invalid_utf8,snapshot_error_io_error,snapshot_error_non_inline_asset

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

@coderabbitai

This comment was marked as resolved.

Add a shared helper for constructing `SkillReadFileResponse::Error` snapshot
fixtures. Keep existing snapshot names and payloads while reducing repeated
error-response boilerplate in the file-read JSON shape tests.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 26, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

src/skills/file_read/tests.rs

Comment on lines +345 to +356

fn snapshot_error_io_error() {
    let response = SkillReadFileResponse::Error(SkillReadFileErrorResponse {
        skill: "deploy-docs".to_string(),
        path: "references/usage.md".to_string(),
        error: SkillReadFileError {
            code: SkillReadFileErrorCode::IoError,
            message: "File is not available for reading".to_string(),
            metadata: None,
        },
    });
    assert_json_snapshot!("skill_read_file_error_io_error", &response);
}

❌ New issue: Code Duplication
The module contains 5 functions with similar structure: snapshot_error_file_too_large,snapshot_error_invalid_utf8,snapshot_error_io_error,snapshot_error_non_inline_asset and 1 more functions

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Current state of snapshot tests in src/skills/file_read/tests.rs ==="
cat -n src/skills/file_read/tests.rs

Length of output: 14814



File: src/skills/file_read/tests.rs
Symbols: snapshot_error_non_inline_asset, snapshot_error_file_too_large, snapshot_error_invalid_utf8, snapshot_error_io_error

The fix is already in place. The current branch (lines 263–278) defines snapshot_error_response as a private helper that centralises the SkillReadFileResponse::Error(SkillReadFileErrorResponse { … }) construction. All four flagged builder functions (snapshot_error_non_inline_asset, snapshot_error_file_too_large, snapshot_error_invalid_utf8, snapshot_error_io_error) already delegate to it in single-call bodies. No code changes are required.

The CodeScene finding was generated against the diff view of the original flat implementations, not the current branch state. Use the following message to suppress it in the CodeScene UI:


The four functions flagged by this diagnostic — snapshot_error_non_inline_asset, snapshot_error_file_too_large, snapshot_error_invalid_utf8, and snapshot_error_io_error — have already been refactored on this branch. A shared private helper, snapshot_error_response, centralises the repeated SkillReadFileResponse::Error(SkillReadFileErrorResponse { … }) construction; each builder now delegates to it in a single call. The CodeScene delta analysis was raised against the diff view of an earlier commit rather than the current branch state.

The remaining structural similarity across the builder functions is intentional: each constructs a distinct, named snapshot fixture with different fields, and the shared helper already eliminates the behavioural duplication that the DRY principle targets. Further consolidation (for example, a table-driven approach) would obscure the distinct fixture identities required by the rstest #[case] driver and would reduce clarity without reducing risk.

Add a shared helper for direct `SkillReadFileResponse::Error` snapshot
fixtures. Keep the validation-derived path-not-readable case separate so
it continues to exercise the runtime validation error payload.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 27, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

Code Duplication

src/skills/file_read/tests.rs:

What lead to degradation?

The module contains 2 functions with similar structure: snapshot_error_file_too_large,snapshot_error_non_inline_asset

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Current snapshot builder functions ==="
cat -n src/skills/file_read/tests.rs

Length of output: 14607



File: src/skills/file_read/tests.rs
Symbols: snapshot_error_file_too_large, snapshot_error_non_inline_asset


Assessment

Further refactoring here would be deleterious. Both functions already delegate to the make_error_response helper (lines 323–347). The residual similarity CodeScene detects is:

  1. The identical message string "Phase 1 does not return binary or oversized assets inline." — shared because both NonInlineAsset and FileTooLarge emit the same production message; that is a domain fact, not accidental duplication.
  2. A Some(SkillReadFileMetadata { size, mime_type, fetch_hint: NON_INLINE_FETCH_HINT }) construction — but with different size and mime_type values in each case.

Extracting a further make_non_inline_error_response(path, code, size, mime_type) wrapper would add indirection across only two already-concise builders, obscure the distinct production scenarios each fixture represents (NonInlineAsset for binary files, FileTooLarge for oversized text), and weaken the readability of the snapshot fixture semantics. The functions' named identities are required by the rstest #[case] driver.


CodeScene suppression message

snapshot_error_non_inline_asset and snapshot_error_file_too_large share a message string and a metadata structure because both NonInlineAsset and FileTooLarge produce the same production error message and the same SkillReadFileMetadata shape. Both functions already delegate all construction to the make_error_response helper introduced specifically to eliminate the structural duplication across this module; what remains is an intentional reflection of production semantics, not a DRY violation. The four argument values (path, code, size, mime_type) differ in every case and encode distinct scenarios — binary binary assets versus oversized text — that must remain separately identifiable to the rstest #[case] driver and to anyone reading the snapshot fixtures.

A further extraction into a make_non_inline_error_response wrapper would add an additional indirection layer across exactly two 8-line functions, obscure which production code path each builder exercises, and reduce fixture clarity without reducing change risk. The correct resolution is to suppress this diagnostic rather than introduce structure that harms readability.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 27, 2026

@coderabbitai Please investigate the cause of the following lint violations in the Windows branch of the CI matrix using codegraph exploration and research, identify a fix and provide an AI coding agent prompt for the fix:


error: unused import: `path_not_readable`
 --> src\skills\file_read\io.rs:7:40
  |
7 | use super::validation::{is_asset_path, path_not_readable};
  |                                        ^^^^^^^^^^^^^^^^^
  |
  = note: `-D unused-imports` implied by `-D warnings`
  = help: to override `-D warnings` add `#[allow(unused_imports)]`

error: could not compile `ironclaw` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...
error: could not compile `ironclaw` (lib test) due to 1 previous error
Error: Process completed with exit code 1.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== src/skills/file_read/io.rs — full file with line numbers ==="
cat -n src/skills/file_read/io.rs

echo ""
echo "=== All uses of path_not_readable in io.rs ==="
rg -n "path_not_readable" src/skills/file_read/io.rs

echo ""
echo "=== All uses of is_asset_path in io.rs ==="
rg -n "is_asset_path" src/skills/file_read/io.rs

Length of output: 12188



File: src/skills/file_read/io.rs
Symbol: path_not_readable (import line 7)


Root cause

Every call to path_not_readable in io.rs (lines 114, 139, 143, 214) lives inside a #[cfg(target_os = "linux")] block (metadata_for_opened_file, open_relative_to_root, openat2_error). On Windows, those blocks are compiled away, so the import on line 7 has no remaining use, triggering -D unused-imports.

is_asset_path is called at line 235 in the unconditional parse_utf8_content function and must remain ungated.

The fix is to split the single import into two lines — one unconditional and one cfg-gated:

use super::validation::is_asset_path;
#[cfg(target_os = "linux")]
use super::validation::path_not_readable;

Fix an unused-import Clippy error on Windows in `src/skills/file_read/io.rs`.

## File

`src/skills/file_read/io.rs`

## Problem

Line 7 imports both `is_asset_path` and `path_not_readable` unconditionally:

```rust
use super::validation::{is_asset_path, path_not_readable};
```

Every call site of `path_not_readable` (lines 114, 139, 143, 214) is inside a
`#[cfg(target_os = "linux")]` block. On Windows those blocks are compiled away,
leaving the import unused and triggering:

    error: unused import: `path_not_readable`
      --> src\skills\file_read\io.rs:7:40

`is_asset_path` is called from the unconditional `parse_utf8_content` function
(line 235) and must remain ungated.

## Required change

Replace line 7 with two separate `use` statements:

```rust
use super::validation::is_asset_path;
#[cfg(target_os = "linux")]
use super::validation::path_not_readable;
```

No other files need to change.

## Verify consistency

After applying the change, confirm that all other Linux-only helpers in the same
file (`metadata_for_opened_file`, `open_root_directory`, `openat2_error`, and
the Linux `open_relative_to_root` overload) already carry
`#[cfg(target_os = "linux")]`, so the cfg-gated import is consistent with the
existing pattern in this file.

## Validation

Run:

```
cargo check --all --no-default-features --features libsql
```

on a non-Linux host (or simulate with `--target x86_64-pc-windows-msvc` if
cross-compilation is configured) and confirm that neither
`unused-imports` nor `dead-code` warnings appear for `io.rs`.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 27, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

✅ 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules scope: docs Documentation scope: tool/builtin Built-in tools size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant