feat: ponytail L1 integration — port runtime to Rust - #45
Conversation
📝 WalkthroughWalkthroughAdds a new ChangesPonytail Rust Runtime Port
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant Main as src/main.rs
participant Switcher as ponytail::switcher
participant Config as ponytail::config
participant State as ponytail::state
participant Instructions as ponytail::instructions
participant Platform as ponytail::platform
User->>Main: agentflare ponytail hook <event>
Main->>Switcher: detect(input)
Switcher-->>Main: SwitchAction
alt SetMode
Main->>State: set_active(mode)
else SetDefault
Main->>Config: set_default_mode(mode)
else Off
Main->>State: clear_active()
end
Main->>Instructions: build(mode, skill_path)
Instructions-->>Main: Instructions{mode, body}
Main->>Platform: detect()
Platform-->>Main: AgentPlatform
Main->>Platform: format_hook_output(event, body, platform)
Platform-->>Main: formatted output
Main-->>User: hook output
Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (4)
src/main.rs (2)
616-624: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueInconsistent path for
clear_active.Here you call
ponytail::state::clear_active()via the full module path, whileOff(Line 589) andPromptSubmit(Line 664) use the re-exportedponytail::clear_active(). Both compile, but aligning on the re-exported alias reads more consistently.🤖 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/main.rs` around lines 616 - 624, The `SessionStart` branch in `PonytailAction::Hook` is calling `clear_active` through `ponytail::state::clear_active()` while `Off` and `PromptSubmit` use the re-exported `ponytail::clear_active()`. Update this branch to use the same re-exported alias so the `PonytailAction::Hook` handling is consistent across cases and easier to read.
670-682: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHardcoded/duplicated ANSI statusline formatting.
The color escape
\x1b[38;5;108mis duplicated and the badge is emitted inline here rather than going throughplatform's output helpers. Consider moving statusline rendering into a small helper (e.g. alongsideformat_hook_output) to keep ANSI formatting in one place.🤖 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/main.rs` around lines 670 - 682, The Statusline branch in PonytailHookEvent is formatting the badge inline and duplicating the ANSI color escape, so move this rendering into a small helper near format_hook_output or the platform output helpers. Use that helper from the Statusline match arm to build the full [PONYTAIL] / [PONYTAIL:MODE] string and keep the ANSI formatting centralized in one place.src/ponytail/switcher.rs (1)
50-57: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueNormalized value discarded, raw user token persisted instead.
config::normalize_config_mode(dmode)?is called purely for itsOptionshort-circuiting; the (possibly canonicalized) return value is discarded and the original, unnormalizeddmodeis stored inSwitchAction::SetDefault. Same pattern at lines 40-41 formode. Ifnormalize_config_modeever does more than strict whitelist validation (e.g. trims/aliases), the persisted value could diverge from what other code expects as canonical (e.g. thelm == effectivecomparisons ininstructions::filter_skill_body). Using the normalized return value directly would be more robust against future changes tonormalize_config_mode.🤖 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/ponytail/switcher.rs` around lines 50 - 57, The parsing in SwitchAction::SetDefault (and the similar mode branch) validates with config::normalize_config_mode but still stores the original token, so the normalized value is being discarded. Update the switcher logic to use the value returned by config::normalize_config_mode directly when constructing the SwitchAction, so the persisted mode/default matches the canonical form expected elsewhere such as instructions::filter_skill_body.src/ponytail/platform.rs (1)
1-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNo unit tests for platform detection/hook formatting.
Unlike
switcher.rsandinstructions.rs, this module has no tests coveringdetect()env-var branches or the per-platformformat_hook_outputJSON shapes (especially the CodexsystemMessageconditional and Copilot's event-gated output).🤖 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/ponytail/platform.rs` around lines 1 - 59, Add unit tests for platform detection and hook formatting in the platform module. Cover the detect() branches for CLAUDE_CONFIG_DIR, COPILOT_PLUGIN_DATA, PLUGIN_DATA, and the fallback case, and verify format_hook_output() for AgentPlatform::Claude, AgentPlatform::Codex, AgentPlatform::Copilot, and AgentPlatform::Fallback. Make sure the tests assert the Codex systemMessage only appears for SessionStart and that Copilot returns output only for SessionStart, using the detect() and format_hook_output() symbols to locate the behavior.
🤖 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/ponytail/instructions.rs`:
- Around line 52-61: The cache-path logic is duplicated in the skill-loading
fallback instead of reusing the existing `skill_cache_path()` helper. Update the
`instructions.rs` loading branch that builds `skill_body` to call
`skill_cache_path()` directly, so the path construction stays centralized and
consistent with the helper used elsewhere.
- Around line 12-37: The skill fetch is currently using a mutable `main` URL and
a blocking `ureq` call without any timeout, so update `SKILL_URL` in
`download_skill()` to point to an immutable tag or commit and configure an
explicit request timeout on the `ureq::get(...).call()` flow. Keep the existing
cache/write logic in `skill_cache_path()` and `download_skill()`, but ensure the
source is pinned and the network request cannot hang indefinitely.
In `@src/ponytail/platform.rs`:
- Around line 33-49: The Codex branch in platform.rs hardcodes the SessionStart
system message to PONYTAIL:FULL, so it ignores the active Ponytail mode. Update
the AgentPlatform::Codex handling to use the effective configured mode for
SessionStart instead of the literal string, and keep the systemMessage field
conditional on that value being present. Use the existing event check and the
output construction in this branch to locate the fix.
In `@src/ponytail/skill-debt.md`:
- Around line 17-20: The grep command in the ponytail scan still traverses
excluded directories, so update the repo search instruction to actually skip
node_modules, .git, and build output when running the comment-marker scan.
Adjust the command in the skill-debt guidance so the exclusions are enforced in
the grep invocation itself, keeping the scan focused on source files and
reducing noise; refer to the repo-scanning instruction that mentions grep and
comment markers.
In `@src/ponytail/state.rs`:
- Around line 27-29: The clear_active helper is swallowing every remove_file
failure instead of only ignoring a missing flag file. Update clear_active to
handle the std::fs::remove_file(flag_path()) result explicitly: ignore only
NotFound, and surface or log any other I/O error such as permission denied so
callers know the active flag was not actually cleared.
In `@src/ponytail/sub_skills.rs`:
- Around line 1-5: The compile-time includes in sub_skills.rs are depending on
markdown files that belong to a later/undeclared layer, which causes build
failures when this layer is compiled in isolation. Update the dependency
structure so the layer containing SKILL_REVIEW, SKILL_AUDIT, SKILL_DEBT,
SKILL_GAIN, and SKILL_HELP is declared after or dependent on the sub-skill
markdown documentation layer, or otherwise move these include_str! references to
a layer where the files are guaranteed to exist at compile time. Verify the
constants in sub_skills.rs only reference files that are available when the
module is built.
---
Nitpick comments:
In `@src/main.rs`:
- Around line 616-624: The `SessionStart` branch in `PonytailAction::Hook` is
calling `clear_active` through `ponytail::state::clear_active()` while `Off` and
`PromptSubmit` use the re-exported `ponytail::clear_active()`. Update this
branch to use the same re-exported alias so the `PonytailAction::Hook` handling
is consistent across cases and easier to read.
- Around line 670-682: The Statusline branch in PonytailHookEvent is formatting
the badge inline and duplicating the ANSI color escape, so move this rendering
into a small helper near format_hook_output or the platform output helpers. Use
that helper from the Statusline match arm to build the full [PONYTAIL] /
[PONYTAIL:MODE] string and keep the ANSI formatting centralized in one place.
In `@src/ponytail/platform.rs`:
- Around line 1-59: Add unit tests for platform detection and hook formatting in
the platform module. Cover the detect() branches for CLAUDE_CONFIG_DIR,
COPILOT_PLUGIN_DATA, PLUGIN_DATA, and the fallback case, and verify
format_hook_output() for AgentPlatform::Claude, AgentPlatform::Codex,
AgentPlatform::Copilot, and AgentPlatform::Fallback. Make sure the tests assert
the Codex systemMessage only appears for SessionStart and that Copilot returns
output only for SessionStart, using the detect() and format_hook_output()
symbols to locate the behavior.
In `@src/ponytail/switcher.rs`:
- Around line 50-57: The parsing in SwitchAction::SetDefault (and the similar
mode branch) validates with config::normalize_config_mode but still stores the
original token, so the normalized value is being discarded. Update the switcher
logic to use the value returned by config::normalize_config_mode directly when
constructing the SwitchAction, so the persisted mode/default matches the
canonical form expected elsewhere such as instructions::filter_skill_body.
🪄 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: 16d50f42-4ae8-4a18-951d-ec291d912e37
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (17)
.superpowers/sdd/progress.mddocs/superpowers/plans/2026-07-07-ponytail-l1-integration.mddocs/superpowers/specs/2026-07-07-ponytail-l1-integration-design.mdsrc/main.rssrc/ponytail/config.rssrc/ponytail/instructions.rssrc/ponytail/mod.rssrc/ponytail/platform.rssrc/ponytail/skill-audit.mdsrc/ponytail/skill-debt.mdsrc/ponytail/skill-gain.mdsrc/ponytail/skill-help.mdsrc/ponytail/skill-review.mdsrc/ponytail/skill.mdsrc/ponytail/state.rssrc/ponytail/sub_skills.rssrc/ponytail/switcher.rs
| const SKILL_URL: &str = | ||
| "https://raw.githubusercontent.com/DietrichGebert/ponytail/main/skills/ponytail/SKILL.md"; | ||
|
|
||
| pub fn skill_cache_path() -> std::path::PathBuf { | ||
| dirs::cache_dir() | ||
| .unwrap_or_else(|| std::path::PathBuf::from(".")) | ||
| .join("agentflare") | ||
| .join("ponytail") | ||
| .join("SKILL.md") | ||
| } | ||
|
|
||
| pub fn download_skill() -> Result<String, String> { | ||
| let resp = ureq::get(SKILL_URL) | ||
| .call() | ||
| .map_err(|e| format!("fetch failed: {e}"))?; | ||
| if resp.status() != 200 { | ||
| return Err(format!("HTTP {}", resp.status())); | ||
| } | ||
| let body = resp.into_string().map_err(|e| format!("read failed: {e}"))?; | ||
| let path = skill_cache_path(); | ||
| if let Some(parent) = path.parent() { | ||
| std::fs::create_dir_all(parent).map_err(|e| format!("mkdir: {e}"))?; | ||
| } | ||
| std::fs::write(&path, &body).map_err(|e| format!("write: {e}"))?; | ||
| Ok(path.display().to_string()) | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
What is the current recommended way to set a request timeout with ureq 2.x, and does ureq::get()/.call() block indefinitely by default with no timeout configured?
💡 Result:
In ureq 2.x, the recommended way to set request timeouts is by using the AgentBuilder to configure the agent or by calling the.timeout method directly on a Request object [1][2][3][4]. When creating an agent, you can define various timeouts using AgentBuilder, such as.timeout_connect,.timeout_read,.timeout_write, and the overall.timeout [1]. The.timeout method covers the entire request lifecycle (DNS resolution, connection, redirects, and reading the response body), and it takes precedence over read and write timeouts [1]. Regarding whether ureq::get/.call blocks indefinitely by default: Ureq uses blocking I/O [5][6]. While ureq includes some default behaviors, relying on it to block indefinitely is generally not the case for network operations, as the underlying operating system and TCP stack often impose their own default timeouts [7]. However, from the perspective of the ureq API configuration, if no explicit timeouts are set, the request may wait for the network operations to complete or until the OS times out the connection [7]. It is best practice to always configure explicit timeouts via the AgentBuilder or Request methods to ensure predictable behavior and prevent potential hangs [1][4]. Summary of methods: - Agent-wide configuration: Use ureq::AgentBuilder to set defaults for all requests made by that agent [1][4]. - Per-request configuration: Use the.timeout method on an individual request to override the agent's settings [2][3]. Note: As documented, the.timeout method on the AgentBuilder takes precedence over.timeout_read and.timeout_write, but not.timeout_connect [1].
Citations:
- 1: https://docs.rs/ureq/2.1.1/ureq/struct.AgentBuilder.html
- 2: https://docs.rs/ureq/2.1.0/ureq/struct.Request.html
- 3: https://docs.rs/ureq/2.3.0/i686-unknown-linux-gnu/ureq/struct.Request.html
- 4: https://docs.rs/ureq/^2
- 5: https://docs.rs/ureq/latest/ureq/
- 6: https://docs.rs/crate/ureq/2.6.2
- 7: Err(Timeout(Global)) can be returned even when no timeouts are set algesten/ureq#1024
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== src/ponytail/instructions.rs ==\n'
cat -n src/ponytail/instructions.rs | sed -n '1,120p'
printf '\n== dependency versions ==\n'
rg -n 'ureq|dirs' Cargo.toml **/Cargo.toml -S
printf '\n== any use of download_skill/skill_cache_path ==\n'
rg -n 'download_skill|skill_cache_path|SKILL_URL' src -SRepository: getappz/agentflare
Length of output: 1959
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== inspect ureq API usage in lock/Cargo ==\n'
rg -n 'ureq' Cargo.lock Cargo.toml -S
printf '\n== inspect instructions module scope ==\n'
ast-grep outline src/ponytail/instructions.rs --view expandedRepository: getappz/agentflare
Length of output: 1959
Pin the skill source and add an explicit timeout. SKILL_URL points at main, so this fetch can pull mutable instruction content into agent behavior; download_skill() also makes a blocking ureq request without any timeout configured. Pin the URL to a tag/commit and set a request timeout.
🤖 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/ponytail/instructions.rs` around lines 12 - 37, The skill fetch is
currently using a mutable `main` URL and a blocking `ureq` call without any
timeout, so update `SKILL_URL` in `download_skill()` to point to an immutable
tag or commit and configure an explicit request timeout on the
`ureq::get(...).call()` flow. Keep the existing cache/write logic in
`skill_cache_path()` and `download_skill()`, but ensure the source is pinned and
the network request cannot hang indefinitely.
| AgentPlatform::Codex => { | ||
| let sys_msg = if event == "SessionStart" { | ||
| "PONYTAIL:FULL" | ||
| } else { | ||
| "" | ||
| }; | ||
| let mut output = json!({ | ||
| "hookSpecificOutput": { | ||
| "hookEventName": event, | ||
| "additionalContext": ctx, | ||
| } | ||
| }); | ||
| if !sys_msg.is_empty() { | ||
| output["systemMessage"] = json!(sys_msg); | ||
| } | ||
| output.to_string() | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the relevant file and surrounding symbols first.
ast-grep outline src/ponytail/platform.rs --view expanded
# Show the file with line numbers if it's small enough.
wc -l src/ponytail/platform.rs
cat -n src/ponytail/platform.rs | sed -n '1,220p'
# Find where format_hook_output is called and how mode is passed around.
rg -n "format_hook_output|AgentPlatform::Codex|PONYTAIL:FULL|SessionStart|lite|ultra|mode" srcRepository: getappz/agentflare
Length of output: 1959
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the exact file and its callers if the repository is available.
wc -l src/ponytail/platform.rs
cat -n src/ponytail/platform.rs | sed -n '1,220p'
echo "---- callers ----"
rg -n "format_hook_output|AgentPlatform::Codex|PONYTAIL:FULL|SessionStart|lite|ultra|mode" srcRepository: getappz/agentflare
Length of output: 1959
🌐 Web query:
getappz agentflare src/ponytail/platform.rs format_hook_output PONYTAIL:FULL SessionStart
💡 Result:
The term "format_hook_output" and the prefix "PONYTAIL:FULL SessionStart" are associated with the "ponytail" project by Dietrich Gebert, an open-source tool designed to provide "rulesets" or "steering" for AI coding agents (such as those running in Claude Code, Cursor, Windsurf, and other environments) [1]. The project uses lifecycle hooks—specifically the SessionStart event—to inject instructions into the agent's context [2]. The specific construct PONYTAIL:FULL SessionStart relates to the operational logic of these hooks [3][2]. When a session begins, the hook ponytail-activate.js is triggered [2]. In the context of the ponytail project, "FULL" refers to the intensity or mode setting of the ruleset being applied [1]. The hook mechanism, particularly for events like SubagentStart, often uses a JSON-formatted output or specific string prefixes to communicate instructions to the host agent, ensuring that the rules governing the agent's behavior are injected during the startup phase [2]. It is important to distinguish this from "Agentflare" (or "agentflare"), which is a separate infrastructure platform for AI agents providing observability, cost tracking, and deployment tools [4][5][6]. There is no technical overlap between the ponytail project's hook system and Agentflare's core functionality based on the available documentation [4][1][5]. The mention of "getappz" in your query does not appear to be a recognized part of the official ponytail or Agentflare repositories; it may be a misidentified entity or a specific local naming convention [1][5][3]. In summary, the command or log format you referenced is part of the ponytail ruleset injection mechanism used to steer coding agents [1][2].
Citations:
- 1: https://github.com/DietrichGebert/ponytail
- 2: Ponytail rules don't reach subagents — add SubagentStart hook DietrichGebert/ponytail#252
- 3: https://www.claudepluginhub.com/hooks/dietrichgebert-ponytail
- 4: https://agentflare.com/
- 5: https://pypi.org/project/agentflare/
- 6: https://agent-flare.vercel.app/
🌐 Web query:
ponytail PONYTAIL:FULL lite ultra ruleset mode SessionStart
💡 Result:
Ponytail is a "lazy senior dev" mode/ruleset for AI coding assistants (such as Claude Code and OpenCode) designed to prioritize minimal, efficient, and YAGNI (You Ain't Gonna Need It) compliant code solutions [1][2][3]. The SessionStart hook is the mechanism used by the Ponytail plugin to automatically inject its ruleset and activate the desired intensity level whenever a new coding session begins [4][5][6]. Key intensity levels for the Ponytail ruleset include: - Lite: Builds what is requested but provides the lazier/simpler alternative in a single line for the user to choose [2][7][8]. - Full (Default): Follows a strict hierarchy: YAGNI -> standard library -> native platform features -> one-liner -> minimum necessary code [2][7][3]. - Ultra: A YAGNI-extremist mode that prioritizes code deletion over addition and challenges requirements before implementation [2][7][8]. You can configure the default behavior via an environment variable (PONYTAIL_DEFAULT_MODE) or a configuration file located at ~/.config/ponytail/config.json (or %APPDATA%\ponytail\config.json on Windows) [7][8][9]. Setting the mode to "off" disables automatic activation on session start, requiring manual activation via the /ponytail command [7][10]. Newer versions also include a SubagentStart hook to ensure the ruleset propagates to subagents during development tasks [4][11].
Citations:
- 1: https://github.com/DietrichGebert/ponytail/blob/main/.opencode/command/ponytail.md
- 2: https://github.com/DietrichGebert/ponytail/blob/dedc97ca7c8a1e7463ac5b36f7fe4b28c3c435a2/skills/ponytail/SKILL.md
- 3: https://github.com/DietrichGebert/ponytail/blob/main/.agents/rules/ponytail.md
- 4: Ponytail rules don't reach subagents — add SubagentStart hook DietrichGebert/ponytail#252
- 5: [Bug]: ponytail does not detect caveman conflict — both inject contradictory instructions DietrichGebert/ponytail#332
- 6: Ponytail instructions are printed on codex session start DietrichGebert/ponytail#331
- 7: https://github.com/DietrichGebert/ponytail/blob/99139a25d07e3523d3f6871419798dda600db49a/skills/ponytail-help/SKILL.md
- 8: https://cdn.jsdelivr.net/npm/opencode-ponytail@4.7.3/.opencode/command/ponytail-help.md
- 9: https://app.unpkg.com/opencode-agent-kit@1.3.3/files/template/.opencode/skills/ponytail-help/SKILL.md
- 10: https://cdn.jsdelivr.net/npm/opencode-ponytail@4.7.3/skills/ponytail-help/SKILL.md
- 11: https://github.com/DietrichGebert/ponytail/releases
Emit the configured Ponytail mode here
SessionStart currently always sends PONYTAIL:FULL, so Codex will advertise the default mode even when the active ruleset is lite or ultra. Pass the effective mode through instead of hardcoding the literal.
🤖 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/ponytail/platform.rs` around lines 33 - 49, The Codex branch in
platform.rs hardcodes the SessionStart system message to PONYTAIL:FULL, so it
ignores the active Ponytail mode. Update the AgentPlatform::Codex handling to
use the effective configured mode for SessionStart instead of the literal
string, and keep the systemMessage field conditional on that value being
present. Use the existing event check and the output construction in this branch
to locate the fix.
| Grep the repo for comment markers, skipping `node_modules`, `.git`, and build | ||
| output: | ||
|
|
||
| `grep -rnE '(#|//) ?ponytail:' .` (add other comment prefixes if your stack uses them) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make the scan honor the exclusions it advertises.
The current grep command still walks node_modules, .git, and build output, so the debt ledger will pick up noise and slow down on larger repos.
Suggested fix
-`grep -rnE '(#|//) ?ponytail:' .` (add other comment prefixes if your stack uses them)
+`grep -rnE --exclude-dir={node_modules,.git,build} '(#|//) ?ponytail:' .` (add other comment prefixes if your stack uses them)📝 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.
| Grep the repo for comment markers, skipping `node_modules`, `.git`, and build | |
| output: | |
| `grep -rnE '(#|//) ?ponytail:' .` (add other comment prefixes if your stack uses them) | |
| Grep the repo for comment markers, skipping `node_modules`, `.git`, and build | |
| output: | |
| `grep -rnE --exclude-dir={node_modules,.git,build} '(#|//) ?ponytail:' .` (add other comment prefixes if your stack uses them) |
🤖 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/ponytail/skill-debt.md` around lines 17 - 20, The grep command in the
ponytail scan still traverses excluded directories, so update the repo search
instruction to actually skip node_modules, .git, and build output when running
the comment-marker scan. Adjust the command in the skill-debt guidance so the
exclusions are enforced in the grep invocation itself, keeping the scan focused
on source files and reducing noise; refer to the repo-scanning instruction that
mentions grep and comment markers.
| pub fn clear_active() { | ||
| let _ = std::fs::remove_file(flag_path()); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
clear_active silently swallows all I/O errors, not just "not found".
Any error (e.g., permission denied) is discarded, leaving the active-mode flag file in place while callers assume it was cleared. This could leave the runtime stuck in a mode the user tried to deactivate.
Proposed fix: surface non-NotFound errors
-pub fn clear_active() {
- let _ = std::fs::remove_file(flag_path());
-}
+pub fn clear_active() -> io::Result<()> {
+ match std::fs::remove_file(flag_path()) {
+ Ok(()) => Ok(()),
+ Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
+ Err(e) => Err(e),
+ }
+}📝 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.
| pub fn clear_active() { | |
| let _ = std::fs::remove_file(flag_path()); | |
| } | |
| pub fn clear_active() -> io::Result<()> { | |
| match std::fs::remove_file(flag_path()) { | |
| Ok(()) => Ok(()), | |
| Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()), | |
| Err(e) => Err(e), | |
| } | |
| } |
🤖 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/ponytail/state.rs` around lines 27 - 29, The clear_active helper is
swallowing every remove_file failure instead of only ignoring a missing flag
file. Update clear_active to handle the std::fs::remove_file(flag_path()) result
explicitly: ignore only NotFound, and surface or log any other I/O error such as
permission denied so callers know the active flag was not actually cleared.
| pub const SKILL_REVIEW: &str = include_str!("skill-review.md"); | ||
| pub const SKILL_AUDIT: &str = include_str!("skill-audit.md"); | ||
| pub const SKILL_DEBT: &str = include_str!("skill-debt.md"); | ||
| pub const SKILL_GAIN: &str = include_str!("skill-gain.md"); | ||
| pub const SKILL_HELP: &str = include_str!("skill-help.md"); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win
Compile-time dependency on files from an undeclared/later layer — matches pipeline failures.
The stack context lists skill-review.md, skill-audit.md, skill-debt.md, skill-gain.md, skill-help.md under a separate layer ("Sub-skill markdown documentation") with no declared dependency link back to this layer ("Instruction building and skill downloading"). Yet these include_str! macros require those exact files to exist at compile time. The pipeline failure logs confirm this: macro expansion failed for include_str! on SKILL_DEBT, SKILL_GAIN, and SKILL_HELP (lines 3-5), consistent with those markdown files being missing when this layer is built in isolation.
If layers are merged/reviewed independently, this file will fail to compile until the sub-skill markdown layer lands first — the stack dependency graph should reflect that.
#!/bin/bash
fd -a . src/ponytail -e md🧰 Tools
🪛 GitHub Actions: PR `#45` / Analyze (rust)
[warning] 3-3: CodeQL/Rust extraction warning: macro expansion failed for 'include_str'.
[warning] 4-4: CodeQL/Rust extraction warning: macro expansion failed for 'include_str'.
[warning] 5-5: CodeQL/Rust extraction warning: macro expansion failed for 'include_str'.
🤖 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/ponytail/sub_skills.rs` around lines 1 - 5, The compile-time includes in
sub_skills.rs are depending on markdown files that belong to a later/undeclared
layer, which causes build failures when this layer is compiled in isolation.
Update the dependency structure so the layer containing SKILL_REVIEW,
SKILL_AUDIT, SKILL_DEBT, SKILL_GAIN, and SKILL_HELP is declared after or
dependent on the sub-skill markdown documentation layer, or otherwise move these
include_str! references to a layer where the files are guaranteed to exist at
compile time. Verify the constants in sub_skills.rs only reference files that
are available when the module is built.
Source: Pipeline failures
Summary
Ports ponytail runtime logic (config, state, instructions, switcher, platform detection, sub-skills) from Node.js hooks into agentflare Rust. Prompt content stays embedded as fallback.
Closes #42
Changes
Follow-up PR (feature/agent-detector-ponytail)