import{_ as i,o as a,c as t,ag as n}from"./chunks/framework.CmQHR1bQ.js";const c=JSON.parse('{"title":"Anti-Pattern Detection Hooks","description":"","frontmatter":{},"headers":[],"relativePath":"guides/anti-patterns.md","filePath":"guides/anti-patterns.md"}'),e={name:"guides/anti-patterns.md"};function l(p,s,h,r,k,o){return a(),t("div",null,[...s[0]||(s[0]=[n(`<h1 id="anti-pattern-detection-hooks" tabindex="-1">Anti-Pattern Detection Hooks <a class="header-anchor" href="#anti-pattern-detection-hooks" aria-label="Permalink to &quot;Anti-Pattern Detection Hooks&quot;">​</a></h1><p>Six hooks that detect and prevent common code anti-patterns. Each runs on <code>PreToolUse:Write/Edit</code> events and provides actionable fix suggestions.</p><h2 id="hook-summary" tabindex="-1">Hook Summary <a class="header-anchor" href="#hook-summary" aria-label="Permalink to &quot;Hook Summary&quot;">​</a></h2><table tabindex="0"><thead><tr><th>Hook</th><th>Pattern Detected</th><th>Severity</th><th>Languages</th></tr></thead><tbody><tr><td>suppress-custom-retry.sh</td><td>Custom retry loops when tenacity available</td><td>WARNING</td><td>Python</td></tr><tr><td>suppress-v2-files.sh</td><td><code>_v2</code>, <code>_new</code>, <code>_old</code> file naming</td><td>ERROR</td><td>All</td></tr><tr><td>suppress-hardcoded-strings.sh</td><td>Hardcoded provider/model/URL strings</td><td>WARNING</td><td>Python, TS, Go</td></tr><tr><td>suppress-print-statements.sh</td><td>print()/console.log when structured logger available</td><td>WARNING</td><td>Python, TS, Go</td></tr><tr><td>suppress-isolated-classes.sh</td><td>God classes (&gt;15 methods or &gt;300 lines)</td><td>WARNING</td><td>Python, TS</td></tr><tr><td>suppress-direct-http.sh</td><td>Direct HTTP calls without client abstraction</td><td>WARNING</td><td>Python, TS, Go</td></tr></tbody></table><h2 id="parpour-specific-anti-patterns" tabindex="-1">Parpour-Specific Anti-Patterns <a class="header-anchor" href="#parpour-specific-anti-patterns" aria-label="Permalink to &quot;Parpour-Specific Anti-Patterns&quot;">​</a></h2><h3 id="don-t-add-specs-to-venture-subdirectory-—-specs-live-at-parpour-root" tabindex="-1">Don&#39;t add specs to venture/ subdirectory — specs live at parpour root <a class="header-anchor" href="#don-t-add-specs-to-venture-subdirectory-—-specs-live-at-parpour-root" aria-label="Permalink to &quot;Don&#39;t add specs to venture/ subdirectory — specs live at parpour root&quot;">​</a></h3><p><strong>Pattern</strong>: Adding specification files inside <code>venture/</code> or other subdirectories.</p><p><strong>Why blocked</strong>: Specs are canonical at the parpour root level. Subdirectories like <code>venture/</code> are for implementation, not specification.</p><p><strong>Fix</strong>:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Wrong</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">venture/TECHNICAL_SPEC.md</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">venture/TRACK_A.md</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Correct</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">TECHNICAL_SPEC.md</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">TRACK_A.md</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">TRACK_B.md</span></span></code></pre></div><hr><h3 id="don-t-create-saas-dependent-artifact-flows-—-use-headless-compiler-ir" tabindex="-1">Don&#39;t create SaaS-dependent artifact flows — use headless compiler IR <a class="header-anchor" href="#don-t-create-saas-dependent-artifact-flows-—-use-headless-compiler-ir" aria-label="Permalink to &quot;Don&#39;t create SaaS-dependent artifact flows — use headless compiler IR&quot;">​</a></h3><p><strong>Pattern</strong>: Building artifact generation flows that depend on external SaaS services (e.g., OpenAI API, hosted databases).</p><p><strong>Why blocked</strong>: Parpour must be runnable standalone. All artifact generation should use the headless compiler IR and local processing.</p><p><strong>Fix</strong>:</p><div class="language-python vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">python</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Wrong — depends on external API</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> generate_civ_spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">():</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">    response </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> openai.ChatCompletion.create(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">model</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;gpt-4&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">messages</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> response.choices[</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">].text</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Correct — uses headless IR</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> parpour.compiler </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> HeadlessCompiler</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> generate_civ_spec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">():</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">    compiler </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> HeadlessCompiler()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">    ir </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> compiler.parse_config(config)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> compiler.generate_artifact(ir, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&#39;spec&#39;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span></code></pre></div><hr><h3 id="don-t-add-fallbacks-or-silent-failure-paths" tabindex="-1">Don&#39;t add fallbacks or silent failure paths <a class="header-anchor" href="#don-t-add-fallbacks-or-silent-failure-paths" aria-label="Permalink to &quot;Don&#39;t add fallbacks or silent failure paths&quot;">​</a></h3><p><strong>Pattern</strong>: Adding <code>try/except</code> blocks that hide errors, or feature flags that silently degrade functionality.</p><p><strong>Why blocked</strong>: Following global CLAUDE instructions — code should fail fast and fail loudly.</p><p><strong>Fix</strong>:</p><div class="language-python vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">python</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Wrong — silent fallback</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">try</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">    spec </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> load_spec(path)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">except</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Exception</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">    spec </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> default_spec  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Hidden failure</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Correct — fail loudly</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">spec </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> load_spec(path)  </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Raises if not found</span></span></code></pre></div><hr><h2 id="hook-details" tabindex="-1">Hook Details <a class="header-anchor" href="#hook-details" aria-label="Permalink to &quot;Hook Details&quot;">​</a></h2><h3 id="suppress-custom-retry-sh" tabindex="-1">suppress-custom-retry.sh <a class="header-anchor" href="#suppress-custom-retry-sh" aria-label="Permalink to &quot;suppress-custom-retry.sh&quot;">​</a></h3><p><strong>What it detects</strong>: Hand-rolled retry/backoff loops in Python when <code>tenacity</code> is declared in project dependencies.</p><p><strong>Patterns caught</strong>:</p><ul><li><code>for attempt in range(N)</code> with <code>sleep</code> + <code>try/except</code></li><li><code>while True</code> retry loops with attempt counters</li><li>Manual exponential backoff (<code>2 ** attempt</code>)</li></ul><p><strong>Fix</strong>:</p><div class="language-python vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">python</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Before (anti-pattern)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">for</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> attempt </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">in</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> range</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">):</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">    try</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">        result </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> httpx.get(url)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">        break</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">    except</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Exception</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">        time.sleep(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">2</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> **</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> attempt)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># After (correct)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> tenacity </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> retry, stop_after_attempt, wait_exponential</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">@retry</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">stop</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">stop_after_attempt(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">), </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">wait</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">wait_exponential())</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> fetch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(url: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">str</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) -&gt; httpx.Response:</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">    return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> httpx.get(url, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">timeout</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span></code></pre></div><hr><h3 id="suppress-v2-files-sh" tabindex="-1">suppress-v2-files.sh <a class="header-anchor" href="#suppress-v2-files-sh" aria-label="Permalink to &quot;suppress-v2-files.sh&quot;">​</a></h3><p><strong>What it detects</strong>: Files with <code>_v2</code>, <code>_new</code>, <code>_old</code>, <code>_backup</code>, <code>_copy</code>, <code>_temp</code> suffixes.</p><p><strong>Why blocked (ERROR)</strong>: v2 files lead to:</p><ul><li>Import confusion (which version to use?)</li><li>Stale code copies that diverge</li><li>No clear migration path</li></ul><p><strong>Fix options</strong>:</p><ol><li>Modify the original file directly</li><li>Use feature flags for behavioral changes</li><li>Use interface versioning (APIv2 endpoint, not handler_v2.py)</li><li>If migrating, rename the original and update all imports in one commit</li></ol><hr><h3 id="suppress-hardcoded-strings-sh" tabindex="-1">suppress-hardcoded-strings.sh <a class="header-anchor" href="#suppress-hardcoded-strings-sh" aria-label="Permalink to &quot;suppress-hardcoded-strings.sh&quot;">​</a></h3><p><strong>What it detects</strong>: Hardcoded provider names, model identifiers, and API URLs in source code.</p><p><strong>Patterns caught</strong>:</p><ul><li>LLM model names: <code>&quot;gpt-4&quot;</code>, <code>&quot;claude-3&quot;</code>, <code>&quot;gemini-1.5&quot;</code></li><li>API URLs: <code>&quot;https://api.openai.com/...&quot;</code></li><li>Provider identifiers: <code>&quot;aws&quot;</code>, <code>&quot;openai&quot;</code>, <code>&quot;anthropic&quot;</code> as string literals</li></ul><p><strong>Excludes</strong>: Test files, imports, comments.</p><p><strong>Fix</strong>:</p><div class="language-python vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">python</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Before</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">response </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> client.chat(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;gpt-4&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, messages)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># After</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> config </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> settings</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">response </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> client.chat(settings.default_model, messages)</span></span></code></pre></div><hr><h3 id="suppress-print-statements-sh" tabindex="-1">suppress-print-statements.sh <a class="header-anchor" href="#suppress-print-statements-sh" aria-label="Permalink to &quot;suppress-print-statements.sh&quot;">​</a></h3><p><strong>What it detects</strong>: Unstructured logging (print, console.log, fmt.Println) when a structured logging library is in project dependencies.</p><p><strong>Dependency triggers</strong>:</p><ul><li>Python: <code>structlog</code> in deps -&gt; blocks <code>print()</code> and <code>logging.getLogger()</code></li><li>TypeScript: <code>pino</code>/<code>winston</code> in deps -&gt; blocks <code>console.log()</code></li><li>Go: <code>zerolog</code>/<code>zap</code> in deps -&gt; blocks <code>fmt.Println()</code></li></ul><p><strong>Fix</strong>:</p><div class="language-python vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">python</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Before</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">print</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Processing user </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">{</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">user_id</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">}</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># After</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> structlog</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">log </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> structlog.get_logger()</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">log.info(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;processing_user&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">user_id</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">user_id)</span></span></code></pre></div><hr><h3 id="suppress-isolated-classes-sh" tabindex="-1">suppress-isolated-classes.sh <a class="header-anchor" href="#suppress-isolated-classes-sh" aria-label="Permalink to &quot;suppress-isolated-classes.sh&quot;">​</a></h3><p><strong>What it detects</strong>: Classes that are too large (&quot;God classes&quot;).</p><p><strong>Thresholds</strong>:</p><ul><li>More than 15 methods</li><li>More than 300 lines</li></ul><p><strong>Fix</strong>:</p><ol><li><strong>SRP decomposition</strong>: Extract cohesive method groups into separate classes</li><li><strong>Composition</strong>: Break into composed components instead of one monolith</li><li><strong>Strategy pattern</strong>: Extract behavioral variations into strategy classes</li><li><strong>DTO extraction</strong>: Move data-only methods into separate dataclasses</li></ol><hr><h3 id="suppress-direct-http-sh" tabindex="-1">suppress-direct-http.sh <a class="header-anchor" href="#suppress-direct-http-sh" aria-label="Permalink to &quot;suppress-direct-http.sh&quot;">​</a></h3><p><strong>What it detects</strong>: Direct HTTP client calls (requests.get, fetch(), http.Get) in business logic.</p><p><strong>Why</strong>: Direct calls scatter URL construction, authentication, retry logic, timeout handling, and error mapping across the codebase.</p><p><strong>Excludes</strong>: Files in <code>clients/</code>, <code>adapters/</code>, <code>infrastructure/</code>, <code>transport/</code> directories. Files named <code>*client*</code>, <code>*http*</code>, <code>*fetcher*</code>.</p><p><strong>Fix</strong>:</p><div class="language-python vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">python</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Before (scattered in business logic)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">response </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> requests.get(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">{BASE_URL}</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">/users/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">{</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">user_id</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">}</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">headers</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">auth_headers)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># After (centralized client)</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> UserClient</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">    def</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> __init__</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(self, base_url: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">str</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, auth: Auth):</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">        self</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.client </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> httpx.AsyncClient(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">base_url</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">base_url, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">auth</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">auth)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">    async</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> get_user</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(self, user_id: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">str</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) -&gt; User:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">        response </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> await</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> self</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.client.get(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;/users/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">{</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">user_id</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">}</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">        response.raise_for_status()</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">        return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> User.model_validate(response.json())</span></span></code></pre></div><h2 id="integration" tabindex="-1">Integration <a class="header-anchor" href="#integration" aria-label="Permalink to &quot;Integration&quot;">​</a></h2><h3 id="claude-code-hooks-settings-json" tabindex="-1">Claude Code Hooks (settings.json) <a class="header-anchor" href="#claude-code-hooks-settings-json" aria-label="Permalink to &quot;Claude Code Hooks (settings.json)&quot;">​</a></h3><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">  &quot;hooks&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">    &quot;PreToolUse&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">      {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">        &quot;matcher&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Write|Edit&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">        &quot;hooks&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">          &quot;hooks/suppress-custom-retry.sh $FILE_PATH&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">          &quot;hooks/suppress-v2-files.sh $FILE_PATH&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">          &quot;hooks/suppress-hardcoded-strings.sh $FILE_PATH&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">          &quot;hooks/suppress-print-statements.sh $FILE_PATH&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">          &quot;hooks/suppress-isolated-classes.sh $FILE_PATH&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">          &quot;hooks/suppress-direct-http.sh $FILE_PATH&quot;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">        ]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">      }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">    ]</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><h3 id="pre-commit-local-hooks" tabindex="-1">Pre-commit (local hooks) <a class="header-anchor" href="#pre-commit-local-hooks" aria-label="Permalink to &quot;Pre-commit (local hooks)&quot;">​</a></h3><div class="language-yaml vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">- </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">repo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">local</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">  hooks</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">    - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">id</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">no-v2-files</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">      name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">Block v2/new/old file creation</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">      entry</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">hooks/suppress-v2-files.sh</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">      language</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">script</span></span>
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">      stages</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">pre-commit</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]</span></span></code></pre></div><h2 id="testing-hooks" tabindex="-1">Testing Hooks <a class="header-anchor" href="#testing-hooks" aria-label="Permalink to &quot;Testing Hooks&quot;">​</a></h2><p>Each hook can be tested standalone:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Test v2 file detection (should print error and exit 1)</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">echo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;test&quot;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &gt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> /tmp/handler_v2.py</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">./hooks/suppress-v2-files.sh</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> /tmp/handler_v2.py</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">echo</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;Exit code: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">$?</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Test custom retry detection (should print warning)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">cat</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &gt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> /tmp/retry_test.py</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;&lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;EOF&#39;</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">for attempt in range(5):</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">    try:</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">        result = httpx.get(url)</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">        break</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">    except Exception:</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">        time.sleep(2 ** attempt)</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">EOF</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">./hooks/suppress-custom-retry.sh</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> /tmp/retry_test.py</span></span></code></pre></div>`,74)])])}const g=i(e,[["render",l]]);export{c as __pageData,g as default};
