Incremental Refresh
Home GitHub

Incremental Refresh

Part II: How Draft Thinks · Chapter 7

4 min read

Your project has been running Draft for three months. The codebase has gained two new modules, migrated from REST to GraphQL on one service, and swapped Redis for Memcached. The architecture document is now lying to the AI. You could re-run /draft:init from scratch — a full 5-phase analysis that re-reads every source file. Or you could run /draft:init refresh and let Draft figure out what actually changed, re-analyze only those files, and patch the existing documents. That is incremental refresh.

Old State architecture.md freshness.json signals.json Change Detection SHA-256 hash diff git commit delta Signal drift check Delta Analysis Re-analyze changed Map to sections Detect contradictions Updated Docs Patched sections New hash baseline Updated signals
Incremental refresh pipeline: existing state is compared against current code, only changed files are re-analyzed, and documentation is patched rather than regenerated.

The Problem: Context Goes Stale

Every document Draft generates is a snapshot. It reflects the codebase at the moment of generation, down to a specific git commit SHA recorded in the YAML frontmatter. The moment code changes after that commit, the documents start drifting from reality. A new module appears that is not in the component map. An API endpoint changes its request shape but the interface catalog still shows the old one. A dependency is removed but the dependency graph still references it.

Stale context is worse than no context. An AI operating with no context will at least read the actual code. An AI operating with stale context trusts the documentation and makes decisions based on information that is no longer true. Draft's incremental refresh exists to keep the cost of freshness low enough that teams actually do it.

Hash-Based Change Detection

The foundation of incremental refresh is draft/.state/freshness.json — a file containing SHA-256 hashes of every source file analyzed during the last initialization or refresh. When you run /draft:init refresh, Draft recomputes hashes of current source files and diffs against the stored baseline.

{
  "generated_at": "2024-01-15T14:30:00Z",
  "git_commit": "a1b2c3d4e5f6789012345678901234567890abcd",
  "total_files": 142,
  "files": {
    "src/index.ts": "sha256:a1b2c3d4...",
    "src/auth/login.ts": "sha256:e5f6a7b8...",
    "src/auth/middleware.ts": "sha256:f9a0b1c2...",
    "src/billing/charge.ts": "sha256:d3e4f5a6...",
    "package.json": "sha256:c9d0e1f2..."
  }
}

The diff produces four categories:

  • Changed files — hash differs from stored. These need re-analysis.
  • New files — present in current tree but not in stored baseline. New modules or components to document.
  • Deleted files — present in stored baseline but not in current tree. Sections to prune.
  • Unchanged files — hash matches. Skip entirely.

If all hashes match and no files were added or deleted, Draft announces that context is current and stops. No wasted analysis, no unnecessary regeneration.

Two Layers of Change Detection

File hashes provide more granularity than git diffs alone. The synced_to_commit field in each document's YAML frontmatter tracks which git commit the document was last synced to. Draft uses git diff --name-only <synced_sha> HEAD to find changed files since that commit. But git diff only tells you that files changed — not whether the changes are meaningful. A file could be reformatted (content change, no semantic change) or modified in a way that does not affect architecture. File hashes provide the precise delta, while git diff provides the broader scope.

Cross-Session Continuity

The second state file is draft/.state/run-memory.json. It tracks what happened during the last Draft run: which phases completed, which files were analyzed, which questions remained unresolved, and where to resume if the run was interrupted.

{
  "run_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "started_at": "2024-03-15T09:00:00Z",
  "completed_at": "2024-03-15T09:02:34Z",
  "run_type": "refresh",
  "status": "completed",
  "phases_completed": ["phase_1", "phase_2", "phase_3", "phase_4", "phase_5"],
  "files_analyzed": 142,
  "files_generated": ["draft/architecture.md", "draft/.ai-context.md"],
  "unresolved_questions": [
    "Could not determine if src/legacy/ is actively used or deprecated",
    "Multiple auth patterns detected — unclear which is canonical"
  ],
  "active_focus_areas": ["backend_routes", "services", "data_models"],
  "resumable_checkpoint": null
}

If a previous run has status: "in_progress", the next invocation detects the incomplete state and offers to resume from the last checkpoint or start fresh. This prevents partial analysis from being silently treated as complete. Unresolved questions from previous runs are surfaced again — they persist until answered.

How Incremental Refresh Works

When you run /draft:init refresh, Draft executes this sequence:

  1. State-aware pre-check — Load run-memory.json to detect interrupted runs. Load freshness.json to compute file-level deltas. Load signals.json to detect signal drift (new or removed architectural concerns).
  2. Tech stack refresh — Re-scan package.json, go.mod, and other manifest files. Compare with draft/tech-stack.md and propose updates.
  3. Architecture refresh — Read the synced_to_commit SHA from the existing architecture.md frontmatter. Get changed files since that commit. Categorize changes (added, modified, deleted, renamed). Perform targeted analysis on only the changed files.
  4. Section mapping — Map changed files to affected architecture sections. New files affect Component Map, Implementation Catalog, and File Structure. Modified interfaces affect API Definitions and Interface Contracts. Changed dependencies affect the Dependency Graph. New tests affect Testing Infrastructure.
  5. Contradiction detection — If facts.json exists, re-extract facts from changed files and compare against stored facts. Confirmed facts get updated timestamps. Contradicted facts get marked as superseded. New facts are added.
  6. Present diff — Show the developer which files were analyzed, which sections will be updated, and a summary of changes per section. Wait for approval.
  7. Apply updates — On approval, update only the affected sections. Preserve unchanged sections exactly. Regenerate .ai-context.md from the updated architecture. Regenerate .ai-profile.md with current dynamic context.
  8. Persist state — Recompute and write freshness.json (new hash baseline), signals.json (updated classification), facts.json (updated registry), and run-memory.json (completed status).
The 100-File Guardrail

If more than 100 files changed since the last sync, Draft recommends a full 5-phase refresh instead of incremental analysis. Too many changes mean the incremental approach loses its token-efficiency advantage — you are essentially re-analyzing most of the codebase anyway. This threshold also catches scenarios where the synced_to_commit is far behind HEAD, such as after a major merge or long period without refresh.

The Delta-Only Approach

The key design decision in incremental refresh is that Draft patches existing documents rather than regenerating them. Unchanged sections are preserved exactly as written. Only sections affected by changed files are rewritten. This has three benefits:

  • Speed — Re-analyzing 8 changed files is dramatically faster than re-analyzing 142 files.
  • Stability — Sections you have manually refined (added notes, corrected descriptions) are not overwritten by regeneration.
  • Precision — Modules added by /draft:decompose (planned but not yet implemented) are preserved. They would be lost in a full regeneration that only sees what currently exists in code.

Deleted files trigger section pruning — components are removed from the map, endpoints are removed from the API catalog, dependencies are removed from the graph. Renamed files trigger reference updates. The architecture document evolves with the codebase without losing accumulated context.

When to Run Full vs. Incremental

Incremental refresh handles day-to-day evolution: new endpoints, modified services, added tests, updated dependencies. But some changes are too structural for incremental patching:

| Scenario                         | Use                  |
|----------------------------------|----------------------|
| Added a few files, fixed bugs    | /draft:init refresh  |
| New module, updated dependencies | /draft:init refresh  |
| Major framework migration        | Full /draft:init     |
| Monolith → microservice split    | Full /draft:init     |
| Changed database technology      | Full /draft:init     |
| Rewrote authentication system    | Full /draft:init     |

The heuristic is straightforward: if the change affects the fundamental architecture (new paradigm, new database, new auth system), run a full init. If the change extends or modifies existing architecture, refresh is sufficient.

CHANGE TYPE RECOMMENDED ACTION /draft:init refresh Full /draft:init Added files / bug fixes New module / updated dependencies Modified services / new tests Structural boundary Framework migration Database technology change Auth system rewrite Patches existing docs Re-analyzes only changed files ~10 seconds Full 5-phase regeneration Re-reads entire codebase ~2 minutes
Refresh decision matrix: incremental-safe changes (green) extend existing architecture and are handled by refresh. Structural changes (amber) alter fundamental architecture and require full re-initialization.
When to Refresh

"After significant codebase changes" is vague. Here are concrete triggers your team should adopt:

  • After merging large PRs (10+ files changed) — new modules, refactored services, or dependency updates shift the architecture context.
  • At the start of each sprint or iteration — ensures all engineers begin with current context, especially after parallel work merges.
  • After major refactors or architecture changes — renamed modules, extracted services, or restructured directories invalidate existing context.
  • When onboarding new team members — guarantees the context they read is accurate, not a snapshot from three months ago.
  • Before starting a new track — stale context means the spec intake aligns to an outdated architecture. A 10-second refresh prevents a 30-minute rework.

Context that goes stale is worse than no context — it silently misguides every AI-assisted decision. Make refresh a habit, not an afterthought.

Practical Impact

A 100,000-line codebase with 400 source files. Full /draft:init reads all 400 files, runs 5-phase analysis, generates a complete architecture.md and .ai-context.md. Time: roughly 2 minutes.

Two weeks later, 12 files have changed: 3 new API endpoints, 2 modified services, 4 new tests, 2 config updates, 1 deleted utility. /draft:init refresh computes file hashes (instant), identifies the 12-file delta, re-analyzes only those files, maps them to 4 affected sections, and patches the existing documents. Time: roughly 10 seconds.

Same result quality. One-twelfth the cost. That difference is what makes teams actually keep their context current instead of letting it rot.

Signal Drift Detection

Beyond file-level changes, incremental refresh also tracks structural drift through signal re-classification. If your project had zero authentication files last month and now has five, that is not just "new files" — it is a new architectural concern. Draft flags signal drift separately from file changes because it has different implications: new signal categories may require generating entirely new sections of architecture.md, not just updating existing ones.

$ /draft:init refresh

Signal drift detected:
  NEW:     auth_files (0 → 5) — Security Architecture needs generation
  GROWN:   backend_routes (12 → 24) — API Definitions needs expansion
  REMOVED: background_jobs (3 → 0) — Concurrency can be simplified
  STABLE:  services (8 → 9), test_infra (15 → 16)

8 files changed since last sync (2024-01-15).
Sections to update: API Definitions, Security Architecture, Concurrency, Testing.
Approve refresh? [y/n]

The combination of file-level freshness tracking, git commit anchoring, signal drift detection, and fact contradiction analysis gives Draft a multi-layered understanding of what changed, why it matters, and exactly which parts of the documentation need updating. No more stale context. No more full regeneration. Just precise, incremental evolution.