AI Dev Tools Keep Shipping Like Every Engineer Has One Project
I tried setting up claude-cognitive last week — an attention-based working-memory layer for Claude Code that promises persistent context across sessions on large codebases. The setup guide said fifteen minutes. It took three hours.
The bug count was small. The pattern behind the bugs is what I want to write about, because this is now the dominant failure mode I see across this generation of AI dev infrastructure: defaults written for the author's one project, on the author's one machine, with no plan for anyone else.
Three specific examples, each one a problem the Unix world solved decades ago.
1. Hardcoded paths
The router looks for documentation in ~/.claude/systems/, ~/.claude/modules/, and so on — the global Claude directory. But the setup guide has you create that documentation in your project's .claude/. These are different directories. The script fails silently when it finds nothing.
# Line 452 in the original context-router-v2.py
docs_root = Path(os.environ.get("CONTEXT_DOCS_ROOT", str(Path.home() / ".claude")))
Project-local-first, global-fallback is the convention every dev tool from git to direnv to mise has shipped for years. The fix is fifteen lines:
if os.environ.get("CONTEXT_DOCS_ROOT"):
docs_root = Path(os.environ["CONTEXT_DOCS_ROOT"])
elif Path(".claude").exists():
docs_root = Path(".claude")
else:
docs_root = Path.home() / ".claude"
That's not a feature request. That's the baseline. Any tool that doesn't do this assumes you only ever work on one codebase, which has never been true for any working engineer.
2. Hardcoded keywords
The router decides which docs are relevant by matching the prompt against a KEYWORDS dictionary that maps trigger words to documentation files. The default dictionary contained over a hundred entries, all tied to the author's specific project — file names like server-one.md, terms like vram, cuda, trajectory, state machine. On my codebase, every one of them was dead weight.
# Original keywords (lines 79–197)
KEYWORDS: Dict[str, List[str]] = {
"systems/server-one.md": [
"server-one", "gpu", "local model", "inference",
"vram", "cuda", "nvidia-smi"...
],
# ...100+ more project-specific keywords
}
The fix is to externalize the config to where the project lives:
def load_project_config():
config_paths = [
Path(".claude/keywords.json"),
Path.home() / ".claude/keywords.json",
]
for config_path in config_paths:
if config_path.exists():
try:
config = json.loads(config_path.read_text())
return (
config.get("keywords", {}),
config.get("co_activation", {}),
config.get("pinned", []),
)
except (json.JSONDecodeError, IOError):
continue
return ({}, {}, [])
KEYWORDS, CO_ACTIVATION, PINNED_FILES = load_project_config()
Each project now gets its own .claude/keywords.json with the trigger words, co-activation map, and pinned files that match its own architecture. This is .eslintrc.json and pyproject.toml and .editorconfig. Per-project configuration in the project, with a sane fallback. It's the contract that lets a tool work for ten thousand different codebases instead of one.
3. Silent failures
When no files reached HOT or WARM status — which, given the previous two bugs, was every time — the router printed nothing.
# Line 492 in original
if stats["hot"] > 0 or stats["warm"] > 0:
print(output)
This makes sense in steady-state production. You don't want injection noise on every prompt. But during setup, you have no signal that the hook is even running. The log file existed at ~/.claude/context_injection.log and contained the answer. I found it by find-ing for anything claude-cognitive had touched in the last hour. That's not a debugging strategy; that's archaeology.
If a tool is going to inject anything into my prompt — or fail to inject anything — it has to tell me. A single line on stderr (activated 3 docs from .claude/ or no docs matched, check .claude/keywords.json) would have saved me the three hours. The fix is one log line, or a --verbose flag, or simply printing the stats banner to stderr the first ten invocations and never again. Any of these costs less to implement than this paragraph cost to write.
The pattern
None of these are claude-cognitive bugs in isolation. They're the same three defaults — global-only paths, hardcoded project assumptions, silent execution — showing up across this generation of AI dev tooling.
There's a reason. Most of these tools start as one person's local script that worked well enough for them, then get open-sourced under the assumption that "open source it" and "make it usable by others" are the same step. They are not. The work of going from "works on the author's machine" to "works on a stranger's machine" is exactly the unglamorous engineering that gets skipped when a tool is gaining momentum on dev Twitter.
For the people building these tools: the bar is git. It's direnv. It's whatever shell-completion framework you ship. We figured this out a long time ago. Project-local config, sane env-var overrides, loud failures by default. The cost of not having these is measured in hours per user per setup, and the user count is going up fast.
For the rest of us evaluating: ask the questions before you install. Does it respect a project-local config directory? Does it tell me what it did? Does it have an env var I can use to override defaults without forking? If the answer to any of those is no, you're going to pay the same three hours I did.
claude-cognitive itself is worth the setup once you've patched the defaults. The attention routing — files heating up as you mention them, cooling off as the conversation drifts, co-activating related modules — is genuinely useful on a codebase large enough that you can't fit the whole repo in context. For anything smaller, you don't need it.
I've opened a PR against the upstream repo to add project-local keyword loading. Whether or not it lands, the broader claim stands: AI-native developer infrastructure is still in its hand-rolled-bash-script phase. The teams that win the next wave aren't the ones with the cleverest models — they're the ones that figure out the boring part: defaults that don't assume the user is the author.