![图像](https://pbs.twimg.com/media/HHXAPmMaMAAaqVa?format=jpg&name=large)

At , I pushed our product team to fully embrace vibe coding with Claude Code and Codex, while enforcing guardrails and code quality standards.[@HacktronAI](https://x.com/@HacktronAI)

Vibe coding works best when the codebase has strong **affordances**性时效果最佳 — a concept in design that describes the possible actions an actor (in this case, a coding agent) can take, in relation to an object (in this case, the codebase):

> Affordance: a use or purpose that a thing can have, that people notice as part of the way they see or experience it.

For a coding agent like Claude Code or Cursor to produce productive code instead of "AI slop" that becomes expensive to maintain and clean up later, building a codebase with obvious structure and automated guardrails becomes important.

Even the smartest models today can't possibly reason about every edge case without a good harness. And even with coding agents like Claude Code, designing repositories in a thoughtful way can go a long way in improving the quality of the code.

仓库A repository should be treated less like a pile of code that can be executed, and more like an 应被视为代理的**execution environment for agents**. Good vibe coding, therefore, would mean that the environment provides:

- Fast validation against "bad engineering"
- A constrained blast radius
- Guardrails that enforce invariants before commiting
- Tests and scripts that the agent can use to "vibe-check" itself

## Make the repository legible to agents

使用并搭建一个单一仓库。如果你想跨多个仓库处理前端和后端微服务，你需要让你的编码代理在这些仓库之间切换上下文，或者赋予它们过于宽泛的权限，让他们能访问同一会话中的所有仓库。这可不好。所以直接用monorepo吧。[PNPM](https://pnpm.io/)

```plaintext
apps/
  frontend/
  backend/
docs/
  architecture.md
  conventions.md
packages/
  eslint-config/
  shared-utils/
  shared-tyles/
  typescript-config/
CLAUDE.md
package.json
pnpm-lock.yaml
pnpm-workspace.yaml
turbo.json
```

monorepo 结构允许你创建多个使用共享包的应用。这些可以是效用和类型定义。此外，我发现将ESLint和TypeScript配置标准化为共享包很有用，这样它们可以轻松导入到新的应用和包中。

例如，一旦你导出类似的ESLint配置，包含在共享包中：

```typescript
// packages/eslint-config/base.js
import js from '@eslint/js'
import eslintConfigPrettier from 'eslint-config-prettier'
import turboPlugin from 'eslint-plugin-turbo'
import tseslint from 'typescript-eslint'
import onlyWarn from 'eslint-plugin-only-warn'
 
/**
 * A shared ESLint configuration for the repository.
 *
 * @type {import("eslint").Linter.Config[]}
 * */
export const config = [
  js.configs.recommended,
  eslintConfigPrettier,
  ...tseslint.configs.recommended,
  {
    plugins: {
      turbo: turboPlugin,
    },
    rules: {
      'turbo/no-undeclared-env-vars': 'warn',
      '@typescript-eslint/no-unused-expressions': 'off',
      '@typescript-eslint/no-unused-vars': [
        'warn',
        {
          argsIgnorePattern: '^_',
          varsIgnorePattern: '^_',
          caughtErrorsIgnorePattern: '^_',
        },
      ],
    },
  },
  {
    plugins: {
      onlyWarn,
    },
  },
  {
    ignores: ['dist/**'],
  },
]
```

每个应用和包都可以直接从这个配置导入。

```typescript
// apps/frontend/eslint.config.mjs
import { config } from '@my-project/eslint-config/base'
export default config
```

## 技能体现了最佳实践

技能包括 帮助编码代理生成成语代码。当然，很多观点带有强烈主观色彩，所以我们也会写自己的技能，来概括这些年来学到的最佳实践。如果你不知道这些最佳实践应该是什么，可以谷歌一下，或者指向一个展示强大软件工程原则的示例仓库，让它自己设计技能。[NestJS最佳实践](https://github.com/Kadajett/agent-nestjs-skills)[Typescript-advanced-types](https://github.com/sickn33/antigravity-awesome-skills/blob/main/plugins/antigravity-awesome-skills-claude/skills/typescript-advanced-types/SKILL.md)

我们有工程师使用各种不同的代理：Claude、Codex、Cursor 等，所以如果我们想让这些技能在团队成员间有用并共享，我们需要每个编码代理使用相同的技能集。

这就是为什么技能存储在 **.agents** 中，.**codex**、**.claude** 等与主 **.agents** 目录中的技能有符号链接。

```plaintext
.agents/
  skills/
    typescript-expert/
      SKILL.md
    typescript-advanced-types/
      SKILL.md
    [...]
.codex/
  skills/
    typescript-expert -> ../../agents/skills/typescript-expert
    typescript-advanced-types -> ../../agents/skills/typescript-advanced-types
    [...]
.claude/
  skills/
    typescript-expert -> ../../agents/skills/typescript-expert
    typescript-advanced-types -> ../../agents/skills/typescript-advanced-types
    [...]
[...]
```

## 经纪人阅读和维护的文档

如果写得好，CLAUDE.md（或等效文件）在提供自我演进的文档方面起到了很大作用。这些文档可以概述架构、技术栈，更重要的是，AI代理应遵守的规则。

```plaintext
# VibeSlop - The Best Vibe Coded Application
 
## Overview
 
VibeSlop has a NestJS backend and a Nuxt frontend. It is a B2B AI SaaS.
 
[...]
 
## Notion Documentation
 
**IMPORTANT**: VibeSlop has comprehensive documentation in Notion that should be kept in sync with code changes.
 
**Main page**: https://notion.so/[...]
 
### Documentation Structure
 
| Section        | Page ID    | Description                    |
| -------------- | ---------- | ------------------------------ |
| Authentication | \`DEADBEEF\` | Auth guards, token types, RBAC |
| [...]          | [...]      | [...]                          |
 
### When to Update Notion Docs
 
Update the relevant Notion page when:
 
- Adding new API endpoints → Update API Reference
- Adding/modifying entities → Update Database & Entities
- Changing auth guards or token handling → Update Authentication
 
[...]
 
### How to Update
 
Use the Notion MCP tools:
 
- \`mcp__notionMCP__notion-fetch\` - Read existing page content
- \`mcp__notionMCP__notion-update-page\` - Update page content
- \`mcp__notionMCP__notion-create-pages\` - Create new nested pages
 
[...]
 
## AI Coding Rules (MANDATORY)
 
These rules are non-negotiable. Every code change — whether new feature, bugfix, or refactor — must comply. Violations must be fixed before committing.
 
### DTO & OpenAPI Contract
 
[...]
 
### TypeScript Strictness
 
- **No casting** except \`as const\`. No \`as unknown as X\`, \`as any\`, \`as SomeType\`, \`@ts-ignore\`, \`// @ts-expect-error\`.
- **Use enums** instead of magic strings. If a value has a fixed set of options, define an enum.
- **Use optional fields sparingly** — prefer union types (\`string | null\`) over optional (\`string?\`) when the field is semantically required but may be absent.
- **No re-declaring types** that already exist in \`@my-project/shared-types\`, entity definitions, or generated code.
- \`pnpm check-types\` must pass before committing.
 
### Architecture
 
[...]
 
### Minimal Changes / No Slop
 
AI-generated code accumulates: narration comments, single-use helpers, dead code from earlier iterations, error handling for cases that can't happen. Before declaring done, re-read your own diff with a hostile eye and cut everything the current implementation doesn't need. The principle is that a bug fix does not need surrounding cleanup, a one-shot change does not need a helper, and previous iterations are obsolete the moment a later iteration supersedes them.
 
- **Re-read the diff end-to-end before finishing.** After several iterations, files carry leftovers — replaced methods, unused imports, stale branches, helpers that nothing calls anymore. Delete them. Git has the history; the codebase does not need a tombstone.
- **No narration comments.** Don't explain WHAT (names do that) or reference the task ("added for X", "used by Y flow", "handles issue Z"). Only write a comment when the WHY is non-obvious: a hidden constraint, a workaround, a surprising invariant.
  - ✗ \`// Loop through findings and send feedback to Slack\`
  - ✗ \`// Added for the unfurl flow\` / \`// TODO: remove old logic once migrated\`
  - ✓ \`// Stripe retries webhooks on 5xx — dedupe on event.id before mutating state\`
- **No commented-out code, no "removed X" tombstones, no backwards-compat shims for code you just deleted in the same PR.** If it's gone, it's gone. Don't keep a renamed \`_oldMethod\` "just in case".
- **No single-use abstractions.** Don't create a helper, wrapper, base class, or custom decorator until a second caller exists. Three similar lines beats a premature abstraction. \`packages/shared-utils/src/status-mapper.ts\` is what justified extraction looks like — used across \`scan/\`, \`findings/\`, and \`cost-estimation/\`. Don't manufacture that bar; let duplication prove it.
- **No speculative error handling.** Trust internal callers and framework guarantees. DTOs already validate controller input via \`class-validator\` — a service that receives a typed \`SendFeedbackDto\` (\`src/findings/dto/send-feedback.dto.ts\`) does not re-check that \`reaction\` is a string. Validate only at true boundaries: HTTP input, webhook payloads, external API responses, untyped env vars.
  - ✗ \`try { return await this.repo.findOne(...) } catch (e) { throw e }\`
  - ✗ \`if (!user) throw new Error('user required')\` where the parameter type is \`User\`, not \`User | undefined\`
  - ✗ Wrapping a single \`repo.save()\` in a try/catch that logs and rethrows
- **Prefer editing existing files and reusing existing types.** Search \`src/utils/\`, \`src/services/\`, \`src/dto/\`, and \`@my-project/shared-utils\` before writing a new helper. Reuse \`PaginationDto\` (\`src/dto/pagination.dto.ts\`) for paginated endpoints instead of defining \`page\`/\`limit\` again. Reuse entity types from \`@my-project/shared-types\` instead of redeclaring shapes. Don't split a 200-line service into four files unless there's an actual reason.
- **Keep the shape minimal.** Controllers stay thin — validate → service → return, no branching, no queries (see \`src/findings/findings.controller.ts\`). DTOs carry request/response fields only, decorated with \`@ApiProperty\` + \`class-validator\` — nothing more (see \`src/findings/dto/send-feedback.dto.ts\`, \`src/dto/pagination.dto.ts\`). Entities stay as columns + relations — no computed getters or lifecycle hooks unless actually needed (see \`src/seat/organization-developer.entity.ts\`).
- **Frontend caveat:** UI iteration is where slop compounds fastest — unused props, stale Tailwind classes, dead conditional branches from designs two revs ago, state nothing reads. Same rule applies with more force: read the component top-to-bottom against the current design before declaring done, and delete anything the current design doesn't use.
 
### Quality Gates
 
- Tests must pass (\`pnpm test\`) before committing.
- Linter must pass (\`pnpm lint\`) before committing.
- Type-checker must pass (\`pnpm check-types\`) before committing.
```

这里有几点：

1. 我们通过指示代理更新 Notion 文档来强制执行自我文档开发。这假设使用 Notion MCP。
2. 我们根据过去观察到的行为来执行AI编码规则。例如，我们看到前端代码由于UI迭代的特性，会产生大量杂乱：它旨在产生许多不同的变体，直到开发者满意为止。这意味着编码代理常常留下大量陈旧且死掉的代码。我们发现执行“最小改动”规则帮助很大。

## “垃圾收集”针对污水

即使我们尽了最大努力，“粗糙代码”依然不可避免。人类以前也不会写出粗糙的代码。但人工智能让我们通过定期审计代码库，比如代码库中出现无引用的函数、过时文档等问题来应对这个问题。

我们通过创建一个GitHub Actions工作流程，每24小时运行一次Claude代码，并提示它：

1. 根据我们在仓库中文档维护的一套规则，清理质量较差的代码。
2. 请根据最新的代码变更更新上述 **CLAUDE.md**。

```yaml
name: Claude Garbage Collection
on:
  workflow_dispatch:
  schedule:
    - cron: '0 0 * * *'
 
concurrency:
  group: claude-garbage-collection
  cancel-in-progress: false
 
jobs:
  cleanup:
    strategy:
      fail-fast: false
      matrix:
        target_branch:
          - staging
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
      issues: write
      id-token: write
      actions: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1
          ref: ${{ matrix.target_branch }}
 
      - name: Setup pnpm
        uses: pnpm/action-setup@v3
        with:
          version: 10
 
      - id: auth
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
          service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
          token_format: access_token
 
      - name: Set NPM_TOKEN for Artifact Registry
        run: echo "NPM_TOKEN=${{ steps.auth.outputs.access_token }}" >> "$GITHUB_ENV"
 
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '24.x'
          cache: 'pnpm'
 
      - name: Install dependencies
        run: pnpm install --frozen-lockfile
 
      - name: Run Claude garbage collection task
        id: claude-cleanup
        uses: anthropics/claude-code-action@v1
        with:
          claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
          base_branch: ${{ matrix.target_branch }}
          prompt: |
            Read \`CLAUDE.md\` and \`docs/cleanup/README.md\`. Use \`docs/cleanup/\` as the source of truth for this garbage collection pass.
            Work only against \`${{ matrix.target_branch }}\` and keep the change scoped to that branch's current state.
            You may make multiple improvements, but each PR must stay focused on one small, safe maintenance concern.
            Leave the repository unchanged if there is no clear cleanup to make.
          additional_permissions: |
            actions: read
          claude_args: "--allowedTools 'Edit,MultiEdit,Write,Read,Glob,Grep,LS,Bash(git:*),Bash(bun:*),Bash(npm:*),Bash(npx:*),Bash(pnpm:*),Bash(gh:*)'"
 
  sync-claude-md:
    strategy:
      fail-fast: false
      matrix:
        target_branch:
          - staging
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
      issues: write
      id-token: write
      actions: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1
          ref: ${{ matrix.target_branch }}
 
      - name: Sync CLAUDE.md with codebase
        id: claude-md-sync
        uses: anthropics/claude-code-action@v1
        with:
          claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
          base_branch: ${{ matrix.target_branch }}
          prompt: |
            Your sole task is to update all \`CLAUDE.md\` files so they accurately reflect the current codebase on the \`${{ matrix.target_branch }}\` branch.
 
            Steps:
            1. Read every \`CLAUDE.md\` file in the repo (root \`.claude/CLAUDE.md\` and any nested ones like \`apps/my-app/CLAUDE.md\`, etc.).
            2. Audit each section against the actual codebase:
               - **Project structure**: list directories under \`apps/my-app/src/\` and update the tree if modules were added, renamed, or removed.
               - **Key entities**: check \`apps/my-app/src/**/entities/*.entity.ts\` and update the entity table.
               - **API namespaces**: check all \`@Controller()\` decorators and update the namespace table.
               - **Key commands**: verify each command in \`package.json\` scripts still exists.
               - **Environment variables**: check \`.env.example\` and update the env var list.
               - **Path aliases**: check \`tsconfig.json\` path mappings.
               - **Shared packages**: check \`packages/*/package.json\` names.
               - **Guards & auth**: check \`src/guards/\` and \`src/middleware/\` for current guard list.
            3. Remove references to files, modules, entities, or endpoints that no longer exist.
            4. Add entries for new modules, entities, or endpoints that are missing from the docs.
            5. Do NOT change style, tone, or conventions sections — only factual/structural sections.
            6. If nothing is out of date, make no changes and do not open a PR.
 
            Keep the PR focused: only \`CLAUDE.md\` file changes, nothing else.
          additional_permissions: |
            actions: read
          claude_args: "--allowedTools 'Edit,MultiEdit,Write,Read,Glob,Grep,LS,Bash(git:*),Bash(bun:*),Bash(npm:*),Bash(npx:*),Bash(pnpm:*),Bash(gh:*)'"
```

这在很多时候产生了易于合并的拉取请求，也为我们节省了无数小时的手动重构和清理工作。它几乎像是一个垃圾回收引擎，在后台清理死代码和陈旧文档，除了审核（大多干净的）PR外，几乎不需要我们手动操作。

## 让糟糕的代码难以提交

让Claude Code帮你运行**git提交**真是太丢人了......现在这已经成了常态，很多人都这么做。所以最好的做法是使用在提交时强制执行质量的钩子。

![图像](https://pbs.twimg.com/media/HHXB961a4AAxcF2?format=jpg&name=large)

你可以很容易地设置这个：

```bash
pnpm add -D husky lint-staged
pnpm exec husky init
```

**package.json**年：

```json
{
  "lint-staged": {
    "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.{json,md,yml,yaml}": ["prettier --write"]
  }
}
```

这确保所有代码至少在进入 GitHub 前通过了 linting 和格式规则。

测试和排版检查呢？现在是时候更进一步，给这位特工......

## 一个命令来验证所有内容

经纪人需要一个终点线。功能完成后，功能测试可以通过Playwright或Cursor内置浏览器轻松完成。但它怎么知道代码是否适合审核？

你可以创建一个这样的脚本，进行类型检查、lints、运行单元测试，并生成生产版本：

```json
{
  "scripts": {
    "validate": "pnpm typecheck && pnpm lint && pnpm test && pnpm build"
    [...]
  }
}
```

然后通过例如**，CLAUDE.md** 指示代理 使用该命令。

```markdown
Before considering a task complete, run:

pnpm validate

If it fails, fix the errors rather than working around the checks.
Do not remove tests or weaken types unless explicitly asked.
```

## 始终是测试驱动开发

代理只有在能够高度自信地完成“代码→测试/→再次验证代码”循环时，代理才有优势，确保测试/验证步骤真正反映开发者的需求。

这正是经过验证的TDD方法论真正闪耀的地方。首先，你向经纪人描述了预期的规格。你可以为此写一个Markdown文件。接下来，代理生成测试用例。现在，你手动检查这些测试用例，看看它们是否反映你想要的行为：

```typescript
it('does not charge customers twice for the same billing period', () => {
  // ...
})
```

If they don't, then the agent should change the tests. Once you're satisfied with the test spec, then (and only then) get the agent to start doing the real coding work.

For coding agents, a good test suite is not only good documentation, but also serve as great supervision.

## CI where local harness engineering isn't enough

Local hooks can only catch so many obvious problems. In the end, CI tests are where many bugs are found before they make it to production.

One example of where CI tests are most useful is for security. It's no secret that vibe coding has produced a lot more software vulnerabilities in recent months! When agents generate code quickly, they also generate more places for auth checks to be skipped, dependencies to sprawl, and business logic assumptions to break.

For example, tools like [GitGuardian](https://www.gitguardian.com/) can catch accidentally-committed secrets, and [Socket](https://socket.dev/) can catch vulnerable or suspicious dependencies to stop supply-chain attacks.

For deeper application security issues, especially the kinds generic scanners struggle with, you can also use AI-native tools like [Hacktron](https://www.hacktron.ai/blog/introducing-hacktron-review) in CI to review pull request for real code-level vulnerabilities: broken authorization, unsafe business logic, and other security regressions that require more context than simple pattern matching.

The advantage of tools like [Hacktron](https://www.hacktron.ai/blog/introducing-hacktron-review) is that unlike traditional scanners that still rely on known syntactic patterns and AI reviewers that provide only functional testing and code quality issues, Hacktron finds real security vulnerabilties that are introduced throughout the lifetime of your organisation using context-aware analysis to identify the security issues that Claude and Codex miss.

## Always think about affordance

I hope this article has been helpful to you. I've outlined some techniques and ways that we think about vibe coding while enforcing code quality and security.

The key thing to bear in mind is to always think about what your codebase and development environment is affording to the model. The output of your coding agent will depend heavily on that, because the environment dictates the constraints in which these agents operate.
