You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This week’s aroma profile is copy-paste flambé, stale abstraction, and a lingering finish of “future me can mop that up” 🤮. The repo is not beyond saving, but it absolutely has that special enterprise musk where things mostly work while quietly breeding maintenance debt in the walls 💩.
The good news: I did not need to invent problems to fill this report. The bad news: that’s because the real ones were already standing in line.
💩 SkillsProcessor.ts already has target metadata, yet still duplicates the same propagation logic per target like it’s being paid by the line.
The file clearly knows what a skill target is, but instead of iterating over that definition it hand-wires an if ladder and a herd of propagateSkillsForX functions. So you get the maintenance cost of a metadata layer and the maintenance cost of copy-paste. Remarkably inefficient. Truly artisanal nonsense.
This is the kind of file that feels “fine” at 3 targets and turns into a haunted spreadsheet at 13. Every new integration now means more branch glue, more duplicated filesystem choreography, and more chances for one path to drift while the others keep pretending they’re cousins.
Replace propagateSkillsForX with one generic directory-propagation helper driven by target metadata.
Store destination path, label, and agent identifiers in a single table and iterate it for dispatch.
Keep truly target-specific behavior behind optional hooks instead of cloning the entire temp-dir / copy / replace sequence.
Agent Integrations as a Hard-Coded Zoo
🤮 Most agent integrations are tiny piles of metadata wrapped in full classes, so adding a new one still requires a ceremonial copy-paste parade.
The registry is a hand-maintained wall of imports and new WhateverAgent() calls. That makes consistency a matter of human memory instead of design, which is always adorable right up until someone forgets one of the five places a new integration needs to be wired.
A few adapters absolutely deserve custom code. Most do not. When the common case is just “identifier, display name, path, capabilities,” forcing that through separate files and registry edits is not abstraction — it’s paperwork wearing a trench coat.
Introduce a single AgentDefinition manifest for standard metadata: id, name, defaultOutput, capabilities, MCP keys, skill/subagent support.
Generate allAgents, CLI help, target maps, and docs from that source of truth.
Keep bespoke classes only for genuinely weird adapters, not for every agent with a pulse.
Two Config Loaders, Zero Peace
🤦 This repo parses ruler.toml in two different subsystems, which is a classy way to schedule config drift for later.
ConfigLoader.ts has an actual schema and validates input like a responsible adult. UnifiedConfigLoader.ts then rereads the same file and manually peels fields off tomlRaw like it’s trying to avoid making eye contact with the first loader.
That split already covers overlapping concerns like default_agents, nested, skills, and MCP parsing. So every new config field now has two places to be updated and one future bug report where everybody swears their parser is the real one. Magnificent.
Parse TOML once into a canonical config DTO/AST and let both consumers project from that.
Centralize validation, defaults, and deprecation handling in one place; keep diagnostics as a layer on top, not a separate parser.
Add equivalence tests that assert both public loaders interpret the same fixture configs the same way.
Backup Failures Get Silently Swallowed
☠️ When backups are enabled, a failed backup can disappear quietly and the overwrite still happens anyway, which is a lovely little betrayal.
backupFile suppresses everycopyFile failure even though the comment only talks about a missing file. So EACCES, ENOSPC, EROFS, and friends get the exact same shrug as ENOENT. Very reassuring. Very safety-oriented. Very clown shoes.
Then the main write path carries on and writes the generated file regardless. If backup is turned on, failure to create it should stop the write, not politely step aside and let the damage continue.
Only suppress ENOENT; surface any other backup failure with context.
Make backupFile return an explicit status or throw, and refuse to overwrite when backup === true but backup creation failed.
Add tests for permission and disk-failure scenarios so the “safety” feature stops being decorative.
Nested Discovery Scans the Whole Damn Repo
🐢 Nested .ruler discovery is a brute-force filesystem crawl with an ignore list so short it feels sarcastic.
Yes, it skips dot directories and nested git repos, so it’s not completely feral. But everything else is still fair game, which means large monorepos can pay a silly I/O tax wandering through packages/, apps/, vendor/, target/, out/, and whatever other directory somebody forgot to add to the sacred set.
This kind of discovery ages like milk. Every new “big junk directory” becomes a performance bug until someone remembers to stuff another magic string into a constant. Delightful little maintenance slot machine 🎰.
Respect .gitignore/.ignore where practical, or allow ignore patterns in ruler.toml.
Expand the default skip set for common heavyweight directories (packages, apps, vendor, target, out, etc.).
Cache discovery results per run and consider bounded-depth or explicit-root options for nested mode.
Test Code Lives Outside the Static Gate
🧪 The test suite is invited to Jest, but not to lint, and only half-invited to type-checking.
The lint script only targets src, so test code gets zero ESLint coverage. For a repo that clearly leans hard on tests, that is a weird amount of trust to place in vibes and good intentions. Dead imports? Unsafe casts? Sketchy setup helpers? Enjoy the surprise party.
tsconfig.json excludes tests, which is fine for build output, but it means test typing piggybacks on ts-jest during test execution instead of having a dedicated static gate. That fuses runtime execution to type validation — slower, noisier, and much worse at saying “your test code is rotting” before the whole circus starts.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
This Codebase Smells!
This week’s aroma profile is copy-paste flambé, stale abstraction, and a lingering finish of “future me can mop that up” 🤮. The repo is not beyond saving, but it absolutely has that special enterprise musk where things mostly work while quietly breeding maintenance debt in the walls 💩.
The good news: I did not need to invent problems to fill this report. The bad news: that’s because the real ones were already standing in line.
Table of Contents
Copy-Paste Propagation Pipeline
💩
SkillsProcessor.tsalready has target metadata, yet still duplicates the same propagation logic per target like it’s being paid by the line.The file clearly knows what a skill target is, but instead of iterating over that definition it hand-wires an
ifladder and a herd ofpropagateSkillsForXfunctions. So you get the maintenance cost of a metadata layer and the maintenance cost of copy-paste. Remarkably inefficient. Truly artisanal nonsense.This is the kind of file that feels “fine” at 3 targets and turns into a haunted spreadsheet at 13. Every new integration now means more branch glue, more duplicated filesystem choreography, and more chances for one path to drift while the others keep pretending they’re cousins.
Files
src/core/SkillsProcessor.tsCode
Target metadata already exists — Lines 140–153: https://github.com/intellectronica/ruler/blob/main/src/core/SkillsProcessor.ts#L140-L153
But dispatch is still handwritten — Lines 377–385: https://github.com/intellectronica/ruler/blob/main/src/core/SkillsProcessor.ts#L377-L385
Claude propagation — Lines 523–542: https://github.com/intellectronica/ruler/blob/main/src/core/SkillsProcessor.ts#L523-L542
Codex propagation, same story with different nouns — Lines 581–600: https://github.com/intellectronica/ruler/blob/main/src/core/SkillsProcessor.ts#L581-L600
Suggestions
propagateSkillsForXwith one generic directory-propagation helper driven by target metadata.Agent Integrations as a Hard-Coded Zoo
🤮 Most agent integrations are tiny piles of metadata wrapped in full classes, so adding a new one still requires a ceremonial copy-paste parade.
The registry is a hand-maintained wall of imports and
new WhateverAgent()calls. That makes consistency a matter of human memory instead of design, which is always adorable right up until someone forgets one of the five places a new integration needs to be wired.A few adapters absolutely deserve custom code. Most do not. When the common case is just “identifier, display name, path, capabilities,” forcing that through separate files and registry edits is not abstraction — it’s paperwork wearing a trench coat.
Files
src/agents/index.tssrc/agents/ClaudeAgent.tssrc/agents/CursorAgent.tssrc/agents/JunieAgent.tsCode
Manual registry wiring — Lines 38–56: https://github.com/intellectronica/ruler/blob/main/src/agents/index.ts#L38-L56
Plain metadata disguised as a class — Lines 7–18: https://github.com/intellectronica/ruler/blob/main/src/agents/ClaudeAgent.ts#L7-L18
Even a mild special case gets another wrapper — Lines 18–35: https://github.com/intellectronica/ruler/blob/main/src/agents/CursorAgent.ts#L18-L35
Suggestions
AgentDefinitionmanifest for standard metadata:id,name,defaultOutput, capabilities, MCP keys, skill/subagent support.allAgents, CLI help, target maps, and docs from that source of truth.Two Config Loaders, Zero Peace
🤦 This repo parses
ruler.tomlin two different subsystems, which is a classy way to schedule config drift for later.ConfigLoader.tshas an actual schema and validates input like a responsible adult.UnifiedConfigLoader.tsthen rereads the same file and manually peels fields offtomlRawlike it’s trying to avoid making eye contact with the first loader.That split already covers overlapping concerns like
default_agents,nested,skills, and MCP parsing. So every new config field now has two places to be updated and one future bug report where everybody swears their parser is the real one. Magnificent.Files
src/core/ConfigLoader.tssrc/core/UnifiedConfigLoader.tsCode
Schema-based validation exists — Lines 68–77: https://github.com/intellectronica/ruler/blob/main/src/core/ConfigLoader.ts#L68-L77
And it is actually enforced — Lines 532–542: https://github.com/intellectronica/ruler/blob/main/src/core/ConfigLoader.ts#L532-L542
Meanwhile the other loader reparses the same TOML — Lines 64–72: https://github.com/intellectronica/ruler/blob/main/src/core/UnifiedConfigLoader.ts#L64-L72
Then manually extracts overlapping fields — Lines 90–119: https://github.com/intellectronica/ruler/blob/main/src/core/UnifiedConfigLoader.ts#L90-L119
And rolls its own MCP parsing too — Lines 193–206: https://github.com/intellectronica/ruler/blob/main/src/core/UnifiedConfigLoader.ts#L193-L206
Suggestions
Backup Failures Get Silently Swallowed
☠️ When backups are enabled, a failed backup can disappear quietly and the overwrite still happens anyway, which is a lovely little betrayal.
backupFilesuppresses everycopyFilefailure even though the comment only talks about a missing file. SoEACCES,ENOSPC,EROFS, and friends get the exact same shrug asENOENT. Very reassuring. Very safety-oriented. Very clown shoes.Then the main write path carries on and writes the generated file regardless. If backup is turned on, failure to create it should stop the write, not politely step aside and let the damage continue.
Files
src/core/FileSystemUtils.tssrc/agents/AbstractAgent.tsCode
Backup swallows all copy errors — Lines 266–279: https://github.com/intellectronica/ruler/blob/main/src/core/FileSystemUtils.ts#L266-L279
Callers write immediately after the backup attempt — Lines 44–51: https://github.com/intellectronica/ruler/blob/main/src/agents/AbstractAgent.ts#L44-L51
Suggestions
ENOENT; surface any other backup failure with context.backupFilereturn an explicit status or throw, and refuse to overwrite whenbackup === truebut backup creation failed.Nested Discovery Scans the Whole Damn Repo
🐢 Nested
.rulerdiscovery is a brute-force filesystem crawl with an ignore list so short it feels sarcastic.Yes, it skips dot directories and nested git repos, so it’s not completely feral. But everything else is still fair game, which means large monorepos can pay a silly I/O tax wandering through
packages/,apps/,vendor/,target/,out/, and whatever other directory somebody forgot to add to the sacred set.This kind of discovery ages like milk. Every new “big junk directory” becomes a performance bug until someone remembers to stuff another magic string into a constant. Delightful little maintenance slot machine 🎰.
Files
src/core/FileSystemUtils.tsCode
Tiny built-in ignore list — Lines 9–20: https://github.com/intellectronica/ruler/blob/main/src/core/FileSystemUtils.ts#L9-L20
Full subtree walk — Lines 315–338: https://github.com/intellectronica/ruler/blob/main/src/core/FileSystemUtils.ts#L315-L338
Suggestions
.gitignore/.ignorewhere practical, or allow ignore patterns inruler.toml.packages,apps,vendor,target,out, etc.).Test Code Lives Outside the Static Gate
🧪 The test suite is invited to Jest, but not to lint, and only half-invited to type-checking.
The lint script only targets
src, so test code gets zero ESLint coverage. For a repo that clearly leans hard on tests, that is a weird amount of trust to place in vibes and good intentions. Dead imports? Unsafe casts? Sketchy setup helpers? Enjoy the surprise party.tsconfig.jsonexcludestests, which is fine for build output, but it means test typing piggybacks onts-jestduring test execution instead of having a dedicated static gate. That fuses runtime execution to type validation — slower, noisier, and much worse at saying “your test code is rotting” before the whole circus starts.Files
package.jsontsconfig.jsonjest.config.jsCode
Lint/build scripts stop at
src— Lines 7–15: https://github.com/intellectronica/ruler/blob/main/package.json#L7-L15The compiler excludes tests — Lines 113–114: https://github.com/intellectronica/ruler/blob/main/tsconfig.json#L113-L114
Tests are TypeScript and only enter through ts-jest — Lines 1–5: https://github.com/intellectronica/ruler/blob/main/jest.config.js#L1-L5
Suggestions
tests/**/*.{ts,tsx}and selectively exclude only ugly fixtures.tsc --noEmitpass for tests viatsconfig.tests.json, or otherwise give test code its own static type-check step.All reactions