이 글은 AI의 코드 작성 속도를 누리면서도 소프트웨어의 유지보수성을 잃지 않는 실무 워크플로우를 소개합니다. AI의 강점인 구현에 앞서, 인간이 문서를 통해 철저히 고민하는 사고 과정을 선행해야 한다고 강조합니다. 이를 통해 막무가내 코딩을 방지하고 의도가 명확한 고품질 소프트웨어를 구축하는 방법을 제시합니다.
번역된 본문
채팅창을 열어 원하는 것을 설명하고, 결과물을 반복해서 다듬은 뒤, 그럭저럭 작동하는 무언가를 출시한다. 굉장히 빠르게 느껴진다. 기술적으로 기능들은 작동한다. 하지만 나를 포함해 그 누구도 그 안에 무엇이 있는지 온전히 이해하지 못한다. 누구도 처리해야겠다고 생각지 못했던 엣지 케이스(Edge cases)들, 당시에는 합리적이었지만 다음 기능을 추가하며 무너진 아키텍처. 점점 더 빠르게 구축하고 있지만, 이해는 덜 해나가고 있다는 느낌이 커져만 갔다. 내가 계속 마주했던 문제는 이것이었다: 소프트웨어를 유지보수 가능하게 만드는 명확성과 의도성을 잃지 않으면서, AI 어시스턴트의 속도적 이점을 어떻게 얻을 수 있을까? 짧은 대답은 이렇다. 진짜 작업은 코딩이 시작되기 전에 이루어진다.
핵심 개념: 코드가 아닌 글로 생각하기
AI는 실제로 무엇을 잘할까? 바로 '구현(Implementation)'이다. 그럼 AI는 무엇을 못할까? 개발자가 진짜로 원하는 것을 파악하고, 명확히 밝히지 않은 전제를 발견하며, 문제에 대한 멘탈 모델이 잘못되었을 때 지적하는 일이다. 이는 오롯이 당신의 몫이다. 그리고 항상 그래야 한다. 내가 만든 가장 가치 있는 전환은 모든 기능을 구현 문제로 보기 전에 '사고(Thinking) 문제'로 먼저 대하는 것이었다. 이 워크플로우는 코드가 작성되기 전에 반드시 그러한 사고가 이루어지도록 강제하며, 사고 과정을 건너뛰기 위해 AI를 쓰는 것이 아니라 사고를 검증(stress-test)하는 용도로 AI를 활용하도록 설계되었다. (이 워크플로우는 Mark Pocock의 스킬들을 나에게 맞게 변형한 것이다.)
워크플로우
1단계: 자유 형식 계획 (Free-form plan)
모든 것은 내가 직접 작성하는 문서에서 시작된다. 정해진 구조 없이 평이한 언어로 작성한다. 문제, 솔루션에 대한 초기 생각, 인지하고 있는 제약 조건, 그리고 불확실한 부분들을 설명한다. 이것은 납품용 산출물이 아니며, 나 말고는 아무도 읽지 않는다. 이 문서의 유일한 목적은 내 머릿속에 있는 생각을 꺼내어 내가 검토할 수 있는 형태로 만드는 것이다. 모든 하위 단계의 품질은 전적으로 이 단계의 품질에 달려 있다. 모호한 계획은 모호한 PRD(제품 요구사항 정의서)를 만들고, 이는 다시 모호한 이슈를 만들어내며, 결국 기술적으로는 실행되지만 내가 의도한 대로 작동하지 않는 코드를 낳는다.
2단계: write-a-prd를 통한 PRD 도출
자유 형식 계획은 구조화된 인터뷰 과정의 입력값이 된다. 이 AI 스킬은 현재 상태를 파악하기 위해 코드베이스를 탐색한 뒤, 계획의 모든 측면에 대해 끊임없이 나에게 질문을 던진다. 설계 트리의 각 가지를 따라 내려가며, 결정 사항 간의 종속성을 하나씩 해결한다. 이 단계에서 나쁜 아이디어가 걸러진다. AI가 나보다 똑똑해서가 아니라, 내 계획에 대해 구체적인 질문에 답하도록 강요받으면서 내가 애매하게 넘어갔던 부분들이 드러나기 때문이다. "사용자가 인증되지 않은 상태에서는 어떻게 작동하나요?" "이 작업이 부분적으로 실패하면 어떻게 되나요?" "이것이 기존 기능을 대체한다고 했는데, 현재 동작에 의존하는 사용자들은 어떻게 되나요?" 같은 질문 말이다. 그 결과물은 문제 정의, 솔루션 설명, 방대한 사용자 스토리 목록, 구현 결정 사항(모듈, 인터페이스, 스키마 변경, API 계약), 모듈 설계, 테스트 결정 사항 및 명시적인 범위 외(out-of-scope) 항목들을 포함하는 구조화된 PRD 파일이다. 모든 것이 명시적이다. 사용자 스토리는 이후 모든 작업의 근간이 된다. 이 스토리들은 이 단계가 아니라 이슈 수준에서 범위가 정의될 때, 하위 단계에서 모호함 없이 인수 기준(acceptance criteria)을 도출할 수 있을 만큼 구체적이어야 한다.
3단계: prd-to-issues를 통한 이슈 도출
PRD는 수직적 슬라이스(Vertical slices)를 사용하여 여러 이슈 세트로 변환된다. 이는 단일 계층의 수평적 슬라이스가 아니라, 모든 통합 계층을 종단간(end-to-end)으로 꿰뚫는 예광탄(tracer bullets)과 같다. 데이터베이스만 다루거나 UI만 다루는 슬라이스는 유효한 슬라이스가 아니다. 각 이슈는 그 자체로 데모가 가능하거나 검증할 수 있는 좁지만 완전한 경로를 전달해야 한다. 각 이슈는 AFK(AI가 구현할 수 있고 인간의 개입 없이 병합할 수 있는 경우) 또는 HITL(구현 중 어느 시점에 인간의 결정이 필요한 경우)으로 분류된다. 가능한 한 HITL보다 AFK를 선호하는 것은 작업이 계속 진행되도록 유지하면서 내 주의를 병목 구간으로 만들지 않는다. 무언가가 작성되기 전에, 이 AI 스킬은 제안된 분류를 보여주며 다음과 같이 묻는다:
You open a chat, describe what you want, iterate on the output, and ship something that more or less works. It feels fast. The features work, technically. But nobody, including me , fully understood what is there. Edge cases nobody thought to handle, architecture that made sense in the moment but didn’t survive contact with the next feature. A growing sense that I was building faster and understanding less. The problem I kept running into was this: how do you get the speed benefits of AI assistance without losing the clarity and intentionality that makes software maintainable? The short answer is that the real work happens before the coding starts. The core idea: thinking in writing, not in code What is AI actually good at? Implementation. What is it genuinely bad at? Figuring out what you actually want, catching the assumptions you forgot to make explicit, and telling you when your mental model of the problem is wrong. That’s your job. It will always be your job. The single most valuable shift I made was treating every feature as a thinking problem first and an implementation problem second. The workflow is designed to force that thinking to happen before any code is written, and to use AI to stress-test it, not to skip it. This workflow has been adapted to work with me from Mark Pocock’s skills . The workflow Step 1: Free-form plan Everything starts with a document I write myself, in plain language, with no required structure. I describe the problem, my initial thinking about the solution, the constraints I’m aware of, and the things I’m uncertain about. This is not a deliverable, nobody reads it but me. Its only purpose is to get the thinking out of my head and into a form I can examine. The quality of everything downstream depends entirely on the quality of this step. A vague plan produces a vague PRD , which produces vague issues, which produces code that technically runs but doesn’t do what you meant. Step 2: PRD via write-a-prd The free-form plan becomes the input to a structured interview process. The skill explores the codebase to understand the current state of things, then interviews me relentlessly about every aspect of the plan, walking down each branch of the design tree, resolving dependencies between decisions one by one. This is the step where bad ideas get caught. Not because the AI is smarter than me, but because being forced to answer specific questions about your own plan reveals the places where you were hand-waving. “How does this behave when the user isn’t authenticated?” “What happens if this operation partially fails?” “You said this replaces the existing feature, what happens to users who depend on the current behavior?” The output is a structured PRD file containing a problem statement, a solution description, an extensive list of user stories, implementation decisions (modules, interfaces, schema changes, API contracts), module design, testing decisions, and explicit out-of-scope items. Everything is explicit . The user stories are the backbone of everything that follows. They need to be specific enough that acceptance criteria can be derived from them unambiguously downstream, not at this stage, but when scope is defined at the issue level. Step 3: Issues via prd-to-issues The PRD becomes a set of issues using vertical slices , tracer bullets that cut through every integration layer end-to-end rather than horizontal slices of a single layer. A slice that only touches the database, or only touches the UI, is not a valid slice. Each issue should deliver a narrow but complete path that is demoable or verifiable on its own. Each issue is classified as either AFK (the AI can implement and the change can be merged without human interaction) or HITL (a human decision is required at some point during implementation). Preferring AFK over HITL wherever possible keeps the work moving without becoming a bottleneck on my attention. Before anything is written, the skill presents the proposed breakdown and asks: does the granularity feel right, are the dependency relationships correct, should anything be merged or split? Issues are written in dependency order so that cross-references between them use real numbers. Each issue contains a concise description of the end-to-end behavior, a “how to verify” section describing exactly how to confirm the slice is complete, acceptance criteria in Given/When/Then format including error cases, a list of blockers, and references back to the user stories it addresses. Everything lives in files. I work across different platforms, sometimes GitHub, sometimes GitLab, and keeping the workflow file-based means it doesn’t depend on any particular tool. Step 4: Tasks via issues-to-tasks Each issue is broken down into concrete, ordered tasks, one task per focused AI session. The constraint is deliberate: if a task can’t be completed in a single session, it’s too large. The skill explores the specific parts of the codebase touched by the issue, identifies existing patterns to follow, and produces a task list with types (WRITE, TEST, MIGRATE, CONFIG, REVIEW), explicit outputs, and dependency order. Schema before logic, logic before API, API before UI, tests interleaved rather than batched at the end. The key design decision in the task descriptions: they are written as instructions to the AI that will execute them, not as notes to a human developer. Each task specifies which files to touch, which existing patterns to follow, and what the output looks like when done. No code snippets, just intent, not implementation. Step 5: The handoff to code Each task description is a self-contained prompt. When I’m ready to implement a task, I open a fresh session and paste the task description along with the parent issue for context. The task description was written for this purpose, it specifies scope, references the right files and patterns, and defines what done looks like. Fresh context per task is intentional. Long sessions with accumulated context tend to drift: the model starts making decisions based on what it already did rather than what the task requires. Starting clean with a well-scoped task consistently produces better output than continuing a long session. For REVIEW tasks, the ones flagged as requiring a human decision, I stop, make the decision, update the task file with the outcome, and continue. These are the moments where the workflow earns its keep: the decision is made deliberately, in context, not buried in a long generation. Step 6: Code review via code-review Every PR goes through a structured six-pass review before merge. The passes cover logic errors, operation ordering, bad practices, security, magic strings and values, and pattern improvements. Operation ordering deserves particular attention in AI-generated code. Models tend to produce code that does the right things but sometimes in the wrong sequence: sending a notification before committing a transaction, writing an audit log after the action it should record, mutating state before validating input. These bugs are easy to miss in review because the code looks correct at a glance. The review runs on a file or diff, not on the whole feature. Scope is deliberately narrow, catching issues at the PR level is far cheaper than finding them later. Step 7: Final audit via final-audit At the end of a feature, a cross-cutting audit looks at things that can only be evaluated across the whole implementation. Not individual bugs, those should have been caught per PR, but systemic issues: inconsistencies between modules, patterns that were introduced early and replicated incorrectly everywhere, security assumptions that hold in isolation but break down across the full surface area. The audit reads the full implementation before flagging anything, which is the point. It groups findings by severity, gives an explicit overall verdict on whether the feature is safe to leave in production, and asks for approval before making any changes. Unsupervised fixes o