Files
blog/pre-commit-agent.html

394 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>A Pre-Commit Agent That Guards Your Secrets for $0.001 — Tinqs Blog</title>
<meta name="description" content="We built a pre-commit hook that calls DeepSeek V4 Flash to review every commit. It catches leaked API keys, classified terms, broken URLs, and drafts announcements — for a tenth of a cent per commit.">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://www.tinqs.com/blog/pre-commit-agent">
<meta property="og:type" content="article">
<meta property="og:url" content="https://www.tinqs.com/blog/pre-commit-agent">
<meta property="og:title" content="A Pre-Commit Agent That Guards Your Secrets for $0.001">
<meta property="og:description" content="A DeepSeek-powered pre-commit hook that catches leaks for $0.001/commit.">
<meta property="og:image" content="https://www.tinqs.com/img/og-cover.jpg">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="A Pre-Commit Agent That Guards Your Secrets for $0.001">
<meta name="twitter:description" content="A DeepSeek-powered pre-commit hook that catches leaks for $0.001/commit.">
<meta name="twitter:image" content="https://www.tinqs.com/img/og-cover.jpg">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "A Pre-Commit Agent That Guards Your Secrets for $0.001",
"datePublished": "2026-05-25",
"author": {
"@type": "Person",
"name": "Ozan Bozkurt"
},
"publisher": {
"@type": "Organization",
"name": "Tinqs Limited",
"url": "https://www.tinqs.com"
},
"description": "We built a pre-commit hook that calls DeepSeek V4 Flash to review every commit. It catches leaked API keys, classified terms, broken URLs, and drafts announcements — for a tenth of a cent per commit."
}
</script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
/* ── Tinqs Studio brand — post styles ── */
:root {
/* Studio near-black base */
--c-bg: #0B0C0E;
--c-bg-raised: #15171A;
/* Foreground */
--c-fg: #ECEEF1;
--c-muted: #8A95A3;
/* Family accents */
--c-lime: #B6FF3C;
--c-violet: #7C5CFF;
/* Borders */
--c-border: rgba(255,255,255,.07);
--c-border-strong: rgba(255,255,255,.12);
}
*, *::before, *::after { box-sizing: border-box; }
html { background: var(--c-bg); }
body {
margin: 0;
padding: 0;
background: var(--c-bg);
color: var(--c-fg);
font-family: 'Inter', system-ui, -apple-system, sans-serif;
font-size: 16px;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
/* ── Post container ── */
.post {
background: var(--c-bg);
max-width: 720px;
margin: 0 auto;
padding: 48px 24px 60px;
}
/* ── Back link ── */
.post__back {
color: var(--c-muted);
text-decoration: none;
font-size: 0.875rem;
display: inline-block;
margin-bottom: 24px;
transition: color 0.15s;
}
.post__back:hover { color: var(--c-lime); }
/* ── Gradient title — lime → violet ── */
.post__title {
font-family: 'Space Grotesk', system-ui, -apple-system, sans-serif;
background: linear-gradient(90deg, var(--c-lime), var(--c-violet));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-weight: 700;
font-size: 2.2rem;
line-height: 1.2;
margin: 0 0 16px;
}
/* ── Date pill ── */
.post__date {
display: inline-block;
font-family: 'JetBrains Mono', ui-monospace, 'SF Mono', Consolas, monospace;
font-size: 0.72rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--c-muted);
border: 1px solid var(--c-border);
border-radius: 999px;
padding: 4px 14px;
margin-bottom: 16px;
}
/* ── Lead ── */
.post__lead {
color: var(--c-muted);
font-size: 1.08rem;
line-height: 1.7;
}
/* ── Body ── */
.post__body { font-size: 1rem; line-height: 1.7; }
.post__body p { margin: 14px 0; }
.post__body h2 {
font-family: 'Space Grotesk', system-ui, -apple-system, sans-serif;
font-weight: 600;
font-size: 1.6rem;
margin: 54px 0 6px;
padding-left: 16px;
border-left: 4px solid var(--c-lime);
line-height: 1.3;
}
.post__body h3 {
font-family: 'Space Grotesk', system-ui, -apple-system, sans-serif;
font-weight: 500;
color: var(--c-violet);
font-size: 1.15rem;
margin: 30px 0 4px;
}
.post__body h4, .post__body h5, .post__body h6 {
margin: 20px 0 4px;
}
/* ── Inline code ── */
.post__body code {
font-family: 'JetBrains Mono', ui-monospace, 'SF Mono', Consolas, monospace;
font-size: 0.84em;
background: var(--c-bg-raised);
color: var(--c-lime);
padding: 2px 6px;
border-radius: 4px;
border: 1px solid var(--c-border);
}
/* ── Code blocks ── */
.post__body pre {
background: var(--c-bg);
border: 1px solid var(--c-border);
border-radius: 8px;
padding: 16px 18px;
overflow-x: auto;
margin: 14px 0;
font-family: 'JetBrains Mono', ui-monospace, 'SF Mono', Consolas, monospace;
font-size: 0.83rem;
line-height: 1.55;
color: var(--c-fg);
}
.post__body pre code {
background: transparent;
padding: 0;
border: none;
font-size: inherit;
color: inherit;
border-radius: 0;
}
/* ── Blockquote ── */
.post__body blockquote {
background: rgba(124, 92, 255, 0.06);
border: 1px solid rgba(124, 92, 255, 0.15);
border-left: 4px solid var(--c-violet);
border-radius: 0 8px 8px 0;
padding: 16px 18px;
margin: 18px 0;
color: var(--c-fg);
font-size: 0.94rem;
}
/* ── Links ── */
.post__body a { color: var(--c-lime); text-decoration: underline; text-underline-offset: 3px; }
.post__body a:hover { color: var(--c-violet); }
/* ── Strong ── */
.post__body strong { color: var(--c-lime); font-weight: 600; }
/* ── HR ── */
.post__body hr {
border: none;
border-top: 1px solid var(--c-border);
margin: 32px 0;
}
/* ── Figures ── */
.post__body figure { margin: 20px 0; }
.post__body figure img {
max-width: 100%;
border-radius: 12px;
border: 1px solid var(--c-border);
}
.post__body figcaption {
color: var(--c-muted);
font-size: 0.85rem;
margin-top: 6px;
}
/* ── Lists ── */
.post__body ul, .post__body ol { padding-left: 1.5em; margin: 10px 0; }
.post__body li { margin: 4px 0; }
/* ── Author ── */
.post__author {
display: flex;
align-items: center;
gap: 14px;
margin-top: 48px;
padding-top: 24px;
border-top: 1px solid var(--c-border);
}
.post__author-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
background: var(--c-violet);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 0.85rem;
flex-shrink: 0;
}
.post__author-info {
font-size: 0.85rem;
color: var(--c-muted);
line-height: 1.4;
}
.post__author-name {
color: var(--c-fg);
font-weight: 600;
}
</style>
</head>
<body>
<!-- POST -->
<article class="post">
<a href="/blog/" class="post__back">&larr; All Posts</a>
<span class="post__date">25 May 2026</span>
<h1 class="post__title">A Pre-Commit Agent That Guards Your Secrets for $0.001</h1>
<p class="post__lead">Every small team has the same problem: too many things to remember before <code>git commit</code>. Don't leak API keys. Don't reference the classified AI codename in public posts. Don't link to GitHub repos we deleted six months ago. Don't push a blog post with a 90-character title.
A checklist in the README doesn't work. Humans skip checklists. Code review catches some issues but not all — reviewers focus on logic, not whether a URL points to a deleted org.
We built a pre-commit hook with two layers: a regex blocklist that's instant and free, and an LLM review that costs $0.001. Together they catch everything.
## Layer 1: Regex blocklist (0ms, $0.00)
A text file of patterns, each tagged with scope and message:
``<code>
public|\b<internal-codename>\b|Classified codename — use the public-facing alias
all|github\.com/(tinqs-ltd|tinqs)/|GitHub repos deleted — use tinqs.com
all|sk-[a-zA-Z0-9]{20,}|Possible API key leaked
all|AKIA[A-Z0-9]{16}|AWS access key leaked
public|admin\.<internal-domain>|Internal admin URL in public content
</code>`<code>
The scope field controls where patterns apply. </code>all<code> means every file. </code>public<code> means only public-facing content — blog posts, website, marketing pages. We <em>want</em> classified codenames in internal architecture docs. We just don't want them in blog posts.
The blocklist runs grep against the staged diff. No network call, no API, no latency. Match found → commit blocked immediately with file path and explanation. This catches 80% of issues before the LLM wakes up.
## Layer 2: DeepSeek V4 Flash review (~4s, $0.001)
If the commit touches public-facing files, the hook sends the staged diff to DeepSeek V4 Flash. The system prompt tells it exactly what to check:
- <strong>Leaked secrets</strong> — API keys, tokens, credentials the regex might have missed
- <strong>Classified terms</strong> — codenames not yet in the blocklist
- <strong>Internal URLs</strong> — references to services that shouldn't be public
- <strong>Blog quality</strong> — title length, meta description, slug consistency
- <strong>Broken links</strong> — malformed URLs, obvious typos
- <strong>Announcements</strong> — if it's a new blog post, draft a one-line summary
The model responds with structured JSON: </code>errors<code> (block) or </code>warnings<code> (inform but allow). If the API is unreachable or times out, the commit proceeds — the hook never blocks work for infrastructure reasons.
## The architecture
</code>`<code>
git commit
Phase 0: Collect staged diff + classify files (public vs internal)
Phase 1: Regex blocklist scan (instant, free)
→ Match → BLOCK
→ Clean → continue
Phase 2: Public files changed?
→ No → exit 0 (skip AI review, zero cost)
→ Yes → send diff to DeepSeek V4 Flash
Phase 3: Parse JSON response
→ Errors → BLOCK
→ Warnings → print, exit 0
→ Announcement → print draft
→ API failure → warn, exit 0 (never block on infra)
</code>`<code>
The hook lives in </code>.githooks/<code> — committed, version-controlled, shared by the team. A setup script points </code>git config core.hooksPath<code> there.
## What it costs
| | Tokens | Cost |
|&ndash;|&mdash;&mdash;&ndash;|&mdash;&mdash;|
| Input (prompt + diff) | ~4,000 | $0.00056 |
| Output (JSON response) | ~200 | $0.00006 |
| <strong>Per commit</strong> | | <strong>$0.00062</strong> |
A tenth of a cent. Twenty commits a day: $0.012/day. About <strong>$0.40/month</strong>. Commits that only touch internal files skip the AI review entirely — zero cost.
## What it caught (first week)
- <strong>2 classified codename leaks</strong> in draft blog posts — caught by blocklist
- <strong>1 GitHub URL</strong> from an old copy-paste — caught by blocklist
- <strong>3 blog SEO warnings</strong> — titles over 60 chars, missing og_description — caught by AI
- <strong>1 announcement draft</strong> auto-generated when a new post was committed
Zero false positives on the blocklist. Two false positives from the AI — flagged an internal URL in a code example that was clearly illustrative. We added a note to the prompt: ignore URLs inside fenced code blocks.
## Setup
</code>`<code>bash
bash scripts/setup-hooks.sh # or .\scripts\setup-hooks.ps1 on Windows
export TINQS_HOOK_TOKEN=<your-token> # same PAT used for git push
</code>`<code>
That's it. Every </code>git commit<code> runs the two-layer review. Bypass with </code>git commit &ndash;no-verify` for emergencies.
## The pattern: guard rails at the edge
This is the same principle we apply everywhere: put the guard rail where the action happens. Don't rely on a human checklist. Don't wait for code review. Don't hope someone remembers.
The pre-commit hook is $0.001 of prevention. A leaked API key in a public post is hours of rotation, revocation, and audit. A classified codename in a blog post is a confidentiality breach. A dead link is a broken experience nobody notices for weeks.
The tools exist. DeepSeek V4 Flash is cheap enough to call on every commit. The hook is 150 lines of bash. The blocklist is a text file. Total infrastructure cost: zero — it runs on the developer's machine, calls an API we already pay for, adds 4 seconds to the commit flow.
&mdash;
<em>The pre-commit hook is part of <a href="https://tinqs.com" style="color: var(--c-lime);">Tinqs Studio</a>. The inference proxy, blocklist patterns, and review prompt are open and reusable. Every commit in <a href="https://arikigame.com" style="color: var(--c-lime);">Ariki</a> runs through the same guard.</em></p>
<div class="post__body">
</div>
<div class="post__author">
<div class="post__author-avatar">OB</div>
<div class="post__author-info">
<span class="post__author-name">Ozan Bozkurt</span><br>
CTO & Developer, Tinqs
</div>
</div>
</article>
</body>
</html>