Current Version: 1.6 Design: design/coding-discipline.design.md v1.6 Session: 48a3ef9b-50cf-4574-8f00-4f6b6e28f76e Full history: changelog/coding-discipline.changelog.md Absorbed: maintainable-code-structure-and-decomposition v1.2, development-verification-and-debug-strategy v1.1, tactical-strategic-programming v1.3
Core Principle: Code with maintainable structure, proportionate verification, and tactical-to-strategic convergence. Preserve responsibility clarity, decompose only when it lowers real change cost, carry a verification strategy before completion claims, and anchor tactical work to a declared strategic target with a visible convergence path.
This rule covers coding-time responsibility, decomposition, semantic source naming, helper-function necessity, source-code comment discipline, bounded code-to-document linkage, behavior-preserving refactor, verification strategy, debug signal selection, TestKit/scenario decisions, evidence-calibrated closeout, tactical entry, strategic target, convergence, and anti-drift posture.
พูดง่าย ๆ: เขียนโค้ดให้คนแก้ต่อได้ง่าย, พิสูจน์ได้ว่าของจริงใช้ได้แค่ไหน, และ tactical ต้องมีปลายทาง strategic ไม่ใช่ชั่วคราวถาวร.
Maintainability means future readers can understand, test, modify, extend, or repair code with low surprise. Optimize for readable intent, clear responsibility, testability, and visible side effects. Treat shorter files, more helpers, or extra layers as useful only when they reduce real change cost. Separate current pressure from speculative futures.
Group or split code by why it changes, not by a fixed template. Keep cohesive logic together when it changes for one reason; separate responsibilities that change for different reasons or are tested/owned differently. Inspect mixed business rules, orchestration, UI, persistence, integrations, validation, formatting, config, logging, and error handling. Reuse existing project structure when adequate.
Source identifiers should describe domain meaning, behavior, state, or responsibility before execution history. Function, variable, helper, class, type, module, and test-helper names must stay semantic/domain/behavior-first unless an external interface forces a literal token. Do not put phase IDs, document IDs, ticket IDs, patch IDs, changelog versions, roadmap labels, or temporary review names into source identifiers merely to show where the code came from. Those tokens belong in governed documents or in bounded source comments only when they materially aid maintenance. An artifact token may appear in code only when it is itself a real external/domain contract term, protocol value, stored data value, public API field, migration identifier, compatibility alias, or operator-facing literal that the system must preserve.
Treat God function/file, long method, large class, helper inflation, shotgun surgery, divergent change, feature envy, primitive obsession, hidden dependency, comment spam, stale comments, and speculative generality as investigation signals. Do not refactor solely because a unit is long; do not extract helpers merely because extraction is possible. Decide from cohesion, coupling, change axes, testability, navigation cost, comment usefulness, and implementation risk. Preserve uncertainty when the right split is not yet clear.
Choose the smallest structural move that improves real maintainability. Prefer clear local code before extraction when inline flow is easier to read. Extract a named local step only when the name adds meaning or test/side-effect boundaries. Split modules/files only when responsibilities or change axes materially differ. Add interfaces, factories, strategies, or plugin-like abstractions only when current evidence justifies variation or isolation. Keep navigation and call flow easier after the split, not harder. If a tactical direct edit is safest now, name the convergence path when material structure debt remains.
Helper functions must earn their indirection cost. Extract when the name captures a real concept, business rule, process step, reusable behavior, testable unit, or side-effect boundary better than inline code. Do not create helpers for obvious expressions, trivial assignments, one-line wrappers, or simple sequential code clearer inline. Avoid pass-through helper chains and inline helpers whose body is as clear as the name. Single-use helpers are allowed only when the name materially clarifies intent, process, or boundary. Helper names must not encode phase, ticket, document, patch, or changelog provenance unless that token is a real domain or external contract term.
Duplication can be safer than coupling unrelated concepts behind a false shared abstraction. Do not merge code merely because it looks similar. Extract shared behavior only when the underlying concept and reason to change are genuinely shared. Prefer short-lived duplication over premature abstraction that makes future change harder. Remove or simplify speculative generality when it adds indirection without current value.
Make important dependencies and state flow visible enough to reason about. Avoid hidden global or ambient state when explicit dependency passing is reasonable. Bind config/environment at edges instead of scattering through domain logic. Separate pure transformation from side effects when it improves testing and clarity. Keep error handling and logging close to the boundary where they have operational meaning.
Names and structure explain normal flow first; comments explain what code cannot express clearly enough. Add concise comments for purpose, why, business rule, process order, constraint, side effect, external contract, compatibility workaround, security/performance/concurrency caveat, or operational consequence otherwise hard to understand. Avoid comments that repeat syntax, narrate every line, or compensate for unclear names/structure. Update or remove nearby comments when behavior changes; stale comments are worse than missing comments. Do not invent explanatory comments for behavior not verified from code, tests, docs, or user-provided requirements. Keep broad policy/spec/architecture authority in governed docs, not oversized source comments.
Comments may cite governed documents only when the link materially lowers maintenance cost, such as preserving durable rationale, an external contract constraint, a migration boundary, compatibility workaround, or rollback context. Prefer stable design or contract references by default when source code needs a durable rationale link. Phase, changelog, patch, ticket, or route-plan references should stay narrow: provenance for a temporary tactical bridge, migration, compatibility exception, rollback note, or reviewed before/after change that would be costly to rediscover. Do not tag every function, helper, class, or branch with execution chronology; ordinary source names and structure should remain readable without carrying phase/doc/ticket history.
Refactoring should improve internal structure while preserving externally visible behavior unless behavior change is explicitly part of the task. Separate structural refactor from behavior change when practical. Use small transformations rather than broad rewrites when behavior risk is high. Run relevant tests, type checks, lint, or bounded verification when available. If verification is incomplete, report the limit instead of claiming the code is fixed, clean, or stable.
Before treating a non-trivial implementation as ready for closeout, inspect whether the selected outcome still has material gaps in behavior, state/dependency boundaries, integration contracts, failure handling, security-relevant paths, operational observability when needed, or verification. Add only what the current objective and checked evidence justify; do not create unrelated abstractions, speculative safeguards, or compatibility layers for imagined futures.
A visible happy path is insufficient when the selected scope materially depends on error behavior, retries/idempotency, persistence, concurrency, rollback, lifecycle cleanup, or external contracts. Cover the obligation, or mark it explicitly deferred, blocked, not applicable, or out of scope with an owner/reason where needed.
Before expanding or introducing a substantial function, file, module, class, helper, or abstraction:
- cohesive, small, one reason to change → keep local and clear
- obvious expression or trivial assignment → keep inline; do not extract a helper
- repeated named step inside one flow → extract only when the name improves understanding
- new identifier would carry phase/doc/ticket/patch/changelog provenance → prefer a semantic behavior/domain name and move any necessary provenance to a bounded comment or governed document
- mixed responsibilities or different change reasons → split by responsibility/module boundary
- side-effect boundary mixed with pure logic → separate orchestration from pure transformation when useful
- same concept in multiple places → extract only when concept and reason to change match
- similar code with different business meaning → allow duplication until the real abstraction is clear
- non-obvious process/constraint/business rule/side effect → use a named helper and/or concise comment when it reduces cognitive load
- tactical shortcut → keep bounded and name convergence when debt is material
- abstraction for imagined future only → avoid, remove, or justify from current evidence
Before extracting a helper, answer:
- Does the helper name express a real concept, rule, process step, reusable behavior, testable unit, or side-effect boundary better than the inline code?
- Does the helper name stay semantic/domain/behavior-first instead of encoding phase, ticket, document, patch, or changelog provenance?
- Is the logic complex enough that a named step lowers cognitive load?
- Is repeated logic genuinely the same concept and reason to change?
- Does extraction improve testability, side-effect separation, or future change locality?
- Does extraction avoid excessive parameter threading and call-chain hopping?
- Would inline code be clearer?
If the inline form is clearer or the helper adds no semantic value, do not extract; keep the code local or inline the helper back.
Before adding or leaving a comment, answer:
- Does the comment explain purpose, why, process order, constraint, side effect, external contract, or business rule not obvious from code?
- Would clearer naming, structure, or a better helper remove the need for this comment?
- If it cites a governed document, does that reference materially lower maintenance cost, and would a stable design/contract reference be the better durable link?
- If it cites a phase, changelog, patch, ticket, or route-plan artifact, is the reference narrowly needed for provenance, a temporary tactical bridge, migration, compatibility, or rollback context?
- Is the comment still true after the change?
- Is the comment concise enough to stay local rather than become durable documentation?
- Is the behavior verified well enough to explain it?
If the comment repeats syntax, narrates obvious code, is stale, tags ordinary code with execution chronology, or belongs in governed docs, remove or rewrite it.
| Smell | Signals | Required response |
|---|---|---|
| God function / God file | too many concerns or change reasons in one unit | inspect whether orchestration, domain logic, validation, side effects, formatting, or ownership should split |
| Helper-function inflation | helpers add navigation cost without semantic value | inline trivial wrappers or keep cohesive logic local |
| Long method / large object | sequence or object responsibility hard to scan/verify | extract named steps or separate collaborators only when names/boundaries improve understanding |
| Divergent change / shotgun surgery | one axis touches too many places, or one unit changes for unrelated reasons | separate unrelated axes, or consolidate only a genuinely shared concept |
| Feature envy / primitive obsession | behavior or raw values live far from better owner | move logic toward the better owner or introduce a type/helper only when it reduces errors or clarifies behavior |
| Hidden dependency | behavior relies on global/ambient/implicit state | make dependency/state flow explicit where practical |
| Chronology-coded identifier | source names carry phase, ticket, document, patch, or changelog labels instead of domain/behavior meaning | rename toward semantic responsibility unless the token is a real external/domain contract term |
| Comment spam / stale comment | comments repeat syntax, no longer match behavior, or tag ordinary code with execution chronology | remove noise, improve names/structure, or update verified explanation |
| Speculative generality | abstraction exists for a future not currently needed | simplify, defer, or justify from current evidence |
Before or alongside non-trivial coding work, identify the behavior under change, the risk of being wrong, the debug signal that would show failure/success, and the smallest useful verification depth. Do not wait until after implementation to invent testing posture.
- classify whether the work changes behavior, structure only, integration, config/runtime behavior, data flow, security-sensitive behavior, or documentation/governance only
- choose verification depth from the actual behavior/risk/signal available, not from a rigid command template
- make the TestKit/scenario decision explicit when behavior is scenario-like, multi-step, integration-heavy, or safety-sensitive
- if verification is not applicable or not practical, state the narrow reason and preserve evidence limits
- do not claim fixed, working, stable, or release-ready from edits alone
Use the smallest verification layer that gives meaningful signal.
| Work shape | Default verification posture |
|---|---|
| typo, docs-only, metadata-only, no behavior change | review or no test with reason |
| small pure logic or UI behavior change | unit or focused test when available |
| refactor intended to preserve behavior | existing focused/regression tests for the affected behavior |
| integration, adapter, API, database, queue, or event behavior | integration/fake-adapter/contract test when practical |
| multi-step user/system flow | scenario harness, TestKit, or equivalent workflow test |
| payment, auth, secrets, quota, runtime, provider, external dependency, or privacy-sensitive behavior | fake/local first plus explicit smoke/live decision and safety boundary |
| production/shared-state/destructive path | approval-sensitive verification gate plus rollback/containment owner |
Strategy, not ceremony. A stronger existing test may replace a new scenario; a scenario harness may replace fragile ad hoc checks; a live check remains conditional on user approval, safety, and environment readiness.
For bugs, failures, flaky behavior, or complex integration, identify the observed failure/reproduction target, working hypothesis, and discriminating signal before patching. Prefer a failing focused test, reproduction, scenario, fixture/fake, log/report, or failure injection that cuts away competing explanations.
If reproduction is unavailable, state the missing evidence and use bounded diagnostics. If implementation proceeds before cause is proven, label it hypothesis-driven. Re-anchor the reproduction target after user correction; do not shotgun-edit several speculative causes.
A missing field, output, route result, configuration value, or consumer-visible value does not by itself prove that the architecture lacks a transport or owner. Before adding infrastructure, map the expected active design contract, owner, producer, state or transport, readers, writers, consumers, dependencies, and last-known-working evidence at proportionate scope.
Classify the checked condition as one of:
existing-path regression: the governed path exists and should satisfy the requirement, but current behavior is defectivestate/config drift: the path exists, but current state or configuration diverges from the selected contractdormant_or_disconnected_existing_path: implementation exists but is not currently connected through the intended active edgescontract mismatch: producer, stored state, transport, or consumer expectations disagreeunresolved: evidence does not yet distinguish the realistic branchesverified_capability_gap: checked evidence shows the existing path is absent, the current owner cannot satisfy the selected requirement, or active design explicitly selects new architecture
Use the smallest discriminating check that separates the leading classification from realistic competitors. Regression, drift, disconnection, and mismatch default to bounded repair of the current authority; unresolved state stays diagnostic. A verified capability gap permits an architecture proposal only through the design-conformance delta and approval owner in action-safety.md.
For architecture-bearing work, functional verification and architecture-conformance verification are separate obligations.
Positive proof should show that required behavior passes through the selected design path, the intended producer-state/transport-consumer chain works, the selected authority remains active, and an approved replacement selects the target when replacement is in scope.
Negative proof should proportionately check that normal source, import, config, build, deployment, install, runtime, and test-discovery edges do not activate an unauthorized alternate owner/route/service/client/adapter/transport/registry/state key, dual read/write, shadow path, or automatic fallback. Scope these checks to the architecture roles affected by the change; do not turn a bounded repair into a whole-system absence ritual.
Tests written for an unapproved or design-divergent path can show that the invented implementation behaves as written, but they cannot satisfy the objective's architecture or completion gate. A mixed result must stay explicit:
functional behavior: passed
architecture conformance: failed
one-authority invariant: failed
completion: blocked
For non-trivial coding work, select one verification route explicitly:
existing_test: an existing focused/regression test covers the behaviornew_focused_test: a small new test should be addednew_testkit_scenario: a scenario/TestKit path should be added or extendedsmoke_check: a lightweight end-to-end or runtime check is appropriatelive_check_required: live/provider/runtime verification is needed and approval/environment gating appliesnot_applicable_with_reason: no meaningful test applies, with a short reason
Do not create TestKit scenarios for every task by reflex. Do not skip the decision silently when behavior risk is material.
| Coding evidence | Maximum claim in scope | Boundary |
|---|---|---|
| source edited | implemented | not fixed/working without checks |
| focused/unit/regression passed | tested / verified-in-scope | not whole-system or live proof |
| fake/local scenario passed | fake/local verified-in-scope | not provider/runtime/deploy proof |
| one smoke check passed | runtime-checked once | not stable over time |
| real provider/runtime/deploy checked | runtime/live-verified | not stable without repeated evidence |
| reported failure reproduced and corrected with matching evidence | fixed in checked scope | not global/all-edge-case stability |
| no test run | not tested | no verified-behavior claim |
Coding closeout should include the compact verification record when material:
Verification:
- Ran: <commands/scenarios/checks or not run>
- Result: <passed/failed/not run>
- Covers: <behavior/scope>
- Does not cover: <live/provider/runtime/deploy/edge cases if material>
- Confidence: <evidence-calibrated wording>
For phase-backed coding work, phase records should show Development Verification / TestKit Coverage or equivalent when verification materially affects exit criteria. Live task lists should include or preserve a verification slice for non-trivial coding work when implementation and verification are distinct outcomes. Execution continuity should continue from implementation into verification when no real stop gate exists.
Tactical work is allowed when it materially improves startup speed, learning speed, or immediate execution. Keep it bounded and temporary unless explicitly promoted; do not require complete strategic design before useful implementation can begin.
Every tactical move must point toward a declared strategic target. Identify the intended end-state before or alongside tactical execution; do not let local progress define the long-term direction by itself; do not proceed tactically when no strategic target can be named.
Every tactical path must show how it converges into strategic structure: absorbed, promoted, replaced, or retired; the future structure it converges into; and the trigger for strategic closure. Temporary structure must not remain indefinitely undefined.
Tactical work must stay narrow enough to converge cleanly. Prefer local, reversible, low-blast-radius moves; do not let tactical entry silently expand into broad unplanned architecture; do not let machine-local paths, hosts, or install assumptions become defaults unless explicitly machine-scoped. Broader portable-default and anti-hardcoding ownership defers to portable-implementation-and-hardcoding-control.md.
Temporary solutions must not become hidden long-term authority. Workarounds, patch stacks, bridges, or compatibility paths require retirement, absorption, or formal promotion when they persist; detect and call out drift early.
After cutover, the implementation must have one active source/path/authority for the migrated role. Remove or execution-disconnect former imports, switches, environment flags, factory branches, aliases, dual read/write paths, shadow implementations, build/config/deployment/test-discovery edges, and target-failure fallbacks unless an explicitly approved temporary compatibility bridge still owns them under action-safety.md.
Preserved former code belongs only in execution-disconnected quarantine or inactive history outside normal discovery. Compatibility code is a temporary exception with a retirement trigger and proof obligation; it must not remain as permanent parallel architecture. Rollback/restoration deliberately replaces the active source from a verified known-good source and must not activate both paths together.
Non-trivial systems should end in strategic structure. Stable boundaries, roles, and sequencing belong in strategic authority; design and phase layers should absorb validated tactical learning; final authority should not live in leftover tactical fragments.
| Mode | Meaning | Typical use |
|---|---|---|
TACTICAL |
local bounded execution slice | patch artifact, focused fix, immediate unblock |
STRATEGIC |
directional/architectural planning layer | design, roadmap, phased execution, boundary setting |
TACTICAL_WITH_STRATEGIC_TRACK |
tactical execution with target and convergence path | fast-start implementation under strategic control |
Before non-trivial tactical execution, answer:
- Is this tactical, strategic, or tactical-with-strategic-track?
- What is the strategic target?
- What is the tactical slice now?
- How does it converge?
- What triggers promotion to strategic closure?
If answers 2-5 are unclear, clarify strategy before tactical execution.
| Artifact type | Default role |
|---|---|
design/*.design.md |
strategic target-state authority |
phase/SUMMARY.md + child phase files |
strategic execution program |
patch/<context>.patch.md or root <context>.patch.md |
tactical change artifact |
TODO.md |
tactical execution tracking |
| runtime temporary workaround | tactical implementation device |
| stable architecture authority | strategic end-state |
When this doctrine materially matters, make these meanings visible: Strategic target, Tactical now, Why tactical first, Convergence path, and Strategic end-state. Equivalent headings acceptable if the meaning stays explicit.
| Trigger | Required behavior |
|---|---|
| non-trivial coding feature | inspect implementation completeness, then choose verification depth and TestKit/scenario decision before closeout |
| bug/debug request | define observed failure/reproduction, hypothesis, signal, fix, and regression check when practical |
| root-cause diagnosis before patching | separate symptom, leading hypothesis, discriminating check, and evidence boundary before shotgun edits |
| missing field/output/route result/config/consumer value suggests new infrastructure | classify regression, drift, disconnected path, mismatch, unresolved state, or verified capability gap before architecture expansion |
| architecture-bearing implementation | require functional proof through the selected path plus proportionate negative architecture-fitness checks against unauthorized alternate authority |
| refactor | preserve behavior and rerun affected tests or state verification limits |
| integration/provider/runtime/payment/auth/privacy work | fake/local verification first, then explicit smoke/live decision and safety boundary |
| scenario-like flow | prefer TestKit/scenario harness or explain why focused tests are stronger |
| implementation completed but not tested | continue to verification when safe instead of stopping at edit-only progress |
| checklist/config/scaffold readiness | report prepared/configured/implemented only; do not claim tested, verified, live, fixed, or stable without matching evidence |
| trivial no-behavior change | avoid ceremony; mark test not applicable with reason when needed |
| broad/growing function/file | inspect for God/responsibility split before continuing to grow |
| source identifier includes phase/doc/ticket/patch/changelog history | rename toward domain/behavior meaning unless the token is a real external/domain contract term |
| source comment cites governed execution artifacts | prefer stable design/contract references for durable rationale; keep phase/changelog/patch/ticket references narrow to provenance, migration, compatibility, tactical bridge, or rollback context |
| fast local fix in unclear terrain | allow only with strategic target and convergence path |
| patch accumulation | check for tactical drift and promotion need |
| phase/roadmap planning | let strategic framing dominate |
| temporary runtime structure | require retirement or absorption path |
| migration/cutover changes source authority | prove one active source after cutover and remove or execution-disconnect former imports/switches/flags/aliases/read-write/fallback/discovery edges |
| compatibility implementation remains | treat it as a temporary bridge under action-safety.md; require owner, retirement trigger, observability, and removal proof |
| architecture still emerging | use TACTICAL_WITH_STRATEGIC_TRACK |
| long-lived workaround | require promotion, retirement, or replacement |
| tactical fix changes code structure | preserve responsibility clarity, avoid avoidable God function/file drift, name convergence if material debt remains |
Avoid:
- fixed line-count policing, template-first architecture, splitting every function into tiny fragments
- helper wrappers for obvious expressions, pass-through helper chains
- interfaces/factories/strategies for one current use, merging coincidental duplication
- refactor-plus-behavior-change without a boundary, rewriting a whole file for one smell
- syntax-narrating comments, stale comments
- phase/doc/ticket/patch/changelog IDs in function, variable, helper, class, type, or module names when those tokens are not real external/domain contract terms
- tagging every function or helper with execution chronology instead of using semantic names plus bounded comments only where they lower maintenance cost
- local tactical patches that become hidden permanent structure
- happy-path-only implementation closeout while material state, integration, failure, observability, or verification obligations remain unclassified
- old/new source paths left active through imports, flags, factories, aliases, dual read/write, shadow code, discovery edges, or automatic target-failure fallback after migration
- edit-only completion, testing as an afterthought, mandatory TestKit creation for trivial work
- fake/local pass presented as live proof, refactor without behavior-preservation checks
- debugging from guesses without a signal, adding tests that do not cover the changed behavior
- treating a missing field/output/route result/config/consumer value as proof of a capability gap without tracing the existing governed path
- accepting passing tests for an unapproved or design-divergent path as architecture or completion proof
- shotgun edits across several speculative causes before a discriminating check narrows the branch
- running broad/noisy tests without worker filtering when appropriate
- reporting fixed/stable beyond checked evidence
- tactical entry without a declared strategic target or convergence path
Related owners: accurate-communication.md and evidence-discipline.md (claim strength); phase-todo-artifact.md and execution-and-goal-frame.md (verification continuation); worker-routing-and-context.md and action-safety.md (noisy/live checks); document-governance.md, portable-implementation-and-hardcoding-control.md, and explanation-and-presentation.md (artifact/binding/presentation ownership).