← Blog

Graft v4.3: Arithmetic Operators

3 min read
graftcompilerllmadversarial-debateclaude

Graft is a domain-specific language for defining LLM agent pipelines. The compiler takes .gft files and generates Claude Code harness structures. Development uses an adversarial debate harness where multiple AI agents independently analyze, critique, and converge before implementation.

v4.3 completes the arithmetic operator set and adds three new built-in functions. Three rounds, ~6 agent calls, 29 new tests (1,077 total).

What This Version Adds

Multiplication and modulo. The * and % operators complete Graft's arithmetic:

graph Pipeline(input: Spec, output: Report, budget: 20k) {
  Analyzer
  -> let total = Analyzer.count * Analyzer.weight
  -> let bucket = Analyzer.score % 10
  -> Formatter -> done
}

These follow standard math precedence. 2 + 3 * 4 evaluates to 14, not 20. A new parseMultiplicative level in the parser handles *, %, and / at higher precedence than + and -.

Division precedence fix. In v4.0-v4.2, division shared precedence with addition. 2 + 6 / 3 evaluated left-to-right as (2 + 6) / 3 = 2.67. v4.3 moves division to multiplicative precedence: 2 + 6 / 3 = 4. This required unlocking a ratchet decision from v4.0 -- the 6th ratchet unlock in the project's history.

Three new builtins. abs(), round(), and keys() join the existing len(), max(), min(), str():

graph Analysis(input: Data, output: Summary, budget: 15k) {
  Scorer
  -> let diff = abs(Scorer.actual - Scorer.expected)
  -> let rounded = round(Scorer.average)
  -> let fields = keys(Scorer.metadata)
  -> let count = len(fields)
  -> Reporter -> done
}

Functions compose: len(keys(Scorer.metadata)) returns the number of fields in an object. All builtins have LSP hover documentation with type signatures.

str() fix. Previously, str() on an object returned [object Object]. Now it uses JSON.stringify for objects and arrays, String() for primitives.

Implementation Highlights

Precedence chain. The parser now has a clean four-level chain:

parseExpr -> parseAdditive (+/-) -> parseMultiplicative (*/%/) -> parseUnary (-/!) -> parsePrimary

Each level calls the next-higher-precedence function and loops on its own operators.

Ratchet unlock. v4.0-R02 locked "Division at additive precedence." When v4.3 introduced multiplicative precedence, this ratchet had to be unlocked -- division belongs with * and %, not with + and -. The ratchet system worked as designed: the lock prevented accidental changes during v4.1 and v4.2, then was deliberately unlocked when the requirement changed.

Process Notes

Three rounds at three different tiers:

  • R1 (MEDIUM): 2-agent analysis. New tokens, precedence level, builtins, ratchet unlock. 15 tests.
  • R2 (DIRECT): str() fix, division precedence verification. Originally planned to include expr-eval.ts extraction, correctly deferred as YAGNI. 6 tests.
  • R3 (TEST-ONLY): Integration and regression tests. 8 tests.

No forced dissenter -- MEDIUM tier uses 2 agents, not the full 4-agent debate.

Stats

| Metric | v4.2 | v4.3 | Delta | |--------|------|------|-------| | Tests | 1,048 | 1,077 | +29 | | Ratchets | ~292 | ~302 | +10 | | Rounds | 4 | 3 | -1 | | Consecutive PASS | 14 | 17 | +3 |

17 consecutive PASS rounds (v4.0-R1 through v4.3-R3). ~6 agent calls -- the most efficient feature release in v4.x.

Try It

npm install -g @graft-lang/graft
graft compile examples/chatbot.gft --out-dir ./output
graft run examples/hello.gft --input '{"question":"test"}' --dry-run

Or from source: git clone https://github.com/JSLEEKR/graft.git && cd graft && npm install && npm run build