38.1 Phase One: Documentation First, Building the Constraint Layer
Project Background: A secure, cross-platform, offline-capable note-taking application -- "Guardian Notes" (a pedagogically recomposed case: the narrative is assembled from typical fragments of real projects, and the numbers are demonstration magnitudes). It brings together multiple common technical challenges:
The Product Manager's Original Requirement: "We want to build a note-taking app where users can write private diaries and memos. Our biggest selling points are 'security' and 'available anywhere': 1. Absolutely secure -- users' note content must be encrypted on their own devices, so even if the server is compromised, attackers cannot obtain any meaningful content; 2. Cross-platform -- must work in Web browsers and desktop applications (Windows, macOS); 3. Offline-first -- users must be able to view, create, and edit notes without a network, with automatic sync once back online; 4. Basic features -- support Markdown and organizing notes by folder."
The "Devil" in This Case: Cross-platform (Web + Desktop) -- handling the differences between various runtime environments is an excellent training ground for "environment constraints"; Offline-First -- persisting data locally and handling complex data sync and conflict resolution logic; End-to-End Encryption (E2EE) -- an extremely serious security requirement where any error in the encryption logic is catastrophic.
Tech Stack: TypeScript core logic (one codebase, many runtimes) + React for Web + Electron for desktop + IndexedDB (Dexie.js) for local storage + RESTful API for cloud sync (simplified) + libsodium-wrappers for encryption.
Phase One: Documentation First, Building the Constraint Layer.
Goal: Before writing a single line of application code, use documentation to build an unbreakable "guardrail of thought" for both the AI and ourselves. This is the concentrated expression of architecture constraints.
Action: Create root CONTEXT.md and three companion files under .docs/, using the ownership split in Chapters 12 and 13. Keep the case's three-layer design while recording facts and constraints separately.
Create CONTEXT.md first (project blueprint):
# Guardian Notes — CONTEXT
Goal: secure cross-platform offline Markdown notes, folders, and sync after reconnection.
Stack: TypeScript, React, Electron, Dexie.js, RESTful API, libsodium-wrappers.
Rationale: reuse core logic, isolate platform differences through adapters,
and support offline work through local storage.
Three-layer facts:
1. Core Logic: platform-independent TypeScript business and encryption modules.
2. Platform Adapters: connect core logic to Web/Electron runtimes.
3. UI Layer: React components consuming adapters.
Models, API contracts and directories: complete and approve here after research;
do not duplicate them in the constitution.
First milestone: encryption interfaces and tests; then local read/write,
platform integration, and sync.
Implementation constraints: [ARCHITECTURE.md](.docs/ARCHITECTURE.md).
Behavior: [AGENTS.md](.docs/AGENTS.md).
Resolve pending contracts before the corresponding implementation; this excerpt is not permission to skip research.
1. Create .docs/AGENTS.md (AI code of conduct):
# AI Agent Directives: Project "Guardian Notes"
## Persona: Senior Security-Focused Engineer
You are to act as a Senior Software Engineer with a specialization in security.
## Reading and Enforcement:
1. Read [project facts and goals](../CONTEXT.md).
2. Read [implementation constraints](ARCHITECTURE.md) and cite applicable rule IDs.
3. Read [verified progress](CHANGELOG.md) before selecting the next task.
4. Report conflicts or unknown security assumptions before implementation.
5. Do not weaken constraints to obtain a passing result.
2. Create .docs/ARCHITECTURE.md (Architecture constitution + negative space red lines):
# "Guardian Notes" - Architecture Document (v0.1)
## 1. Project Facts
See [CONTEXT.md](../CONTEXT.md) for goals, stack, three layers, models and contracts.
CONTEXT.md links back to .docs/ARCHITECTURE.md. Do not duplicate those facts here.
## 2. Red Lines and Review Triggers
- G1: Treat user-data changes as security-critical; review before implementation.
- G2: Do not break offline behavior or supported-platform compatibility.
- G3: Prefer simple auditable code; review complexity before adding abstractions.
- G4: Never treat the server as a trust anchor.
- G5: Do not disable TypeScript strict mode.
## 3. The "Forbidden Zone" (Negative Constraints): Negative-Space Red Lines
- G6 — No Unencrypted Data on the Wire: Encrypt all data before sending it to the server.
- G7 — No Private Keys on the Server: The user's master decryption key must never reside on the server.
- G8 — Core Logic Cannot Access `window` or `document`: Core logic must be platform-agnostic.
- G9 — UI Components Cannot Perform Direct Data-Access: All data operations must go through Core Logic.
3. Initialize .docs/CHANGELOG.md (ship's log):
# Changelog
## Unreleased
- Decision: Established the initial project structure and core architectural principles.
- Next Step: Begin implementation of the "Core Logic" layer, starting with the encryption module.
Phase Review: We spent about an hour without writing a single line of application code, yet the results were decisive -- the tone was set (via AGENTS.md, letting the AI know this is a serious security-first project), the skeleton was built (via CONTEXT.md, recording the three layers and selection rationale, with ARCHITECTURE.md separately restricting cross-layer access), red lines were drawn (via negative space constraints, closing off in advance the "shortcuts" most likely to sink the project), and the starting point was clarified (via CHANGELOG.md, knowing clearly what to do next).
Core Mindset: "Slow is fast" -- the 1 hour spent on documentation usually saves far more rework later (the "1 hour saves 10" figure is a magnitude metaphor, not a measurement). "Decide first what not to do" -- negative space constraints reveal more architectural wisdom than positively describing features.
38.2 Phase Two: Core Workflow, Enforcing the "Three-Step" Process
Goal: Implement the project's core functionality -- local encryption, storage, and retrieval of notes. Throughout this process, strictly enforce the "Research → Strategize → Implement" process constraint and force "slow thinking".
Scenario: Implement EncryptionService, responsible for generating keys and encrypting and decrypting text.
Step One: Research. Do not let the AI write code directly; first have it act as a "research assistant" (read the blueprint and three companion documents in a fresh session after saving state):
[Read CONTEXT.md, .docs/AGENTS.md, .docs/ARCHITECTURE.md, .docs/CHANGELOG.md] Acknowledge and internalize. Our next step is to implement the encryption module. Your Role: Act as the Senior Security-Focused Engineer defined in the agent directives. Task: We have decided to use libsodium-wrappers. Before we write code, conduct a brief research:
- Key Derivation: What is the recommended function for deriving a strong encryption key from a user's password? What parameters (salt, ops-limit, mem-limit) are involved, and what are sane defaults?
- Encryption/Decryption: What is the specific function for symmetric encryption using XChaCha20-Poly1305-IETF? What are the inputs (key, nonce, plaintext) and outputs? How should the nonce be generated and stored? Constraint: Do not provide a full implementation yet. Focus on background information and function signatures.
The AI returns a detailed technical memo about crypto_pwhash (key derivation) and crypto_aead_xchacha20poly1305_ietf (encryption), complete with security warnings, explaining the importance of the "salt" and the "nonce".
Step Two: Strategizing. Based on the research results, ask the AI to design the "blueprint" for EncryptionService (interface + implementation-strategy comments + task list, no method bodies):
Task: 1. Define the Interface: Propose a TypeScript interface named IEncryptionService that exposes methods for: generating a new master key, deriving a key from a password, encrypting a string, decrypting a ciphertext. 2. Plan the Implementation: For each method, write a short comment describing its implementation strategy based on your research. 3. Task Breakdown: Create a sequential task list. Constraint: Provide only the interface, comments, and task list. No method bodies yet.
This "strategizing" process forces us to think about the module's "public contract" instead of prematurely sinking into implementation details.
Step Three: Implementation. Once all thinking and design are complete, enter the "high-speed coding" phase: work through the task list one by one, and have the AI write unit tests for each method to ensure coverage goals are met (quality constraints begin to intervene).
Phase Review: We resisted the temptation to "let the AI write encryption code directly." Through the "three-step" process, a complex, security-sensitive task was decomposed into controllable, auditable small steps. The final code was not something the AI "came up with on a whim", but a deliberate product of joint research and design between us and the AI -- far more reliable than one-shot generation.
38.3 Phase Three: Environment Debugging, Conquering Platform Differences with Telemetry Logs
Goal: Integrate the core logic into two different platforms -- Web and Electron -- and solve "environment-dependent" problems. This is environment constraints in action.
Scenario: On the Electron desktop app, note saving occasionally becomes extremely slow, even freezing the application; the same operation is very smooth on the Web. The code is the same (Core Logic), yet the behavior differs drastically across environments.
The Wrong Process: Asking the AI "My Electron app is laggy but the Web app is not. Why?" -- this is an unanswerable question, so the AI can only guess.
The Right Process (Telemetry-Driven):
- Plant telemetry probes: Instrument the "save note" flow in Core Logic with detailed structured logs covering key stages: SAVE_NOTE_STARTED → ENCRYPTION_STARTED → ENCRYPTION_FINISHED (record elapsed time) → LOCAL_DB_WRITE_STARTED → LOCAL_DB_WRITE_FINISHED (record elapsed time) → SAVE_NOTE_FINISHED (record total elapsed time). Logs include context such as trace_id, timestamp, and note_id;
- Gather evidence in the actual environment: Open the developer tools in the Electron app, perform several slow "save" operations, and copy the complete structured log stream in JSON format from the console;
- Feed the logs back to the AI: Feed the log stream to the AI (role: a performance engineer proficient in Electron and browser storage) and pose the key question: "The same operation takes only 100ms for DB writes in Chrome -- why is IndexedDB writing over 80 times slower in Electron? (They use the same V8 engine.)";
- The AI's precise diagnosis: The logs clearly show that 99% of the 8.5-second elapsed time is spent on LOCAL_DB_WRITE (encryption is fast). The AI's knowledge base holds a wealth of knowledge about the performance differences between Electron and the Web -- although both Electron's main process and renderer process use V8, their underlying I/O model and disk interaction differ fundamentally from the sandboxed browser environment. It raises a highly insightful hypothesis: "Frequent small-batch writes to IndexedDB (backed by the filesystem) in Electron may be affected by main-process I/O bottlenecks or real-time antivirus scanning. Chrome has deeper optimizations for this. A common solution is to batch multiple small writes into one large write.";
- Fix and verify: Following the AI's suggestion, use Dexie.js's
bulkPut()API to buffer multiple save operations and write them to the database in a single pass. Run it again -- Electron save speed returns to the same millisecond level as the Web app.
Phase Review: Faced with a "black box" environment problem, we did not fall into guesswork. We used "telemetry" to turn the problem into data and make it visible, drew on the AI's vast cross-domain knowledge base to interpret the data precisely, and found the "devil" hidden in the platform differences.
38.4 Phase Four: Refactoring and Optimization, Tests Holding the Line
Goal: After the core functionality is complete, perform a "cleanup" -- remove technical debt and optimize structure while ensuring no existing functionality is broken. This is the practice of quality constraints and regular garbage collection.
Scenario: NoteService.ts has become bloated after multiple iterations, mixing data operations, encryption calls, and preliminary sync logic. We want the AI to refactor it and split it into more cohesive modules.
Actions:
- Establish the "anti-regression" baseline: Functional line of defense -- write unit tests with 100% branch coverage for all public methods of NoteService.ts (run once, all pass, serving as the "functional correctness baseline"); Performance line of defense -- write benchmark tests for the most critical saveNote and loadNotes functions, recording average execution time (serving as the "performance baseline");
- Authorize the AI to "dance in chains" -- issue the anti-regression contract:
Context: We need to refactor our NoteService.ts module. It has grown too large. Role: Senior Software Architect, obsessed with the Single Responsibility Principle (SRP). Task: 1. Propose a Refactoring Plan (split into NoteRepository.ts, SyncService.ts, etc.); 2. Execute after approval. ANTI-REGRESSION CONTRACT (ABSOLUTE & NON-NEGOTIABLE):
- No Functional Regression: must pass all existing unit tests without modifying test files.
- No Performance Regression: key operations must remain within 5% of baselines.
- Clean Up: after refactoring, run ts-prune to remove now-unused helpers/imports.
- AI execution and automated acceptance: The AI splits the 500-line file into 3 new files of around 100 lines each. Acceptance has three gates: gate one is unit tests (if any fail, feed the failure logs back to the AI for self-repair); gate two is performance tests (if there is regression, give feedback to the AI for optimization); gate three is garbage collection (use ts-prune and depcheck to clean up dead code and orphan dependencies).
Phase Review: We successfully completed a complex, risky refactoring, and the whole process was full of confidence -- the confidence did not come from "blind trust" in the AI, but from the automated, impassable "quality grid" we had built. The result is not only cleaner code; through "garbage collection" we also made the codebase smaller and purer than before the refactoring -- achieving a genuine "reverse growth".
Retrospective: The Complete Closed Loop of Effective Constraints
| Phase | Core Task | Key Decision Point / Mindset | Constraint System |
|---|---|---|---|
| One: Documentation First | Establish consensus, define boundaries | "Slow is fast": 1 hour on documentation saves far more rework later (the "10 hours" is a magnitude metaphor); "decide first what not to do": negative space constraints reveal more architectural wisdom than positive descriptions | Architecture constraints |
| Two: Core Workflow | Enforce the "three-step" process | Resist the temptation to "let the AI write encryption code directly"; decompose complex security tasks into controllable, auditable small steps | Process constraints |
| Three: Environment Debugging | Conquer platform differences with telemetry logs | Rely on data, not guesses; use telemetry to turn "black box" problems into data, use the AI's cross-domain knowledge to interpret | Environment constraints |
| Four: Refactoring and Optimization | Subtract while using tests to hold the line | The test grid is the refactoring license; the anti-regression contract makes the AI "dance in chains"; garbage collection achieves "reverse growth" | Quality constraints |
The Guardian Notes case demonstrates the complete closed loop of "effective constraints": architecture constraints set the direction (documentation first), process constraints control the pace (three steps), environment constraints provide visibility (telemetry-driven), and quality constraints hold the line (test grid). The four types of constraints form an all-around "moat", ensuring the AI behemoth always travels within the safe course you have planned.