Appearance
Chapter 2 - Intent and Context: Before the Code
Before a single line of code is generated, the human Navigator must anchor intent, boundaries, and context. In traditional software development, ambiguity is resolved on the fly during implementation or through ad-hoc hallway chats. In human-AI pairing, ambiguity is fatal: an underspecified prompt does not produce a pause or a clarifying question; it produces an avalanche of plausible, incorrect code that drains cognitive energy to review.
Four disciplines establish intent and context before generation begins:
- Spec-First Prompting: Grounding intent and acceptance criteria upfront.
- Domain Gap Surfacing: Interrogating missing domain knowledge before it becomes bugs.
- Context Stewardship: Maintaining repository memory and living context artifacts.
- Coding Standards as Context Infrastructure: Enforcing architectural guardrails via prompt constraints.
Spec-First Prompting
Spec-First Prompting elevates a user story to the rigor of an Architecture Decision Record (ADR) before any implementation code is generated.
The Failure Mode
When an engineer prompts an AI with natural-language feature requests without a rigid spec, the AI improvises. It makes unspoken assumptions about data structures, edge cases, error handling, and API boundaries. Because modern AI models generate syntactically clean code at machine speed, these assumptions slip through unnoticed during early reviews. By the time the flaw is discovered, the entire implementation is tangled around speculative design choices that must be painstakingly unwound.
The Mechanism
The specification is developed collaboratively with the AI pair partner, but the human author retains strict authorship over intent:
- Problem Statement and Boundaries: Clearly define what is in scope and explicitly what is out of scope.
- Acceptance Criteria: Define testable scenarios, expected inputs, and exact outputs.
- Data Contracts: Explicitly name interfaces, schema changes, and state transitions.
- Implementation Plan: Break the feature into small, independently verifiable tasks.
In aiXP, the specification functions as a manufactured substitute for classic XP's "on-site customer." Instead of relying on a human customer who is rarely physically present, the spec externalizes domain intent and business rules before generation begins. Crucially, the spec is a living document, not a frozen waterfall artifact: as implementation uncovers new edge cases, technical constraints, or phase updates, the spec is updated in lockstep so that documented intent and running code never diverge. When design choices change, superseded sections are overwritten or deleted rather than preserved as a historical log - Git holds the history, while the spec holds only current law.
Acceptance Test-Driven Design (ATDD) and Micro-Specs: In aiXP, the spec is the law. Spec-First Prompting is not reserved for massive architectural features; every feature request, bug report, or styling standard is converted into its own micro-spec anchored by explicit acceptance criteria. While unit-level TDD drives the inner coding loop, ATDD drives the outer loop: a spec is only satisfied when its acceptance criteria pass their defined verification gates - whether automated (a test suite, an End-to-End (E2E) check, or a linter rule) or human-gated (on-device hardware testing or perception-gated visual sign-off where synthetic tests cannot reach). When a micro-spec includes an on-device gate, the agent's work halts at producing the candidate build; only the Navigator's physical verification makes the spec true.
Field Case: The Linter Is the Acceptance Test
Verification gates are not limited to traditional unit or integration tests - static analysis and linters often serve as the primary acceptance test, and they dictate how work must be scoped.
During a pass establishing CSS visual standards for contrast and styling hygiene, the micro-spec required enforcing strict contrast and style rules across the UI. We added a custom lint rule alongside E2E tests to verify the standard.
Immediately, the AI agent proposed splitting the effort into separate pull requests and fragmented areas of focus - isolating the linter setup from the component fixes. As Navigator, I pushed back: the lint rule itself is the area of focus. Together with the E2E tests, that lint rule is the acceptance criteria that makes the spec true. Splitting the rule from the compliance fixes breaks the ATDD contract - you either merge a failing build or merge a disabled rule that proves nothing. The unit of delivery had to be the spec made true: the lint rule active, every violation resolved, and the E2E suite passing in a single verifiable boundary.
Production Artifact: Multi-Backend Feature Design Spec (BojuBot)
The following excerpt from notes/dev/specs/agent-backends-spec.md in the BojuBot repository demonstrates Spec-First Prompting in production. Before modifying the plugin to support a second AI CLI backend (Google's Antigravity CLI alongside Claude Code), the author authored a comprehensive design spec establishing phased milestones, verified empirical CLI behavior, and explicit data contracts:
markdown
# Agent Backends (Antigravity CLI first) - Feature Design Spec
Status: Phase 1 IMPLEMENTED (seam, on dev) · Phases 2-3 PROPOSED · development on the dev branch
Written 2026-10-16. Verified against Antigravity CLI 1.4.0 (agy, run on Windows 11 with a personal Google account) and Gemini CLI 0.64.0 (read from its published npm package). "Verified" means tested or read in code on those versions; everything else is design intent to re-check at implementation time.
## Overview
BojuBot only drives the Claude Code CLI. Every turn spends the user's Claude plan quota, which heavy Claude Code users have already spent on coding. A second backend lets them keep using BojuBot on a different plan.
Goal for the release: the user picks a backend, Claude Code or Antigravity CLI, globally or per session. Everything BojuBot does today keeps working on Claude Code exactly as before, and the core chat, vault, UI Bridge, skills and permission experience works on Antigravity.
| Phase | Scope | Shippable on its own |
| --- | --- | --- |
| 0 | dev branch, CI for dev, this spec | n/a |
| 1 | AgentBackend seam; Claude Code moved behind it with zero behavior change | Yes (pure refactor) |
| 2 | Antigravity backend MVP: chat, resume, tools, Standard/Full/Read-only, denial card, models, effort, history | No, internal milestone |
| 3 | Release hardening: onboarding, Chat only decision, docs, test matrix, dev -> main | Release |
| Later | Gemini CLI adapter, usage meter for both, persistent process, agy remote control | Separate releases |
## Research findings: Antigravity CLI 1.4.0 (verified on this machine)
Invocation: --print takes the prompt as its value (--print "..."), unlike Claude's flag, and an empty --print= doesn't read stdin. BojuBot must never put prompt text in args on Windows, so it uses the stdin streaming input instead. Verified working from Node:
agy --input-format stream-json --output-format stream-json --model <id> [--conversation <id>] [mode flags]
stdin: {"event":"user","message":{"role":"user","content":"<prompt>"}}\n then close stdinNotice the engineering mechanisms in action:
- Phased Gating: Phase 1 is isolated as a pure, behavior-preserving refactor to extract the
AgentBackendseam before introducing new backend code. - Empirical Boundary Checking: The author tested invocation behavior directly on Windows, catching the fact that
--printdoes not read stdin on Windows and mandating stdin streaming to avoid shell quoting corruption. - Explicit Invariants: Non-goals and strict boundaries are settled up front (for example, marking SQLite database parsing as forbidden for the agent).
Domain Gap Surfacing
In Kent Beck's original 1999 formulation of Extreme Programming, domain ambiguity was resolved by the On-Site Customer - placing an authentic business expert or end user directly beside the development team. Whenever an edge case or business rule was unclear, the programmers turned around, asked in ten seconds, got authoritative domain truth, and encoded it into an immediate acceptance test.
In production practice, the on-site customer almost universally degraded due to logistics: authentic domain experts had full-time jobs, organizations substituted proxy managers, and in teams of 1 to N, the engineer is the sole human in the room.
The Failure Mode: The Imposter Customer
In human-only development without an on-site customer, ignorance created natural hesitation: writing code was slow and expensive, so an uncertain developer stopped to research or ask.
Modern AI pair partners have zero hesitation and infinite confidence. When an LLM hits an unfamiliar domain - whether it is cross-border tax calculation, digital audio DSP, or planetary optics - it never pauses to confess ignorance. Instead, it hallucinates plausible, authoritative-sounding domain logic, writes hundreds of lines of syntactically flawless code, and drafts passing unit tests that assert against its own fabricated rules.
The AI effectively acts as an imposter on-site customer. If the human Navigator cannot evaluate the domain mechanics independently, they accept the hallucinated consensus - falling straight into the vibe coding trap.
The Mechanism: The 80/20 Polymath and the Sandboxed Tutor
High-leverage engineering does not require a developer to hold a thirty-year doctorate in every adjacent field. Instead, it relies on the discipline of the 80/20 polymath: acquiring the essential 80% of a domain's mental model with 20% targeted effort.
To bridge domain gaps without letting the AI hijack the architecture, aiXP enforces an operational four-step protocol:
- Surfacing the Gap (The Mechanical Trigger): If the developer does not possess deep intuitive pattern recognition in the problem space, they command the AI to interrogate the boundary upfront:
"What physical laws, regulatory rules, mathematical edge cases, or platform constraints govern this feature that a generic implementation typically gets wrong?"
- Epistemic Isolation (The Sandboxed Tutor): The developer never allows the production coding Driver to teach domain concepts while writing code. Conflating research with implementation floods the context window with conversational debris and incentivizes the agent to cut corners. Instead, the developer spins up an isolated, sandboxed AI session dedicated exclusively to mental models and education.
- The Feynman Gating Bar (Automating the Reverse-Grill): The Navigator adheres to an absolute acceptance gate: you have not learned a domain principle until you can teach it. To prevent the illusion of competence (reading an AI explanation and mistaking passive recognition for true understanding), the developer instructs the sandboxed tutor:
"Explain the core mechanics of X. Once explained, grill me with edge cases and require me to explain the mechanics back to you in plain English. Do not let me pass if I rely on hand-waving or buzzwords."
- Authoring Domain Truth into the Spec: Only when the Feynman bar is cleared does the Navigator return to the production terminal loop. The developer authors the verified domain rules into the feature specification as explicit acceptance criteria before any code is generated.
Field Case: The Lunar Surface Shader
A concrete example illustrates the danger of the imposter customer and the power of the sandboxed tutor.
Suppose an engineer needs to write a custom shader for rendering the surface of the moon at varying distances:
- The Naive AI Generation: A developer prompts a coding agent: "Write a GLSL moon surface shader." The agent generates standard Blinn-Phong or Lambertian diffuse lighting. The code compiles, runs at 60 FPS, and produces a smooth, shaded sphere. But visually and physically, it is completely wrong: real planetary regolith does not behave like a dusty billiard ball with smooth limb darkening.
- Surfacing the Gap: An engineer with visual intuition spots that something is unique about lunar lighting. A developer without that visual background triggers the gap mechanically by asking the AI what unique physical properties lunar reflectance exhibits.
- The Sandboxed Tutor: Rather than asking the coding agent to fix the shader directly, the engineer opens a sandboxed research session. The tutor explains the optical mechanics: lunar regolith is composed of highly porous, jagged microscopic dust particles that cause retroreflective backscattering (Hapke scattering) and shadow-hiding, creating the lunar opposition surge (why the full moon appears uniformly flat and bright across its entire disc, with almost no limb darkening).
- The Feynman Bar: The engineer works with the tutor until they can explain retroreflective scattering and phase angle curves in their own words.
- Driving the Code: The engineer returns to the production Driver with an exact specification: "We are not using Lambertian diffuse falloff; we are implementing a retroreflective Lommel-Seeliger or Hapke approximation with an opposition surge at zero phase angle."
The human Navigator acts as the authentic domain authority, turning what would have been plausible AI slop into physically accurate software.
Production Artifact: Hardware Boundary Isolation and Unconfirmed Proposals (QuKi Notes)
In cross-platform client tooling, platform runtime boundaries (OS graphics layers, mobile WebViews, touch input engines) represent the highest-risk domain gaps. Automated desktop test suites pass with flying colors, but real hardware behaves completely differently.
The following excerpt from CLAUDE.md in QuKi Notes demonstrates Domain Gap Surfacing applied to an Android WebView touch input boundary:
markdown
## Android text input
On Android the editor types through the WebView's own editing (contenteditable), not CodeMirror's default EditContext path: EditorView.EDIT_CONTEXT = false is set in project/src/main.ts before the view is created, because EditContext bypasses the WebView's spellcheck. Desktop and web never use EditContext either way.
The cost: in this mode the Android keyboard often sends no real key events. Backspace, Enter and keyboard suggestions arrive as DOM edits that CodeMirror reads back (tagged input.type), so keymap bindings may not run. When Android typing misbehaves, look for these first:
- A drawn widget (checkbox, bullet, number, quote bar, image, table) deleted or replaced as one unit by a single Backspace.
- Behaviour that works with real key presses on desktop but not on Android: test the DOM path too (document.execCommand("delete"), CDP Input.insertText), not only key presses.
- Composition glitches: doubled or lost characters, cursor jumps, or a word's suggestion lost when decorations change under an active composition.
- Swipe typing and voice input.
Desktop e2e runs exercise this same input path but with real key events; the Android keyboard's own behaviour can only be confirmed on Scott's device.To prevent the agent from mistaking synthetic test environments for real hardware validation, the context explicitly isolates the domain boundary: desktop E2E tests exercise synthetic inputs; physical touch behavior can only be verified on the actual device.
In the same file, architectural domain gaps are handled through a strict labeling protocol:
markdown
Mark proposals as proposals. Any architectural or structural choice made while implementing - one not a direct restatement of something the user actually decided - gets written into a spec, CLAUDE.md, or other persistent doc as [Proposed - unconfirmed], not plain declarative text carrying the same authority as a real decision. Don't unmark your own proposal; only the user confirming it (or leaving it alone) makes it settled.By tagging agent contributions as [Proposed - unconfirmed], the Navigator ensures that machine speculation never masquerades as validated domain truth.
Context Stewardship
Context Stewardship is the discipline of maintaining repository memory - architecture notes, decision records, and living context configuration files - as first-class engineering infrastructure.
The Failure Mode
LLM-based pairing sessions are stateless by design. Each fresh session starts with zero memory of yesterday's architectural debates, last week's refactor rationale, or the subtle timing bug that was patched two months ago. Without maintained context infrastructure, the codebase falls victim to context collapse: new sessions reintroduce previously resolved bugs, violate established patterns, and require exhaustive re-explanation from the human Navigator.
Conversely, when AI agents are asked to update documentation, they exhibit the opposite failure mode: compulsive historical hoarding. Left to themselves, LLMs resist deleting obsolete text. Instead, they treat specs and CLAUDE.md files as append-only historical records - preserving dead rules, logging what merged in which pull request, and writing notes like "Previously we used X, but now we use Y." Because transformer models weight every token in their context window, leaving deprecated patterns or negative history in active documentation is the equivalent of telling the agent, "try not to think about monkeys." Current AI models are notoriously bad at ignoring tokens sitting in their attention window; mentioning how the system used to work actively primes and biases the agent to resurrect those exact obsolete patterns.
The Mechanism
Stateless collaborators are only as competent as the context they consume at session start. Context Stewardship treats context documentation with the exact same rigor as automated test suites:
- Single Source of Truth: A central configuration artifact (such as a
CLAUDE.md,.cursorrules, or repository prompt) defines runtime commands, code style rules, and forbidden anti-patterns. - Continuous Maintenance: When an architectural rule changes, the context artifact is updated in the same pull request as the code change. Letting context infrastructure decay is functionally equivalent to letting unit tests rot.
Field Case: Context Sterilization for a Zero-Diff-Behavior Rewrite
The "try not to think about monkeys" problem extends beyond markdown files - it lives inside inline comments and method names in the code itself.
During an internal architectural rewrite of an application, the requirement was strict: zero user-facing behavior changes. Because the external behavior had to remain identical, the AI agent needed access to the existing codebase as a behavioral reference. Across multiple failed attempts, the agent kept falling into the exact same architectural ruts and repeating the same design mistakes, no matter how explicitly the prompts instructed it to take a new approach.
The culprit was historical gravity. Every time the agent read the old spec, the dev docs, the inline code comments, and even a handful of method names that reflected the old internal design assumptions, those tokens pulled its attention right back into the legacy architecture. I was spending all my time fighting the agent's assumptions instead of navigating the rewrite.
To break the loop, I had to sterilize the context before the rewrite could even begin:
- Deleted the old spec and all dev docs so no prose remained from the previous architecture.
- Stripped all in-code comments that explained the old "why."
- Renamed a handful of methods whose names leaned into internal intent that was no longer true (enforcing "make the code speak" only its literal mechanics).
- Started a fresh agent session against the stripped, comment-free code to analyze what the software actually did and author a brand-new spec from the ground up.
From that point forward, the rewrite went smoothly. Removing the historical breadcrumbs did what no amount of corrective prompting could: it stopped telling the agent about the monkeys.
Production Artifact: Living Context Stewardship (QuKi Notes and BojuBot)
Context Stewardship requires treating repository context files as active, living operational infrastructure rather than static documentation.
The following excerpt from CLAUDE.md in QuKi Notes defines the operational lifecycle of repository memory:
markdown
## Keeping This File Current
This file is the primary context for any agent working in this repo - keep it accurate as the project evolves. When you learn what the project is, add a brief description at the top. As key files, build commands, and architectural decisions emerge, record them here so future sessions start with full context rather than re-deriving it.
Update this file in the same commit as the work it documents.
What this file claims as current must be true now. Don't record history here (what merged when, which PR fixed what): git and the PRs hold that, and it only clouds the context. When something stops being true, change or delete it.Notice the critical distinction: context files record current truth, not historical narrative. Git and GitHub already preserve the project's full archaeological history. Keeping historical logs or superseded rules inside living context files doesn't just waste token budget - it triggers the "try not to think about monkeys" trap, polluting the model's attention with past state. When something stops being true, overwrite or delete it.
When hard-won architectural discoveries are made under fire, they are committed to the context file as locked decisions to eliminate regression loops across sessions, as shown in this excerpt from BojuBot's CLAUDE.md:
markdown
## Architecture (locked - don't revisit without discussion)
- child_process.spawn -> powershell.exe -NonInteractive -Command "& 'claude.exe' ..." (Windows/Electron stdout fix)
- Prompt is written to proc.stdin, NOT passed as a CLI arg. proc.stdin.write(prompt) then proc.stdin.end(). This avoids all Windows shell-quoting issues (smart quotes, double quotes, special chars).
- proc.stdin.end() closes stdin so claude doesn't hang waiting for more input
- Flags: --output-format stream-json --verbose --print + permission-mode args
- --verbose required with stream-json + --print or claude errors
- Delete CLAUDECODE from spawn env or claude refuses nested launch
- --resume <sessionId> on every turn after first; uses cache_read_input_tokens (~10x cheaper)
- Do NOT prepend history manually - that costs MORE than --resumeBy locking these operational realities into repository memory, fresh AI sessions inherit months of debugging lessons on turn one, preventing the partner from re-introducing shell quoting bugs or manual history prepending.
Coding Standards as Context Infrastructure
Coding Standards in aiXP are not informal team guidelines; they are deterministic prompt constraints and mechanical gating tests systematically fed into every agent session.
The Failure Mode
When multiple AI sessions (or multiple subagents) write code without strict, enforced formatting, typing, and stylistic boundaries, stylistic drift compounds at machine speed. One session introduces arbitrary third-party color names; another bypasses strict TypeScript checking; a third imports utility packages that duplicate existing internal functions. Natural language admonitions ("please write clean code") fail because LLMs drift under pressure. The resulting diffs become mentally exhausting to read, directly accelerating human vigilance fatigue.
The Mechanism
In classic XP, coding standards were shared agreements between human programmers so the team could move seamlessly across files. In aiXP, coding standards operate through a two-layer defense that closes the loop between prompt directives and mechanical enforcement:
- The Context Layer: Standards, autonomy limits, and bug-fix protocols are embedded in repository context files (
CLAUDE.md,.cursorrules), explicitly directing the agent to discover and run local verification commands before proposing diffs. - The Mechanical Layer: Standards are codified as automated test suites and continuous integration pipelines. Changes cannot merge unless static analysis, linters, and architectural tests pass.
Production Artifact: Mechanical Enforcement and Autonomy Constraints (QuKi Notes and BojuBot)
In QuKi Notes, styling and architectural standards are not left to code review vigilance; they are codified directly into the test suite and CI pipeline.
For example, to enforce UI theming consistency across platforms, the repository mandates Obsidian CSS variables and literally fails the test suite if an engineer or AI introduces an unapproved color or font declaration:
markdown
## Theming
- CSS, TypeScript inline styles and project/index.html use Obsidian's CSS variable names for every colour (--background-primary, --text-normal, --interactive-accent, ...) and never an app-invented colour name, so an Obsidian theme can restyle the whole interface. project/src/colourVariables.test.ts fails on an undeclared variable or a return of the old names (--surface, --accent, ...).
- Fonts use Obsidian's mechanism: the defaults layer declares --font-interface-theme, --font-text-theme and --font-monospace-theme ... Body text uses the text font, chrome the interface font, inline code and plain-text mode the monospace font ... colourVariables.test.ts fails on a literal font-family.To make compliance non-negotiable, CLAUDE.md explicitly commands the agent to discover and satisfy the repository's continuous integration gates before committing:
markdown
## Working Conventions
- Unit tests must be written alongside all new code. All bug fixes require red/green tests - a failing test that reproduces the bug, then the fix that makes it pass.
- CI, lint, and formatting must all pass before committing or opening a PR. Discover the project's commands from the CI config, package.json, Makefile, or equivalent - do not assume they match another project's toolchain.The CI pipeline (.github/workflows/ci.yml) enforces these gates automatically across every package, beginning with static workflow linting before any tests run:
yaml
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
actionlint:
name: Lint workflows
uses: ScottKirvan/.github/.github/workflows/reusable-actionlint.yml@main
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
# Core must type-check, test, and build before dependents
- name: Type check (core)
run: npm run typecheck
working-directory: project/core
- name: Test (core)
run: npm test
working-directory: project/core
- name: Build (core)
run: npm run build
working-directory: project/core
- name: Type check + build (app)
run: npm run build
working-directory: project
- name: Test (app)
run: npm test
working-directory: projectFinally, to prevent the agent from arguing, inventing unauthorized workarounds, or expanding scope when a constraint is hit, QuKi Notes establishes explicit operational autonomy limits:
markdown
## Autonomy
Make implementation decisions independently - don't ask permission for technical choices within the stated requirements. Escalate only when something would change scope, defer a requirement, or contradict what the user has described as the goal.
IF YOU CANNOT DO EXACTLY WHAT WAS ASKED - DUE TO A TECHNICAL CONSTRAINT OR ANY OTHER REASON - STATE THE CONSTRAINT AND STOP. Do not silently substitute an alternative and proceed to implement it in the same turn. Naming the blocker is not itself permission to pick a workaround; the user decides which alternative (if any) to pursue. This applies even when the substitute seems obviously reasonable.
Two-strike auto-comply. If corrected twice on the same point, treat the second correction as an automatic stop: comply immediately, with no further justification or re-explanation. Don't make the user repeat themselves a third time or invoke a stop-word to get compliance - repetition itself is the signal.By coupling deterministic CI gates with explicit autonomy bounds, the Navigator eliminates endless review cycles: the machine checks its own compliance against automated standards, and halts immediately when hitting a genuine constraint.