AI‑Friendly Patches 2.0: Plan First, Then Code

Not long ago I shared with you the birth of a new format: .ap (AI‑friendly Patch). My goal was simple—eliminate the pain of manual copy‑paste when working with AI assistants. Instead of generating code blocks that I had to hand‑copy into source files, the AI would produce a semantic patch in a format designed for itself and apply it automatically. The number of people bookmarking the article says the idea resonated.

But theory is one thing; real practice another. While using .ap on live projects—including work on far2l—I uncovered bottlenecks and accumulated ideas to make the format even more reliable, convenient, and, most importantly, “understandable” for neural nets. Today I’m excited to show you the result: a major upgrade, .ap 2.0.

What’s new in 2.0: from instructions to meaningful plans

There are several key changes, each addressing a concrete problem that surfaced during real‑world use.

1. The “Plan‑First” Principle

This is the ideological pivot. In v1.0 the AI had to produce a machine‑readable structure immediately—like asking a junior dev to jump straight into code without explaining why. In 2.0 we enforce that the AI must first describe its intentions in a comment at the top of the .ap file. The comment follows a strict format: a Summary (what and why) and a Plan (step‑by‑step how).

Remember the example from the earlier article? Here’s how it looked in v1.0:


# afix.ap (v1.0)
version: "1.0"
changes:
  - file_path: "greeter.py"
    modifications:
      - action: REPLACE
        target:
          snippet: 'print("Hello, world!")'
        content: 'print("Hello, AI-powered world!")'

And here’s the same patch in v2.0 with the new principle applied:


# afix.ap (v2.0)
# Summary: Update the greeting message in greeter.py.
#
# Plan:
#   1. In `greeter.py`, replace the "Hello, world!" string with
#      "Hello, AI-powered world!".
#
version: "2.0"
changes:
  - file_path: "greeter.py"
    modifications:
      - action: REPLACE
        snippet: |
          print("Hello, world!")
        content: |
          print("Hello, AI-powered world!")

Why this is good:

  • For humans: The patch becomes self‑documenting. You immediately see the AI’s intent, like a well‑written commit message—great for review.
  • For AI: We split two cognitively heavy tasks—planning and coding. First the AI builds a logical chain of actions; then it generates code based on that plan, reducing errors and improving patch quality.

2. Ranges: Say goodbye to gigantic snippets

A major pain point in v1.0 was replacing large, multi‑line blocks of code. The AI had to copy the entire block into snippet, which was fragile—one stray space could break the match.

I recalled how models generate instructions when asked to “explain how a junior would make changes”: they say “replace the block from this point to that point,” specifying unique fragments at the start and end rather than the whole block. We can automate that!

Enter range modifications. For REPLACE and DELETE, instead of a single snippet, you can provide start_snippet and end_snippet. The patcher finds the block that starts with one fragment and ends with another, then applies the action to everything in between (including the fragments).

Suppose we need to replace a complex logic block inside a function:

def complex_calculation(data):
    # ... some preparation ...

    # Stage 2: Core logic (this whole block will be replaced)
    # It has multiple lines and comments.
    intermediate_result = 0
    for val in processed_data:
        intermediate_result += val * 1.1
    result = intermediate_result / len(processed_data)

    # Stage 3: Post-processing
    return f"Final result: {result}"

With a range, the patch becomes concise and robust:


version: "2.0"
changes:
  - file_path: "22_range_replace.py"
    modifications:
      - action: REPLACE
        start_snippet: |
          # Stage 2: Core logic (this whole block will be replaced)
        end_snippet: |
          result = intermediate_result / len(processed_data)
        content: |
          # New, simplified implementation
          result = sum(processed_data)

The AI no longer needs to reproduce dozens of lines accurately. Finding a reliable start and end is far easier and less error‑prone.

3. Simplification and unification of the format

Based on practice, I added several refinements that make the format cleaner and more robust:

  • Flat structure: No redundant nesting via a target object. Fields like snippet, anchor, etc., sit at the same level as action. This simplifies generation, parsing, and manual editing.
  • Mandatory YAML literal blocks (|): To eliminate escaping issues and forgotten quotes, all code fields (snippet, snippet_start, snippet_end, anchor, content) must use the literal block syntax—even for a single line. This guarantees consistency, 100% reliability, and improves readability.
  • Search refinement: The spec now explicitly states that searching for a snippet after an anchor starts on the line following the anchor’s end, preventing accidental matches inside the anchor itself.

A full example in action

Let’s see how these small but powerful changes work together with an updated spec. Suppose we have this file:


# src/calculator.py
import math

def add(a, b):
    # Deprecated: use sum() for lists
    return a + b

def get_pi():
    return 3.14

And here’s the v2.0 patch that adds an import, refactors add, and removes get_pi:


# Summary: Refactor the calculator module to enhance the `add` function
# and remove deprecated code.
#
# Plan:
#   1. Import the `List` type for type hinting.
#   2. Update the `add` function to also handle summing a list of numbers.
#   3. Remove the unused `get_pi` function.
#
version: "2.0"
changes:
  - file_path: "src/calculator.py"
    modifications:
      - action: INSERT_AFTER
        snippet: |
          import math
        content: |
          from typing import List

      - action: REPLACE
        anchor: |
          def add(a, b):
        snippet: |
          return a + b
        content: |
          # New implementation supports summing a list
          if isinstance(a, List):
              return sum(a)
          return a + b

      - action: DELETE
        snippet: |
          def get_pi():
              return 3.14
        include_leading_blank_lines: 1

Clean, readable, and reliable—exactly what an automation tool should be.

The include_leading_blank_lines option is worth noting. By default, .ap ignores indentation and empty lines when matching patterns, making the format resilient to generation errors and minor manual formatting changes before applying a patch. If you need to delete a block along with a specific number of preceding or following blank lines, just set this parameter.

Self‑testing

All changes—specification, patcher, tests—were introduced via .ap files themselves, providing an additional stress test of the format during its development. The range modification emerged as a response to several failures related to replacing long text blocks in the spec. Previously I’d fix such issues by re‑generating; with the new format, that button is pressed far less often.

Conclusion

Moving to 2.0 isn’t just a syntax update—it’s a paradigm shift. We’re shifting focus from “AI as a token generator” to “AI as a development partner” that first formulates a plan and then executes it. This approach proves more effective for both machines and humans.

Every change—from the plan to ranges—is aimed at one goal: increasing reliability and predictability of automatic patch application. Fewer fragile points, fewer errors, more time and sanity saved.

The entire project—including the updated specification, patcher, and full test suite—remains available on GitHub. I’ve updated all examples and documentation to 2.0 and added new tests.

I’d love your feedback, ideas, and of course pull requests. Let’s make working with our silicon helpers even smoother together!

Previous article in the series.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *