← Blog

Graft v3.5: Rename Hardening and the A3 Backlog

4 min read
graftcompilerlspadversarial-debateai-agentstypescript

Graft is a graph-native language for AI agent harness engineering. It compiles .gft source files into execution harnesses — declaring how agents communicate, what context they share, and how token budgets flow through a pipeline. The compiler is built with a multi-agent adversarial debate process where 2-4 AI agents independently analyze and converge on implementations.

v3.5 is a hardening release — it resolves every correctness issue that A3-Skeptic identified in v3.4's rename feature but couldn't incorporate because the analysis completed after implementation.

The A3 Backlog Problem

In v3.4-R1, the A3-Skeptic agent identified four correctness issues with collectRenameLocations:

  1. Comment matching: \bAnalyzer\b matches Analyzer inside // rename Analyzer here
  2. String matching: the regex matches names inside string literals
  3. Import path matching: \bFoo\b matches Foo in from "./Foo.gft" because . is a word boundary
  4. CRLF handling: \r\n line endings shift character offsets on Windows

But A3's analysis completed after implementation had already proceeded. The MEDIUM tier runs analysis and implementation in parallel — if analysis takes longer, its findings arrive too late.

This led to two process improvements:

  • R-PROC-14: Enforce analysis completion before convergence in MEDIUM rounds
  • R-PROC-15: Create a tracked backlog for late-arriving A3 findings

v3.5 addresses all four items from that backlog.

What's New

Rename Correctness Fixes

Every match from collectRenameLocations is now filtered through three checks:

// Skip matches inside comments, strings, or import paths
if (isInComment(lines, range.start.line, range.start.character)) continue;
if (isInString(lines[range.start.line], range.start.character)) continue;
if (isInImportPath(lines, range.start.line)) continue;

isInComment() and isInString() were extracted from completions.ts (where they suppressed autocomplete inside comments/strings) to shared utils.ts. The same logic now protects rename.

CRLF normalization strips \r at the top of collectRenameLocations, before any line splitting or offset calculation.

newName Validation

Before applying a rename, the new name is validated:

  • Must match /^[A-Za-z_][A-Za-z0-9_]*$/ (legal Graft identifier)
  • Must not be a Graft keyword (context, node, memory, graph, edge, parallel, foreach, etc.)

The GRAFT_KEYWORDS set contains all 26 reserved words.

Cross-file Conflict Detection

buildRenameEdits — a new pure function extracted from server.ts — checks for name collisions before applying:

// If renaming "Analyzer" to "Reviewer", check that "Reviewer"
// doesn't already exist as a declaration in any importing file

If a conflict is found, it returns { error: "..." } instead of edits.

FlowNode Source Locations

The parser now captures SourceLocation on all three FlowNode kinds:

export type FlowNode =
  | { kind: 'node'; name: string; location?: SourceLocation }
  | { kind: 'parallel'; branches: string[]; location?: SourceLocation }
  | { kind: 'foreach'; ...; location?: SourceLocation };

This enables accurate positioning in the outline view. Previously, flow node children used the parent graph's location as a fallback.

Enhanced Document Symbols

Parallel and foreach blocks now appear as children of graph symbols:

▾ Pipeline (Graph)
    Analyzer (Function)
    parallel(SecurityReviewer, PerformanceReviewer) (Function)
    foreach(Splitter.tasks) (Function)
        Processor (Function)
        Validator (Function)

Foreach children show their body nodes as nested children — recursive symbol hierarchy.

Process Notes

All four rounds were DIRECT or TEST-ONLY — no debate needed. Every fix was spec-constrained with a single viable implementation approach. This validates R-PROC-13: design ambiguity, not feature complexity, determines whether debate adds value.

The entire version used ~8 agent calls (the theoretical minimum for 4 rounds: 2 calls each). This is the leanest version since v3.2.

Try It

npm install -g @graft-lang/graft@3.5.0

graft compile pipeline.gft
graft check pipeline.gft
graft run pipeline.gft --input '{"query": "test"}' --dry-run

Stats

| Metric | v3.4 | v3.5 | Delta | |--------|------|------|-------| | Tests | 690 | 739 | +49 | | Ratchet decisions | 200 | ~210 | +10 | | Agent calls | ~10 | ~8 | -2 | | Rounds | 4 | 4 | 0 | | NEEDS_CHANGES | 0 | 0 | 0 | | Debated rounds | 1 | 0 | -1 | | Direct rounds | 2 | 3 | +1 | | A3 backlog items resolved | — | 4/4 | — |


Built with Claude Opus 4.6 via Claude Code. April 2026.