Living document. Last updated July 2026. I update this when the stack meaningfully changes.
Why Claude Code?
Think of it as a software engineer and data analyst working as one. It builds production pipelines, writes tests, enforces type safety, and commits clean code. It queries your data warehouse directly: ad platform data, revenue, margins, whatever you’ve connected. It connects to external services through APIs or MCP servers: Google Sheets, Slack, BigQuery, Google Ads, Salesforce. Almost any tool you use can be connected nowadays.
You give it a goal, it executes using real tools on your machine: SQL, Python, CLI, whatever the task needs. You design the systems and validate the outcomes. Claude operates them.
I run it from the terminal inside Cursor. My project files are visible in the editor while I talk to Claude in the integrated terminal. You can also run Claude Code standalone from any terminal.
Writing code with Claude is the easy part. Getting code you can trust is harder, and it’s not about prompting tricks. It’s about specification: what the system should do, what correct looks like, and how to verify the output before it touches a live account. That’s how you transfer intent to the agent. Everything that follows is a variation on that idea.
The Engineering Lifecycle
Getting that trust is not a matter of one careful prompt. It comes from a sequence you run for any change that isn’t trivial, sized to the risk. It’s the same lifecycle a good engineering team already uses, adapted so a marketer can run it with an agent doing the typing. You spend your judgment at the two ends, deciding what “done” means and checking the result, and let the agent run the middle.
The moves, in order:
-
Settle intent, before any design. The danger with a fast agent is that it builds the wrong thing quickly. So you start by being interviewed: what exactly are we building, why does it matter, what could go wrong. Get the goal and the risks straight, in writing, before a line of code exists. Package this as an
interview-meskill that asks one question at a time and turns a vague idea into a confirmed, written intent. -
Decide what “done” means, and how you’ll test it. Still before building, name the success criteria as concrete, verifiable conditions (“the export tab has 100 rows, no nulls in the routing column”), not “it works,” and say how each one gets checked. Deciding this before you see the code is what keeps you honest, because once code exists and runs, it’s easy to talk yourself into believing it’s correct.
-
Write the spec. The intent and the success criteria get written down as a short spec, five to fifteen lines, that outlives the conversation that produced it. A
spec-writerskill drafts it in the repo’s format and stops for your sign-off before anything is built. An approved plan is a prediction; a spec is a contract the finished work has to meet. (The format is in the Auto Mode section below.) -
Build test-first. Now the agent builds, and it writes the test before the code: the failing test first, then the least code that makes it pass, then a cleanup. Every new behavior gets a test seen to fail first, and every bug fix gets a reproduction test that fails before the fix. A
test-driven-developmentskill packages this loop. (More in Code Quality & Testing.) -
Review before “done.” The author never grades their own homework, human or agent. Code reads as correct to whoever just wrote it, so a fresh reviewer that didn’t write it does the check:
/code-reviewfor an adversarial pass,/review-testsfor an honest read on what’s actually covered. Every confirmed finding is fixed or explicitly deferred with a reason. Then, and only then, “ready to commit.” (More in Review the Result, Not the Plan.) -
Close off, and feed the next cycle. Before you call it done: verify for real (run the tests, the linter, the dry-run, report the counts, never from memory), refresh every surface the change touched (the docs it outdated, the roadmap, the spec’s success criteria), and fold what you learned back into your instructions. This is the move that turns the list above from a line into a cycle: the next iteration starts from updated docs and better rules, not a cold start. (More in Session Closeoff below.)
Size the ceremony to the risk. A typo or a config value: just change it, no spec, no interview. A behavior or logic change: at least test-first plus a review. Anything touching production writes, SQL, money, or a table your team reads: the full chain, no shortcuts. The lifecycle is a dial, not a gate.
The lifecycle is method, so it lives once. None of these moves are specific to one project. They’re true in every repo, which is exactly why they don’t belong in any single repo’s CLAUDE.md. The method lives once in your global CLAUDE.md (~/.claude/CLAUDE.md) and in the skills each stage is packaged as. Each project’s own CLAUDE.md holds only that repo’s facts: its real commands and its landmines. Method up, facts down, neither restating the other. That split is what the next section is about.
The Persistent Context
Without persistent context, every session starts from zero. CLAUDE.md and your repo structure are what turn Claude Code from a generic coding tool into an assistant that understands your projects, your data, and your conventions.
CLAUDE.md: The Operating Instructions
Every repository has a CLAUDE.md file at the root. Claude Code reads it automatically at the start of every session, in full. It ships with the repo (it’s in git), so anyone who opens the project, a teammate or a cloud agent running in CI, gets it too.
Because it loads every session, keep it lean. It holds this repo’s facts: the real commands, the architecture boundaries, the load-bearing invariants (the rules that break the build or corrupt data if ignored), and the landmines not to touch. It does not restate how you work in general. Your change lifecycle, your verification discipline, your bug-fixing protocol, your writing style: that’s method, and method is true in every repo, so it lives once in your global CLAUDE.md plus the skills each step is packaged as. The repo file points to that lifecycle in one line and then carries only its own specifics.
Your CLAUDE.md looks different for every project: 20 lines for a prototyping repo, full architecture rules and quality gates for production. The example below is from a production pipeline repo that classifies and routes search terms.
What to notice: this file doesn’t explain how testing works (Claude can figure that out from the test files), doesn’t describe the folder structure in prose (Claude can run ls), doesn’t mention pre-commit hooks (discoverable from .pre-commit-config.yaml), and doesn’t re-teach the spec-first, test-first, review lifecycle (that’s method, and it lives in the global file plus named skills). It only contains things Claude can’t figure out on its own AND that are specific to this repo: architecture boundaries, the exact commands, and safety guardrails.
# Paid Search Keyword Automation
> This file is the index, the landmines, and the thresholds for THIS repo.
> The change lifecycle (spec-first, test-first, adversarial review, verify
> before claiming success) lives in the global CLAUDE.md and the skills it
> routes to; follow it, don't restate it here. Step-by-step protocols live
> in docs/, skills, and module docstrings. A rule enforced by a test names
> the test. Before adding a paragraph here, ask whether a pointer (or a
> test) does the job instead.
## How to Assist
Production repo. Follow the standard spec-first, test-first,
review-the-result lifecycle (global CLAUDE.md + the spec-writer /
test-driven-development / code-review skills). This file only adds what is
specific to this repo.
## Before starting work
1. Read tasks/todo.md to know what's active and in scope.
2. Read relevant docs/ files for context (ls docs/ to discover).
3. Run make test first, to ground the session and catch pre-existing failures.
## Commands and test tiers
- make test: fast dev loop, real-BQ tests skipped. This is what CI runs.
- make test-bq: the real-BQ integration tests. Run before committing any
SQL or BQ schema change.
- make test-all: both. make lint: ruff + mypy.
## Architecture
CLI entry points > pipelines/ > clients/ (bq_client, gemini, tavily)
Both share: config/, domain/, prompts/, sql/
- Pipeline BQ access through clients/bq_client.py (Python SDK)
- Dashboard BQ access through services/bq_service.py (Python SDK)
## Code Quality
E2E test requirement: every new or modified pipeline needs at least one
E2E test with all externals mocked. Cover: happy path, empty input, dry run.
## Pipeline Rules
Staging-to-production: INSERT staging (audit trail), MERGE production
(dedup). Idempotent, safe to re-run. Every pipeline has --dry-run
and --limit N flags.
## Auto-Mode Hard Guards
Enforced mechanically, not by convention, so they hold even if an agent
goes off-script. Precedence deny > ask > allow, then the built-in
classifier judges the rest. Tightening a guard is fine for an agent;
loosening one is human-only, by hand.
- .claude/settings.json: the deny floor (.env reads/writes, rm -rf,
force-push) plus confirm-before on outward MCP writes.
- .claude/hooks/guardrail.py: a PreToolUse hook that scans the whole Bash
command. ASKs before installs, live runs (LIVE=1), bq mutations, and
git push. Safe in-repo work runs unattended.
- Built-in auto-mode classifier: judges every remaining action for
irreversibility and external aim. Trust boundary lives in
~/.claude/settings.json; only a human can widen it.
## What NOT to Do
- Don't run pipelines without --dry-run unless explicitly asked.
- Don't modify BigQuery schemas without DDL in sql/ddl/.
- Never use string concatenation with user input in SQL.
A few things worth calling out.
Notice what is NOT here. No verification-discipline paragraph, no bug-fixing protocol, no “after completing work” checklist, no spec-first rule spelled out, no adversarial-review procedure. Those are all method: true in every repo, so they live once in your global CLAUDE.md and in the skills each step is packaged as, not copied into every project file. The payoff is real, not cosmetic: an always-loaded file that stays short gets followed, where a wall of standing rules gets skimmed.
The test tiers stay, because they’re a fact. “Write the test before the code” is method and lives globally. But the name of the command that runs the BigQuery tier in this repo (make test-bq) is a fact only this repo knows, so it belongs here. That’s the pattern for the whole file: the method is stated once elsewhere; the repo names the specific command, path, or table the method acts on.
Auto-Mode Hard Guards stays in the repo file, because the guards are wired to this repo’s own paths and hooks. It’s what makes it safe to let Claude run unattended: the rules are enforced mechanically (a deny list, a hook, and the built-in classifier), not by trusting the agent to behave. That’s the difference between “I read the plan before letting it go” and “the dangerous moves are physically blocked, so I don’t have to babysit.” More on the workflow this enables in the Auto Mode section below.
The key principle: if Claude can figure it out from your code, configs, or tool output, don’t put it in CLAUDE.md. If it’s true in every repo (a way of working), it belongs in your global file, not this one. Only include what agents can’t infer on their own AND is specific to this repo. If something feels like a warning, ask: can I fix this in the code instead? A docstring, a better variable name, or a clearer structure is always better than another rule.
The file evolves through use. The Session Closeoff (covered later) is the mechanism: Claude proposes updates based on mistakes or patterns it noticed, and each lesson gets routed to its layer (a repo fact stays here, a way-of-working goes global, a reusable behavior graduates into a skill). Cut aggressively: every line competes with the actual task for context. Recurring rules about the same area usually mean the code itself should be clearer.
One discipline keeps it readable as it evolves: write CLAUDE.md and your docs as if the current design always existed. Present tense, no “v1 vs v2,” no “originally we had,” no changelog framing. When you refactor, edit these surfaces in place. History belongs in git and in your write-once specs, not in the file the agent reads every session. A doc that narrates its own past spends context re-explaining decisions that no longer apply.
How Claude Reads Your Repo
Claude doesn’t need you to pre-load everything. It explores on demand: CLAUDE.md is loaded automatically every session (this repo’s facts, plus pointers like “read tasks/todo.md”), then Claude runs ls to discover directories, reads files when it needs specifics, and runs tools (make test, git log, bq query) to understand the current state. Good file naming matters more than maintaining an index. For a focused production repo, CLAUDE.md + sensible file names is the whole system.
Projects & File-Based Tracking
Each project tracks its own work. The task tracking lives where the work lives:
paid-search-keyword-automation/
├── CLAUDE.md ← Operating instructions (loaded every session)
├── pyproject.toml ← One package; CLIs registered as entry points
├── Makefile ← make test / make lint / make install
├── ROADMAP.md ← Vision, milestones, non-goals
├── paid_search_automation/ ← The one source package (no .py at the repo root)
│ ├── pipelines/ ← Pipeline logic
│ ├── clients/ ← API clients (BigQuery, Gemini, etc.)
│ ├── domain/ ← Core business types and rules
│ ├── sql/ ← BigQuery queries and DDL
│ ├── prompts/ ← LLM prompts as Markdown files
│ └── config/ ← Settings, constants
├── tests/ ← E2E and unit tests (sibling to the package)
├── docs/ ← Architecture, operator manual, decisions/ (ADRs)
├── experiments/ ← Cheap, honest method/data experiments
└── tasks/
├── todo.md ← Active sprint work, checked off as it completes
├── maintenance-backlog.md ← Someday pile (tech debt), grouped P0/P1/P2
├── specs/ ← Write-once intent docs for non-trivial builds
└── runs/ ← Per-run operational logs (one per pipeline run)
One shape holds across the pipeline repos: all the source lives inside a single package named after the project (paid_search_automation/ here), with no loose scripts at the repo root. Command-line tools are registered in pyproject.toml and run with uv run <name>, and tests/ sits beside the package rather than inside it. The payoff is that “where does this live” has one answer, and a structure test can guard it so the layout doesn’t quietly drift.
ROADMAP.md defines what “done” looks like, what the milestones are, and what the project deliberately won’t do. Without it, the agent makes locally reasonable decisions that conflict with where the project is heading: building a feature you planned to replace next month, or over-engineering something that’s deliberately out of scope.
The four tasks/ buckets each answer a different question. todo.md is “what am I working on now.” maintenance-backlog.md is “what should I get to eventually” (starting an item promotes it to todo.md; finishing one moves it to a ## Done section as an audit trail). specs/ is “why was this built this way,” written once per architectural change and never rewritten. runs/ is “what happened last time this pipeline ran,” so the next session reads the latest log instead of re-deriving pipeline state. The decision is mechanical: working on it now goes to todo; someday goes to backlog; multi-file or architectural gets a spec first; just ran a pipeline gets a run log.
Two more homes sit outside tasks/. Decisions that outlive a single build (why this library, why this schema, why we deliberately didn’t do the obvious thing) go in docs/decisions/ as short ADRs (Architecture Decision Records: one file per decision, capturing the context, the choice, and what it rules out). And when you want to know whether an approach is worth building before you build it, experiments/ holds one folder per experiment, each with its own writeup, so the answer is on record instead of relitigated. Git shows what changed; a spec captures intent before a build; an ADR captures why a decision went the way it did; an experiment captures what you learned from trying.
tasks/todo.md is self-updating. CLAUDE.md tells the agent to read it before starting work and update it after completing work. You come back a week later, say “let’s continue,” and the agent picks up where it left off. For multi-step builds, it plans the work as checkable items before starting, checks them off as it goes, and adds follow-ups. The file is committed to git, so the full history is always there.
For non-trivial builds, a spec file in tasks/specs/ captures intent that doesn’t survive execution. More on this in Auto Mode below.
Tooling
Tools You Can Use Daily
bq CLI: Query BigQuery directly from the terminal. Claude writes SQL, runs it, reads the results, identifies issues, and iterates until the output is correct.gcloud: Google Cloud authentication, project switching, service account management.git: Commits, diffs, branch management, history.python/uv: Execute scripts, manage dependencies, run full pipelines.uvhandles package management with lock files so environments are reproducible.gh: GitHub CLI for pull requests, issues, code review.ruff: Linting and formatting. Catches style issues and basic code problems before they compound.mypy: Static type checking. Verifies that type hints are consistent across the codebase, catching logic errors before runtime.pytest: Runs tests automatically. Unit tests for logic, integration tests for BigQuery dry runs.
The Execute, Validate, Iterate Loop
This is where the real power sits. Claude doesn’t just write a query. It runs it, validates the output, and fixes problems autonomously.
No copy-pasting between tools. No switching to BigQuery console. No manual debugging of column names. The agent handles the iteration loop that a human would otherwise do manually across multiple browser tabs.
Auto Mode: Specs, Success Criteria, and Looping In
The workflow is simple to state: write a spec with clear success criteria, let Claude execute autonomously in auto mode, and loop in when it matters. Auto mode means Claude keeps working (writing code, running tests, querying BigQuery, fixing failures) without stopping to ask permission at every step. The leverage comes from spending your attention on the two things that actually move the outcome: the spec that defines what “done” means, and the review of the result. The middle, the typing, runs on its own.
This only works because the guardrails are mechanical, covered below. Without them, “let it run unattended” would be reckless. With them, the dangerous moves are physically blocked, so unattended becomes the default and you intervene by choice, not necessity.
Auto mode isn’t all-or-nothing. Think of autonomy as a dial, and let how much you can verify decide how far you turn it, not how big the task feels. At the low end the agent only suggests and you approve each step. One notch up, it runs bounded tasks you defined and you check the evidence it produces. Further up, it works toward a goal and stops when a measurable condition is met, which only holds when “done” is something a test or a query can confirm on its own. Past that sits fully hands-off orchestration, agents managing agents with a person looking in only when something breaks. For marketing-engineering work that last step is deliberately too far: the goal is leverage with someone still accountable, not an unattended factory. Most of this work sits in the middle, scoped tasks and goals with automated success criteria, because that’s exactly as far as the verification currently reaches. When you can prove more automatically, you can safely hand off more. The dial follows the proof.
Write the Spec First
Before any non-trivial build, write a spec in tasks/specs/. A spec is short, five to fifteen lines of markdown. Not a design doc. Just enough for your future self or your agent to understand the intent without replaying the original conversation. A spec-writer skill drafts it in your repo’s format and stops for sign-off before any code gets written.
# [What you're building, one line]
## Goal
One sentence: what exists when this is done.
## Approach
2-3 sentences: the chosen approach and key trade-offs.
## Success criteria
- [Concrete, verifiable condition]
- [Concrete, verifiable condition]
## Safeguards
The rules this change MUST NOT break. For each: "if we get this
wrong, what would silently corrupt data or violate a load-bearing
invariant?" One line per safeguard, with how the change respects it.
If none apply, write "None: isolated / read-only / additive."
## Notes
Optional: constraints, decisions, things to avoid.
Two sections carry the weight.
Success criteria are the acceptance gate, and the part most people skip. Each one is a concrete, verifiable condition: “the export tab has 100 rows with no nulls in the routing column,” not “the export works.” Writing them before you’re anchored to an implementation keeps you honest. Once code exists, it’s easy to convince yourself it’s correct because it runs. Success criteria written first are an external benchmark the code has to meet, not a story you tell yourself afterward. The ones worth keeping become permanent tests.
Safeguards is what makes auto mode trustworthy for real changes. It forces you and the agent to name the load-bearing invariants the change could break before touching anything: a data-conservation check, a shared SQL definition that must stay identical across files, a safety gate that must not invert. Naming them up front turns “I hope this didn’t break anything” into a specific checklist the review can verify against.
Let It Run, Loop In When It Matters
With the spec written, you let Claude execute. Describe the goal and constraints once (“use the existing Gemini client, add a --limit flag for testing”), then let it run. Don’t micro-manage individual steps. Claude reads the relevant code and docs on its own, writes the code, runs the tests, hits failures, and fixes them.
“Loop in when it matters” is the skill that replaces babysitting. You’re not watching every edit. You step in at the moments that actually change the outcome:
- A success criterion is ambiguous and Claude is about to guess. Cheaper to clarify than to let it build the wrong thing fast.
- It’s heading down an approach the spec didn’t anticipate. A redirect now saves a rebuild later.
- It hits the 3-strike rule (three failed fixes on the same issue) and stops to ask. That’s the signal to think together, not to say “try again.”
- A guard pauses it (a live run, an install, a
git push). That pause is by design: it’s asking you to confirm a boundary-crossing action.
Everything between those moments is just execution, and execution is what auto mode is for.
Why Auto Mode Is Safe
The obvious worry: if it stops asking, what stops it from doing something dangerous? The answer is that auto mode isn’t “no rules.” It’s rules that run automatically instead of interrupting you, and they sit outside the agent so it can’t talk its way past them. Three layers, blunt to smart:
- A hard deny list (
.claude/settings.json): flat walls. Never read the.envsecrets file, neverrm -rf, never force-push. No judgment, no exceptions. - A guardrail hook (
.claude/hooks/guardrail.py): a script that inspects every shell command before it runs and pauses for serious-but-routine actions: installing a tool, a live run that writes to production, agit push, a BigQuery mutation. Safe in-repo work (edits, tests, dry-runs, read-only queries, commits) runs unattended. - The built-in classifier: for everything else, Claude Code’s own safety gate judges each action for whether it’s reversible and whether it’s aimed outside your environment (could it leak data or reach the open internet). It reads your
CLAUDE.md, so your project rules steer it too.
The property that makes it trustworthy: the agent can’t give itself more power. Even if a booby-trapped web page tells it “add this site to your trusted list,” it’s blocked. Only a human, editing a file by hand, can widen what’s trusted. Tightening a guard is fine for an agent to do; loosening one is human-only.
The honest caveat: this stops irreversible and outside-your-environment harm. It doesn’t catch “this code is subtly buggy.” That’s what the next step is for.
Review the Result, Not the Plan
The old habit was to review the plan before letting Claude execute. With auto mode, the review moves to the other end: you review the result. For anything spec-level (multi-file, architectural, or touching safety machinery, SQL, or production writes), that review is adversarial: agents that did not write the code check it before it’s declared ready to commit.
So do we use plan mode? Claude Code has a built-in plan mode: the agent drafts a plan and waits for your approval before it touches anything. I don’t use it anymore. Not because the thinking is optional, but because the thinking moved earlier and got sharper. Before building anything, answer the real questions in writing: why are we building this, how, how will we know it works, is it secure. That is the spec, with its success criteria, and it does everything an approved plan was meant to do, except it’s concrete, verifiable, and it outlives the conversation. An approved plan is a prediction that reads as reasonable because nothing has been tried yet; a spec is a contract the finished work has to meet. Once the spec is right, the mechanical guards keep execution safe and the adversarial review checks the real result, so a plan gate has nothing left to add.
Run /code-review, or a workflow where each finding is independently verified by a separate skeptic agent. The principle is simple: the author never grades their own homework, human or agent. Code reads as correct to whoever just wrote it; a fresh reviewer with no stake in the implementation catches what the author read past. In one session this pattern caught seven real bugs in changes that had already passed tests, including a safety gate that had been silently inverted.
Every confirmed finding is fixed, or explicitly deferred with a reason. Then, and only then, “ready to commit.”
Commands & Skills: Controlling the Pace
As Claude Code gets faster at producing code, you need more deliberate pause points, not fewer. Skills and slash commands are how you package a workflow once and run it the same way every time, whether that’s a debugging protocol, an adversarial review, or a Slack update.
One point of terminology, because it changed. Skills and slash commands are now the same thing. A skill is a folder with a SKILL.md that carries a name and a one-line description. Only that description sits in context by default; the full instructions load only when the skill is actually used, either because you typed /its-name or because the agent matched your task to the description. That load-on-demand design is the important part. It means a large, well-described library is cheap: every session you pay for each skill’s one-line description, not its full text. The old worry that a big skill set bloats every conversation no longer holds.
So the useful distinction isn’t “skills versus commands.” It’s always-on versus on-demand, and within on-demand, you invoke it versus the agent matches it. Note there are two CLAUDE.md layers here, not one. Your global file carries the always-on method (the change lifecycle, verification discipline, house style); the project file carries the always-on facts and guardrails for one repo (architecture boundaries, the real commands, the safety guards). Both load in full every session; skills load on demand.
| Layer | When it runs | Purpose | Examples |
|---|---|---|---|
| Global CLAUDE.md | Every session, every repo | Always-on method + house style | Verification discipline, bug-fixing protocol, change lifecycle |
| Project CLAUDE.md | Every session, in that repo | Always-on facts + guardrails | Architecture boundaries, the real commands, safety rules |
| Skills you invoke | When you type /name |
Deliberate checkpoints you control | /interview-me, /spec-writer, /code-review, /review-tests, /debug |
| Skills the agent matches | When a task fits the description | Recurring rituals with a clear trigger | slack-update, adr, a schema validator |
CLAUDE.md is autopilot. Rules here run every session without you asking: the global file for method, the project file for facts, both always active.
Skills are the library. Each one is a reusable workflow with a description precise enough that the right one surfaces at the right moment. You invoke the deliberate ones by name when you decide the moment has come: /debug when something is genuinely stuck, /code-review when you’re ready for an adversarial pass, /review-tests when you want an honest read on coverage. Others carry a clear enough trigger that the agent reaches for them itself:
sprint-review Monday/Friday ritual: reviews tasks and git history across all repos
sync-confluence pushes markdown docs to Confluence, regenerates READMEs from codebase
update-bq-docs scans repo for BigQuery table references, flags missing documentation
context-audit portfolio-wide health check: repo structure, CLAUDE.md quality, stale docs
visualize-flow generates Mermaid diagrams of data flows and pipeline architecture
The most useful ones wrap Python scripts. The agent frames the input and reads the output; the script does the work. Same input, same output, every run. That deterministic core is what makes a skill trustworthy enough to fire against a production BigQuery dataset or a queue of Google Ads mutations. The rule of thumb: every step that can be code instead of AI should be code. Cheaper, faster, repeatable, and one fewer place for prompt variance to leak in.
A shared library, kept to one standard. Past a handful, skills rot into one-offs nobody trusts unless they follow a convention. A good library lives in one place under a single authoring standard: every skill has full metadata, a description written the way someone actually phrases the request (“Use when a scheduled job fired on stale data…”), and a category. A router skill sits on top and points each task to the right skill, and a graduation bar keeps the set clean: a skill only joins the shared cross-repo library if it’s genuinely reusable and concept-level, so anything specific to one project stays in that project’s own .claude/skills/. The point of all of it is discovery. A skill nobody can find is a skill that doesn’t exist. A mature library already covers the operations you wouldn’t want to improvise: safely mutating a shared BigQuery table, proving a rewritten query returns the same numbers, an idempotent daily load, a freshness check before a scheduled job runs, recording a decision as an ADR.
Don’t reinvent. Before writing a new skill, check what already covers the job across three places: your own shared library, any installed plugins, and the commands Claude Code ships with (/code-review, /run). Route to the one that exists instead of building a fourth that does almost the same thing.
Global versus per-repo. Global skills are available across every repo, good for cross-project rituals like a weekly status update that reads tasks/todo.md in several repos. Per-repo skills live in the repo’s own .claude/skills/ and only trigger there, good for codebase-specific rituals like a schema validator that checks BigQuery docs against the real tables. Because a global skill is in play everywhere, write its description precisely so it doesn’t surface in the wrong repo.
Control the Pace by Invocation
When I first set up my workflow I put everything on auto-trigger, then over-corrected and moved almost everything to type-it-yourself commands, worried a big skill set would fire at the wrong moment and tax every session. On-demand loading settled half of that argument: the library is cheap now, so size isn’t the problem it was. The discipline that remains is narrower, and it’s about timing, not count. For a skill the agent auto-matches, make its trigger unambiguous, so a context audit doesn’t kick off in the middle of a refactor. For anything where you want to pick the moment yourself (debugging, an adversarial review, a live-touching step), keep it invoke-by-name. You still control the pace. The library just puts the right move one word away.
Why Pace Control Matters
This connects to something fundamental about working with AI coding agents. The agent can produce code incredibly fast. Faster than you can review it, faster than you can understand it. That speed is the feature, but it’s also the risk.
Invoking a skill by name is how you deliberately slow down. Not because slow is better, but because understanding is better. /debug forces you to see the debugging process laid out step by step instead of watching Claude try 10 random fixes. /review-tests forces an honest accounting of what’s actually tested versus what just looks tested. These aren’t just engineering tools. They’re comprehension tools.
One more mechanism: for changes that touch 3 or more files, the agent pauses after each logical unit of work to summarize what it did before moving on. No more big-bang reveals where 10 files changed and you’re reviewing everything at once. If you want to course-correct, you do it before the agent is five steps deep, not after. This is method, so it goes in your global CLAUDE.md, not per repo:
### Review gates
For changes touching 3+ files: after completing each logical unit of work,
pause and summarize what was done before moving to the next unit.
The balance looks like this: let CLAUDE.md handle the things that should always happen (the global file for method, the project file for facts). Let auto-matched skills handle predictable recurring rituals with a clear trigger. Keep deliberate checkpoints as skills you invoke by name, and fire them when you decide you need to slow down and think.
Model Routing & Context Window
Use the best model available. On a flat-rate subscription, always use the most capable model. Model switching only matters on API billing: /model mid-conversation lets you drop to a lighter model once the spec is set and the work is mostly mechanical execution.
Three things to understand about context during long sessions:
Auto-compaction. When a conversation gets long, Claude Code automatically compresses earlier messages to make room. Your session doesn’t crash, but details from early in the conversation may fade. This is why CLAUDE.md matters: it’s re-loaded every time, so critical rules always survive. Keep your CLAUDE.md tight, and front-load the most important instructions.
Subagents and isolated context. When Claude spawns a subagent (a parallel task like reading docs or running searches), that subagent gets its own separate workspace. It doesn’t consume space from your main conversation. The subagent does its work, returns a summary, and the full intermediate exploration stays outside your session.
Memory vs. local context. Memory (stored in ~/.claude/) is cross-session storage: personal preferences, workflow patterns, things that apply everywhere regardless of which repo you’re in. Local context (CLAUDE.md, docs/, tasks/) is repo-specific knowledge committed to git: architecture rules, pipeline patterns, domain knowledge that only matters for this project. The rule of thumb: if it applies to you as a person across all projects, it’s memory. If it applies to a specific codebase or system, it’s local context.
Code Quality & Testing
There are two layers of testing, and you need both.
Layer 1: Technical tests. Claude handles these. Unit tests, type checking, linting, mocking. The agent writes them, runs them, fixes failures, and iterates until everything passes. You review the result, not every intermediate step.
Layer 2: Domain validation. You handle this. Does the output match reality? Do the numbers match what you see in the platform? Would you trust this running unattended? No automated test can answer these questions. Before every project, ask: how would I validate this manually? That answer becomes one of the spec’s success criteria, and the ones worth keeping become permanent tests.
The Code Quality Stack
Dependency management with uv. uv is a modern Python package manager that creates a lock file (uv.lock), guaranteeing your code runs with the exact same library versions everywhere. No more “works on my machine” failures. This matters especially for agent-built code, where dependency management tends to be an afterthought.
Type checking with mypy. One of the most effective guards against AI hallucinations in code. Type hints force every function to declare exactly what it expects and returns:
# Without types: what is "amount"? A number? A string? A currency object?
def calculate_bid(amount):
return amount * 1.2
# With types: clear contract, mypy verifies it everywhere
def calculate_bid(amount: float) -> float:
return amount * 1.2
If Claude writes a function that returns a float but later uses it as a string, mypy catches it immediately, before you run anything. This one rule eliminates an entire class of bugs.
Testing with pytest: does the pipeline work? Be precise about what tests verify. Not whether your AI output is accurate. That’s what evals are for. Tests answer a different question: does data flow through the pipeline correctly? Does your response parser handle every format the LLM returns? Does the BigQuery write fail gracefully when a row is malformed? Tests are deterministic. Same input, same output, every time.
The key technique: mocking. Tests never call the real API. Instead, you replace it with a fake that returns a pre-scripted response. Tests run in milliseconds, cost nothing, and work even when the API is down. You’re testing your pipeline’s logic, not the LLM’s behavior.
Types of tests:
-
End-to-end tests are the most valuable starting point. “If I feed 100 search terms into this pipeline, do I get 100 classified results back in the right format?” This doesn’t check whether the AI classifications are good (that’s what evals are for). It checks that the pipeline runs without crashing and produces structured output.
-
Unit tests catch specific logic bugs. “Does the score normalization math work correctly?” “Does the JSON parser handle a malformed LLM response?” Each test checks one function with a known input and expected output. Fast to run, and when one fails you know exactly which piece of logic broke.
-
Integration tests verify that components talk to each other correctly. “Does the BigQuery client return data in the format the pipeline expects?” “Does the LLM response parse into the right schema?”
The autonomous developer pattern. Define the contract first: “for this input, expect this output,” written as a test before the code exists. A test-driven-development skill runs exactly this loop: the failing test first, then the least code that makes it pass, then a cleanup. Then it doesn’t matter how the code gets built, because you can verify the result. Claude runs make test, reads the output, fixes failures, and iterates until everything passes. When tests fail, it doesn’t stop and ask. It fixes and re-runs. The 3-strike rule is the safety net that keeps this loop from running away.
Tests are your safety gate. No code gets committed unless all tests pass. Pre-commit hooks enforce this: if tests fail, the commit is blocked, Claude reads the error, fixes the code, and retries until green. The tests that verify your spec’s success criteria aren’t just checks for today’s build: they become a permanent regression gate. Commit them to CI and any future change that breaks the spec fails the pipeline automatically. The spec doesn’t just guide the build; it becomes the contract all future code must satisfy.
Dry runs complete the picture. Mocked tests are fast and cheap, but they can’t catch everything: a renamed column, a schema mismatch, a date filter off by one. Every pipeline should have a --dry-run flag that runs the full pipeline against your real database but skips the final write step. Run it after every change that touches SQL, table references, or API calls. It takes 30 seconds and catches an entire class of bugs that mocks can’t. Layer 2 validation (comparing output against your dashboard, the ad platform, or a known baseline) is the final check that no automated test can replace. Only you have that context.
This fast-versus-real split is worth making explicit as test tiers. The fast tier (mocked, runs in seconds) is your inner loop and what CI runs on every push, so it can’t depend on database credentials. The real-database tier (the tests that actually hit BigQuery) is slower and runs before you commit anything touching SQL or schemas, plus on a scheduled CI job so it can’t silently rot between runs. In the example CLAUDE.md above that’s make test (fast), make test-bq (real), and make test-all (both). Keeping the tiers separate keeps the inner loop fast without giving up the coverage that only real queries provide.
Re-evaluate architecture as you grow. Early on, getting things working matters more than getting the structure perfect. But as your codebase grows, the architecture decisions you made at 500 lines start to strain at 5,000. Periodically step back and ask whether your module boundaries, data flow patterns, and abstractions still make sense. Claude can help you map dependencies and spot structural issues, but for real architectural decisions, getting a more experienced engineer to review your code is even better. A fresh pair of eyes catches things that no amount of refactoring from within will surface.
Evaluations for AI Pipelines
Tests verify that code works. Evals verify that AI output is good. You need both, and what evals look like is entirely dependent on your use case.
The process behind evals is universal, though, and it’s the same as software engineering: systematic measurement, controlled changes, measured outcomes. Among AI product builders, there is growing consensus that evals are the most important new skill. Hamel Husain and Shreya Shankar teach the definitive framework, and the core of it is surprisingly simple: error analysis first, automation second.
Step 1: Open coding. Look at 20-50 actual AI outputs and write free-form notes about what’s wrong. No predefined categories, just honest observation. You don’t know what you don’t know, and the whole point is discovering failure modes you didn’t anticipate. One person with domain expertise owns this (what Hamel calls the “benevolent dictator”), not a committee.
Step 2: Axial coding. After enough observations, patterns emerge. Group your notes into failure mode categories. An LLM is actually good at this part: feed it your open codes and ask it to synthesize categories.
Step 3: Basic counting. Count how often each category appears. This is, as Hamel puts it, the most powerful analytical technique in data science because it’s so simple. Now you know what to fix first.
From there, you build a gold set of labeled examples from your errors, run your pipeline against it after every change, and track accuracy over time. Only experiment on patterns (the same mistake appearing 5+ times), not individual errors. Make one change, measure the impact, keep what works. Most teams skip error analysis and jump straight to writing tests or tweaking prompts based on gut feel. That’s where things go off the rails.
When you’re tuning a prompt to chase accuracy, run it as a tracked experiment (old prompt versus new, scored on the same gold set) instead of eyeballing a handful of outputs, so the call rests on the number rather than on the last example you happened to read.
Session Closeoff: The Continuous Improvement Loop
This is the practice that compounds everything above. Package it as a close-off skill so it runs the same way every time. At the end of every substantial work session, before you close the terminal:
- Verify. Run the real tests, the linter, and a dry-run of every changed pipeline. Not from memory. Actually run them. Report counts and results before claiming success.
- Reflect and promote. What patterns emerged? Did a test catch something? Route each lesson to its layer, and when a lesson is really a coupling (two things that must stay in sync, like a doc that describes a query), promote it into a check that maintains itself: a test that fails on drift, or a one-line
covers:header on the doc that names its scope. The standing instructions shrink as rules migrate from prose into executable checks. - Backlog. Capture every idea surfaced during the session in
tasks/todo.mdso they don’t evaporate when the terminal closes.
The move that keeps this cheap is refreshing docs by scanning what actually changed this session, not by maintaining a hand-written list of “update X when Y changes” (that list is itself a doc that rots). Sort the couplings by how self-maintaining the check is, best to worst: a test that fails on drift, a self-declaring doc header, a runtime scan of the change, a hand-maintained list. The work is to keep pushing rules up that ladder, so fewer of them live in prose you have to remember.
Here’s why this matters more than it sounds: your CLAUDE.md gets better with every session. The first version is generic. After a month of closeoffs, it contains hard-won lessons: “always dry-run SQL changes against real BQ before moving on,” “capture baseline counts before touching query logic,” “shared SQL definitions must stay identical across files, enforce with tests.” These aren’t rules you could have written upfront. They come from experience.
Every session starts with better instructions than the last one. Over months, you build a personalized engineering playbook that an AI agent follows precisely, every time.
BigQuery: The Data Foundation
Every pipeline pulls from BigQuery and writes results back to it. It’s the single queryable location for your complete account structure and performance history.
Google Ads Data Transfer
The Google Ads Data Transfer syncs daily snapshots of every entity in your account to BigQuery: campaigns, ad groups, keywords, search terms, historical tROAS targets, budget settings, cost and conversion stats. Claude knows how to query all of it.
Two table types exist for each entity:
ads_*views: latest snapshot only. Use for dimension lookups. Filter:WHERE _DATA_DATE = _LATEST_DATEp_ads_*partitioned tables: full history. Use for date-range analysis. Filter:WHERE segments_date BETWEEN ... AND ...
The critical rule: always use p_ads_ tables with segments_date for any performance stats query over a date range. The ads_* views only contain the latest day.
The BigQuery Data Transfer Service also supports DV360, Campaign Manager 360, Search Ads 360, and GA4. For non-Google platforms, use a connector (Fivetran, Supermetrics, Funnel) or build your own ingestion. Once the data lands in BigQuery, the same patterns apply.
Beyond Ad Platforms
BigQuery is a data warehouse, so you can connect anything your business has: revenue, LTV, margins, product catalogs, CRM data, whatever. Once you do, you’re no longer optimizing based on what Google Ads tells you. You’re optimizing based on what your business actually cares about: real margins, customer quality, predicted lifetime value. Document the table schema, and Claude starts using it.
Documentation & Tooling
Document table purpose, not schema. Claude can read schemas via INFORMATION_SCHEMA. What it can’t discover: business context, naming quirks, metric definitions. Keep a lightweight index that routes Claude to the right table for the right question.
Two files matter most: a calculated metrics file (canonical SQL for every business formula like CAC, LTV:CAC, conversion rate) and a channel taxonomy (channel hierarchy, funnel stages, default filters). Without these, Claude writes technically correct SQL that produces meaningless results.
In Claude Code, use the bq CLI over the BigQuery MCP. Zero context token cost, works across any GCP project, full CLI feature set. The MCP costs 15-20K tokens just to load and is locked to a single project.
Query Pattern
One example to show the shape. Search term stats over a date range, with campaign name, ad group name, triggering keyword, and match type:
SELECT
sqs.search_term_view_search_term AS search_term,
c.campaign_name,
ag.ad_group_name,
ag.ad_group_criterion_display_name AS keyword,
sqs.segments_search_term_match_type AS match_type,
SUM(sqs.metrics_cost_micros) / 1000000 AS cost,
SUM(sqs.metrics_clicks) AS clicks,
SUM(sqs.metrics_impressions) AS impressions,
SAFE_DIVIDE(SUM(sqs.metrics_conversions), SUM(sqs.metrics_clicks)) AS cvr
FROM `your-project.your_dataset.p_ads_SearchQueryStats_XXXXXXXXXX` sqs
LEFT JOIN (
SELECT campaign_id, campaign_name
FROM `your-project.your_dataset.ads_Campaign_XXXXXXXXXX`
WHERE _DATA_DATE = _LATEST_DATE
GROUP BY 1, 2
) c USING (campaign_id)
LEFT JOIN (
SELECT
ad_group_id,
ad_group_criterion_criterion_id AS criterion_id,
ad_group_name,
ad_group_criterion_display_name
FROM `your-project.your_dataset.ads_AdGroupCriterion_XXXXXXXXXX`
WHERE _DATA_DATE = _LATEST_DATE
GROUP BY 1, 2, 3, 4
) ag
ON CAST(SPLIT(SPLIT(sqs.segments_keyword_ad_group_criterion, '/')[OFFSET(3)], '~')[OFFSET(0)] AS INT64) = ag.ad_group_id
AND CAST(SPLIT(SPLIT(sqs.segments_keyword_ad_group_criterion, '/')[OFFSET(3)], '~')[OFFSET(1)] AS INT64) = ag.criterion_id
WHERE sqs.segments_date BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY) AND CURRENT_DATE()
GROUP BY 1, 2, 3, 4, 5
ORDER BY cost DESC
LIMIT 500
The tricky part is the criterion join: Data Transfer embeds the ad group and keyword IDs as a resource path in segments_keyword_ad_group_criterion, which has to be parsed out. Claude handles it correctly once it knows your dataset and account ID. From there the pattern extends to anything: SA360, GA4, DV360, backend revenue, margins, CRM data. The query shape stays the same.
Agentic Workflows: Adding LLM APIs to Your Pipelines
This is where the system becomes truly agentic. An agentic workflow is a Python pipeline that connects an LLM API to your data, makes decisions based on that data, and executes actions. For a worked end-to-end example (BigQuery, prompts, evals, human review, API push), see Applied AI Engineering for Google Ads with Python.
The pattern is consistent across every workflow:
- Query BigQuery for the data the workflow needs (search terms, performance stats, budget actuals)
- Send that data to an LLM API (Gemini, Claude, or any model) with a structured prompt that defines the classification or analysis task
- Process the LLM’s response, validate it against a strict output schema (structured JSON via Pydantic or equivalent), write results back to BigQuery
- Execute the output via whatever API is relevant: Google Ads API for account changes, Google Docs API for reports, Slack webhooks for notifications
The key insight: these are just Python scripts with API calls. No special orchestration framework needed. Claude Code builds them, you test them, and they run. If something breaks, Claude reads the logs and fixes it. If output quality drifts, the eval pipeline catches it.
The Staging-to-Production Pattern
Raw AI results go into a staging table first (append-only audit trail), then a second step merges them into production. If a run produces bad output, staging has the full history. Re-runs are safe and idempotent.
Safe Data Mutations
The staging-to-production pattern handles new data. But what about UPDATE and DELETE on production tables? Every mutation must be reversible, auditable, and validated.
The protocol: scope lock (explicit row IDs, never open-ended WHERE clauses), pre-snapshot to a timestamped backup table, pre-count affected vs unaffected rows, execute, post-count, validate. If counts don’t match, roll back automatically. For multi-table mutations, wrap everything in a single transaction group: all-or-nothing.
This feels like overkill until a bad WHERE clause corrupts production data and you spend a day reconstructing it from logs.
Production Pipeline Requirements
Every pipeline ships with batching, rate limiting, retry logic, error handling, and logging. For pipelines that write to external APIs (Google Ads, Merchant Center), log every mutation with an execution ID so you can trace and revert. Claude includes these automatically because the production checklist is encoded in the architecture rules.
Scheduling and Orchestration
Once built and tested, schedule it: GitHub Actions cron, Cloud Scheduler, or a simple bash script. When pipelines start depending on each other, graduate to proper orchestration: Airflow (or Cloud Composer on GCP), Prefect, or Dagster. Most systems start with cron.
SQL Transformation Layer
If your SQL layer is growing (more tables, more transformations, more downstream consumers), dbt deserves a serious look. It lets you build SQL queries that depend on each other in a defined order, with automatic checks that your data is clean (no nulls where there shouldn’t be, no duplicate rows, all references valid). It also generates documentation that stays in sync with your actual queries. Your Python pipelines then read from clean, tested tables instead of writing raw SQL against source tables directly.
Google Ads API: Closing the Loop
The Google Ads API is the execution layer. Once your pipelines produce decisions in BigQuery, the API is how those decisions become platform actions. Use the Data Transfer to read data. Use the API to write changes: add keywords, add negatives, adjust bids, modify tROAS targets, pause entities, reallocate budgets, update product feeds via Merchant Center.
Credentials live in .env (never committed to git, chmod 600 immediately). For production, use a secrets manager and scope service accounts to only the tables and mutation types each pipeline needs.
The Architecture
This is a closed feedback loop:
BigQuery (analysis) -> Python (mutations) -> Google Ads API (live changes)
^ |
|_________________ Data Transfer __________________|
- BigQuery queries identify pending changes: a negative to add, a bid to adjust, an ad to update
- Python prepares and executes mutations against the Google Ads API
- Google Ads API applies the changes to the live account
- Data Transfer syncs the updated account state back into BigQuery
- The next run reads from that updated state, so every cycle builds on the last
The loop is what makes it autonomous. Each execution closes over its own output: the analysis in step 1 already includes the effect of what was changed in the previous run. You’re not writing one-off scripts; you’re building a system that observes, acts, and observes again.
Claude handles the API mechanics: GAQL syntax, mutation structure, error handling for partial failures. You define the business rules: what performance threshold triggers an action, what budget reallocation formula to use, when to pause entities.
Beyond Pipelines: Building the Interfaces Too
Claude Code isn’t only for backend pipelines. The same workflow builds the things people look at: a review dashboard where a marketer approves or rejects the pipeline’s recommendations, an internal tool, a reporting screen. The pipeline decides; the interface is how a human checks and acts on the decision. Building both with the same agent means the screen and the data that feeds it never drift out of step.
Same disciplines, a different stack. The loop doesn’t change: write a spec, let it build, run the full test suite, review the result, report real counts. What changes is the tooling. Instead of pytest, ruff, and mypy you reach for the front-end equivalents:
- Vitest runs fast unit tests, the same idea as
pytest: give a function a known input, assert the output. Keep the real logic (collapsing rows into a decision, formatting a number, encoding a value) in plain functions with no screen code in them, and these tests stay fast and simple. - Playwright runs end-to-end tests by driving a real browser. It clicks the actual buttons and reads the actual screen, catching what a unit test cannot: a broken keyboard shortcut, a row that fails to render, a button wired to nothing.
eslintandprettierare the lint-and-format layer, the front-endruff.eslintflags likely mistakes;prettierkeeps formatting consistent so diffs stay about substance, not whitespace.
The verification rule is identical to the pipeline side: never say “should work,” run the suite, read the output, report the counts. A green suite is how a change earns trust, especially when the owner reads the behavior rather than writing the code by hand.
The method is portable; the repo holds the facts. Good UI work isn’t guesswork about spacing and colors. A design system exposes named values (its spacing scale, its colors, its type sizes, together called design tokens), and the job is to use those instead of inventing new ones, so the interface matches the rest of the product. The discipline is the same one this whole guide runs on: discover what the repo already uses, then adapt to it, rather than assuming a framework or hardcoding a color. A skill can carry the method (sound component structure, accessible markup, a layout that survives phone-to-desktop); the repo carries the specifics.
Keep the interface and the pipeline decoupled: share a contract, not code. The most reusable pattern here is how the two repos connect. The interface never imports or runs the pipeline. The only thing they share is the shape of the data, written down in one versioned file (the contract) and produced as a read-only snapshot. The pipeline owns that contract: if the data shape has to change, it changes in the pipeline first, the version bumps, and the interface re-syncs its copy and adapts. A boundary test fails the moment one repo reaches into the other. Why bother: the marketing-facing app stays small and simple, all the data logic stays in the backend where it’s tested, and neither side can silently break the other. Any time a pipeline produces something a human then reviews, this split (the pipeline writes a snapshot, a separate app reads it) keeps both halves honest.
Why Documentation Matters More Now
AI agents are only as good as the context they receive. Every piece of documentation, not just table schemas but marketing workflows, naming conventions, business logic, makes every agent faster and more accurate. With AI agents, documentation compounds: better docs lead to better output, which generates better docs.
Git-based markdown works for technical context agents read directly. Shared wikis (Confluence, Notion) make documentation visible to the whole team. RAG is for scale: when you have hundreds of documents and need agents to find relevant pieces automatically. Most teams start with git docs and a shared wiki. The tooling matters less than the habit: document what you know, keep it current, make it findable.
Build Deliberately
You own everything your agent builds. Claude writes the code, but you are responsible for what ships. If a pipeline breaks or a budget reallocation fires incorrectly, that’s on you. You need to understand what was built well enough to review it, debug it, and fix it.
This isn’t a limitation. It’s the mechanism that makes you better.
There’s a clean way to name the split. The agent runs the inner loop: investigate, write, test, repeat. You own the outer loop: you set the constraints, you weigh the evidence, and you make the call on whether it ships. The agent can write the line, but the verdict is yours, because you’re the one who has to answer for it. That last part matters more than it sounds. If a budget reallocation fires and someone asks why it was safe to let it run, “the agent decided” is not an answer. Being able to explain what changed, why it was safe, and what happens if you got it wrong is the real output of the outer loop, and it’s exactly what the rest of this guide is built to protect: the spec, the tests, the dry runs, and the adversarial review are how you stay able to give that answer.
Comprehension debt is real
The biggest risk with AI-generated code isn’t that it’s wrong. It’s that it’s right, and you don’t understand why. Everything looks fine: tests pass, linter is clean, pipeline runs every morning. But you can’t explain how the retry logic works or modify the routing rules without worrying you’ll break something else.
The practices in this article (specs, tests, dry runs, evals, /debug, /review-tests) exist to close that gap. Skip them and you’ll ship faster in the short term. But the first time something breaks at 2am and you’re staring at code you can’t explain, you’ll wish you’d slowed down.
Know what you can review, and when to bring in help
As your systems grow, you’ll keep running into new engineering territory: system design, security, testing patterns, scaling. You learn it by building. Engineers on your team become invaluable as you do, not to take over, but to point you in the right direction and help you make better decisions as your skills develop. A code review can redirect weeks of work.
The converging skills effect
Something interesting happens when marketers build with engineering practices: the skills converge. Your specs are written in plain language: engineers read them and immediately understand the business logic. They can explain why a design choice is better, and you follow the explanation because you’ve been building the system yourself.
Making code cheap to generate doesn’t make understanding cheap to skip. Every script you ship creates a dependency. A prototype costs nothing to abandon. A scheduled pipeline your team relies on costs real time to maintain. Know the difference before you ship.