Skip to content

Commit d0d97d8

Browse files
peteromalletclaudesims1253nickperkinscdunda-perchwell
authored
0.9.10 (#463)
* fix: strip image blocks from release notes on website The release notes contain a mascot image that renders as a broken or unwanted image on the website. Strip HTML <p><img></p> blocks from release body before rendering. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: derive project root from state_file path in do_import_run follow-up scan When running `desloppify review --import-run --scan-after-import`, the follow-up scan was using _runtime_project_root() which could return a contaminated path (pointing to the results directory instead of the actual project root). This caused state to be written to the wrong location. Instead, derive the project root from the state_file parameter which is known to be correct: state_file.parent.parent gives us the project root from `<root>/.desloppify/state-<lang>.json`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: preserve plan_start_scores during force-rescan to protect manual clusters _reset_cycle_for_force_rescan() was clearing plan_start_scores, which made is_mid_cycle() return False. This caused auto_cluster_issues() to run full cluster regeneration instead of early-returning, wiping manual cluster items via issue ID reconciliation in scan_issue_reconcile.py. The fix stops clearing plan_start_scores so is_mid_cycle() remains True during force-rescan, preserving manual cluster data. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: require explicit triage decisions for auto-clusters Auto-clusters (auto/unused, auto/security, etc.) were silently left in backlog because the triage prompt said "silence means leave in backlog" and the output schema had no field for auto-cluster decisions. Now the triager must make an explicit promote/skip/break_up decision for each auto-cluster, and apply_triage_to_plan() processes those decisions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: require explicit backlog decisions for auto-clusters in staged triage The staged triage pipeline previously treated auto-clusters as optional in the reflect stage ("silence means it stays in backlog"). This change makes auto-cluster decisions mandatory, matching the treatment review issues get via the Coverage Ledger. Changes: - Reflect instructions: require a ## Backlog Decisions section listing every auto-cluster with promote/skip/supersede (replaces "silence means leave") - Organize instructions: clarify that ALL backlog decisions from reflect must be executed, not just promotions - Reflect validation: parse and persist BacklogDecision entries; warn (but don't block) when auto-clusters exist without a Backlog Decisions section - Organize validation: warn when reflect requested promotions that weren't executed during organize Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: unified triage pipeline + step detail display improvements Unified triage pipeline: - Widen is_triage_finding to all defects (mechanical + review + concern) - Sub-group auto-clusters by rule kind (auto/security-B602 instead of auto/security) - Add MEDIUM+LOW bandit filter and skip_tests config option - Auto-cluster statistical summaries in triage prompt (severity, confidence, samples) - Cluster-level observe sampling (ClusterVerdict parsing) - Blocking backlog decisions validation (every auto-cluster must have a decision) - Threshold-based staleness (10% mechanical growth, any new review issue) - Two-tier accounting: review issues get per-item ledger, mechanical via cluster decisions - Auto-add manual cluster members to queue_order on add_to_cluster Display improvements: - cluster show: steps now show effort tag, wrapped detail (4 lines), short refs - cluster show: members compact when steps exist (ID list, not full issue detail) - cluster list --verbose: effort summary column (3T 1S), hide empty auto-clusters, drop noise columns - next: cluster drill header shows step done markers and effort tags - next: individual task shows full untruncated step detail matched via issue_refs - next: focus mode shows cluster context + relevant step detail Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: lifecycle transition messages and agent directives Add transition_messages config and directives CLI for phase-specific agent instructions (model switching, constraints). Emit transition messages at lifecycle phase changes across resolve, skip, reopen, review import, and reconcile flows. Auto-focus cluster during mid-cluster execution so desloppify next stays in context. Hermes reset includes cluster-aware next-task instructions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add dev test-hermes command and bump skill doc version desloppify dev test-hermes: smoke-test Hermes model switching by switching to a random model and back. Skill doc version bumped to v6. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: handle missing git in review coordinator, remove unused import Wrap git status call in try/except OSError so review coordinator doesn't crash when git is unavailable. Remove unused triage_scoped_plan import from stage_validation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: update Hermes overlay for delegate_task, add directives docs, update website Rewrite HERMES.md: delegate_task subagent pattern replaces worktree-based parallel review. Add agent directives section to SKILL.md. Website: initiative #2 now active with $1k bounty challenge details. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(r): correct shell quote escaping in lintr command (PR #424) Co-Authored-By: Maximilian Scholz <dev.scholz@mailbox.org> * feat(r): add Jarl as fast R linter with autofix (PR #425) Co-Authored-By: Maximilian Scholz <dev.scholz@mailbox.org> * fix: phpstan stderr/JSON parser fixes (PR #420) Co-Authored-By: Nick Perkins <nick@nickperkins.au> * fix(engine): prevent workflow::create-plan re-injection after resolution (PR #435) Co-Authored-By: Charles Dunda <charles.dunda@perchwell.com> * feat: add SCSS language plugin (PR #428) Co-Authored-By: Klaus Agnoletti <github@agnoletti.dk> * fix: Rust dep graph hangs from string-literal fake imports (PR #429) Co-Authored-By: Riccardo Spagni <ric@spagni.net> * fix: binding-aware unused import detection for JS/TS (PR #433) Co-Authored-By: Tom <tswift1991@icloud.com> * fix: project root detection, force-rescan plan wipe, and manual cluster visibility (PR #439) * perf(scan): detector prefetch + cache for faster scans (PR #432) Co-Authored-By: Tom <tswift1991@icloud.com> * feat(frameworks): FrameworkSpec layer + Next.js spec (PR #414) Co-Authored-By: Tom Swift <tswift1991@icloud.com> * fix: allow scan when queue is fully drained regardless of lifecycle phase Fixes #441 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: quote paths for Windows cmd /c and use utf-8 encoding in log recovery Fixes #442 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: merge retry batch results with original run before coverage check Fixes #443 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(docs): SKILL.md cleanup — remove unsupported frontmatter, fix file naming, generalize install Fixes #444, #445, #446 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * cleanup: remove dead _strip_c_style_comments_preserve_lines shim from rust/tools.py Follow-up to PR #440 (Rust inline-test filtering). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: move queue_total==0 check into score_display_mode (#441) Move the empty-queue guard from scan_queue_preflight into score_display_mode() so ALL callers (status, plan nudge, next flow) benefit from the fix, not just scan preflight. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: extract anonymous functions in tree-sitter specs (R lang) PR #449 added an R anonymous function query pattern that captures @fn but the extractor requires @name, silently skipping all anonymous function matches. Fix the extractor to synthesize an "<anonymous>" name when @name is absent but @func is present. Original R spec contributed by sims1253 in PR #449. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(docs): sync .agents SKILL.md with docs copy, add pip fallback and batch naming note - Remove `allowed-tools` frontmatter from .agents/skills/desloppify/SKILL.md (#444) - Add `pip install` fallback note alongside uvx in both copies (#446) - Add batch output naming clarification (batch-N.raw.txt vs .json imports) (#445) - Sync agent directives section and version bump to .agents copy Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: collapse cmd /c arguments into single string for proper Windows quoting The previous fix pre-quoted the executable path, but the actual breakage was in argument paths (-C repo_root, -o output_file) containing spaces. Pre-embedding quotes in a subprocess list causes double-quoting because Popen's list2cmdline() adds its own quotes. The real issue: cmd /c concatenates everything after /c and re-parses it with its own tokeniser. The fix introduces _wrap_cmd_c() which uses subprocess.list2cmdline() to build the inner command as a single properly-quoted string, then passes that as one token after /c: ["cmd", "/c", "codex exec -C \"path with spaces\" ..."]. - Revert incorrect executable pre-quoting in _resolve_executable - Add _wrap_cmd_c() to properly collapse cmd /c commands - Apply _wrap_cmd_c in codex_batch_command after building the full arg list - Keep correct encoding="utf-8", errors="replace" fix in io.py - Add tests for _wrap_cmd_c and Windows codex_batch_command path quoting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: skip coverage gate on partial batch retry instead of merging results Replace the 195-line merge approach (find_prior_run_merged_results + overlay_retry_results_on_prior) with a ~5-line bypass: when --only-batches selects a subset of the packet's batches, set allow_partial=True so the coverage gate does not reject the partial retry. The merge approach had multiple issues: wrong prior-run selection after failed retry chains, dimension name normalization mismatches, and stale metadata in combined output. The simpler fix recognizes that a partial retry inherently cannot cover all dimensions, and the original run already handled the rest. Fixes #443 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * bump version to 0.9.10 * bump version to 0.9.10 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * bump version to 0.9.10 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: gitignore .agents/ and untrack generated skill doc The .agents/skills/desloppify/SKILL.md is a generated file (same as .claude/skills/). Canonical copies live under docs/. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add hermes and droid to update-skill help text Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: draft 0.9.10 release notes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(ruby): improve plugin — excludes, detect markers, default_src, spec/ support, README, tests (#462) * feat(ruby): improve plugin — excludes, detect markers, default_src, README, tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-Authored-By: Gemini <gemini@google.com> Co-Authored-By: OpenAI Codex <codex@openai.com> * feat(ruby): add spec/ test dir, bin/ exclusion; expose external_test_dirs in generic_lang - Add external_test_dirs and test_file_extensions parameters to generic_lang() so plugins can override the hardcoded ["tests", "test"] defaults - Configure Ruby plugin with external_test_dirs=["spec", "test"] (RSpec + Minitest) - Add bin/ to Ruby exclusions (binstubs/shims) - Update tests: add bin/ to excluded dirs list, add test_external_test_dirs_includes_spec Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-Authored-By: Gemini <gemini@google.com> Co-Authored-By: OpenAI Codex <codex@openai.com> * docs(ruby): add bin/ to exclusions list in README Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-Authored-By: Gemini <gemini@google.com> Co-Authored-By: OpenAI Codex <codex@openai.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Gemini <gemini@google.com> Co-authored-by: OpenAI Codex <codex@openai.com> * feat: add Factory Droid skill harness support (#451) - Add 'droid' to SKILL_TARGETS (.factory/skills/desloppify/SKILL.md) - Add .factory/skills/ to SKILL_SEARCH_PATHS for auto-discovery - Create docs/DROID.md overlay with review and triage workflow - Bump SKILL_VERSION to 6 - Add droid to README agent prompt harness list * docs(python): add user-facing section to README (#459) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(javascript): add plugin tests and documentation (#458) Co-authored-by: Gemini <gemini@google.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docs): correct autofix command in Ruby and JS plugin READMEs The command is `desloppify autofix`, not `desloppify fix` or `desloppify scan --fix`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: update release notes with late-merged PRs and stats Add #458, #459, #462 contributions from klausagnoletti. Update stats to reflect final commit/file/test counts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(scss): replace {file_path} placeholders with glob patterns and use unix formatter The tool runner does not substitute {file_path} placeholders, so stylelint was receiving literal "{file_path}" and failing silently. Switch to glob patterns (matching every other plugin) and use --formatter unix with the gnu parser, since stylelint's JSON output doesn't match the expected json parser format. Based on findings from @klausagnoletti in PR #452. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Maximilian Scholz <dev.scholz@mailbox.org> Co-authored-by: Nick Perkins <nick@nickperkins.au> Co-authored-by: Charles Dunda <charles.dunda@perchwell.com> Co-authored-by: Klaus Agnoletti <github@agnoletti.dk> Co-authored-by: Riccardo Spagni <ric@spagni.net> Co-authored-by: Tom <tswift1991@icloud.com> Co-authored-by: Klaus Agnoletti <24544601+klausagnoletti@users.noreply.github.com> Co-authored-by: Gemini <gemini@google.com> Co-authored-by: OpenAI Codex <codex@openai.com>
1 parent eaf0a9c commit d0d97d8

135 files changed

Lines changed: 8071 additions & 321 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pytest-full.xml
3838

3939
# Local agent-generated skills (keep canonical copies under docs/)
4040
.claude/
41+
.agents/
4142
/skills/
4243
CLAUDE.md
4344
AGENTS.md

RELEASE_NOTES_0.9.10.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<p align="center">
2+
<img src="https://raw.githubusercontent.com/peteromallet/desloppify/main/assets/mascot-no-bg.png" width="180" alt="Desloppify mascot">
3+
</p>
4+
5+
This release adds **experimental Hermes Agent integration** for fully autonomous cleanup loops, **framework-aware detection** with a full Next.js spec, **SCSS language support**, significant **R language improvements**, and a **scan performance boost** from detector prefetch + caching — alongside a batch of bug fixes from the community.
6+
7+
---
8+
9+
**152 files changed | 54 commits | 5,466 tests passing**
10+
11+
## Hermes Agent Integration (Experimental)
12+
13+
We've been exploring what it looks like when a codebase health tool can actually *drive* an AI agent — not just generate reports, but orchestrate the entire cleanup loop autonomously. This release ships our first experimental integration with [Hermes Agent](https://github.com/NousResearch/hermes-agent).
14+
15+
The core idea: desloppify already knows what needs to be done (scan, triage, review, fix). Instead of printing instructions for a human, it can now tell the agent directly — switch to a cheap model for mechanical fixes, switch to an expensive one for architectural review, reset context between tasks, and keep the agent working via `/autoreply`, all without a human in the loop.
16+
17+
What the integration enables:
18+
19+
- **Autonomous review loops** — desloppify orchestrates blind reviews via `delegate_task` subagents (up to 3 concurrent), no human needed
20+
- **Model switching at phase boundaries** — cheap models for execution, expensive for planning/review, switched automatically
21+
- **Context management** — automatic resets between tasks to keep the agent focused on long sessions
22+
- **Lifecycle transitions** — desloppify tells Hermes what to do next via the Control API
23+
24+
### How to try it
25+
26+
**This requires the Control API branch of Hermes** ([NousResearch/hermes-agent#1508](https://github.com/NousResearch/hermes-agent/pull/1508)), which hasn't been merged upstream yet. Without it, Hermes works as a normal harness but can't do autonomous model switching or self-prompting.
27+
28+
**Step 1 — Install Hermes from the Control API branch:**
29+
30+
```bash
31+
git clone -b feat/control-api-autoreply https://github.com/peteromallet/hermes-agent.git
32+
cd hermes-agent
33+
pip install -e .
34+
```
35+
36+
**Step 2 — Install desloppify and set up the Hermes skill doc:**
37+
38+
```bash
39+
pip install desloppify[full]
40+
cd /path/to/your/project
41+
desloppify update-skill hermes
42+
```
43+
44+
This writes a `AGENTS.md` skill document into your project that teaches Hermes how to use desloppify.
45+
46+
**Step 3 — Start Hermes with the Control API enabled, pointed at your project:**
47+
48+
```bash
49+
cd /path/to/your/project
50+
HERMES_CONTROL_API=1 hermes
51+
```
52+
53+
**Step 4 — Tell it to scan.** In the Hermes session, type:
54+
55+
```
56+
Run desloppify scan, then follow its coaching output to clean up the codebase.
57+
```
58+
59+
Desloppify will guide Hermes through the full lifecycle — scanning, triaging findings, running blind reviews with subagents, and fixing issues. It switches models and resets context automatically at phase boundaries.
60+
61+
**This is experimental and we're iterating fast.** We'd love feedback on the approach, rough edges, and what you'd want to see next. If you try it, please open an issue — every report helps.
62+
63+
## Framework-Aware Detection
64+
65+
Massive contribution from **@MacHatter1** (PR #414). A new `FrameworkSpec` abstraction layer for framework-specific detection, shipping with a full Next.js spec that understands App Router conventions, server components, `use client`/`use server` directives, and Next.js-specific lint rules. This means dramatically fewer false positives when scanning Next.js projects — framework idioms are recognized, not flagged. The spec system is extensible, so adding support for other frameworks (Remix, SvelteKit, etc.) is now a matter of writing a spec, not changing the engine.
66+
67+
## SCSS Language Plugin
68+
69+
Thanks to **@klausagnoletti** for adding SCSS/Sass support via stylelint integration (PR #428). Detects code smells, unused variables, and style issues in `.scss` and `.sass` files. @klausagnoletti has also submitted a follow-up PR (#452) with bug fixes, tests, and honest documentation — expected to land shortly after release.
70+
71+
## Plugin Tests, Docs, and Ruby Improvements
72+
73+
**@klausagnoletti** also contributed across multiple language plugins:
74+
75+
- **Ruby plugin improvements** (PR #462) — expanded exclusions, detect markers (`Gemfile`, `Rakefile`, `.ruby-version`, `*.gemspec`), `default_src="lib"`, `spec/` + `test/` support, and 13 wiring tests. Also adds `external_test_dirs` and `test_file_extensions` params to the generic plugin framework.
76+
- **JavaScript plugin tests + README** (PR #458) — 12 sanity tests covering ESLint integration, command construction, fixer registration, and output parsing.
77+
- **Python plugin README** (PR #459) — user-facing documentation covering phases, requirements, and usage.
78+
79+
## R Language Improvements
80+
81+
**@sims1253** has been steadily building out R support and contributed four PRs to this release:
82+
83+
- **Jarl linter** with autofix support (PR #425) — adds a fast R linter as an alternative to lintr
84+
- **Shell quote escaping fix** for lintr commands (PR #424) — prevents command injection on paths with special characters
85+
- **Tree-sitter query improvements** (PR #449) — captures anonymous functions in `lapply`/`sapply` calls and `pkg::fn` namespace imports
86+
- **Factory Droid harness support** (PR #451) — adds Droid as a new skill target, following the existing harness pattern exactly
87+
88+
## Scan Performance: Detector Prefetch + Cache
89+
90+
Another big one from **@MacHatter1** (PR #432). Cold and full scan times reduced significantly. Detectors now prefetch file contents and cache results across detection phases, avoiding redundant I/O. On large codebases this is a noticeable improvement.
91+
92+
## Lifecycle & Triage
93+
94+
- **Lifecycle transition messages** — the tool now tells agents what phase they're in and what to do next, with structured directives for each transition
95+
- **Unified triage pipeline** with step detail display
96+
- **Staged triage** now requires explicit decisions for auto-clusters before proceeding — no more accidentally skipping triage steps
97+
98+
## Bug Fixes
99+
100+
- **Binding-aware unused import detection for JS/TS**@MacHatter1 (PR #433). No longer flags imports used via destructuring, `as` renames, or re-export patterns. This was a significant source of false positives in real JS/TS projects.
101+
- **Rust dep graph hangs**@fluffypony (PR #429). String literals that look like import paths (e.g., `"path/to/thing"`) no longer cause the dependency graph builder to hang. @fluffypony also contributed Rust inline-test filtering (PR #440), which prevents `#[cfg(test)]` diagnostic noise from inflating production debt scores.
102+
- **Project root detection** (PR #439) — fixed cases where the project root was derived incorrectly, plus force-rescan now properly wipes stale plan data, and manual clusters are visible in triage.
103+
- **workflow::create-plan re-injection**@cdunda-perchwell (PR #435). Resolved workflow items no longer reappear in the execution queue after reconciliation. @cdunda-perchwell also identified the related communicate-score cycle-boundary sentinel issue (#447, fix in PR #448).
104+
- **PHPStan parser fixes**@nickperkins (PR #420). stderr output and malformed JSON from PHPStan no longer crash the parser. Clean, focused fix.
105+
- **Preserve plan_start_scores during force-rescan** — manual clusters are no longer wiped when force-rescanning.
106+
- **Import run project root**`--scan-after-import` now derives the project root correctly from the state file path.
107+
- **Windows codex runner** (PR #453) — proper `cmd /c` argument quoting + UTF-8 log encoding for Windows. Reported by **@DenysAshikhin**.
108+
- **Scan after queue drain** (PR #454) — `score_display_mode` now returns LIVE when queue is empty, fixing the UX contradiction where `next` says "run scan" but scan refuses. Reported by **@kgelpes**.
109+
- **SKILL.md cleanup** (PR #455) — removes unsupported `allowed-tools` frontmatter, fixes batch naming inconsistency (`.raw.txt` not `.json`), adds pip fallback alongside uvx. Three issues all reported by **@willfrey**.
110+
- **Batch retry coverage gate** (PR #456) — partial retries now bypass the full-coverage requirement instead of being rejected. Reported by **@imetandy**.
111+
- **R anonymous function extraction** (PR #461) — the tree-sitter anonymous function pattern from PR #449 now actually works (extractor handles missing `@name` capture with `<anonymous>` fallback).
112+
113+
## Community
114+
115+
This release wouldn't exist without the community. Seriously — thank you all.
116+
117+
**@MacHatter1** delivered three major PRs (framework-aware detection, detector prefetch + cache, binding-aware unused imports) that each individually would have been a headline feature. The framework spec system in particular opens up a whole new category of detection accuracy.
118+
119+
**@fluffypony** contributed both the Rust dep graph hang fix and the inline-test filtering — the latter being 1,000+ lines of carefully tested Rust syntax parsing with conservative cfg predicate handling and thorough edge-case coverage.
120+
121+
**@sims1253** has been the driving force behind R language support, with four PRs spanning linting, tree-sitter queries, and harness support. The R plugin is becoming genuinely useful thanks to this sustained effort.
122+
123+
**@klausagnoletti** added SCSS support, improved the Ruby plugin, and contributed tests and documentation for JavaScript and Python plugins — seven PRs total (#428, #452, #457, #458, #459, #462). The kind of contributor who makes the codebase more trustworthy across the board.
124+
125+
**@cdunda-perchwell** fixed two separate workflow re-injection bugs that were causing phantom plan items. **@nickperkins** shipped a clean PHPStan parser fix.
126+
127+
Bug reporters **@willfrey**, **@DenysAshikhin**, **@kgelpes**, and **@imetandy** filed detailed, actionable issues that made fixes straightforward. Every one of those reports saved debugging time.

assets/scorecard.png

-729 Bytes
Loading

desloppify/app/cli_support/parser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
_add_backlog_parser,
1111
_add_config_parser,
1212
_add_detect_parser,
13+
_add_directives_parser,
1314
_add_dev_parser,
1415
_add_exclude_parser,
1516
_add_autofix_parser,
@@ -139,6 +140,7 @@ def create_parser(*, langs: list[str], detector_names: list[str]) -> argparse.Ar
139140
# configure
140141
_add_zone_parser(sub)
141142
_add_config_parser(sub)
143+
_add_directives_parser(sub)
142144
_add_langs_parser(sub)
143145
_add_dev_parser(sub)
144146
_add_update_skill_parser(sub)

desloppify/app/cli_support/parser_groups.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from desloppify.app.cli_support.parser_groups_admin import ( # noqa: F401 (re-exports)
88
_add_config_parser,
99
_add_detect_parser,
10+
_add_directives_parser,
1011
_add_dev_parser,
1112
_add_autofix_parser,
1213
_add_langs_parser,
@@ -25,6 +26,7 @@
2526
"_add_backlog_parser",
2627
"_add_config_parser",
2728
"_add_detect_parser",
29+
"_add_directives_parser",
2830
"_add_dev_parser",
2931
"_add_exclude_parser",
3032
"_add_autofix_parser",

desloppify/app/cli_support/parser_groups_admin.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,17 @@ def _add_config_parser(sub) -> None:
9090
c_unset.add_argument("config_key", type=str, help="Config key name")
9191

9292

93+
def _add_directives_parser(sub) -> None:
94+
p = sub.add_parser("directives", help="View/set agent directives for phase transitions")
95+
d_sub = p.add_subparsers(dest="directives_action")
96+
d_sub.add_parser("show", help="Show all configured directives")
97+
d_set = d_sub.add_parser("set", help="Set a directive for a lifecycle phase")
98+
d_set.add_argument("phase", type=str, help="Lifecycle phase name")
99+
d_set.add_argument("message", type=str, help="Message to show at this transition")
100+
d_unset = d_sub.add_parser("unset", help="Remove a directive for a lifecycle phase")
101+
d_unset.add_argument("phase", type=str, help="Lifecycle phase name")
102+
103+
93104
def _fixer_help_lines(langs: list[str]) -> list[str]:
94105
fixer_help_lines: list[str] = []
95106
for lang_name in langs:
@@ -168,6 +179,8 @@ def _add_dev_parser(sub) -> None:
168179
)
169180
d_scaffold.set_defaults(wire_pyproject=True)
170181

182+
dev_sub.add_parser("test-hermes", help="Test Hermes model switching (switch and switch back)")
183+
171184

172185
def _add_langs_parser(sub) -> None:
173186
sub.add_parser("langs", help="List all available language plugins with depth and tools")
@@ -182,6 +195,6 @@ def _add_update_skill_parser(sub) -> None:
182195
"interface",
183196
nargs="?",
184197
default=None,
185-
help="Agent interface (amp, claude, codex, cursor, copilot, windsurf, gemini, opencode). "
198+
help="Agent interface (amp, claude, codex, cursor, copilot, windsurf, gemini, hermes, droid, opencode). "
186199
"Auto-detected on updates if omitted.",
187200
)

desloppify/app/commands/dev.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ def cmd_dev(args: argparse.Namespace) -> None:
2323
except ValueError as ex:
2424
raise CommandError(str(ex)) from ex
2525
return
26+
if action == "test-hermes":
27+
_cmd_test_hermes()
28+
return
2629
raise CommandError("Unknown dev action. Use `desloppify dev scaffold-lang`.")
2730

2831

@@ -165,3 +168,54 @@ def _cmd_scaffold_lang(args: object) -> None:
165168
" Next: implement real phases/commands/detectors and run pytest.", "dim"
166169
)
167170
)
171+
172+
173+
def _cmd_test_hermes() -> None:
174+
"""Test Hermes model switching — switch to a random model and back."""
175+
import random
176+
import time
177+
178+
from desloppify.app.commands.helpers.transition_messages import (
179+
_hermes_available,
180+
_hermes_get,
181+
_hermes_send_message,
182+
)
183+
184+
if not _hermes_available():
185+
print(colorize('Hermes not enabled. Set "hermes_enabled": true in config.json', "yellow"))
186+
return
187+
188+
# Get current model
189+
info = _hermes_get("/sessions/_any")
190+
if "error" in info:
191+
print(colorize(f"Cannot reach Hermes: {info['error']}", "red"))
192+
return
193+
194+
original_model = info.get("model", "unknown")
195+
original_provider = info.get("provider", "unknown")
196+
print(f" Current model: {original_provider}:{original_model}")
197+
198+
# Pick a random test model
199+
test_models = [
200+
("openrouter", "google/gemini-2.5-flash"),
201+
("openrouter", "meta-llama/llama-4-scout"),
202+
("openrouter", "mistralai/mistral-medium-3"),
203+
]
204+
test_provider, test_model = random.choice(test_models)
205+
206+
# Switch to test model
207+
print(f" Switching to: {test_provider}:{test_model}")
208+
result = _hermes_send_message(f"/model {test_provider}:{test_model}", mode="queue")
209+
if not result.get("success"):
210+
print(colorize(f" Switch failed: {result.get('error', '?')}", "red"))
211+
return
212+
print(colorize(" ✓ Switch command sent", "green"))
213+
214+
# Wait a moment, then switch back
215+
time.sleep(2)
216+
print(f" Switching back to: {original_provider}:{original_model}")
217+
result = _hermes_send_message(f"/model {original_provider}:{original_model}", mode="queue")
218+
if not result.get("success"):
219+
print(colorize(f" Switch-back failed: {result.get('error', '?')}", "red"))
220+
return
221+
print(colorize(" ✓ Restored original model", "green"))

0 commit comments

Comments
 (0)