今日已更新 240 条资讯 | 累计 23823 条内容
关于我们

标签:#code

找到 201 篇相关文章

AI 资讯

A Practical Workflow for Contributing to a Large, Structured Codebase

This is the workflow I follow before I use AI agents to implement any feature or bug fix. 🧭 Requirements/Specification ↓ Design/Architecture ↓ AI Code Generation ↓ Human Review ↓ Build & Static Analysis ↓ Testing & Validation ↓ Defect Resolution ↓ Security & Compliance Review ↓ Release ↓ Production Monitoring vs Claude Code ↓ Implements feature ↓ Codex QA Agent ↓ Runs application ↓ Tests happy path ↓ Tests edge cases ↓ Tests error handling ↓ Produces QA report This will resolve the self-review bias, confirmation bias, or AI-to-AI bias. 1️⃣ Understand Before Writing Code Before touching any code, I try to understand what I'm building and why . I usually start by reading: specs/<module>/<TICKET>-<slug>.md plan/<module>/<TICKET>-<slug>.md status.md Then I review the project conventions: specs/CONVENTIONS.md specs/conventions/core-porting.md Finally, I read the existing implementation (entities, services, mappers, etc.) so my changes follow the existing architecture instead of introducing a new style. 💡 Pro-Tip Good code fits into the codebase. Great code looks like it was always there. 2️⃣ Plan the Change Once I understand the requirements, I identify which architectural layers are affected. I always respect the dependency order: Schema / Entities / DAOs ↓ Mappers / DTOs ↓ Service Layer ↓ Application Layer ↓ Controllers I don't jump ahead of dependencies. If a change is complicated or ambiguous, I document the approach before writing code. --- ## 3️⃣ Write the Code While implementing, I follow the repository's rules. Some examples: | Rule | Detail |---|---|---| | DTOs | Generated from `schema.yml` — never handwritten | | Status values | Sourced only from the Core Porting specification | | Traceability | Every ported behavior includes a source citation | Citation formats I use: - `← Source <path>` - `← PS §...` - `← BR-###` Beyond repository rules, I also try to: - ✅ Match existing naming conventions - ✅ Keep comments minimal and meaningful - ✅ Make small, focused chang

2026-07-19 原文 →
AI 资讯

It Can Die in Its Sleep — Self-Healing launchd Jobs with Multi-Slot Firing and a Done-Marker

My previous piece, " Making a launchd Job Unload Itself ," built a job that runs exactly once and then unloads itself. This time it's the mirror image: a pattern designed around the assumption that the job will die mid-run — it fires several slots a day and delivers "retry until it succeeds, then quit immediately on success." Every morning I hand Claude Code the task of updating my Obsidian Vault, and it kept dying partway through — killed by macOS sleep, no network on wake, or a claude timeout. Instead of trying to prevent every failure perfectly, I decided "it can die overnight as long as it's done by the time I wake up" was the more realistic goal, and I redesigned around that. The problem: "started but didn't finish" piles up silently There are three ways a launchd job fails to run to completion. Lid-close sleep (on battery) — caffeinate -s only works on AC power. On battery, the job freezes the instant you close the lid, and gets reaped by timeout after wake. No network — right after wake, WiFi isn't connected yet. git push and claude's API calls time out. claude timeout — an ingest that chews through 28 hours of conversation logs doesn't fit in a single slot and times out (this happened three days in a row, 2026-06-11 to 13). All three can look like "the job started, exit code 0," so you notice late. Overall design: 4 slots + a done-marker The fix is simple: stuff multiple StartCalendarInterval entries into the plist, and at the top of the script check "if today's run already succeeded, exit 0 immediately." <!-- com.shun.vault-auto-ingest.plist (StartCalendarInterval excerpt) --> <key> StartCalendarInterval </key> <array> <dict> <key> Hour </key><integer> 4 </integer><key> Minute </key><integer> 55 </integer> </dict> <dict> <key> Hour </key><integer> 8 </integer><key> Minute </key><integer> 20 </integer> </dict> <dict> <key> Hour </key><integer> 10 </integer><key> Minute </key><integer> 45 </integer> </dict> <dict> <key> Hour </key><integer> 12 </integer><key>

2026-07-19 原文 →
AI 资讯

The Architectural Trap: Accessing CONST Attributes Across a Series of Classes

When building scalable systems, we often need a collection of classes to expose a fixed, read-only configuration value. Whether it is a unique API_ENDPOINT, a DATABASE_TABLE name, or a specific PERMISSIONS_MASK, handling constants across a series of classes looks simple on day one but can quickly turn into an architectural nightmare. Setting the Foundation: How to Make It In modern object-oriented programming, the standard way to declare a constant on a class is by leveraging the static readonly modifiers. This ensures the attribute belongs to the class itself, rather than an instance, and cannot be mutated at runtime. TypeScript class BillingService { static readonly SERVICE_TYPE = "BILLING"; } class InventoryService { static readonly SERVICE_TYPE = "INVENTORY"; } This works perfectly when you know exactly which class you are dealing with at compile time. You simply call BillingService.SERVICE_TYPE and move on. The Architectural Breakdown: What Will Be the Problems The clean code facade breaks the moment you attempt to handle these classes dynamically. In production environments, you rarely hardcode class names; instead, you process them as an array or a series of registry keys. Loss of Type Safety: If you pass a series of these classes into a processing function, standard type systems will treat them as generic constructor functions, wiping out access to the static property unless you resort to unsafe type casting. Polymorphism Failure: Subclasses do not inherently enforce or override static properties cleanly through standard interfaces. You cannot enforce a static readonly property on an interface, meaning a developer could easily forget to define the constant on a new service class, causing silent runtime failures. Instance vs. Class Metadata Confusion: If your architecture receives an instance of the class rather than the class definition itself, accessing the static attribute requires jumping through hoops like instance.constructor.SERVICE_TYPE, which breaks

2026-07-18 原文 →
AI 资讯

I Almost Hand-Rolled JSON-RPC for an MCP Server. Eight Tools Later I'm Glad I Didn't.

When I built the MCP server for this project — it combines GitHub and DEV.to into a set of tools an agent can call — I had a decision to make before writing a single tool: talk to the low-level MCP protocol directly, or use FastMCP 's decorator API. I've seen a few "your first MCP server" writeups lately walk through the low-level path because it's more "honest" about what MCP actually is under the hood — JSON-RPC over stdio, a capabilities handshake, typed request/response schemas. That's true, and it's a reasonable thing to want to understand. But I want to write about the other side: what it actually costs you in practice once you have more than one or two tools, because I went through both and the difference showed up fast. what the low-level path actually asks you to write Strip away the decorator and MCP is a JSON-RPC server. For every tool you add, you're responsible for: Registering the tool's name, description, and a JSON Schema for its inputs in a list_tools handler Writing a call_tool dispatcher that matches on tool name and unpacks arguments by hand Serializing the return value into the TextContent / ImageContent wrapper types MCP expects Keeping the schema you wrote in step 1 in sync with the arguments you actually read in step 2, by hand, forever None of that is hard in isolation. The problem is it's boilerplate that scales linearly with tool count and has zero connection to the actual logic of the tool. My server has 8 tools. Hand-rolled, that's 8 schema blocks plus a dispatcher if/elif chain plus 8 response-wrapping calls, all of which exist purely to satisfy the protocol, not to do anything a GitHub or DEV.to API call needs. what it looks like with FastMCP Here's an actual tool from server.py , unedited: @mcp.tool () def get_repo_stats ( repo : str ) -> dict : """ Get stars, forks, watchers, open issues for enjoykumawat/<repo>. """ r = _gh ( f " /repos/ { GITHUB_USERNAME } / { repo } " ) return { " name " : r [ " name " ], " stars " : r [ " stargaze

2026-07-18 原文 →
AI 资讯

Swarming Claude Code and Codex in Parallel: Running Multiple Agents at Once with tmux and Git Worktrees

In my previous post about a cost budget advisor , I built a mechanism that checks how much quota is left before it runs anything. This time I want to take that idea one step further: instead of cycling a single Codex through jobs one after another, run several of them at the same time as a swarm. I'll walk through the actual code for a three-layer protocol where workers declared in plan.json are expanded by orchestrate-worktrees.js into a tmux session plus git worktrees, so each Codex runs in parallel without interfering with the others. The problem: running Codex serially on the same branch When you run Codex jobs one after another on a single repository, you hit issues like: A commit from the previous job changes the preconditions for the next one Task B keeps waiting until Task A finishes When a conflict happens, the "which change is correct" decision bounces back to a human Separating branches with git worktree reduces the conflict risk to zero. Combining that with tmux to launch everything in parallel is the design for this post. The big picture: a three-layer protocol plan.json ← declaration layer (what goes to which worker) ↓ orchestrate-worktrees.js ← orchestrator layer (creates worktrees, launches tmux) ↓ orchestrate-codex-worker.sh ← worker layer (runs Codex, writes artifacts) The orchestrator doesn't know about the workers, and the workers don't know about each other. Artifacts are consolidated into three files under .orchestration/{session}/{worker_slug}/ . File Role task.md Work instructions for the worker (generated by the orchestrator) status.md State: not started → running → completed / failed handoff.md Codex output + git status (written by the worker) Declaring the plan in plan.json { "sessionName" : "refactor-sprint" , "repoRoot" : "~/my-project" , "worktreeRoot" : "~/worktrees" , "coordinationRoot" : "~/my-project/.orchestration" , "baseRef" : "HEAD" , "replaceExisting" : true , "launcherCommand" : "bash ~/.claude/scripts/orchestrate-codex-worker

2026-07-18 原文 →
AI 资讯

The cost of saying yes has changed

The cost of writing code dropped; the cost of owning it didn't. A framework for deciding which changes are actually cheap in the AI era. The post The cost of saying yes has changed appeared first on The GitHub Blog .

2026-07-18 原文 →
AI 资讯

I Built a VS Code Extension to Upload Code Snippets to PasteDB in One Click 🚀

I created the official PasteDB VS Code extension that lets you upload your current file or selected code directly from Visual Studio Code. It includes secure API key storage, customizable upload settings, recent paste history, automatic language detection, and instant shareable links. In this post, I'll explain how it works, how to install it, and the challenges I faced while building it. More Info View on Marketplace

2026-07-17 原文 →
AI 资讯

A Good AI Code Reviewer Knows When to Stay Quiet

A developer added an AI reviewer to a small Node and React project expecting an easy win. At first, the comments looked useful. Then the reviewer started repeating style complaints, commenting on code that had already changed, and missing a misplaced null check that crashed the application in staging. The team still had to perform a complete human review. That experience, shared in a public DevOps discussion, captures the real question engineering leaders should ask before adding an AI reviewer to every pull request: Did the reviewer remove work from the team, or did it create another thing the team had to review? The problem is not that AI review never works Developers report genuinely useful results too. In one Experienced Developers discussion, engineers described AI reviewers catching privacy leaks, incorrect data-flow assumptions, and logic errors that human reviewers had missed. In the same discussion, another engineer said their review bot was useful but produced plausible, inaccurate comments about one-third of the time. These are anecdotes, not a benchmark. But together they explain why the debate feels confused. AI review is not simply good or bad. Its value depends on the codebase, the context available to the reviewer, the kind of issue being reviewed, and how much verification its output requires. A tool can catch one subtle bug and still make the overall review process slower. It can also say nothing on several pull requests and then save a team from a serious failure. Counting comments cannot distinguish between those outcomes. Comment volume measures activity, not value GitHub says Copilot code review has completed more than 60 million reviews. Its definition of a good review has changed as that volume has grown. The team says it moved from optimizing for thoroughness to optimizing for accuracy, signal, and speed. GitHub reports actionable feedback in 71% of Copilot reviews. In the other 29%, the reviewer says nothing. That silence is intentional: if

2026-07-17 原文 →
开发者

C++ Optimized Compilation Ways

The very common way we know to compile a C++ program is by running the following command: g++ filename.cpp -o filename Talking in terms of stages of optimized compilation, this method is the basic one — we can say stage 0, also written as: g++ -O0 filename.cpp -o filename There are a few more, from zero to three. Let's talk about these ways of compilation. 1] -O0 : No Optimization (Default) Fast compile time. Every variable gets a real stack slot; nothing gets reordered or removed. 2] -O1 : Basic Optimization Some dead code elimination. Simple register allocation. 3] -O2 : 'Standard' Optimization Register allocation. Dead code elimination. Inlining small functions. Loop unrolling and vectorization. Constant folding/propagation. Does not enable optimizations that trade accuracy/safety for speed. 4] -O3 : More Aggressive than -O2 Sometimes faster, sometimes not. Can hurt cache performance. Other than this, there is also a space-optimization option. 5] -Os : Optimization for Size Instead of Speed The command to use these optimizations is as follows: g++ -O2 filename.cpp -o filename Remember, in -O2 the "O" is a capital letter, not a zero — the same applies to the other optimization levels. If you don't know what's going on, or you just wish to compile C++ files the way developers do, use the following standard command: g++ -O2 filename.cpp -o filename

2026-07-16 原文 →
AI 资讯

LLM as a judge

Gone are the hours of careful thought and planning that go into coding a new feature. Vibe coding is too risky though, so another Driven Development was created. I'm referring to SDD (Spec Driven Development) of course. The vibe coding approach is great for prototypes and throwaway code, but this way of working falls apart when teams realise that the code needs to be maintained. So the thing that helps fix this is SDD. Create a spec once from clear technical specs and then generate some high quality code. Sounds great, right. Reminds me of IaC, where you use a templating language to create infrastructure. Software as Code maybe. SaC anyone? Unfortunately, in practice it's not that straightforward. Thoughtworks have placed SDD into an "Assess" category and warned that it could be an anti-pattern for releasing software. Deterministic vs Probabilistic This article isn't about SDD. I'm more interested in discussing the output of SDD and how that is tested. Code can now be generated fast these days. So what better to test AI-written code than with AI itself. There are a lot of concepts and technical terms for the Quality Assurance part of AI generated code. One of these is the LLM-as-a-Judge idea. This idea is used to score the output of an LLM based on some explicit criteria. Traditionally, the way to evaluate an LLM was to judge its output on the helpfulness or faithfulness (using something called "exact-match" metrics). Sometimes it was usually down to a human to do this. It also changes the way that Quality is Assured when dealing with AI-written code. Traditional QA is built on deterministic checks; either something does or does not fail. Something like expect(x).toContainText(y); . A failing test means that something is wrong. Then the bug can be fixed in the code and the test will pass. However, the outputs of an LLM are probabilistic , so it breaks the traditional pass/fail model. This is where a judge comes in. Instead of pass/fail, it can assign a score based o

2026-07-16 原文 →
AI 资讯

Build a Prompt-Injection Regression Fixture for CodeQL 2.26.0

GitHub announced on July 10, 2026 that CodeQL 2.26.0 adds AI prompt-injection detection. Enabling a query is useful; owning a regression test is better. Primary source: GitHub Changelog, July 10, 2026 . The examples below are implementation templates, not results from a repository I tested. Model a path, not a phrase A useful fixture contains an untrusted source, prompt construction, and a model sink: security-fixtures/prompt-injection/ ├── positive/direct-flow.ts ├── positive/helper-flow.ts ├── negative/trusted-instruction.ts └── expected-alerts.json Do not test for the literal phrase ignore previous instructions . Static analysis needs a data-flow path. Preserve a supported SDK call from your production stack so CodeQL can recognize the sink. // Intentionally vulnerable fixture. Never ship this path. import { model } from " ./supported-client " ; declare function loadIssueBody ( id : number ): Promise < string > ; export async function summarize ( id : number ) { const untrusted = await loadIssueBody ( id ); return model . generate ({ system : " Summarize the issue " , user : untrusted , }); } Add a second positive case that passes the value through a helper. Then add a negative control where attacker input cannot select or alter the instruction. A function named sanitize() is not evidence of sanitization. Assert SARIF evidence Uploading SARIF alone does not create a regression gate. Commit the expected rule and fixture location: { "required" : [ { "ruleId" : "REPLACE_WITH_DOCUMENTED_RULE_ID" , "pathSuffix" : "positive/direct-flow.ts" } ], "forbiddenPathSuffixes" : [ "negative/trusted-instruction.ts" ] } Keep the rule ID as a placeholder until it is copied from the CodeQL 2.26.0 documentation or an observed SARIF result. Machine-facing identifiers should never be guessed. A small assertion can compare runs[].results[].ruleId and each physical location against this file. Fail when a required alert disappears or a negative fixture starts alerting. Do not assert the

2026-07-16 原文 →
AI 资讯

Your Codex model shuts off July 23 — a 7-day migration map

I pin model IDs on purpose. Floating aliases have burned me before — a silent swap under a -latest tag once changed a tool-calling detail in production and cost me a Saturday with git bisect and a coffee I didn't enjoy. So everything I run points at a dated snapshot. gpt-5-codex was one of them. Last week I finally read OpenAI's deprecations page top to bottom instead of skimming it, and there it was: gpt-5-codex , retiring 2026-07-23 , sitting quietly next to ten of its neighbors. As I write this it's July 16. That's seven days . If you pinned any of the snapshots below, consider this your heads-up. Here's the full retirement list, how to find out in about ten minutes whether you're exposed, the replacement map with the things I'd actually regression-test, and the one structural change that turned my last forced migration from scary into boring. The shutdown list (retiring 2026-07-23) Straight from OpenAI's API deprecations page . I checked each row against the source while writing this — do the same before you act on it, because dates and replacements do get revised. Retiring model OpenAI's suggested replacement gpt-5-codex gpt-5.5 gpt-5.1-codex gpt-5.5 gpt-5.1-codex-max gpt-5.5 gpt-5.1-codex-mini gpt-5.4-mini gpt-5.2-codex gpt-5.5 gpt-5-chat-latest gpt-5.5 gpt-5.1-chat-latest gpt-5.5 gpt-4o-search-preview-2025-03-11 gpt-5.4-mini gpt-4o-mini-search-preview-2025-03-11 gpt-5.4-mini gpt-4o-mini-tts-2025-03-20 gpt-4o-mini-tts-2025-12-15 computer-use-preview-2025-03-11 computer-use-preview (or gpt-5.4-mini ) Note the shape of it: the *-codex line collapses into gpt-5.5 — except gpt-5.1-codex-mini , which drops to gpt-5.4-mini — the chat-latest aliases fold into gpt-5.5 too, the two *-search-preview snapshots move to gpt-5.4-mini , and the two preview families ( tts , computer-use ) just roll to a newer dated snapshot / the undated alias. Once you see the buckets, the migration is less intimidating than the eleven-row table looks. Find out if you're exposed (about ten m

2026-07-16 原文 →
产品设计

Dual role of * in C

Prerequisites Let's create a variable. int myNum = 5 ; Now, myNum refers to the value 5 . However, we can get its memory address using the & operator like this: &myNum . Role 1: Creating pointers A pointer holds a memory address. int * pointerToMyNum = & myNum ; Role 2: Modifying values using a pointer In this case, * works as the dereference operator. * pointerToMyNum = 10 ; Now, if we print myNum , the output will be 10 . Understanding that they are different in each context makes things much easier ✨ Note Both int ptr and int ptr are functionally identical in C.

2026-07-15 原文 →
AI 资讯

The Bug That Kept Coming Back

The first sign something was wrong wasn't a crash. It was a pattern. blockly-platform was the first real thing I built with Claude Code end to end — a Blockly-based platform for university programming exercises, driven entirely through Claude Code's Telegram channel. No editor open, no repo checked out on my machine, just a chat thread. I'd describe what I wanted, Claude Code would build it on a box I never looked at directly, and I'd judge the result by clicking around the deployed app. On March 22nd, the home page came up empty. GET /api/exercises/published was returning 403. I said so in the chat; a few messages later, Claude Code said it was fixed — the endpoint hadn't been added to Spring Security's permitAll() list. I moved on, tried the category filter. Also empty, also 403, also missing from the same permitAll() list — same file, same class of fix, different line. Then the exercise detail page. Same story, third time, same day. Three days later, the like button stopped working — root cause, again: POST /api/exercises/*/like had never been whitelisted either. Four times, one file, one recurring gap. None of these were hard bugs. Each one, in isolation, is a one-line fix a competent engineer makes without thinking twice. What bothered me, once I noticed the pattern, was that I hadn't noticed it as it happened. I had no diff to scroll through, no file to glance at and think "wait, didn't we just fix this exact class of thing twice already?" I had a chat log and a live app to poke at. The fourth fix looked, from where I sat, exactly like the first: a message telling me it was resolved. That was the moment I started to suspect the problem wasn't the model. It was that nobody — not the model, not me — had anything to look at. Why chat-only vibe coding breaks down Here's what makes that pattern more interesting than "the AI made a mistake": every one of those four fixes was correct. Claude Code read the error, found the missing permitAll() entry, added it, and move

2026-07-15 原文 →
AI 资讯

Building LIA (Part 1 Implementation): Clean Architecture and Argon2id in a Real Fastify + Prisma Registration Flow

LIA is a hyperlocal employability platform I'm building for an isolated coastal district in Brazil — think fixed retail jobs, gigs, and a reputation layer, all matched by proximity instead of routed through a national job board. This post is about the implementation: the actual folder structure, the real RegisterUserUseCase, and the Argon2id decision — pulled straight from the repository, not reconstructed from memory. The Clean Architecture folder structure LIA's backend is organized in four layers, and the direction of dependency is non-negotiable: outer layers depend on inner layers, never the other way around. backend/src/ ├── domain/ │ ├── entities/ │ └── repositories/ # interfaces only ├── application/ │ ├── dto/ │ └── use-cases/ ├── infrastructure/ │ ├── database/ │ └── repositories/ # Prisma implementations ├── presentation/ │ ├── controllers/ │ └── routes/ └── shared/ └── errors/ Let's walk through the registration feature end to end, following that exact order. Domain — the entity and the repository contract The User entity is a plain interface. No decorators, no ORM annotations, no framework leaking in: typescript// domain/entities/user.ts export interface User { id: string; name: string; email: string; password: string; createdAt: Date; updatedAt: Date; } The repository is defined as a contract, not an implementation. The domain doesn't know — and doesn't care — whether it's backed by PostgreSQL, an in-memory map, or something else entirely: typescript// domain/repositories/user.repository.ts import { RegisterUserDTO } from '../../application/dto/register-user.dto.js'; export interface UserRepository { create(data: RegisterUserDTO): Promise<{ id: string; name: string; email: string; createdAt: Date; updatedAt: Date; }>; findByEmail(email: string): Promise<{ id: string; name: string; email: string; password: string; createdAt: Date; updatedAt: Date; } | null>; } Notice create() never returns the password hash. That's not an accident — it's the same "strip

2026-07-15 原文 →
AI 资讯

Laptop Memory Leak Story

I found a slow, insidious memory leak in a Node.js API gateway caused by lingering event listeners; I fixed it by scoping emitters per request, enforcing cleanup in finally blocks, and adding leak‑aware tests and runtime safeguards—memory usage flattened and OOM restarts stopped. The Incident The gateway handled TLS termination, auth, and request fan‑out for many microservices. Over weeks its resident set size climbed in a staircase pattern until Kubernetes began OOM‑killing pods under load. The failure was gradual —light traffic ran for days, peak traffic crashed in hours—so it escaped casual monitoring. Investigation Heap snapshots and allocation profiles showed growing counts of small objects —closures, request metadata, and event listeners—rather than one giant allocation. Tracing revealed an internal event bus where request‑scoped listeners were attached but not always removed: an early‑exit authentication path returned before the cleanup function ran, leaving listeners that held references to request state. The GC saw those objects as live and never reclaimed them. The Fix (technical details) 1. Scoped emitters per request. Replace global emitters for request‑local concerns with a short‑lived EventEmitter created at request start. When the request ends, the emitter goes out of scope and the whole closure graph becomes collectible. 2. Guaranteed teardown via try/finally . Wrap the entire request pipeline so cleanup runs on success, error, or early return; the finally detaches any remaining listeners, clears timers, and releases caches. 3. Leak‑aware CI tests and runtime metrics. A harness simulated thousands of requests across code paths, captured heap snapshots, and asserted bounded object counts. Production metrics tracked listener counts and emitted alerts when thresholds were exceeded. 4. Operational safeguards. Added backpressure on accept queues, a soft memory threshold that disabled nonessential tracing, and rollout halting on excessive crash loops. Thes

2026-07-15 原文 →
AI 资讯

Every Commit in My Repo Gets Reviewed by a Second AI. Here's What Actually Changed.

My CLAUDE.md has one line near the bottom that I wrote months ago and mostly forgot about until I started actually paying attention to what it does: ## Important Note after your work done codex will review what you done. Terse, no punctuation, clearly typed in a hurry. But it's a real instruction that fires on every session in this repo: I finish a change, and a second model reviews it before I consider the work done. I added it half as an experiment. A few months in, it's changed how I work more than almost anything else in the setup, and not in the way I expected. I thought it would catch bugs. Mostly it doesn't, not directly. What it actually does is force a triage decision on every single piece of feedback, and getting that triage wrong is where all the pain lives. The three buckets Early on I treated every review comment the same way: read it, do it. That lasted about a week before I was silently making changes I didn't agree with because a second AI suggested them, and separately burning a stupid amount of time re-litigating comments that were just wrong or out of scope. What actually works is sorting every comment into one of three buckets before touching code: Fix it, no discussion. The comment is unambiguous, low-risk, and doesn't touch anything architecturally significant. Just do it and move on. Ask first. The comment is ambiguous, or it touches something that would require a real judgment call, or the "fix" would be a bigger refactor than the comment implies. Stop and get a human decision before acting. Skip silently. The comment is a duplicate of something already handled, or genuinely doesn't apply. Don't reply just to say "not doing this," don't leave a comment thread as evidence of having read it. Silence is the correct response to a non-issue. The failure mode I kept falling into before I had these buckets explicitly was collapsing 2 into 1: treating "ambiguous" as "just pick an interpretation and go." That's the actual source of review fatigue, not

2026-07-14 原文 →