Repository navigation
feat(flare-docs): Python ecosystem, README usage examples, and the docs site - #356
Conversation
Adds two non-consent components so agentflare init and every SessionStart self-heal the setup needed to actually use flare-docs, flare-search, and lean-ctx through the gateway, instead of relying on hand-run CLI commands: - core-coaching: seeds/refreshes 4 builtin coaching rules (usedocs, usesearch, useleanctx, usetsearch) that nudge the flare gateway's docs/search/lean-ctx/tool-search wrappers over their native equivalents. Drift-protected across version bumps; a same-id rule the user has overridden to a different tier is left alone. - gateway-permissions: keeps ~/.claude/settings.json's permissions.allow containing the flare gateway tools (mcp__flare__docs, mcp__flare__search, mcp__flare__tool, ToolSearch) and strips superseded direct mcp__lean-ctx__* entries. Also widens coaching::store::list_rules to pub(crate) so components.rs can read existing rule state when deciding whether to seed or refresh.
Adds docs-site/, an Astro Starlight project documenting the flare-docs module (overview, supported languages, MCP tool reference, CLI reference, examples, and a comparison against Context7), reusing the landing page's exact design system (colors, fonts, mono headings) via customCss. site/ and docs-site/ are linked as sibling packages in an aube-workspace.yaml monorepo rooted at the repo root, with mise tasks (install/dev:site/dev:docs/ build:docs/deploy/tail) as the entry points. The two connect only at deploy time: site's deploy script builds docs-site out-of-tree and copies its output into site/public/docs before wrangler deploy, so day-to-day dev on either package never touches the other.
Adds Ecosystem::Python alongside Rust and npm: fetches a package's PyPI manifest, unpacks its pure-Python wheel, and indexes whichever typed source it carries — separate .pyi stubs, or (for PEP 561 inline-typed packages like click and pandas) the package's own annotated .py source with a py.typed marker — via a tree-sitter-python extraction pass mirroring the existing npm/.d.ts module. Falls back to typeshed's types-<package> convention on PyPI when a package ships neither. Wired into the CLI (`agentflare docs get <pkg> --ecosystem python`) and the MCP `docs` tool. Verified live against PyPI: requests correctly falls back to types-requests; click indexes 470 items from its own inline-typed source with no fallback needed.
Fills the one real gap Rust didn't have: rustdoc-JSON already carries a
crate's doc-comment Examples verbatim, but npm and Python previously
discarded the README/long-description entirely, keeping only .d.ts/.pyi
signatures.
Adds a shared, LLM-free fenced-code-block extractor (crates/flare-docs/src
/readme.rs) reused by both ecosystems at zero extra network cost: npm scans
every .md file already inside the tarball it downloads for .d.ts, and
Python reads info.description straight out of the PyPI manifest JSON it
already fetches (markdown releases only -- RST is explicitly out of scope
rather than mis-parsed). Each block is titled by its nearest heading and
indexed as npm-example/python-example, reconciled the same way API items
are.
Filters two kinds of noise before indexing: examples under process/meta
headings (Installation, Contributing, License, Testing, ...), and
shell-flavored blocks that are pure setup commands (git clone, pip install)
even under an otherwise-good heading like "Quick Start" -- verified live
against requests' real PyPI description, which previously surfaced its
"Cloning the repository" git commands as if they were usage examples.
Also fixes a path-matching gap in both the new markdown-file filter and the
existing Python inline-typed-package filter: a top-level `test/` directory
(no leading slash after tarball-prefix stripping) wasn't caught by a
mid-path `contains("/test/")` check.
… ones Now that flare-docs indexes usage examples too, add a comparison row and strengthen the "where flare-docs wins" case: examples are extracted verbatim from the package's own docs, not synthesized by an LLM the way Context7's snippets are -- so they can't drift from what the maintainer actually wrote.
…ment-components # Conflicts: # src/components.rs # src/mcp_server/flare_docs.rs
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds Python/PyPI documentation extraction and indexing, README example indexing for npm, an Astro/Starlight docs workspace with deployment wiring, coaching rule enforcement controls, and revised batching nudge milestones. ChangesPython documentation and workspace
Coaching rule enforcement
Batching nudge milestones
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/flare-docs/src/npm/mod.rs (1)
125-144: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winExtract examples from the requested package tarball.
When
manifest.has_typesis false,source_manifestis the@typesmanifest, so Lines 125-144 read examples from@types/<package>while storing them under the original package. Keep the declaration tarball selection, but fetch/extract Markdown frommanifest’s tarball; add a fallback regression test with distinct original and@typesREADMEs.🤖 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 `@crates/flare-docs/src/npm/mod.rs` around lines 125 - 144, The example extraction flow should use the requested package manifest rather than source_manifest, which may refer to the `@types` package. Keep source_manifest for declaration tarball selection, but build the Markdown tarball URL and fetch/extract Markdown from manifest’s tarball before populating examples. Add a regression test with distinct original-package and `@types` README content to verify examples come from the original package.
🧹 Nitpick comments (7)
site/package.json (1)
8-10: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
copy:docsis POSIX-only and silently nests if therm -rfever stops running.
rm -rf/cp -Rfail on Windows shells, sodeployisn't runnable there. The two commands are also coupled:cp -R src dstcreatesdstonly whendstis absent — if therm -rfis dropped or fails, the same line starts producingpublic/docs/dist. A single Node copy removes both problems.♻️ Cross-platform copy
- "copy:docs": "rm -rf public/docs && cp -R ../docs-site/dist public/docs", + "copy:docs": "node -e \"const fs=require('fs');fs.rmSync('public/docs',{recursive:true,force:true});fs.cpSync('../docs-site/dist','public/docs',{recursive:true});\"",🤖 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 `@site/package.json` around lines 8 - 10, Replace the POSIX-specific commands in the copy:docs script with a single cross-platform Node-based copy operation that removes or replaces public/docs before copying ../docs-site/dist. Keep deploy invoking build:docs, copy:docs, and wrangler deploy in the existing order, and ensure the destination cannot become nested under public/docs.mise.toml (1)
1-2: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRequired node version is declared nowhere, despite
astro@7.1.3needing>=22.12.0. The new workspace pulls Astro 7 (engines: node >=22.12.0) and wrangler 4 (>=22.0.0) peraube-lock.yaml, but neither the mise toolchain nor the workspace manifest states a node version, so contributors and CI silently use whatever is onPATH. The unpinnedaube = "latest"compounds this by floating the package manager that consumes the committed lockfile.
mise.toml#L1-L2: addnode = "22"to[tools]and replaceaube = "latest"with the version that generatedaube-lock.yaml.package.json#L1-L11: add"engines": { "node": ">=22.12.0" }so installs fail fast on an unsupported runtime.🤖 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 `@mise.toml` around lines 1 - 2, Declare the required Node.js runtime and pin the package manager consistently with the committed lockfile: in mise.toml at lines 1-2, add node = "22" under [tools] and replace aube = "latest" with the version that generated aube-lock.yaml; in package.json at lines 1-11, add an engines.node requirement of >=22.12.0.docs-site/src/styles/custom.css (1)
43-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winStylelint
value-keyword-caseis misfiring on font family names here — don't lowercase these.
Menlo,Consolas,Roboto,Helvetica,Arialare family names, not CSS keywords; lowercasing them breaks matching on case-sensitive platforms. Configure the rule to ignore font values instead.🔧 Stylelint config tweak
{ "rules": { "value-keyword-case": [ "lower", { "ignoreProperties": ["font", "font-family", "--mono", "--sans"] } ] } }🤖 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 `@docs-site/src/styles/custom.css` around lines 43 - 45, Update the Stylelint configuration for value-keyword-case to continue enforcing lowercase generally while ignoring font and font-family values, including the custom properties --mono and --sans. Leave the font-family declarations unchanged.Source: Linters/SAST tools
crates/flare-docs/src/python/fetch.rs (1)
94-104: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winConsider pinning the wheel URL to the expected file host.
pure_python_wheel_urlreturns whateverurlthe manifest carries, andpython::fetch_packagefetches it directly. A tampered or mirrored manifest response can therefore point the fetch at an arbitrary host. Anhttps://files.pythonhosted.org/prefix check here keeps the blast radius inside the registry you meant to talk to.🤖 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 `@crates/flare-docs/src/python/fetch.rs` around lines 94 - 104, The pure_python_wheel_url function must only return wheel URLs hosted by the expected registry. Validate the extracted url uses the https://files.pythonhosted.org/ prefix before returning it, and return None for other hosts while preserving the existing bdist_wheel and -none-any.whl filters.crates/flare-docs/src/python/extract.rs (1)
81-90: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueNested classes/functions qualify against the nearest class only.
enclosing_pathstops at the firstclass_definition, soOuter.Inner.methodbecomesInner.methodand a function nested inside a method is typedmethod. Both are acceptable for retrieval, but two same-named inner classes in one module will now collide onfq_pathand the dedup inpython/mod.rswill drop the second silently. Collecting the full class chain would remove that class of loss.🤖 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 `@crates/flare-docs/src/python/extract.rs` around lines 81 - 90, Update enclosing_path to traverse all ancestor class_definition nodes and build the fully qualified class chain in outer-to-inner order, rather than returning at the nearest class. Preserve the existing function or method name suffix behavior so nested classes and functions produce unique fq_path values and downstream deduplication does not discard entries.src/mcp_server/flare_docs.rs (1)
169-180: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd the Python counterpart of
npm_get_reads_the_npm_cache_prefix_not_the_docsrs_one.Three ecosystems now share one store; the npm arm has a test proving its
npm/...prefix isn't shadowed, and Python'spypi/...prefix has no equivalent coverage in this file. A pre-seededEcosystem::Python.docs_id_path("requests", "latest")+ecosystem: "python"get would pin the routing without any network.🤖 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/mcp_server/flare_docs.rs` around lines 169 - 180, Add a Python-specific routing test in the same test module as the existing npm cache-prefix test, pre-seeding the store with Ecosystem::Python.docs_id_path("requests", "latest") and ecosystem "python", then issuing the corresponding get request and asserting it reads the Python entry without network access. Reuse the existing test helpers and naming pattern for the npm counterpart.crates/flare-docs/src/python/mod.rs (1)
280-300: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the shared example slugify helper
crates/flare-docs/src/python/mod.rsandcrates/flare-docs/src/npm/mod.rsduplicate the same slug logic; move it into a shared docs helper so example paths stay aligned.src/mcp_server.rs::slugifyuses different rules and shouldn’t be grouped with this.🤖 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 `@crates/flare-docs/src/python/mod.rs` around lines 280 - 300, Extract the duplicated slugify logic from the Python and npm documentation modules into a shared docs helper, then update both callers to reuse that helper while preserving the current fallback and normalization behavior. Keep src/mcp_server.rs::slugify separate because its rules differ.
🤖 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 `@crates/flare-docs/src/npm/fetch.rs`:
- Around line 210-212: Update the Markdown extension check in the package path
iteration to compare a lowercased version of path, accepting uppercase variants
such as .MD and .MARKDOWN. Preserve the original path value for subsequent
storage and is_excluded_markdown processing.
In `@crates/flare-docs/src/python/fetch.rs`:
- Around line 211-224: Update the entry-reading logic around read_to_string so
it enforces MAX_PYI_BYTES and MAX_UNPACKED_BYTES using actual bytes read, rather
than trusting entry.size(). Avoid size as usize truncation by using checked,
architecture-independent comparisons, and read at most the remaining allowed
budget before converting valid UTF-8 data into buf. Preserve skipping malformed
non-UTF-8 entries and stop processing once the actual unpacked-byte budget is
exhausted.
In `@crates/flare-docs/src/python/mod.rs`:
- Around line 244-259: Update deduplication in the fetched .pyi processing loop
to distinguish overload variants by both each item's fq_path and signature,
preserving every unique signature while still removing exact duplicates. When
repeated names remain, disambiguate their stored paths with a stable suffix such
as -2, ensuring downstream references can retain all overload entries.
In `@crates/flare-docs/src/readme.rs`:
- Around line 61-76: Update is_setup_only_shell to normalize the optional
language label by trimming whitespace and lowercasing it before matching;
include the supported shell aliases such as “bash” and “shell-session” alongside
the existing labels, while preserving the current setup-prefix filtering
behavior.
In `@docs-site/src/content/docs/compare.md`:
- Around line 54-55: Update the source-of-truth comparison in compare.md to
include PyPI and Python’s published stubs or metadata alongside docs.rs and npm,
while preserving the existing explanation that flare-docs fetches directly from
each ecosystem’s authoritative source.
In `@docs-site/src/content/docs/how-it-works.md`:
- Around line 35-38: Add a language identifier such as text or console to the
opening fenced code block containing the docs.rs error example, while preserving
its existing content.
In `@docs-site/src/content/docs/mcp-tool.md`:
- Around line 20-27: The MCP reference and CLI link use an unsupported `#list`
anchor. In docs-site/src/content/docs/mcp-tool.md lines 20-27, add the promised
list/paging section with a list anchor, or, if no such section is intended,
update docs-site/src/content/docs/cli.md lines 40-42 to remove or replace the
`#list` fragment; keep both references consistent.
In `@docs-site/src/fonts/LICENSE.txt`:
- Around line 1-7: Add the exact upstream Commit Mono copyright notice and
Reserved Font Name header at the beginning of LICENSE.txt, before the existing
“This Font Software is licensed...” license body. Preserve the complete OFL-1.1
text unchanged after the added header.
In `@mise.toml`:
- Around line 28-30: Update the [tasks.tail] configuration to add depends =
["install"], ensuring the install task runs before aube run tail --filter
agentflare-web while preserving the existing description and command.
In `@site/README.md`:
- Around line 65-67: Update the README text near the deploy command to state
that public/docs/ is already ignored by site/.gitignore, removing the
instruction to add it. Preserve the guidance that public/docs/ is generated and
should not be hand-edited or committed.
In `@src/mcp_server.rs`:
- Line 1183: Update the description assigned in the MCP server tool metadata to
describe Python indexing as using PEP 561 type information from either .pyi
stubs or inline-typed source, and include README usage examples indexed by
index_examples. Keep the existing Python package source and typeshed fallback
details intact.
---
Outside diff comments:
In `@crates/flare-docs/src/npm/mod.rs`:
- Around line 125-144: The example extraction flow should use the requested
package manifest rather than source_manifest, which may refer to the `@types`
package. Keep source_manifest for declaration tarball selection, but build the
Markdown tarball URL and fetch/extract Markdown from manifest’s tarball before
populating examples. Add a regression test with distinct original-package and
`@types` README content to verify examples come from the original package.
---
Nitpick comments:
In `@crates/flare-docs/src/python/extract.rs`:
- Around line 81-90: Update enclosing_path to traverse all ancestor
class_definition nodes and build the fully qualified class chain in
outer-to-inner order, rather than returning at the nearest class. Preserve the
existing function or method name suffix behavior so nested classes and functions
produce unique fq_path values and downstream deduplication does not discard
entries.
In `@crates/flare-docs/src/python/fetch.rs`:
- Around line 94-104: The pure_python_wheel_url function must only return wheel
URLs hosted by the expected registry. Validate the extracted url uses the
https://files.pythonhosted.org/ prefix before returning it, and return None for
other hosts while preserving the existing bdist_wheel and -none-any.whl filters.
In `@crates/flare-docs/src/python/mod.rs`:
- Around line 280-300: Extract the duplicated slugify logic from the Python and
npm documentation modules into a shared docs helper, then update both callers to
reuse that helper while preserving the current fallback and normalization
behavior. Keep src/mcp_server.rs::slugify separate because its rules differ.
In `@docs-site/src/styles/custom.css`:
- Around line 43-45: Update the Stylelint configuration for value-keyword-case
to continue enforcing lowercase generally while ignoring font and font-family
values, including the custom properties --mono and --sans. Leave the font-family
declarations unchanged.
In `@mise.toml`:
- Around line 1-2: Declare the required Node.js runtime and pin the package
manager consistently with the committed lockfile: in mise.toml at lines 1-2, add
node = "22" under [tools] and replace aube = "latest" with the version that
generated aube-lock.yaml; in package.json at lines 1-11, add an engines.node
requirement of >=22.12.0.
In `@site/package.json`:
- Around line 8-10: Replace the POSIX-specific commands in the copy:docs script
with a single cross-platform Node-based copy operation that removes or replaces
public/docs before copying ../docs-site/dist. Keep deploy invoking build:docs,
copy:docs, and wrangler deploy in the existing order, and ensure the destination
cannot become nested under public/docs.
In `@src/mcp_server/flare_docs.rs`:
- Around line 169-180: Add a Python-specific routing test in the same test
module as the existing npm cache-prefix test, pre-seeding the store with
Ecosystem::Python.docs_id_path("requests", "latest") and ecosystem "python",
then issuing the corresponding get request and asserting it reads the Python
entry without network access. Reuse the existing test helpers and naming pattern
for the npm counterpart.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 79b2adde-51f0-4573-b8d4-a9fa5d188442
⛔ Files ignored due to path filters (5)
Cargo.lockis excluded by!**/*.lockdocs-site/public/favicon.svgis excluded by!**/*.svgdocs-site/src/assets/logo.svgis excluded by!**/*.svgdocs-site/src/fonts/commit-mono-400.woff2is excluded by!**/*.woff2docs-site/src/fonts/commit-mono-700.woff2is excluded by!**/*.woff2
📒 Files selected for processing (37)
.gitignoreaube-lock.yamlaube-workspace.yamlcrates/flare-docs/Cargo.tomlcrates/flare-docs/src/ecosystem.rscrates/flare-docs/src/lib.rscrates/flare-docs/src/npm/fetch.rscrates/flare-docs/src/npm/mod.rscrates/flare-docs/src/python/extract.rscrates/flare-docs/src/python/fetch.rscrates/flare-docs/src/python/mod.rscrates/flare-docs/src/readme.rsdocs-site/.gitignoredocs-site/README.mddocs-site/astro.config.mjsdocs-site/package.jsondocs-site/src/content.config.tsdocs-site/src/content/docs/cli.mddocs-site/src/content/docs/compare.mddocs-site/src/content/docs/examples.mddocs-site/src/content/docs/how-it-works.mddocs-site/src/content/docs/index.mdxdocs-site/src/content/docs/mcp-tool.mddocs-site/src/fonts/LICENSE.txtdocs-site/src/styles/custom.cssdocs-site/tsconfig.jsonmise.tomlpackage.jsonsite/.gitignoresite/README.mdsite/package.jsonsite/public/index.htmlsrc/cli/docs.rssrc/coaching/mod.rssrc/mcp_server.rssrc/mcp_server/flare_docs.rssrc/mcp_server/types.rs
| if !path.ends_with(".md") && !path.ends_with(".markdown") { | ||
| continue; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Match Markdown extensions case-insensitively.
README.MD and other uppercase-extension guides are skipped before is_excluded_markdown can normalize their path, losing package examples. Compare against a lowercased path while preserving the original path for storage.
🤖 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 `@crates/flare-docs/src/npm/fetch.rs` around lines 210 - 212, Update the
Markdown extension check in the package path iteration to compare a lowercased
version of path, accepting uppercase variants such as .MD and .MARKDOWN.
Preserve the original path value for subsequent storage and is_excluded_markdown
processing.
| let size = entry.size(); | ||
| if size as usize > MAX_PYI_BYTES { | ||
| continue; | ||
| } | ||
| total_bytes += size; | ||
| if total_bytes > MAX_UNPACKED_BYTES { | ||
| break; | ||
| } | ||
| let mut buf = String::new(); | ||
| // A non-UTF-8 file is malformed; skip it rather than failing the | ||
| // whole package, so one bad entry can't lose the rest of the API. | ||
| if entry.read_to_string(&mut buf).is_err() { | ||
| continue; | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
The byte caps are enforced against declared sizes, so they don't actually bound memory.
entry.size() is the size the archive claims; read_to_string then reads the entry unbounded. A wheel that under-declares its sizes passes both checks and still inflates arbitrarily into buf, which is exactly the hostile/corrupt case the MAX_UNPACKED_BYTES comment on Line 13 says it guards. size as usize also truncates on 32-bit targets, letting a >4 GiB declared entry slip under MAX_PYI_BYTES.
Cap the actual read instead of trusting the header.
🛡️ Proposed fix: bound the read, not the declared size
let size = entry.size();
- if size as usize > MAX_PYI_BYTES {
+ if size > MAX_PYI_BYTES as u64 {
continue;
}
- total_bytes += size;
- if total_bytes > MAX_UNPACKED_BYTES {
- break;
- }
let mut buf = String::new();
// A non-UTF-8 file is malformed; skip it rather than failing the
// whole package, so one bad entry can't lose the rest of the API.
- if entry.read_to_string(&mut buf).is_err() {
+ // Reading through `take` keeps a lying size field from inflating
+ // memory past the cap.
+ if std::io::Read::take(&mut entry, MAX_PYI_BYTES as u64)
+ .read_to_string(&mut buf)
+ .is_err()
+ {
continue;
}
+ total_bytes += buf.len() as u64;
+ if total_bytes > MAX_UNPACKED_BYTES {
+ out.push(PyiFile { path, source: buf });
+ break;
+ }
out.push(PyiFile { path, source: buf });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let size = entry.size(); | |
| if size as usize > MAX_PYI_BYTES { | |
| continue; | |
| } | |
| total_bytes += size; | |
| if total_bytes > MAX_UNPACKED_BYTES { | |
| break; | |
| } | |
| let mut buf = String::new(); | |
| // A non-UTF-8 file is malformed; skip it rather than failing the | |
| // whole package, so one bad entry can't lose the rest of the API. | |
| if entry.read_to_string(&mut buf).is_err() { | |
| continue; | |
| } | |
| let size = entry.size(); | |
| if size > MAX_PYI_BYTES as u64 { | |
| continue; | |
| } | |
| let mut buf = String::new(); | |
| // A non-UTF-8 file is malformed; skip it rather than failing the | |
| // whole package, so one bad entry can't lose the rest of the API. | |
| // Reading through `take` keeps a lying size field from inflating | |
| // memory past the cap. | |
| if std::io::Read::take(&mut entry, MAX_PYI_BYTES as u64) | |
| .read_to_string(&mut buf) | |
| .is_err() | |
| { | |
| continue; | |
| } | |
| total_bytes += buf.len() as u64; | |
| if total_bytes > MAX_UNPACKED_BYTES { | |
| out.push(PyiFile { path, source: buf }); | |
| break; | |
| } | |
| out.push(PyiFile { path, source: buf }); |
🤖 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 `@crates/flare-docs/src/python/fetch.rs` around lines 211 - 224, Update the
entry-reading logic around read_to_string so it enforces MAX_PYI_BYTES and
MAX_UNPACKED_BYTES using actual bytes read, rather than trusting entry.size().
Avoid size as usize truncation by using checked, architecture-independent
comparisons, and read at most the remaining allowed budget before converting
valid UTF-8 data into buf. Preserve skipping malformed non-UTF-8 entries and
stop processing once the actual unpacked-byte budget is exhausted.
| // Stub packages re-export heavily (a package's `__init__.pyi` commonly | ||
| // re-imports names its submodules define), so the same symbol can show | ||
| // up in more than one file. Keep the first occurrence -- files are | ||
| // sorted, so the choice is stable across refetches. | ||
| let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new(); | ||
|
|
||
| for file in &fetched.pyi { | ||
| let items = match extract::extract(&file.source) { | ||
| Ok(items) => items, | ||
| // One unparseable file must not lose the rest of the package. | ||
| Err(_) => continue, | ||
| }; | ||
| for item in items { | ||
| if !seen.insert(item.fq_path.clone()) { | ||
| continue; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
First-occurrence dedup collapses @overload families to a single signature.
Overloaded stubs (@overload def get(...) -> A / -> B) all share one fq_path, so every variant after the first is dropped — precisely the signatures an agent needs to avoid a wrong call. Consider keying seen on (fq_path, signature) and disambiguating the stored path (e.g. an -2 suffix) when a name repeats.
🤖 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 `@crates/flare-docs/src/python/mod.rs` around lines 244 - 259, Update
deduplication in the fetched .pyi processing loop to distinguish overload
variants by both each item's fq_path and signature, preserving every unique
signature while still removing exact duplicates. When repeated names remain,
disambiguate their stored paths with a stable suffix such as -2, ensuring
downstream references can retain all overload entries.
| fn is_setup_only_shell(language: Option<&str>, code: &str) -> bool { | ||
| let is_shell_like = matches!( | ||
| language, | ||
| None | Some("bash" | "sh" | "shell" | "console" | "zsh" | "text") | ||
| ); | ||
| if !is_shell_like { | ||
| return false; | ||
| } | ||
| code.lines() | ||
| .map(str::trim) | ||
| .filter(|l| !l.is_empty()) | ||
| .all(|l| { | ||
| let l = l.trim_start_matches('$').trim(); | ||
| SETUP_ONLY_PREFIXES.iter().any(|p| l.starts_with(p)) | ||
| }) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Normalize shell fence labels before setup filtering.
Bash and common labels such as shell-session bypass this check, so long install-only blocks under “Quick Start” are indexed. Lowercase/trim the label and include supported shell aliases.
🤖 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 `@crates/flare-docs/src/readme.rs` around lines 61 - 76, Update
is_setup_only_shell to normalize the optional language label by trimming
whitespace and lowercasing it before matching; include the supported shell
aliases such as “bash” and “shell-session” alongside the existing labels, while
preserving the current setup-prefix filtering behavior.
| docs. `flare-docs` fetches the source of truth directly — docs.rs for Rust, a package's | ||
| own published types for npm — so there's no guessing which search result is trustworthy. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Include Python/PyPI in the source-of-truth comparison.
This page advertises Python support, but this summary names only docs.rs and npm. Mention PyPI and Python’s published stubs/metadata as well so readers do not infer that Python is excluded from direct registry fetching.
🤖 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 `@docs-site/src/content/docs/compare.md` around lines 54 - 55, Update the
source-of-truth comparison in compare.md to include PyPI and Python’s published
stubs or metadata alongside docs.rs and npm, while preserving the existing
explanation that flare-docs fetches directly from each ecosystem’s authoritative
source.
| ## Configuring your own agent or MCP client | ||
|
|
||
| If you're setting up an MCP client yourself (rather than using agentflare's own | ||
| gateway), the tool is called `docs` and takes an `action` of `search`, `get`, `list`, | ||
| or `refresh`, plus a package name and optional ecosystem/version. The | ||
| [CLI reference](/docs/cli/) documents the same four operations with the exact flags — | ||
| it's the easiest way to see what's available, since the CLI and the agent-facing tool | ||
| do the same things. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Keep the MCP reference page and CLI link consistent.
The CLI links to /docs/mcp-tool/#list, but docs-site/src/content/docs/mcp-tool.md does not define that heading or anchor. Add the target section/anchor or update the CLI link.
docs-site/src/content/docs/mcp-tool.md#L20-L27: add the promisedlist/paging section and anchor, or remove the unsupported reference.docs-site/src/content/docs/cli.md#L40-L42: change the#listfragment if no target section is intended.
📍 Affects 2 files
docs-site/src/content/docs/mcp-tool.md#L20-L27(this comment)docs-site/src/content/docs/cli.md#L40-L42
🤖 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 `@docs-site/src/content/docs/mcp-tool.md` around lines 20 - 27, The MCP
reference and CLI link use an unsupported `#list` anchor. In
docs-site/src/content/docs/mcp-tool.md lines 20-27, add the promised list/paging
section with a list anchor, or, if no such section is intended, update
docs-site/src/content/docs/cli.md lines 40-42 to remove or replace the `#list`
fragment; keep both references consistent.
| This Font Software is licensed under the SIL Open Font License, Version 1.1. | ||
| This license is copied below, and is also available with a FAQ at: | ||
| http://scripts.sil.org/OFL | ||
|
|
||
| ----------------------------------------------------------- | ||
| SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | ||
| ----------------------------------------------------------- |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add the Commit Mono copyright notice above the license body.
OFL-1.1 §2 requires each redistributed copy to contain "the above copyright notice and this license"; this file ships only the license body with no Copyright (c) … (Commit Mono) / Reserved Font Name header, so the notice is missing.
📄 Proposed header addition
+Copyright (c) 2022-2024, Eigil Nikolajsen (https://commitmono.com),
+with Reserved Font Name "Commit Mono".
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.Copy the exact notice from the upstream Commit Mono distribution rather than the placeholder above.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| This Font Software is licensed under the SIL Open Font License, Version 1.1. | |
| This license is copied below, and is also available with a FAQ at: | |
| http://scripts.sil.org/OFL | |
| ----------------------------------------------------------- | |
| SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |
| ----------------------------------------------------------- | |
| Copyright (c) 2022-2024, Eigil Nikolajsen (https://commitmono.com), | |
| with Reserved Font Name "Commit Mono". | |
| This Font Software is licensed under the SIL Open Font License, Version 1.1. | |
| This license is copied below, and is also available with a FAQ at: | |
| http://scripts.sil.org/OFL | |
| ----------------------------------------------------------- | |
| SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |
| ----------------------------------------------------------- |
🤖 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 `@docs-site/src/fonts/LICENSE.txt` around lines 1 - 7, Add the exact upstream
Commit Mono copyright notice and Reserved Font Name header at the beginning of
LICENSE.txt, before the existing “This Font Software is licensed...” license
body. Preserve the complete OFL-1.1 text unchanged after the added header.
| [tasks.tail] | ||
| description = "Tail live Cloudflare Worker logs for agentflare.dev" | ||
| run = "aube run tail --filter agentflare-web" |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
tail is the only task missing depends = ["install"].
aube run tail --filter agentflare-web needs site/'s wrangler installed, so this fails on a fresh clone.
🔧 Proposed fix
[tasks.tail]
description = "Tail live Cloudflare Worker logs for agentflare.dev"
+depends = ["install"]
run = "aube run tail --filter agentflare-web"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| [tasks.tail] | |
| description = "Tail live Cloudflare Worker logs for agentflare.dev" | |
| run = "aube run tail --filter agentflare-web" | |
| [tasks.tail] | |
| description = "Tail live Cloudflare Worker logs for agentflare.dev" | |
| depends = ["install"] | |
| run = "aube run tail --filter agentflare-web" |
🤖 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 `@mise.toml` around lines 28 - 30, Update the [tasks.tail] configuration to add
depends = ["install"], ensuring the install task runs before aube run tail
--filter agentflare-web while preserving the existing description and command.
| `aube deploy` (or `mise run deploy`) runs all three. `public/docs/` is generated, not | ||
| hand-edited — add `/public/docs/` to `site/.gitignore` so the copied build output isn't | ||
| committed. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Stale instruction — /public/docs/ is already ignored in this PR.
site/.gitignore gains /public/docs/ in this same change, so telling readers to add it reads as an outstanding TODO. State the fact instead.
📝 Proposed wording
`aube deploy` (or `mise run deploy`) runs all three. `public/docs/` is generated, not
-hand-edited — add `/public/docs/` to `site/.gitignore` so the copied build output isn't
-committed.
+hand-edited — it's git-ignored via `/public/docs/` in `site/.gitignore`, so the copied
+build output is never committed.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| `aube deploy` (or `mise run deploy`) runs all three. `public/docs/` is generated, not | |
| hand-edited — add `/public/docs/` to `site/.gitignore` so the copied build output isn't | |
| committed. | |
| `aube deploy` (or `mise run deploy`) runs all three. `public/docs/` is generated, not | |
| hand-edited — it's git-ignored via `/public/docs/` in `site/.gitignore`, so the copied | |
| build output is never committed. |
🤖 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 `@site/README.md` around lines 65 - 67, Update the README text near the deploy
command to state that public/docs/ is already ignored by site/.gitignore,
removing the instruction to add it. Preserve the guidance that public/docs/ is
generated and should not be hand-edited or committed.
| // --- docs tool --- | ||
| #[tool( | ||
| description = "On-demand third-party package/API documentation — search|get|list|refresh. Fetches and caches docs in a global store shared across all projects, separate from project-scoped documents. Rust crates via docs.rs (ecosystem=\"rust\", the default); npm packages via npmjs.org, indexed per exported symbol from their TypeScript declarations with a DefinitelyTyped fallback for untyped packages (ecosystem=\"npm\"; scoped names like \"@types/node\" are detected automatically)." | ||
| description = "On-demand third-party package/API documentation — search|get|list|refresh. Fetches and caches docs in a global store shared across all projects, separate from project-scoped documents. Rust crates via docs.rs (ecosystem=\"rust\", the default); npm packages via npmjs.org, indexed per exported symbol from their TypeScript declarations with a DefinitelyTyped fallback for untyped packages (ecosystem=\"npm\"; scoped names like \"@types/node\" are detected automatically); Python packages via PyPI, indexed from their PEP 561 .pyi type stubs with a typeshed fallback for untyped packages (ecosystem=\"python\")." |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Description undersells what the Python path actually indexes.
extract_pyi also handles inline-typed packages (a py.typed marker plus annotated .py source — click, pandas), and index_examples indexes README usage examples. As written, "PEP 561 .pyi type stubs" reads as stub-only, which may lead an agent to skip ecosystem="python" for the majority of modern packages. A phrase like "PEP 561 type information (.pyi stubs or inline-typed source), plus README usage examples" matches the implementation.
🤖 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/mcp_server.rs` at line 1183, Update the description assigned in the MCP
server tool metadata to describe Python indexing as using PEP 561 type
information from either .pyi stubs or inline-typed source, and include README
usage examples indexed by index_examples. Keep the existing Python package
source and typeshed fallback details intact.
…ad of firing every call Previously the batching_nudge fired on every single tool call once a streak crossed the trailing-window threshold, spamming the same message. Now it only fires at streak milestones (3, 6, 12, 24, ...), computed statelessly from recent_tool_calls history.
… enforced rule Coaching rules were 100% advisory: a nudge in systemMessage the agent could ignore. Adds an enforced flag (# Enforce: true header, coaching enforce <id> [--off] CLI) that the PreToolUse hook checks before falling through to advisory nudges — a match denies the call with a redirect-framed reason instead of just suggesting the preferred tool. Adopts lean-ctx's own PreToolUse mechanism as the reference (studied in ~/workspace/refs/lean-ctx): it substitutes tool input rather than denying, which only works when the substitute is the same tool with different input (e.g. a Bash command rewrite). None of agentflare's coaching rules are same-tool swaps, so this instead reuses the existing deny-decision shape from hook_redirect.rs, with the reason phrased as a redirect. Of the four builtin rules, only usesearch (WebFetch/WebSearch) and usetsearch (ToolSearch) are marked enforced by default: they're clean 1:1 substitutes with no fallback the mandatory tool can't cover. useleanctx stays advisory since ctx_* can't handle every path/case native Bash/Read can (hard-blocking risks a self-lockout), and usedocs stays advisory because its trigger (Edit/Write) isn't a same-action substitute for mcp__flare__docs — blocking edits to redirect to a docs lookup would just break editing.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/coaching/store.rs (1)
104-132: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winKeep enforced rules tool-triggerable after updates.
Line 104 preserves
enforced, but an update can replace its trigger withNone. The file remains marked mandatory whileenforced_rule_reason_for_toolcan never match it. Reject this update unless the replacement trigger has at least one tool.Proposed fix
let enforced = existing .iter() .find(|r| r.id == id) .map(|r| r.enforced) .unwrap_or(false); +if enforced && trigger.as_ref().map_or(true, |trigger| trigger.tools.is_empty()) { + return Err(format!( + "rule '{id}' is enforced and must retain a tool trigger" + )); +}🤖 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/coaching/store.rs` around lines 104 - 132, Validate updates in the rule-writing flow after locating the existing rule via enforced and before write_rule_file: when an enforced rule is being updated, require the replacement trigger to contain at least one tool and return an error otherwise. Preserve existing behavior for non-enforced rules and updates that provide a tool-triggerable trigger.
🤖 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/coaching/store.rs`:
- Around line 165-173: Update the enforcement-toggle flow around
rule::write_rule_file so set_enforced changes only the existing “# Enforce”
header in the rule file. Preserve the original body, formatting, and date rather
than regenerating content from target.body or rewriting the full rule.
In `@src/optimize/runtime.rs`:
- Around line 202-215: Update the streak-tracking flow around
is_batch_nudge_milestone and pre_tool_use to persist the active tool’s
consecutive-call count in SessionRecord, resetting it whenever the tool changes.
Use this persisted count for milestone evaluation instead of deriving
trailing_streak_len solely from the truncated prior-call history, while
preserving the existing milestone behavior.
---
Outside diff comments:
In `@src/coaching/store.rs`:
- Around line 104-132: Validate updates in the rule-writing flow after locating
the existing rule via enforced and before write_rule_file: when an enforced rule
is being updated, require the replacement trigger to contain at least one tool
and return an error otherwise. Preserve existing behavior for non-enforced rules
and updates that provide a tool-triggerable trigger.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 9fa261b3-3464-4444-baaa-77e2faae12b1
📒 Files selected for processing (8)
src/cli/coaching.rssrc/coaching/cli.rssrc/coaching/mod.rssrc/coaching/rule.rssrc/coaching/store.rssrc/components.rssrc/hook.rssrc/optimize/runtime.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- src/coaching/mod.rs
| /// True at the doubling milestones a streak length lands on: BATCH_WINDOW, | ||
| /// 2x that, 4x that, .... Firing at every one of these (not every call past | ||
| /// the threshold) means a burst gets nudged once, and a pathologically long | ||
| /// streak still gets occasional reminders instead of either going silent | ||
| /// forever or repeating on every single call. | ||
| fn is_batch_nudge_milestone(streak_len: usize) -> bool { | ||
| if streak_len < BATCH_WINDOW { | ||
| return false; | ||
| } | ||
| let mut milestone = BATCH_WINDOW; | ||
| while milestone < streak_len { | ||
| milestone *= 2; | ||
| } | ||
| milestone == streak_len |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Persist the streak count; milestones past 6 cannot fire in production.
pre_tool_use retains only 10 prior calls after invoking this function, so trailing_streak_len can never exceed 11 at runtime. The 12/24/etc. milestones are therefore unreachable despite the direct unit test. Track the active tool’s consecutive-call count in SessionRecord (resetting it when the tool changes), rather than deriving it solely from the truncated history.
🤖 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/optimize/runtime.rs` around lines 202 - 215, Update the streak-tracking
flow around is_batch_nudge_milestone and pre_tool_use to persist the active
tool’s consecutive-call count in SessionRecord, resetting it whenever the tool
changes. Use this persisted count for milestone evaluation instead of deriving
trailing_streak_len solely from the truncated prior-call history, while
preserving the existing milestone behavior.
write_rule_file crossed clippy's default 7-arg threshold when the enforced param was added; allow it like the codebase's other multi-field constructors. The fmt diff is pre-existing drift in flare-docs and the coaching files from this branch's own history, not from this commit.
Summary
site/as sibling packages in an aube-workspace monorepo. The two connect only at deploy time —site's deploy buildsdocs-siteout-of-tree and copies the output intosite/public/docs.Ecosystem::Pythonalongside Rust and npm — fetches a package's PyPI manifest, unpacks its wheel, and indexes whichever typed source it carries: separate.pyistubs, or (for PEP 561 inline-typed packages like click/pandas) the package's own annotated.pysource. Falls back to typeshed'stypes-<package>convention when a package ships neither. Verified live against PyPI:requestscorrectly falls back totypes-requests;clickindexes 470 items from its own inline-typed source with no fallback needed..mdfile already inside the tarball it downloads, and Python readsinfo.descriptionstraight out of the PyPI manifest JSON it already fetches (markdown releases only). Filters process/meta noise (Installation, Contributing, License headings; shell blocks that are pure setup commands likegit clone/pip install) — verified live againstrequests, whose "Cloning the repository" git commands previously surfaced as if they were usage examples.docs-site/src/content/docs/compare.md) updated to reflect all of the above against Context7.Test plan
cargo test --workspace— 865 + 107 (flare-docs) + all other crates passing, 0 failures, on top of a merge withmaster's independently-landed docs-cache retention/eviction feature (feat(store): bound the docs cache with retention, eviction, and page reclaim #348) and other workcargo build --workspaceclean, no warningshono), PyPI (requests,click)mise run build:docs(Astro Starlight build) succeeds, 7 pagesSummary by CodeRabbit