ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-09-10Beginner

When the iOS 27 Date Landed, the First File I Grepped Was AGENTS.md

iOS 27 and iPadOS 27 ship on September 14. Old version numbers linger in your source, but also in the files only your agent reads. Here is a script that counts both zones, plus the one line keyword search will never find.

antigravity455ios272agents142app-dev54

I sat looking at the date for a while: September 14, for iOS 27 and iPadOS 27, iPhone 11 and later. Counting on my own calendar, that left four days.

As an indie developer shipping apps on my own, how I spend those four days decides how heavy the rest of September feels. I used to open the source first. Count the @available branches, check the deployment target, review the per-device conditionals. That felt like preparation.

It didn't hold up. The week after the release, a new screen my agent wrote came back with the old availability branch in it again. I hadn't forgotten to fix anything. The place I had fixed and the place my agent was reading were two different places.

Version strings live in two different zones

Version numbers settle into a repository in two places, and they behave very differently.

ZoneWho reads itExample filesWhat catches a mistake
AHumans and the compiler.swift / .h / .xcconfig / .plist / build.gradleBuild warnings, code review, CI
BYour agentAGENTS.md / rules.json / agents.json / skills.json / anything under .antigravity/Nothing

The last column is the whole point. A stale version in zone A gets flagged by the compiler. A stale version in zone B gets flagged by no one, because the sentence is grammatical and the JSON is valid. It simply keeps steering what gets generated.

Zone B is also growing. Antigravity 2.11.0 started discovering skills.json, agents.json, and rules.json in project subdirectories, and it let AGENTS.md and custom rule files pull in other files inline with @path/to/file. Then 2.12.2 made Markdown-defined custom agents inherit the surrounding skills, rules, and subagents by default.

So opening the AGENTS.md at the root and calling it checked doesn't really mean anything anymore.

Count the two zones separately

Here is the inventory script I run on the day a ship date is announced. Its only job is to keep the two zones apart.

#!/usr/bin/env bash
# os-literals.sh — count where an old OS version still lives in a repository.
# Usage: bash os-literals.sh <repo path> <old version> [new version]
#   e.g. bash os-literals.sh . 26 27
set -uo pipefail
 
ROOT="${1:-.}"; OLD="${2:?pass the old version, e.g. 26}"; NEW="${3:-}"
 
# Only match numbers that follow a meaningful word. A bare "26" also hits years and coordinates.
PAT="(iOS|iPadOS|macOS|watchOS|tvOS|visionOS|Xcode|SDK|DEPLOYMENT_TARGET|available)[^0-9A-Za-z]{0,12}${OLD}([.][0-9]+)?"
 
list_a() {  # what humans and the compiler read
  find "$ROOT" -type f \( -name '*.swift' -o -name '*.h' -o -name '*.m' -o -name '*.kt' \
    -o -name '*.xcconfig' -o -name '*.plist' -o -name '*.gradle' -o -name '*.pbxproj' \) \
    -not -path '*/.git/*' -not -path '*/node_modules/*' -print0 2>/dev/null
}
 
list_b() {  # what only the agent reads
  find "$ROOT" -type f \( -name 'AGENTS.md' -o -name '*.agent.md' -o -name 'skills.json' \
    -o -name 'agents.json' -o -name 'rules.json' -o -name '.antigravityrules' \
    -o -path '*/.antigravity/*' \) \
    -not -path '*/.git/*' -not -path '*/node_modules/*' -print0 2>/dev/null
}
 
report() {  # report <label> <file-listing function>
  local label="$1" hits n
  hits=$("$2" | xargs -0 -r grep -InE "$PAT" 2>/dev/null)
  n=$(printf '%s' "$hits" | grep -c . )
  printf '%s: %d hits\n' "$label" "$n"
  [ "$n" -gt 0 ] && printf '%s\n' "$hits" | sed "s|^${ROOT}/||; s/^/    /"
  return 0
}
 
echo "== Zone A: humans and the compiler =="
report "source and build config" list_a
echo
echo "== Zone B: agent-only config =="
report "agent config" list_b
 
echo
echo "== Files pulled in with @ =="
list_b | xargs -0 -r grep -hoE '@[A-Za-z0-9_./-]+\.(md|json|ya?ml)' 2>/dev/null \
  | sed 's/^@//' | sort -u | while read -r ref; do
      if [ -f "$ROOT/$ref" ]; then
        c=$(grep -cE "$PAT" "$ROOT/$ref" 2>/dev/null || true)
        printf '    %-40s %s hits\n' "$ref" "${c:-0}"
      else
        printf '    %-40s referenced file not found\n' "$ref"
      fi
    done

Run against a small fixture repository, the output looks like this.

$ bash os-literals.sh ./fixture 26 27
== Zone A: humans and the compiler ==
source and build config: 2 hits
    ios/Config/Base.xcconfig:1:IPHONEOS_DEPLOYMENT_TARGET = 26.0
    src/Views/HomeView.swift:4:    if #available(iOS 26.0, *) { Text("new") } else { Text("old") }
 
== Zone B: agent-only config ==
agent config: 3 hits
    packages/mobile/rules.json:1:{ "rules": ["Assume Xcode 26 toolchain", "minimum iOS 26.0"] }
    .antigravity/rules/swiftui.md:1:Prefer the iOS 26 glass material APIs.
    AGENTS.md:3:Target platform: iOS 26 and later. Use `@available(iOS 26, *)` when adding new APIs.
 
== Files pulled in with @ ==
    .antigravity/rules/swiftui.md            1 hits

Zone B carries more hits than zone A. The first time I counted it this way I felt oddly relieved. There was more to fix, but I finally knew where to look.

Don't skip the last section either. That .antigravity/rules/swiftui.md is pulled into AGENTS.md with @. If you've split your rules into tidy files, the root file is exactly the one that will look clean while the real text sits somewhere else.

The one line keyword search will never find

There is a line this inventory deliberately refuses to match: a bare version number with no iOS or Xcode next to it.

// ios/Config/DefineManager.h
#define kMinOS 26.0

Searching for 26.0 on its own drags in coordinates, years, and unrelated constants. So I leave it out of the first pass and run it separately, expecting to read every line by eye.

# Second pass. Few results, and I look at each one.
grep -rnE '(^|[^0-9A-Za-z.])26\.0([^0-9]|$)' . \
  --include='*.h' --include='*.xcconfig' --include='*.json'

I split it because trying to catch everything at once failed. Early on I cast the net too wide, got a few hundred lines back, and my eyes slid right off. I fixed none of them before the release date. What works for me now is a plain division of labour: the machine takes the labelled numbers, I take the naked ones.

That led to a rule I've kept since. Write a version number next to a word that says which version it is. #define kMinOS_iOS 26.0 instead of #define kMinOS 26.0, and next year's me — and the agent — can find it in a single search. Writing things down in a searchable shape is maintenance in itself.

Fix in order of reach

Once you have counts, the order matters.

  1. Zone B files pulled in with @ — they expand into several agents at once, so one edit travels the furthest
  2. The rest of zone B (AGENTS.md, each rules.json) — with inheritance on by default, a rule you wrote for a narrow context now reaches its neighbours
  3. Zone A build settings.xcconfig and .plist. The build will tell you, so there's no rush here
  4. Zone A availability branches — the largest count, and the slowest. Work through them once the new version is actually on your device

Zone B goes first because leaving it alone quietly undoes zone A. An agent reading a rule file with the old premise will put your carefully fixed branch back the way it was on its next pass.

Review-side prep is worth keeping on a separate track. Since September 2026, new submissions, updates, and notarization requests for alternative distribution all require answers to the new age-rating questionnaire. I wrote about assembling those answers from a declaration in the repository in Answering the age-rating questionnaire from a file in the repo, not from memory. Submissions get crowded right after a release, so getting it done inside these four days buys you room. The app-side work — init order and entitlement restore — is in The Five Days Between the iOS 27 RC and Release.

Run it once in the next four days

One thing is enough for today. Run os-literals.sh against your own repository and look at the zone B count. Zero, and you can walk into release day as you are. One or more, and you've just found the premise your agent has been working from.

The knot in my stomach during release week has loosened since I started doing this. Not because I'm better prepared — I just know which parts I haven't looked at. That turns out to be most of the difference. Thank you for reading.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

App Dev2026-09-04
The Five Days Between the iOS 27 RC and Release — I Check Init Order and Entitlement Restore Before Layout
A record of how I narrow the agent's search scope during the short window before an OS release — by launch path rather than by screen. Includes the audit script I run to catch consent and ads init ordering, entitlement restore gaps, and hardcoded size branches.
App Dev2026-08-15
How Far to Narrow an Agent's Choices in a 30-Category Wallpaper Classification Pipeline
Asking an agent to pick one of 30 categories per image means re-running every image the moment a definition changes. Here is the reasoning and the implementation behind switching to closed-vocabulary tags plus a deterministic rule mapping.
App Dev2026-08-24
The Cleanup Step Removed the Working Directory, Not Its Contents — Making Unattended Destruction Fail Closed
An unattended cleanup step deleted the working directory itself instead of what was inside it. Here is why the mkdir -p that followed was not a safety net, and how a defensive-looking default value ended up selecting the destructive branch, with the actual verification output.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links