Skip to content

feat(flare-docs): Python ecosystem, README usage examples, and the docs site - #356

Merged
getappz merged 10 commits into
masterfrom
core-module-enforcement-components
Jul 28, 2026
Merged

getappz merged 10 commits into
masterfrom
core-module-enforcement-components

Conversation

@getappz

@getappz getappz commented Jul 28, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • docs.agentflare.dev: new Astro Starlight docs site for flare-docs, reusing the landing page's exact design system, linked to site/ as sibling packages in an aube-workspace monorepo. The two connect only at deploy time — site's deploy builds docs-site out-of-tree and copies the output into site/public/docs.
  • Python (PyPI) ecosystem support: Ecosystem::Python alongside Rust and npm — fetches a package's PyPI manifest, unpacks its wheel, and indexes whichever typed source it carries: separate .pyi stubs, or (for PEP 561 inline-typed packages like click/pandas) the package's own annotated .py source. Falls back to typeshed's types-<package> convention when a package ships neither. 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.
  • Usage examples from npm/Python docs: a shared, LLM-free fenced-code-block extractor now indexes real usage examples — npm scans every .md file already inside the tarball it downloads, and Python reads info.description straight 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 like git clone/pip install) — verified live against requests, whose "Cloning the repository" git commands previously surfaced as if they were usage examples.
  • Comparison page (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 with master's independently-landed docs-cache retention/eviction feature (feat(store): bound the docs cache with retention, eviction, and page reclaim #348) and other work
  • cargo build --workspace clean, no warnings
  • Live-verified against real registries: npm (hono), PyPI (requests, click)
  • mise run build:docs (Astro Starlight build) succeeds, 7 pages

Summary by CodeRabbit

  • New Features
    • Added Python/PyPI documentation support, including API extraction, type-stub fallback, and usage examples.
    • Added README-based examples for npm and Python packages.
    • Added coaching rule enforcement controls and mandatory-rule indicators.
    • Added a new documentation site with CLI guidance, examples, comparisons, and supported-ecosystem details.
  • Bug Fixes
    • Improved batching reminders so they appear only at meaningful usage milestones.
    • Preserved coaching enforcement settings when rules are refreshed.
  • Documentation
    • Expanded CLI, MCP, deployment, and workspace documentation.

shiva added 6 commits July 27, 2026 17:24
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
@coderabbitai

coderabbitai Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

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

Changes

Python documentation and workspace

Layer / File(s) Summary
Workspace and Astro documentation site
.gitignore, aube-lock.yaml, aube-workspace.yaml, docs-site/*, mise.toml, package.json
Adds the JavaScript workspace, locked dependencies, Astro/Starlight site, documentation pages, theme styling, and development tasks.
Site deployment and navigation
site/.gitignore, site/README.md, site/package.json, site/public/index.html
Builds and copies the docs site during deployment, documents the monorepo workflow, ignores generated output, and adds a /docs navigation link.
README example extraction and npm indexing
crates/flare-docs/src/readme.rs, crates/flare-docs/src/npm/*
Extracts qualifying Markdown usage examples from npm archives and indexes them with npm documentation.
Python manifest, wheel, and API extraction
crates/flare-docs/Cargo.toml, crates/flare-docs/src/python/*, crates/flare-docs/src/lib.rs
Parses PyPI manifests, extracts typed wheel sources, derives public Python API items, and validates the extraction behavior with tests.
Python fetch and storage pipeline
crates/flare-docs/src/python/mod.rs
Fetches package data, falls back to typeshed packages, stores overviews, indexes API items and examples, and reconciles refetched content.
Ecosystem routing and integrations
crates/flare-docs/src/ecosystem.rs, src/cli/docs.rs, src/mcp_server/*
Adds Python ecosystem parsing, storage paths, hints, CLI dispatch, MCP dispatch, and request documentation.

Coaching rule enforcement

Layer / File(s) Summary
Persisted enforcement state
src/coaching/rule.rs, src/coaching/store.rs, src/coaching/mod.rs
Persists an enforced rule flag, preserves it during updates, validates enforcement changes, and derives tool denial reasons.
Default rules and CLI enforcement
src/components.rs, src/cli/coaching.rs, src/coaching/cli.rs
Synchronizes enforced default rules, adds the coaching enforce command, and marks mandatory rules in CLI output.
Pre-tool enforcement gate
src/hook.rs
Denies tool execution when a matching enforced coaching rule is found.

Batching nudge milestones

Layer / File(s) Summary
Consecutive streak milestone logic
src/optimize/runtime.rs
Changes batching nudges to trigger at doubling milestones and resets the streak after a different tool call.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • getappz/agentflare#316: Extends the existing flare-docs retrieval flow that this PR expands with Python/PyPI support.
  • getappz/agentflare#342: Directly relates to the ecosystem abstraction extended here with Ecosystem::Python.
  • getappz/agentflare#352: Relates to the coaching enforcement pipeline and maintenance of built-in enforced rule state.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly summarizes the main changes: flare-docs Python support, usage examples, and the docs site.
Description check ✅ Passed The description includes the required Summary and Test plan sections and is mostly complete, though Notes for reviewers is missing.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch core-module-enforcement-components

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 win

Extract examples from the requested package tarball.

When manifest.has_types is false, source_manifest is the @types manifest, 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 from manifest’s tarball; add a fallback regression test with distinct original and @types READMEs.

🤖 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:docs is POSIX-only and silently nests if the rm -rf ever stops running.

rm -rf / cp -R fail on Windows shells, so deploy isn't runnable there. The two commands are also coupled: cp -R src dst creates dst only when dst is absent — if the rm -rf is dropped or fails, the same line starts producing public/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 win

Required node version is declared nowhere, despite astro@7.1.3 needing >=22.12.0. The new workspace pulls Astro 7 (engines: node >=22.12.0) and wrangler 4 (>=22.0.0) per aube-lock.yaml, but neither the mise toolchain nor the workspace manifest states a node version, so contributors and CI silently use whatever is on PATH. The unpinned aube = "latest" compounds this by floating the package manager that consumes the committed lockfile.

  • mise.toml#L1-L2: add node = "22" to [tools] and replace aube = "latest" with the version that generated aube-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 win

Stylelint value-keyword-case is misfiring on font family names here — don't lowercase these.

Menlo, Consolas, Roboto, Helvetica, Arial are 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 win

Consider pinning the wheel URL to the expected file host.

pure_python_wheel_url returns whatever url the manifest carries, and python::fetch_package fetches it directly. A tampered or mirrored manifest response can therefore point the fetch at an arbitrary host. An https://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 value

Nested classes/functions qualify against the nearest class only.

enclosing_path stops at the first class_definition, so Outer.Inner.method becomes Inner.method and a function nested inside a method is typed method. Both are acceptable for retrieval, but two same-named inner classes in one module will now collide on fq_path and the dedup in python/mod.rs will 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 win

Add 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's pypi/... prefix has no equivalent coverage in this file. A pre-seeded Ecosystem::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 win

Extract the shared example slugify helper crates/flare-docs/src/python/mod.rs and crates/flare-docs/src/npm/mod.rs duplicate the same slug logic; move it into a shared docs helper so example paths stay aligned. src/mcp_server.rs::slugify uses 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

📥 Commits

Reviewing files that changed from the base of the PR and between 952b09d and 884acaf.

⛔ Files ignored due to path filters (5)
  • Cargo.lock is excluded by !**/*.lock
  • docs-site/public/favicon.svg is excluded by !**/*.svg
  • docs-site/src/assets/logo.svg is excluded by !**/*.svg
  • docs-site/src/fonts/commit-mono-400.woff2 is excluded by !**/*.woff2
  • docs-site/src/fonts/commit-mono-700.woff2 is excluded by !**/*.woff2
📒 Files selected for processing (37)
  • .gitignore
  • aube-lock.yaml
  • aube-workspace.yaml
  • crates/flare-docs/Cargo.toml
  • crates/flare-docs/src/ecosystem.rs
  • crates/flare-docs/src/lib.rs
  • crates/flare-docs/src/npm/fetch.rs
  • crates/flare-docs/src/npm/mod.rs
  • crates/flare-docs/src/python/extract.rs
  • crates/flare-docs/src/python/fetch.rs
  • crates/flare-docs/src/python/mod.rs
  • crates/flare-docs/src/readme.rs
  • docs-site/.gitignore
  • docs-site/README.md
  • docs-site/astro.config.mjs
  • docs-site/package.json
  • docs-site/src/content.config.ts
  • docs-site/src/content/docs/cli.md
  • docs-site/src/content/docs/compare.md
  • docs-site/src/content/docs/examples.md
  • docs-site/src/content/docs/how-it-works.md
  • docs-site/src/content/docs/index.mdx
  • docs-site/src/content/docs/mcp-tool.md
  • docs-site/src/fonts/LICENSE.txt
  • docs-site/src/styles/custom.css
  • docs-site/tsconfig.json
  • mise.toml
  • package.json
  • site/.gitignore
  • site/README.md
  • site/package.json
  • site/public/index.html
  • src/cli/docs.rs
  • src/coaching/mod.rs
  • src/mcp_server.rs
  • src/mcp_server/flare_docs.rs
  • src/mcp_server/types.rs

Comment on lines +210 to +212
if !path.ends_with(".md") && !path.ends_with(".markdown") {
continue;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment on lines +211 to +224
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;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Suggested change
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.

Comment on lines +244 to +259
// 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;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment on lines +61 to +76
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))
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment on lines +54 to +55
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment on lines +20 to +27
## 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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 promised list/paging section and anchor, or remove the unsupported reference.
  • docs-site/src/content/docs/cli.md#L40-L42: change the #list fragment 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.

Comment on lines +1 to +7
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
-----------------------------------------------------------

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Suggested change
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.

Comment thread mise.toml
Comment on lines +28 to +30
[tasks.tail]
description = "Tail live Cloudflare Worker logs for agentflare.dev"
run = "aube run tail --filter agentflare-web"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Suggested change
[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.

Comment thread site/README.md
Comment on lines +65 to +67
`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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Suggested change
`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.

Comment thread src/mcp_server.rs
// --- 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\")."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

shiva added 2 commits July 28, 2026 13:38
…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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 win

Keep enforced rules tool-triggerable after updates.

Line 104 preserves enforced, but an update can replace its trigger with None. The file remains marked mandatory while enforced_rule_reason_for_tool can 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

📥 Commits

Reviewing files that changed from the base of the PR and between 884acaf and 1ced2b4.

📒 Files selected for processing (8)
  • src/cli/coaching.rs
  • src/coaching/cli.rs
  • src/coaching/mod.rs
  • src/coaching/rule.rs
  • src/coaching/store.rs
  • src/components.rs
  • src/hook.rs
  • src/optimize/runtime.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/coaching/mod.rs

Comment thread src/coaching/store.rs
Comment thread src/optimize/runtime.rs
Comment on lines +202 to +215
/// 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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.
@getappz getappz changed the title flare-docs: Python ecosystem, README usage examples, and the docs site feat(flare-docs): Python ecosystem, README usage examples, and the docs site Jul 28, 2026
@getappz
getappz merged commit 5dab94b into master Jul 28, 2026
16 checks passed
@getappz
getappz deleted the core-module-enforcement-components branch July 28, 2026 08:58
@getappz
getappz restored the core-module-enforcement-components branch July 28, 2026 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant