Commit Graph

1553 Commits

Author SHA1 Message Date
Charles Wiltgen 4fec142381 fix(gate): make pre-deploy compare manifest descriptions, not just presence
`claude-code.json`'s skills array is generated from each SKILL.md's frontmatter,
but check 4 only verified that every manifest skill had a SKILL.md on disk — never
that the description matched. The one assertion comparing the two texts lived in
scripts/skill-listing.test.ts, which runs under `npm run test:unit` (what CI runs)
but not under `npm test` (what a maintainer runs), and `node scripts/skill-listing.ts`
reports character budgets only and exits 0 on drift.

So editing a router's description — the most ordinary kind of change in this repo —
left the manifest stale, and the `/axiom:ask` generated from it, through a green
local gate and four consecutive red CI runs.

Check 4 now compares the text and names the first drifted skill. Verified in both
directions: it reports 26 manifest descriptions matching, fails with
`[manifest-drift]` naming axiom-media when a description is injected, and passes
again once that is reverted.

Verified: npm test (static validation clean) and npm run test:unit (519/519).
2026-09-17 10:32:22 -07:00
Charles Wiltgen e469fa76c6 fix(manifest): regenerate the axiom-media listing after its description changed
`claude-code.json`'s skills array is generated from SKILL.md frontmatter, so the
router description corrected in 19208d3a left the committed manifest carrying the
old trigger text — and `/axiom:ask`, which is generated from that array, with it.
The unit suite's drift guard caught it; four consecutive Test Suite runs went red
on nothing else.

Regenerated with `scripts/set-version.js` at the current version, so no version
change is involved: the diff is the description, the generated `ask.md`, and the
Cursor mirror.

Verified: npm run test:unit (519/519) and node scripts/leak-scan.ts (0 errors).
2026-09-17 10:25:15 -07:00
Charles Wiltgen 2ec48f7c72 fix(axiom-media): unblock a quoted Objective-C constant, and a search that could not match
Two defects found by reviewing the previous round's own output.

`carplay-navigation-ref.md` quotes the guide's lane-guidance instructions, which
are written against the Objective-C API: "return a symbol style of
CPManeuverDisplayStyleSymbolOnly for the maneuver." The quote is faithful, but
that constant has no Swift spelling — the Swift form is `.symbolOnly` — so a
reader copying it out of a verbatim quote gets a compile error. The quote stays as
the guide wrote it; the Swift spelling is now noted beside it.

`camera-auditor`'s Pattern 2 search list included
`UIDeviceOrientationDidChangeNotification`, which does not exist in Swift at all.
As a text search it matched only legacy Objective-C-style code, so it could never
fire on a modern app — the same dead-detection-pattern class as the interruption
names corrected earlier, and missed by every previous round. The replacement is
checked against a three-file fixture: the old pattern matched only the legacy
file, the new one matches both spellings, and neither matches the control.

Cursor, Codex, inlined-auditor and MCP distributions regenerated.

Verified: npm test (static validation clean).
2026-09-17 10:18:39 -07:00
Charles Wiltgen 574048c017 fix(axiom-media): requote the CarPlay passages taken from the superseded guide
Nine passages in the CarPlay files were quoted as verbatim from the CarPlay
Developer Guide while carrying June 2026 page citations, but the wording was the
February 2026 revision's. An earlier pass relabelled the citations and re-derived
the page numbers without requoting the text, so anyone checking a quotation
against the cited page would find different words there.

All are requoted against the current 2026-06-08 revision. Two are whole-sentence
replacements rather than word swaps: the June guide's section is headed "Touch
gestures" and never uses the word "multitouch" anywhere, and the maneuver-metadata
guidance is reworded there.

A sweep of the neighbouring lines found eight further instances of the same two
classes — quotations that had elided words, or closed with a period a sentence the
guide continues — in the same three files.

Separately, the note in `camera-capture.md` about raising the quality ceiling said
it turns on optical image stabilization unconditionally; the header makes that
conditional on the active format's `isHighPhotoQualitySupported`.

Cursor, Codex, inlined-auditor and MCP distributions regenerated.

Verified: npm test (static validation clean).
2026-09-17 10:10:14 -07:00
Charles Wiltgen 3105b1118b fix(axiom-media): correct what reviewing the fixes found, and sweep the docs pages
An independent review of the audit's own fixes found defects the fixes had
introduced or left behind, and the hand-written docs pages — which restate many
of the same claims and are not generated from the skills — had not followed the
corrections.

Fixes that were wrong or incomplete:

- The paused-player elapsed fix was correct only where it was measured.
  `playerTime(forNodeTime:)` returns nil whenever the player is not playing —
  paused, stopped, or never started — and falling back to the lock screen's
  dictionary republished the *previous item's* elapsed on a track change. It now
  keeps an app-owned value, reset when a new item loads.
- "Other request types, hand-pose among them, still succeed" is false: measured
  across eleven Vision requests, only hand-pose and text recognition run.
  Barcodes, animal, body pose, human rectangles, saliency and feature print all
  fail to build an inference context, and an order-reversed control on a second
  device showed the failure is request-specific, not context exhaustion.
- `teardownCommands()` was left defined and called from nowhere. Two
  `isEnabled = true` writes contradicted the file's own rule that registering a
  target enables a command by default. The Bluetooth option legend that produced
  a recording-recipe straddle still omitted that A2DP is output-only routing.
- Two citations pointed at the wrong guide page, one range omitted the page
  holding its section's last step, and one quoted a sentence the guide does not
  contain.

The docs sweep found fifteen stale claims across the pages mirroring this suite,
including the automatic-passthrough mechanism the audit disproved, a fabricated
`prepare()` timing figure, and a fabricated set of diagnostic percentages.

`photo-library`'s PNG/JPEG/HEIC split is now measured rather than asserted:
`Image.importedContentTypes()` and `exportedContentTypes()` are both
`["public.jpeg", "public.png"]`, with HEIC and HEIF absent.

Cursor, Codex, inlined-auditor and MCP distributions regenerated.

Verified: npm test (static validation clean).
2026-09-17 09:45:56 -07:00
Charles Wiltgen 19208d3a83 fix(axiom-media): verify the suite against the 27.2 SDK and fix what it disproves
Every claim checked against the iOS 27.2 SDK and Swift 6.4 — camera, photos,
audio, haptics, ShazamKit, MusicKit, Now Playing, CarPlay, DockKit, screen
capture — with blocks extracted verbatim from the files, compiled individually,
and measured where the claim is behavioural. 25 files, ~13.3k lines, ~408 blocks.

Several published examples cannot compile as shown. `SHSignatureGenerator.signature(from:)`
is static-only, and four call sites invoked it on an instance — including the
file's own "RIGHT" example. `AVProVideoStorage.isBusy` does not exist; the busy
surface is `busyReasons`. `AVCapturePhotoOutput.AppleProRAWQuery` appears nowhere
in the SDK, and the exposure sentinels are `AVCaptureDevice` members, so the
leading-dot spelling in argument position never resolved. The deferred-photo-proxy
delegate parameter is optional, or it fails the protocol's requirement. The
`shazam custom-catalog` invocations used flags the shipping binary rejects.

Two measured claims were the opposite of what the suite taught. Mic input is not
"always 44.1 kHz" — it is the hardware format, 48 kHz on the measured device — and
iOS does not deliver bit-perfect USB DAC output by default: a 96 kHz source renders
at 48 kHz unless the session's preferred sample rate is set. Both files contradicted
themselves 100 lines further down, where the correct advice already sat.

Other corrections: AHAP has no `Metadata` key and the framework drops it silently;
`automaticallyPublishesNowPlayingInfo` was misspelled; `SHLibrary.default.items` is
main-actor isolated; a published `deinit` calling a main-actor method could not
compile, and its replacement left the teardown unreachable while the checklist still
demanded it; CarPlay has 11 app categories, not 10 (video apps arrived at iOS 27),
and 7 universal guidelines, not 8 — with 76 Developer Guide citations re-derived
against the June 2026 edition, whose page numbers differ from the February one the
files cited.

The camera auditor's interruption detection matched three notification names that
exist in no AVFoundation spelling, so it reported every file containing
`AVCaptureSession` as missing observers. The auditor is generated, so the fix landed
in `agents/camera-auditor.md`.

Cursor, Codex, inlined-auditor and MCP distributions regenerated.

Verified: npm test (static validation clean).
2026-09-17 09:18:41 -07:00
Charles Wiltgen a0a6c2e94f fix(axiom-swiftui): verify the suite against the 27.2 SDK and fix what it disproves
Every claim checked against the iOS 27.2 SDK and Swift 6.4, with blocks
extracted verbatim from the files, compiled individually, and — where the claim
is behavioural — rendered or run. 543 blocks scanned across 26 files.

The suite's debugging guidance quoted output that cannot appear. `Self._printChanges()`
prints underscore-prefixed physical names whose depth depends on access level
(`__count` for a private `@State`, `_a` for an internal one), ends every line with
a period, and comma-joins causes onto one line. The advertised `MyView: count
changed` never appears, so the line cannot be grepped out of a console — and the
file omitted `@identity changed`, the marker Apple documents for the state-reset
symptom the file exists to diagnose.

Elsewhere the published fix did not fix the problem:

- The animation guidance for displaying a counting integer does not animate.
  Measured with both arms in one run: the plain `Text` body evaluated once, at the
  final value, while the file's own `Animatable` view evaluated 76 times.
- ScrollView's `.padding()` vs `.safeAreaPadding()` pair was wrong on all three
  counts — both produce identical frames on a safe-area device, and only
  `.ignoresSafeArea()` reaches the edge.
- A "conditional ChartContent crashes below 27.0" gotcha names a warning that is
  emitted at no deployment target and no language mode; its workaround compiles
  as a no-op.
- A `@State` deferral claim holds at the audit floor and is false where the file
  says it applies: below iOS 17 the private case silently reverts to eager.

APIs that do not exist in any version were removed or corrected: `PreviewTrait.fixed`,
`@TimelineEntryBuilder`, `@ContentStateBuilder`, `toolbarCustomizationBehavior`,
`usesTextKit2`, `writingToolsResultOptions`, `TableAlias` as a protocol. Search is
bottom-aligned by default from iOS 26, not hidden on scroll. `@MainActor` on a
Codable `@Observable` model is the cause of its conformance-isolation failure, not
the concurrency safety the file advertised. And `simctl … booted` silently targets
whichever device is up — on a host with a stray booted simulator it verifies that
device's screen and exits 0.

Auditor procedures were corrected at their sources in `agents/`: thirteen grep
patterns could never match their target, including `.navigationTitle()` in a
UX-flow auditor, which reports clean rather than broken.

Verified: pre-deploy --static clean; inlined auditors, Cursor render, and audit
areas all current; 543 blocks swept before and after with no compile regressions.
2026-09-16 19:27:29 -07:00
Charles Wiltgen cf1ebc772c fix(axiom-data): verify the suite against the 27.2 SDK and fix what the toolchain disproves
Every claim in axiom-data checked against the iOS 27.2 SDK, Swift 6.4, and the
pinned third-party versions (sqlite-data 1.12.0, StructuredQueries 0.39.2,
swift-sharing 2.10.1, GRDB 7.11.1, realm-swift 20.0.5). Every code block was
compiled individually and the behavioural ones were run — on simulators, against
real stores, and through the system sqlite3 whose version and compile options
match the platform's.

The suite's guidance was wrong in ways that cost data rather than time:

- A chunked importer ended with an unscoped `delete(model:where:)` over an
  always-true predicate. It did not compile, and adding the missing `try` would
  have made it delete the entire store — 2,000 imported rows plus a pre-existing
  unrelated row, measured. The compile error was the only protection.
- The migration file denied that SQLite can add a foreign key to an existing
  table, forbade the table rebuild that is the documented route, and recommended
  a PRAGMA that is inert inside a transaction — which the same file mandated.
  Following it left orphan rows that `PRAGMA foreign_key_check` cannot detect,
  the state the suite's own auditor rates CRITICAL.
- The storage reference gated must-save data on
  `volumeAvailableCapacityForImportantUsage` and refused to write; Apple's own
  documentation for that key says to attempt the write regardless.
- The tvOS storage claim denied that the directories exist and that local data
  survives. A simulator probe showed a local-only store keeping every row across
  relaunch and reboot; nine files carried the absolute and five cited the
  corrected file as their authority while contradicting it.

Also corrected: APIs that exist in no version of their framework
(`@Attribute(.indexed)`, `addColumn(ifNotExists:)`, `migrator.hasBeenMigrated`,
statement `.fetch(_:)`, `.filter`, `TableAlias` as a protocol,
`ResultsSectionCollection`); quoted diagnostics the compiler never emits; an
`@Model` initializer rule the suite documented in one file and violated in
eleven; a `perform` removal that introduced a data race; and the expired
Realm Device Sync migration premise, written in the future tense after its
2025-09-30 shutdown.

Auditor procedures were corrected at their sources in `agents/`, since a dead
detection pattern reports clean — `@Model struct` cannot compile, so the pattern
that advertised it could never fire.

Verified: pre-deploy --static clean; 1,004 sub-skill pointers resolve across 344
emitted Codex files; Cursor output matches a deterministic full render.
2026-09-16 16:44:17 -07:00
Charles Wiltgen a480497bf2 fix(axiom-swift): correct the tvOS storage phrasing in the router
The Quick Reference row read "nothing local is persistent" — an absolute that a
tvOS 27.2 simulator probe falsifies. A local-only store kept every row across
terminate, relaunch, and a full device reboot, so local data does persist; it is
simply not guaranteed.

Line 136 of the same file already carried the accurate form, leaving the router
contradicting itself. Both now say "no guaranteed-persistent local storage",
which is the phrasing tvos.md uses.
2026-09-16 16:41:02 -07:00
Charles Wiltgen 92b05bfe5c fix(axiom-swift): review the fix pass — propagate the tvOS storage correction to the router
The fix pass corrected tvos.md in eight places — the core principle, the platform matrix,
the anti-rationalization bullets and the storage section all now say that tvOS has no
guaranteed-persistent local storage, and that the directories exist but none can be relied
on once the app is not running. The router was not updated, so axiom-swift contradicted
itself: tvos.md said the Documents directory exists, and SKILL.md said "no Documents
directory" in three places, including the anti-rationalization row a reader is most likely
to act on.

That is the same class the last review pass caught — a claim corrected in one file and
left standing in another — so it is worth noting that the fix pass cannot see it by
construction: each agent is scoped to one file, and the file that disagrees is never in
scope.

Also verified in this pass, against their sources rather than on report: no "no Documents"
claim remains anywhere in the suite; tvos.md's "§3" pointer resolves to its Storage
Constraints section; and the four mixed-era WWDC numbers the suite cites all return 200.

Verified: 519/519 unit tests, pre-deploy --static clean, distributions regenerated.
2026-09-16 15:28:45 -07:00
Charles Wiltgen b56558e6ca fix(axiom-swift): verify the suite against the toolchain and fix 47 findings
Third suite of the corpus audit. 7 files, 3,181 lines, 99 code blocks, audited by four
agents plus the three small files by hand. Compiles at the iOS 18 floor with -emit-sil
(never -typecheck alone, which cannot see the ownership checker) and runs on iOS and tvOS
27.2 simulators where the claim was behavioural.

Two findings were invisible to any compiler and both sat on the suite's core promises.

tvos.md taught that tvOS has no Documents directory and that all local storage is Cache.
Verified on a tvOS 27.2 simulator: the directory is returned, it exists, writes succeed,
and the file survives terminate + relaunch. Library/Application Support is returned as a
URL but does not exist until created, so the table's "Exists?" column was inverted for
both rows. The section now teaches the real constraint — no guaranteed-persistent local
storage — and the prose and table agree.

deep-link-debugging.md presents `simctl openurl` as the unattended primitive behind
/axiom:screenshot and the simulator-tester agent. It is not: on a simulator that has not
approved the scheme once, the first call raises a modal confirmation, exits 0, and
delivers nothing, so the follow-up screenshot captures the alert. Reproduced on a virgin
device and on an already-booted one with the app reinstalled; fixed with a verified
first-run pre-approval step.

Also: transferable-ref.md claimed the importing closure cannot await — it is `async
throws` and the block compiles clean, so the whole workaround was unnecessary — and its
"RIGHT" pattern did not compile and, repaired naively, silently dropped the value it
fetched. The queryItems helper offered as the safe alternative to force-unwrapping trapped
on a repeated key. ownership-conventions.md taught that `borrowing` avoids a copy (same
SIL; the default already borrows) and that `consuming` invalidates a copyable value (only
`~Copyable` does), and quoted three diagnostics the 6.4 compiler never emits. tvos.md
taught subclassing AVPlayerViewController and the player gesture recognizer that the WWDC
session it cites says to avoid. swift-modern.md claimed SwiftUI does not re-export UIKit;
its umbrella header does, on both iOS and macOS.

The fix pass was told to compile and run blocks extracted verbatim from the edited file
rather than probes written alongside — and for the first time in this audit the harness
caught its own work: three bugs in the ownership fix pass, a `~Copyable` global consume
twice and a diagnostic that would not have fired.

Verified: 519/519 unit tests, pre-deploy --static clean, distributions regenerated.
2026-09-16 15:26:20 -07:00
Charles Wiltgen 03a84d3de8 fix(gate): scan the repo configuration too, and assert nothing tracked goes unread
The leak scan reads an enumeration — SURFACES plus ROOT_FILES — so any tracked file
outside it is invisible, and an unread file reads as a clean one. Nine were: .gitignore,
.gitattributes, .mise.toml, and .github/** (dependabot plus the three workflows). All
clean today, which is the point: nothing would have caught it if they were not. This is
the second time the enumeration has drifted — Axiom-77q9 fixed the first, 38 files under
axiom-mcp — and both were found by hand rather than by the gate.

.github is now a surface and the three dotfiles are root files, so the scan reads 1985
files instead of 1978. The workflow's paths filter gained the same four entries, since a
change to a file the scan reads must still start the job — the CI-paths test enforces
that pairing and failed until it did.

The durable fix is the assertion rather than the four additions: a new test derives the
tracked file list from git and fails if any file is in neither the scanned set nor
SELF_EXEMPT, naming the file and where to declare it. Verified by deleting the .github
surface and watching it fail with all four paths listed, then restoring. The third
omission now fails in CI instead of waiting to be noticed.

Verified: 518/518 unit tests, 15/15 leak-scan tests, pre-deploy --static clean.
2026-09-16 15:00:04 -07:00
Charles Wiltgen 4257671cee fix(ci): skip the dev-state guard tests in a checkout that has no dev state
The Test Suite workflow has failed on every push since it was added. Three tests in
scripts/methodology-leak.test.ts read `.claude/rules/skill-development.md` and
`.claude/skills/preflight/skills/behavioral-testing.md` and die with ENOENT in CI,
because `.claude/` is gitignored: those files exist on a maintainer's machine and
nowhere else. Locally the suite is 518/518; in a fresh checkout it was 515/518.

The tests themselves are sound — they guard a real invariant, that behavioral-test
methodology stays out of the file the harness appends to every skill-file Read, so
it cannot reach a GREEN arm and confound a behavioral test. The subjects are just
local dev state, so the precondition is "this checkout has dev state" and the tests
skip when it does not. They still run wherever the state exists, which is the only
place the leak they guard against could actually occur.

The second test deliberately gates on the presence of the RULES file rather than its
own subject: deleting the canonical file while the rules file remains is the
"fixed it by deleting the content" regression it exists to catch, and it must fail
then, not skip. Verified in three checkouts: dev state present 3 pass / 0 skip;
no dev state 3 skip / 0 fail; rules file present with the canonical file deleted
1 fail. Before the change, the first two were 3 fail and the third was 3 fail.

The workflow did its job here — it is the first thing to run this suite outside a
maintainer's machine, and it found a suite that could not pass outside one.
2026-09-16 14:54:16 -07:00
Charles Wiltgen 89e05c116b fix(axiom-testing): review the fix pass — an await that does not await, and three unverified claims
Reviewing the fix pass found one defect it had reintroduced and several claims it
could not have checked.

The confirmation fixes in testing-async.md replaced a non-waiting pattern with
`await api.fetch { ... }`. That compiles against a callback-taking call, but only
by warning `#UnnecessaryEffectMarker` — the `await` is a no-op and returns
immediately, so the confirmation still races the callback. The fix's own comment
claimed the call "must suspend until the callback has run", which is exactly what
it does not do for the API shape the pattern teaches. Both sites now bridge with
`withCheckedContinuation`, the form the sibling file test-failure-analyzer.md was
already using for the same rule — so the two files agree again. Verified: the
continuation form compiles against a callback-only API (exit 0), the `await` form
compiles only with the no-op warning.

Three claims checked against their sources rather than taken on report:
- `/testing/issue/severity-swift.enum` resolved correctly, and the accessibility
  path's underscore-to-hyphen change is right.
- WWDC 2023-10269 and 2024-10206 both redirect; their replacements resolve.
- Xcode's own UI-test template puts `@MainActor` on the test method, so the
  annotation ui-recording.md gained is Xcode's convention — but the block's
  comment attributed it to the recorder, which the audit could not verify. The
  comment now says what the annotation is for instead of where it came from.

Also checked and deliberately left: `await bgContext.perform { }` in axiom-data is
a genuine async overload (compiles clean, no no-op warning), so it is not the same
defect. A stray blank line inside the empty `defaultOptions` object was removed.

Verified: 518/518 unit tests, pre-deploy --static clean, distributions regenerated.
2026-09-16 14:31:03 -07:00
Charles Wiltgen 5c804a38d2 fix(axiom-testing): verify the suite against the toolchain and fix 57 findings
Second suite of the corpus audit. 8 files, 161 code blocks, ~447 API claims checked.
Blocks were compiled at the iOS 18 floor with the Developer-tree XCTest/Testing search
paths and, where the claim was behavioural, executed — `swift test` on the host and
`xcodebuild test` on booted iOS 27.2 simulators.

Two systemic misconceptions, each found independently in more than one file.

`confirmation` does not wait. It checks the count when the closure returns, so a
callback that fires after that is a hard failure, not a wait. testing-async.md taught it
as deterministic waiting and mapped XCTest's `wait(for:timeout:)` to it; the fix it
recommended converted a passing-but-vacuous test into a deterministically failing one.
test-failure-analyzer.md's two " CORRECT" fixes failed 4/4 runs, and its Pattern 5
taught `confirmation(expectedCount: 0)` as proof a callback never fires — it is not: a
late confirm() is counted zero times, reported nowhere, and the test stays green.

A test plan's `userInterfaceStyle` does nothing. The app follows the simulator. Two files
published it as the way to test Dark Mode; a probe app printing its own colorScheme
reported the simulator's setting in every variant, and the JSON block would not load at
all until two missing keys were added. The deterministic route is device-level
`xcrun simctl ui <udid> appearance dark`. `XCUIDevice.shared.appearance` works but races
the launch — measured — so it is described as racy rather than recommended.

Also fixed: a published `.xctestplan` that could never load; a menu item that exists
nowhere in Xcode ("Debug → Record UI Automation"); `XCUIApplication.openURL`, which is not
the API; a fix that could not compile (`self is immutable`); `.timeLimit(.seconds(5))`,
which is explicitly unavailable; and `@Test` inside an XCTestCase subclass, which the
macro rejects.

One agent edited the generated copy of an inlined auditor instead of its agent source.
The drift gate caught it and the work was moved; the gate did its job.

Verified: 518/518 unit tests, pre-deploy --static clean, inlined-auditor and Cursor drift
checks current, all three generated distributions regenerated, and both suites recorded in
the out-of-repo ledger (corpus coverage 44 -> 60 files, 0 stale).
2026-09-16 14:17:43 -07:00
Charles Wiltgen c6fa657e3f fix(axiom-concurrency): review the fix pass — a compile break, a self-contradiction, a stale bullet
Reviewing the fix pass found three things the fixers' own probes did not.

concurrency-profiling.md's fix added `@concurrent func heavyComputation() async -> Int`
with a comment-only body. That is `error: missing return in global function expected to
return 'Int'` — the fix introduced a defect of the same class it was repairing. It now
returns a value.

The two files that qualified the NonisolatedNonsendingByDefault claim qualified it in
opposite directions: the discipline file said "on by default in new Xcode projects", the
router and the auditor said "off by default". Both are true and neither is complete —
Xcode's Base_ProjectSettings.xctemplate sets SWIFT_APPROACHABLE_CONCURRENCY = YES, so new
Xcode projects have it on, while the build setting's own default is NO and SwiftPM does not
enable it. All five sites now say that. The earlier note in this session calling the claim
unverifiable was wrong: it is checkable in the template's TemplateInfo.plist.

isolation-inheritance-diag.md still told the reader to reproduce the crash by driving Core
Data through `context.perform` from a background-spawned task, after the fix deleted the
section explaining that it no longer traps. Removed — a cutover the fix pass missed.

Verified: 518/518 unit tests, pre-deploy --static clean, inlined-auditor and Cursor drift
checks current.
2026-09-16 12:58:34 -07:00
Charles Wiltgen 7a15dcbbfb fix(axiom-concurrency): verify every API claim against the SDK and fix the 35 findings
First suite of the corpus audit. All 160 Swift blocks were compiled as probes at
the iOS 18 floor and every API claim checked against the 27.2 SDK interfaces and
swiftinterfaces. 35 findings across the 8 files: 21 defects (wrong as written),
4 stale (teaching a fix for a problem the SDK has since solved), 10 sub-floor.
Every file had at least one.

The worst was silent. assume-isolated.md recommends testing MainActor code with a
nonisolated `@Test` calling `MainActor.assumeIsolated`; that compiles clean and
traps at runtime, because Swift Testing does not run nonisolated synchronous tests
on the main thread. It takes down the whole test process with SIGTRAP, and no
compiler can warn about it — which is the argument for a harness that runs what it
checks rather than only compiling it.

Elsewhere of that class: Pattern 9's copy-paste SwiftData template returns
non-Sendable models across an actor boundary and cannot compile for any conforming
type; four  blocks fail under `-swift-version 6` for the same missing `@MainActor`;
a gotcha table teaches that `case failed(Error)` is a compile error when `Error` has
been Sendable since Swift 5.5; and a documented debug env var is absent from every
runtime Apple ships.

Two failure modes worth naming, because neither is reachable by compiling code
blocks alone. Content that teaches a FIX for a problem the SDK already solved —
Core Data's `perform` already takes a sendable block, so the  does not fail and
the "fix" is a no-op. And content that teaches a DIAGNOSTIC that no longer exists.
Gotcha tables and decision trees need the same scrutiny as the code.

One claim was wrong in the same direction in three files at once: that an async
function resumes on the caller's actor. True only under
NonisolatedNonsendingByDefault, which defaults to NO, so the auditor could report
a false CRITICAL. Qualified at every site, and in the agent source rather than in
its generated output — the inlined sub-skill is built from that source by
build-inlined-auditors, so editing the output alone would have tripped the drift
gate and been overwritten.

The suite is substantially correct elsewhere: the @concurrent and actor machinery,
the Sendable rules, the OS27 ProgressManager section and the crash signature table
all check out, several of them character-for-character.

Verified: 518/518 unit tests, pre-deploy --static clean, all six generators current,
and all 8 files recorded in the out-of-repo ledger (corpus 44 -> 52 verified).
2026-09-16 12:52:46 -07:00
Charles Wiltgen 93f793709f fix(tools): replace captured identifiers in test fixtures with synthetic ones
The warn tier's last 8 findings were all real values sitting in fixtures: three
Apple OS binary UUIDs (libsystem_kernel.dylib, /usr/bin/yes, dyld) from a real
cpuprofile trace, and a display UUID captured from `devicectl device appResize
set`. None was sensitive, and the obvious response was to exempt them — which
would have been the wrong one. An exemption is permanent: it lives in the scanner
forever, and each one narrows what the gate can still catch.

The values were also inert. cmd_resize_test.go asserts the parsed "Actual size"
field and never reads the display id; the cpuprofile assertions need only the
fixture and the expectation to agree.

So the fixtures carry synthetic UUIDs now, and the scanner's own test assembles
its unpatterned control values from parts rather than writing literals. That last
part matters because the test file is shipped content like any other — a literal
there is a value the gate has to carry forever. Same move e6f267ce made for the
deny-list values: build it, don't ship it.

The tier is therefore empty with no exemptions, so the next warning is real.
Measured with `node scripts/leak-scan.ts` over 1978 shipped files: 16 -> 8 -> 0.
The 8 in the middle were real but not identifying — one OS-binary UUID per
architecture, plus a display id that devicectl regenerates on every boot
(measured across three boots of one simulator).

cmd_resize_test.go's comment claimed a verbatim capture above a value that is
now synthetic; corrected.

Verified: 518/518 unit tests, xcprof/xcsym/xcui go suites, leak scan 0/0.
2026-09-16 11:47:51 -07:00
Charles Wiltgen 180000ce24 fix(tools): remove a real hardware UUID from the xcprof fixtures
tools/xcprof/testdata/toc.xml and network-toc.xml carried the maintainer's
IOPlatformUUID in the trace's <device> node, next to model="Mac Studio" and
os-version="26.5 (25F71)". Verified against `ioreg -d2 -c IOPlatformExpertDevice`,
which returns the same value. It entered in 2a82c80a and was re-added in 427d4922.

Replaced with the RFC 4122 canonical placeholder, which the scanner already
recognizes. No code parses that node's uuid — the toc tests read duration and family
data — so the change is inert, confirmed by the xcprof suite and the unit suite.

The warn tier is what surfaced this, so the tier stays populated rather than being
silenced. Two changes reduce it on merit instead: the 4C4C44 prefix joins the
recognized placeholder shapes (ASCII "LLD"; a real UUID carrying those three bytes
has probability ~1/16.7M), and the removed value is simply gone. Tier 16 -> 8, and
the remainder is four OS-binary identifiers plus one captured devicectl session id.

Verified: 518/518 unit tests, xcprof go tests, leak scan 0 errors over 1978 files.
2026-09-16 08:53:30 -07:00
Charles Wiltgen 980ae18486 fix(gate): report a scanner failure as a failure, and make the block-scalar guard able to fire
Two P3 guards in the content gate could not do the job they were written for.

`pre-commit-axiom.sh` captured the scanner's output and then filtered it to
`^  ✗|error(s)`. When the scanner crashed rather than reported, that filter
discarded its entire message and the hook still asserted "private data in shipped
content" — a confident diagnosis the run had produced no evidence for. The hook now
branches on whether a `✗` line exists: findings are printed and the failure says so,
and their absence means the scanner failed, which gets its own wording and the raw
output. Verified in a sandbox against a scanner that throws and one that reports.

`always-on-footprint.test.ts` asserted the absence of a block scalar against
`String(matter(file).data.description).trim()`. gray-matter resolves `description: |`
to its content, so that assertion could only fire on a description whose literal text
was `|` — the byte count the test sums changes for exactly the case it was meant to
catch. The shape is now asserted against the frontmatter region of the file text, and
fires on `|` and `>-` while leaving a quoted "|" alone.

Also drops an SC2181 in the same hook. pre-deploy check 11 shellchecks only the
plugin's own hooks/ directory, so scripts/git-hooks/ — this gate's machinery — has
never been linted.

Verified: 518/518 unit tests, 14/14 installer tests, shellcheck clean, and both hook
branches exercised end to end.
2026-09-16 08:10:22 -07:00
Charles Wiltgen aa1d1ddd08 fix(gate): cover what the scan reads in the CI paths filter, and make the hook installer idempotent
The test-suite workflow's `paths` filter listed nine prefixes while its two steps
read considerably more. `node scripts/leak-scan.ts` walks every SURFACES entry
plus ROOT_FILES — `.cursor-plugin`, `.agents`, `axiom-codex`, `axiom-cursor`,
`axiom-mcp` and the root release files among them — and the unit suite covers
`axiom-pi` and `axiom-mcp`. A PR that added a home path to README.md or a real
UUID to `axiom-codex` therefore started no job at all, which is exactly the case
this workflow exists to backstop on a fresh clone or a `--no-verify` commit. The
filter now lists all twenty entries per event, and a test derives the coverage
requirement from the scanner's own SURFACES and ROOT_FILES so the two cannot
drift apart again.

`install-git-hooks.sh` grew the hook by one line on every run: the strip removed
the block but left the separator blank line on both sides of it, and the insert
added one back. The block now contributes no leading blank and the strip consumes
the trailing one, so a re-install leaves the file byte-identical.

Verified: 518/518 unit tests, 14/14 installer tests, leak scan 0 errors across
1978 shipped files.
2026-09-16 08:05:42 -07:00
Charles Wiltgen 34d174869c fix(gate): close the content gate's coverage and reporting holes
Five defects, all in the gate added on 2026-09-15, found by two independent review
passes and then traced to root causes.

The installer appended its block to .git/hooks/pre-commit, so a foreign block that
terminates the script left the whole gate unreachable while the installer reported
success. This repo's own hook is that case: the beads block carries
`if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi`. It now strips any existing block
and re-inserts after the shebang, which also MOVES a block an older revision left
in the wrong place — the live hook was reordered by running it.

SURFACES replaced the tracked `axiom-mcp` entry with `axiom-mcp/dist`, leaving 38
tracked files — src/, package.json, and the npm-published README and LICENSE — in
no surface. Both entries are needed; both are present now, and the scan sees 1,977
files against 1,939 before.

Both warn-tier suppressions were dead. The issue-id guard tested m[0], which
carries the `issue_id": "` prefix, against an anchored id pattern: it could never
match, so it warned on the ACME-*/ASC-* placeholders it exists to suppress. The
UUID allowlist missed the RFC 4122 example and every doubled-character shape.
Warnings fall from 89 to 16, and a test now asserts a suppressed value stays quiet
— the one thing the suite never checked.

Check 12s counted its warnings and printed none while telling the reader to eyeball
findings it never showed. They go through warn() now, which the Phase 1 block
renders.

The CLI derived its root from new URL(import.meta.url).pathname, which keeps
percent-encoding: from a checkout path containing a space every surface walked to
nothing and the guard then blocked every commit, naming the wrong cause. Verified
in a worktree at such a path — old idiom exit 1, new idiom exit 0. A lint keeps the
lossy idiom from returning anywhere in scripts/.

.github/workflows/test-suite.yml runs the unit suite and the content scan on pull
requests and on main. Neither had ever run off a maintainer's machine. It
deliberately omits `npm run test`: that gate's first check shells out to
`claude plugin validate` and treats the CLI being absent as a failure.

Verified: 515/515 unit tests, Phase 1 static PASSED, leak scan 0 errors / 16
warnings over 1,977 files. The remaining 16 are real dylib and profile UUIDs in
tools/*/testdata, listed in the summary output for a decision on allowlisting.
2026-09-16 07:15:07 -07:00
Charles Wiltgen 2abd614ba1 docs(hooks): drop the stale Codex no-prompt-routing claim
The Harness Support intro said Codex has "no per-prompt routing hook in the
same form", three lines above a table that marks per-prompt routing supported
on Codex. The clause predated the port of the hooks to the Codex variant: the
generated axiom-codex/hooks/hooks.json binds UserPromptSubmit with no matcher
(Codex rejects one on that event), and user-prompt-submit.py returns
hookSpecificOutput.additionalContext, the context-injecting shape a routing
nudge needs.

Removed the clause so the prose agrees with the table and with what ships. The
page already states the real caveat elsewhere — Codex needs
features.hooks = true, and a skills-only install gets no hooks at all.

Verified: docs build clean, npm run test:unit 498/498, Phase 1 static PASSED.
2026-09-15 17:10:12 -07:00
Charles Wiltgen a05602455c fix(leak-scan): silence git's stderr for files that ship from disk
Asking for the staged blob of an untracked-but-published file is an expected miss;
printing 'exists on disk, but not in the index' forty times inside check 12s buries
the result line.
2026-09-15 15:28:26 -07:00
Charles Wiltgen 6bfbfd766c fix(leak-scan): close the holes an independent review found
Sixteen findings across two reviews, all in the guard and the fixes it prompted.

The guard:
- The pre-commit step resolved `tsx` through `npx`, and tsx is not a dependency:
  on a fresh clone or offline, the download failure took the failure branch and
  blocked the commit with "private data in shipped content" — a wrong and alarming
  diagnosis. It runs under plain node now (Node 24 strips types), like every other
  TypeScript entry point here.
- A failing run printed `tail -20`, which is trailing warnings plus the summary —
  the `✗ path:line` naming the offender scrolled off. It greps for the error lines.
- Files were listed from the index and read from the working tree, so staging a
  leaking revision and restoring the clean file on disk passed the scan. It reads
  the staged blob, and a staged-then-deleted file no longer throws ENOENT.
- Several tracked, shipping surfaces were never scanned: `.cursor-plugin/`,
  `.agents/`, the whole `axiom-pi/` package, the submission Markdown at the root,
  and `package-lock.json`. The npm bundle needed the opposite treatment —
  axiom-mcp/package.json publishes `dist/`, which is gitignored except two JSON
  files, so that surface is read from disk.
- CHANGELOG.md was in ROOT_FILES but is gitignored, so the entry could only fire on
  the fallback path: the scan reported clean while the file still named the app.
  Root files are read from disk now, and that is exactly what it found — a third
  mention the earlier cleanup missed, and which the close note on Axiom-1z8m
  wrongly claimed was fixed. Corrected here.
- The uuid rule was uppercase-only (Claude Code session paths are lowercase), the
  tracker rule's exclusion list sat where it could not apply (checked in code now),
  and printable runs in binaries were collected at 16+ characters, long enough to
  miss short values.
- A scan that finds almost nothing now reports a broken scan instead of clean
  (floor on the file count, in both the CLI and check 12s).
- The hook itself was untracked, so the guard vanished on a fresh clone. It lives
  in scripts/git-hooks/pre-commit-axiom.sh and is spliced in by
  scripts/install-git-hooks.sh, idempotently, leaving the beads block alone.

The fixtures the earlier round was supposed to have cleaned:
- The two xccrashpoint copies of the crash still carried the framework's full codec
  inventory and its build UUIDs, and the copy that was edited kept the same names
  in the path column. Both files and that column are neutralised.
- The real crash instant survived in the two fixture file names while the header
  said 2026-01-01; the names now match the header, and nothing asserts them.
- crash_text_test.go pins that the dedup-symbol frame resolves its image, so the
  case cannot pass while exercising the unknown-image path.

Scan: 0 errors, 89 warnings across 1937 shipped files. Unit 498/0; xcsym and xcui
pass, including the anonymizer fixed-point test against the edited fixtures.
2026-09-15 15:27:02 -07:00
Charles Wiltgen 9a937f573b build(tools): strip the shipped binaries again
My earlier rebuild (the privacy commit) dropped the stripping the committed
binaries had: they went from 3.9-8.8 MB to 9.0-9.9 MB. The Makefiles now pass
-ldflags "-s -w", which is what the hand-built binaries had before a Makefile
existed for all four tools. Sizes are 6.0-6.6 MB now — three of the four are
smaller than the versions they replace, and xclog is larger than its 3.9 MB
hand-built predecessor, which used an older toolchain. All four Go modules pass.
2026-09-15 14:52:03 -07:00
Charles Wiltgen e6f267cebb test(leak-scan): inject the rule set, so no real deny-list value lives in a test
Groundwork for scrubbing the repository's history: a test that asserts on private
values puts those values back into the tree, where any history rewrite has to
touch them. The engine now takes its rules as a parameter, and the tests exercise
it with their own synthetic set. The shipped list gets a structural test instead —
rule ids and the five error classes, not values.
2026-09-15 14:40:46 -07:00
Charles Wiltgen 8a696061f5 feat(gate): scan shipped content for private data (check 12s + pre-commit step)
Axiom-1z8m. The identifier class leaked four separate times — skill examples, the
xcsym/xcui fixtures, a Cursor hook fixture carrying a real session path and
session UUID, and all four bundled binaries embedding the maintainer's source
layout — and no gate saw any of it, because the pre-commit hooks match file names
and directories only.

scripts/leak-scan.ts scans what ships, where "ships" means tracked: the plugin,
the generated Codex/Cursor variants, the MCP bundle, docs, and the tool and
script trees that carry fixtures. Files are read as UTF-8; anything with a NUL in
its first block is scanned through its printable runs, which is how a source path
embedded in a binary gets caught by the same rules as prose.

Rules, each with a test: project names, personal home paths (including Claude
Code's encoded -Users-<name>- form), session temp paths, and timestamp-shaped
build stamps are errors; unpatterned UUIDs and quoted tracker ids warn, and
obviously synthetic values (AAAA…, 1A2B…, ACME-*, /Users/you, REDACTED) are left
alone so the warning tier stays worth reading. One allow entry exists, for the
Cursor fixture, whose whole purpose is to be shaped like a real session path.

Wiring: check 12s runs inside pre-deploy, and the pre-commit hook runs the scan on
every commit regardless of staged paths — the class has come through tools/,
fixtures and binaries, so staged-path gating cannot cover it. tools/ also joins
the hook's full-validation trigger set. Proof it can fail: planting "ExampleApp" in
README.md makes the scan exit 1 with the file and line, and reverting restores it.

It also found two real leaks on first run: the xccrashpoint fixtures still carried
the real app version and crash timestamps (the earlier fix covered only the
apple_crash fixture), and two public CHANGELOG entries named the app while
describing past scrubs.
2026-09-15 14:26:15 -07:00
Charles Wiltgen a776996e4c fix(agents): keep iap-implementation's hand-off without a dangling command reference
The restored sentence — "This agent writes code; it has no /axiom:audit form" — was
invisible while it lived in the frontmatter description, which every emitter
strips. In the body it is emitted, the Codex build annotates it as unresolvable,
and check 12o flags the resulting pointer. Not a check bug: Codex ships no
commands, so a bare /axiom:audit reference has no target to resolve. Reworded to
keep the hand-off ("use the iap-auditor agent, which /axiom:audit iap invokes")
and drop the dangling reference. Phase 1 clean.
2026-09-15 14:12:34 -07:00
Charles Wiltgen 4e8555b3d7 chore: rebuild the Codex, Cursor and MCP copies
Regenerated from the restored agent bodies and the two router descriptions.
2026-09-15 14:10:30 -07:00
Charles Wiltgen dfcae86e07 test: derive expectations from parsers and data, not from copies of the reader
- always-on-footprint.test.ts computed the expected skill-listing size with a
  regex that mirrored the reader's own inline rule, so it agreed with the reader
  even when both were wrong: a quoted description kept its quotes, a
  continuation line was dropped, and a `description:` with an indented
  continuation failed with a misleading message. It now parses with gray-matter,
  as the sibling listing test does for the same reason.
- crash_text_test.go's dedup case named its frame image "SomeApp" while the
  images slice it is passed holds "App", so UUID lookup missed and the case
  exercised the unknown-image path instead of the one it documents.
- cmd_triage_test.go's comment still promised "huge user count" over data that
  had become users:9 events:12. The contrast is restored with invented values
  (1200/4500) rather than the real figures.
2026-09-15 14:10:30 -07:00
Charles Wiltgen fdd66c7a34 fix(agents): restore the description text the truncation deleted
Axiom-2fa kept the first sentence and moved the <example> blocks, but dropped
everything else the description carried, in all 42 agents — the closing prose
that said what the agent covers and, in three cases, who should get the prompt
instead:

- iap-implementation lost "This agent writes code; it has no /axiom:audit form.
  To review existing IAP code instead, use /axiom:audit iap (the iap-auditor
  agent)." The implementation agent has Write/Edit and the shipped trigger had
  no exclusion, so review-shaped prompts named the same vocabulary.
- triage-analyzer lost "For a single crash file (.ips, MetricKit, .crash,
  .xccrashpoint), use the crash-analyzer agent instead."
- swiftui-architecture-auditor lost "Complements (but is distinct from)
  performance and navigation audits."

My own verification missed it because it compared bodies, and this content was
in the frontmatter. Each agent's dropped prose now lives in a `## Scope`
section, and a check confirms every tail sentence exists in its file.

Two router descriptions lost capabilities in the same rewrite and the restore
pass missed them: axiom-build's test-crash trigger ("a crash log needs
diagnosing, a test run crashes") — the capability ships in SKILL.md and
xcode-debugging.md, and axiom-testing routes it here — and axiom-shipping's
"age ratings", which the suite documents in four places and the deterministic
hook matches verbatim.
2026-09-15 14:10:29 -07:00
Charles Wiltgen 7e9facd7dc fix(privacy): remove the personal and production data still shipping
Found by an independent security review of the repo, not by a gate — the
leak-prevention hooks are filename-scoped and cannot see content classes.

- scripts/cursor/fixtures/cursor-3.17.8-hook-payloads.json embedded a real
  session path (which encodes the maintainer's home directory) and a real
  session UUID in seven fields.
- The committed .crash fixture kept the real app's Version and build stamp
  (1.0.0 / 1000000000), the crash's Date/Time and Launch Time with the
  device's UTC offset, and the full binary inventory of a framework whose name
  the anonymizer deliberately hides (wavpack, ogg, FLAC, opus, vorbis, lame,
  mpc, mpg123, sndfile, tta-cpp, Lottie) with their build UUIDs. The
  anonymizer has no rule for any of those keys, which is why they survived;
  editing them does not touch the fixture's fixed-point test because nothing
  rewrites them.
- scripts/migrate-skill-namespace.sh hardcoded /Users/you/Projects/Axiom,
  publishing the layout and making the script unusable anywhere else.
- Four shipped skill files used the maintainer's identity where the corpus
  uses placeholders: /Users/you/... in sandbox-and-file-access.md,
  ~/charles-personal.p12 and "Apple Distribution: Charles Personal (ABC123)"
  in code-signing.md, and an unpatterned device UDID in xctrace-ref.md.
- testflight-triage.md published measured production triage figures and a real
  app-internal symbol (16 of 17 signatures, crashHandlerSymbol); the lesson
  survives without the numbers or the symbol name.
- The three tool Makefiles built without -trimpath, so the maintainer's source
  path was embedded in every shipped binary, and xclog had no Makefile at all —
  its binary was hand-built with the absolute path in its debug info. All four
  tools now build with -trimpath; strings over the four shipped binaries
  reports zero /Users/you occurrences, down from xclog=1 and the three
  others at their previous values.
2026-09-15 14:10:29 -07:00
Charles Wiltgen a2827fa449 test(footprint): guard the inline skill-description path
Every skill writes `description: Use when …` inline; every agent writes a `|`
block scalar. The reader handles both, but the test guarded only the block form —
so the shape the skills actually use was untested, and an inline-blind reader
scores 27 descriptions at a handful of chars and reports the listing as free.

That silent-zero class bit three times on 2026-09-15: run.py located the hook via
HERE.parent.parent and printed 0/17 recall with 0% false positives after a
directory move; an ad-hoc crash-triage check read only block scalars and reported
zero hits against 27 inline descriptions; and this test could not have caught
either.

The expectation is computed from the files rather than from the reader, with a
floor on the sample count so the guard cannot become vacuous, and an assertion
that a future block-scalar skill description extends the test instead of silently
passing it.
2026-09-15 13:55:55 -07:00
Charles Wiltgen 888fd0a566 perf(context): ship agent descriptions as their trigger sentence
Claude Code's always-on cost was 54,966 chars, 39,359 of it agent descriptions —
the same 42 agents Cursor and Codex carry for ~6,100, because both emitters
already truncate each description to its first sentence. The <example> dialogues
exist to teach triggering; the first sentence already carries the trigger, and
the dialogues now live in the body, which loads only when the agent runs.

Measured before landing — 169 prompts (each agent's own <example> user lines,
labelled with the agent that example names) classified against all 42
descriptions, two runs per condition:

  full text       169/169, 169/169  (the examples contain their own answer, so
                                     this level is an artifact, not a baseline)
  first sentence  168/169, 166/169

The three deviations are two-way calls between plausible agents —
spm-conflict-resolver over build-fixer for "No such module after I updated
packages", test-debugger over build-fixer for "tests passed yesterday but now
fail", security-privacy-scanner over grdb-performance-auditor for "scan for SQL
injection in GRDB code". No cross-domain misroutes. The corpus cannot rule out a
larger effect on other model classes, and it is built from the removed examples,
so it is the hardest available test for the truncated form, not a field result.

Why the risk is bounded: the surface that changes is discretionary selection.
The skills carry 102 "Launch `<agent>` agent" directives covering all 42 agents,
and commands another 33, so designed flows choose their agent from skill text
either way — untouched here.

Always-on: claude-code 54,966 -> 22,059 chars (~18,322 -> ~7,353 tokens, -60%).
Ceiling ratcheted 56,000 -> 24,000.

Gates that had to move, with reasons:
- always-on-footprint.test.ts asserted the 6.4x Cursor/Claude Code gap. The gap
  was the defect, so it now asserts near-parity; re-opening it fails the test.
- audit-parity's advertised-area and advertised-command parsers read the
  frontmatter description only. The `Explicit command:` hints now live in the
  body, so both scan the whole file — a ghost command promised in the body is
  exactly as broken as one promised in the frontmatter, which is the check's
  purpose. Two tests encoded the old frontmatter-only contract; they now assert
  the new one, plus a new case proving a body-promised ghost is caught.
- Four agents (energy, memory, swift-performance, swiftui-performance) had lost
  the vocabulary their audit area checks for; it is back in the trigger
  sentence, which also routes on those words now. foundation-models and
  spritekit gained theirs as well.
- `/axiom:audit all` carries "(Claude Code only)": Codex has no commands and the
  emitter cannot map `all` to an area.

Note: 250 chars is Axiom's own emitter budget, not a platform cap. The Agent
Skills spec allows 1024; Claude Code documents 1536 for description +
when_to_use; Codex enforces 1024 and shortens long descriptions for its listing.
2026-09-15 13:34:40 -07:00
Charles Wiltgen 88a0fc4694 chore(tools): replace real app data in xcsym/xcui fixtures with placeholders
The xcsym and xcui test fixtures carried data from a real project: the app
name in sample crash reports, a live Sentry issue ID (APP-3V) with its real
impact counts, and a dated incident reference in a comment. Axiom is a public
repository; a private app's crash telemetry has no business in it.

Placeholders only — "SomeApp [14250]", "APP-3V", users:9 events:12, and the
incident comment drops the app name. Every execution path is unchanged: the
anonymizer's bracket/pid handling, the header-line extraction, the dedup
symbol classification, and the triage noise-flagging all still exercise the
same shapes. The impact numbers in normalized_test.go moved with the assertion
that checks them.

Note: this removes the data from the current tree, not from git history.
2026-09-15 13:11:00 -07:00
Charles Wiltgen 240fb500cf chore: re-sync manifests and rebuild the Codex, Cursor and MCP copies
Generated from the corrected descriptions; claude-code.json and /axiom:ask carry
the text the harnesses actually list.
2026-09-15 13:04:09 -07:00
Charles Wiltgen 40cf4f4028 test(skill-listing): derive the expectation with gray-matter, not a regex
The assertion's expectation re-implemented the loader's own parse rule, so it
compared the module against a copy of itself and could not catch the loader
mis-reading a shape it accepts: a quoted description keeps its quotes and a plain
multi-line scalar loses its continuation, and both passed. gray-matter (already a
devDependency) returns the frontmatter's meaning instead. The manifest-vs-disk
guard for this file remains the sibling test, which fails when the listing comes
from claude-code.json because axiom-tools is deliberately absent there.
2026-09-15 13:03:30 -07:00
Charles Wiltgen 89b3ec5e9c fix(skills): restore the capabilities the description rewrite orphaned
An independent review of the previous commit found that seven of the twelve
rewrites removed the only description surface for content their own suites still
ship — the same defect the rewrite existed to repair, reintroduced elsewhere.

- axiom-integration had dropped "timers" and "reminders"; timer-patterns.md and
  eventkit.md own both, and axiom-performance routes four timer prompts here.
- axiom-swiftui had dropped "performance" and "architecture" to stop colliding
  with other routers, but swiftui-performance.md and architecture.md are its own
  children and its own text says "try axiom-swiftui first" for slow UI. Scoped
  instead of dropped: "view-level performance, feature architecture".
- axiom-media had narrowed CarPlay to metadata; carplay-hig.md,
  carplay-templates-ref.md and carplay-navigation-ref.md own app design,
  entitlements, templates and navigation, and axiom-design routes that work here
  by name.
- axiom-build had claimed TestFlight crash triage, which axiom-shipping owns
  (its own table forwards "beta tester reported a crash" to that suite); it now
  claims the local crash log it actually analyses.
- axiom-design restores auth-flow structure (app-composition.md owns the state
  machine); axiom-shipping restores App Store Connect automation alongside the
  accurate Xcode Cloud claim (asc-mcp.md).
- axiom-apple-docs loses its trailing mechanism clause, which told the model what
  the skill does rather than when to load it.
- axiom-performance's platform anchor is "Apple-platform", not an enumeration:
  its children carry watchOS and tvOS memory, energy and MetricKit content, and
  "battery drains" is the canonical watchOS question.

Measured with the same description-only classification over 59 real and 25
synthetic prompts, before and after the whole change: SHOULD recall 16/17 ->
17/17, pointer rows routed 1/24 -> 0/24, false fires 2 rows (one unique prompt,
"Do we have a pop-triage skill?", which axiom-tools' own text claims to answer).
axiom-swiftui's share of picks falls 40% -> 35%; the deeper reduction the first
pass measured came from orphaning its two children and does not survive contact
with the suite's actual contents.
2026-09-15 13:02:51 -07:00
Charles Wiltgen ebda2140a0 chore: re-sync manifests and rebuild the Codex, Cursor and MCP copies
Generated from the descriptions above; claude-code.json's skills array and
/axiom:ask carry the same text the harnesses list.
2026-09-15 12:52:58 -07:00
Charles Wiltgen 4d688af290 test(skill-listing): check the shipped listing against frontmatter, not a prefix
The test is named "carries the frontmatter description verbatim" but asserted a
hardcoded prefix of one description, so it failed on every edit to that text
without testing its own claim. It now reads axiom-shipping's SKILL.md and
requires exact equality with the shipped listing entry — the same guard against
the manifest-vs-disk defect it was written for, and immune to description edits.
2026-09-15 12:52:11 -07:00
Charles Wiltgen 75c5c98a51 fix(skills): repair nine measured router-description defects
Nine defects in the 27 router descriptions, from the Axiom-65q.10 measurement of
the one invocation mechanism that had never been tested — the model's own choice
from the description list. Every harness is affected, including MCP, where
descriptions are the only lever.

- Crash reports had no description surface at all. /axiom:analyze-crash,
  /axiom:triage, xcsym and the crash-analyzer and triage-analyzer agents ship, but
  nothing said so: axiom-build now names crash logs and TestFlight crashes,
  axiom-shipping names TestFlight/Sentry corpus triage.
- axiom-apple-docs fired on nothing. Its trigger was a superset of every other
  router's and its Covers list duplicated five suites, so a maximal trigger with
  no discriminating content was easy to ignore. It now triggers on needing
  Apple's own documentation rather than on any Apple question.
- Four routers named no platform, so axiom-data's trigger literally covered "set
  up a Postgres migration". iOS/macOS/visionOS anchors added to axiom-data,
  axiom-networking, axiom-performance and axiom-concurrency.
- axiom-swiftui claimed "performance" and "architecture", colliding with two other
  routers; it absorbed 40% of picks. Both claims dropped.
- axiom-media claimed the bare phrase "Now Playing" — also a screen name — so
  layout work on a screen called Now Playing routed to the wrong suite. Scoped to
  lock-screen and CarPlay metadata.
- axiom-design is scoped to visual and interaction decisions for an Apple app, and
  no longer claims authentication flows.
- axiom-swift is narrowed off "any Swift edit".
- axiom-integration's 13-noun keyword list became the condition it stands for,
  which is what skill-descriptions.md asks for.
- Xcode Cloud configuration claimed by axiom-shipping.

Listing text: 5,429 -> 5,317 chars. axiom-media (273) and axiom-shipping (252)
were past the 250-char point where the Codex variant truncates mid-sentence; both
now ship whole.
2026-09-15 12:51:33 -07:00
Charles Wiltgen 8cdc3e7919 chore: rebuild the Codex, Cursor and MCP copies for the 27.0 restamp
Generated from the skill files above; the Cursor inventory hash list moves with them.
2026-09-15 12:38:57 -07:00
Charles Wiltgen 32de94bd2c fix(docs): DataDetector.MatchType has been available from 26 on every platform
The page scoped MatchType to iOS/macOS/watchOS/visionOS 27 and excluded tvOS.
DataDetection carries @available(iOS 26.0, macOS 26.0, watchOS 26.0, tvOS 26.0,
visionOS 26.0, *): all five platforms since 26. Only the SwiftUI
.dataDetection(_:options:) modifier this bullet sits beside is 27-only, and only
on iOS/watchOS/visionOS.
2026-09-15 12:38:21 -07:00
Charles Wiltgen 7962933c0a docs: restamp 27.0 RC wording on the MCP reference page
Mirrors the skill change: the 54-tool verification is described as reproduced on
the shipped 27.0 SDK, not on the release candidate.
2026-09-15 12:37:45 -07:00
Charles Wiltgen 15693b4859 fix(skills): restamp 27.0 RC wording to the shipped SDK
Xcode 27.0 released as build 27A266a — the same build the release-candidate sweep
probed, with the iOS SDK at 24A430 — so wording that called it "the 27.0 RC" now
reads as pre-release in guidance describing what shipped.

Restamped only where RC stood in for the release ("verified on the 27.0 RC", "as
of the Xcode 27.0 RC SDK", "still gone in the Xcode 27.0 RC"). Beta-by-beta
history and every build string are kept: both stay accurate for readers on older
Xcodes.

Three lines needed more than a rename, after re-checking each against the 27.0
SDK. The Foundation Models adapters' "no replacement" claim drops its "(beta 1)"
provenance — Adapter carries deprecated 26.4 / obsoleted 27.0 with no renamed: or
message: hint, and LanguageModelExecutor ships as the pivot. The xcode-mcp tool-set
sentence names the release instead of a bare version sitting next to "beta 6".
Tap-to-pay keeps its provenance hedge, now scoped to the shipped SDK.
2026-09-15 12:37:07 -07:00
Charles Wiltgen 02b8cea5c2 chore(axiom-pi): build the parity probe inside the try/finally
A setup failure (or a hard kill) would otherwise leave axiom-parity-probe-<pid>.swift
in the shared temp root — the junk class this suite exists to guard against, and
now harmless to the detector but still litter.
2026-09-15 10:52:35 -07:00
Charles Wiltgen f7b891d9d7 fix(axiom-performance,hooks): restore visionOS availability; harden the temp-root guard
Corrects two things the earlier commits in this area got wrong, and closes the
false positive they left open.

CrashReportExtension visionOS availability. e5f1018b dropped the visionOS claim
on the strength of developer.apple.com symbol-page badges; the SDK contradicts
them. Against the installed Xcode 27 SDK,
'xcrun --sdk xros swiftc -typecheck -target arm64e-apple-xros27.0' compiles
clean, xros26.0 reports "only available in visionOS 27.0 or newer" (a version
gate, not an exclusion), while tvOS and watchOS report "unavailable" outright
and Mac Catalyst has no module at all. The .swiftinterface carries
@available(iOS 27.0, macOS 27.0, *) with @available(tvOS, unavailable) and
@available(watchOS, unavailable), and no visionOS clause. Apple's pages disagree
with one another, so the SDK leads: skill text, the version-support row and the
docs page are restored with the reasoning inline.

Temp-root guard. Three ways the guard added by ba04043b could still misjudge a
project:
- a TMPDIR-less process got /tmp from tempfile.gettempdir(), leaving the macOS
  per-user scratch root unneutralized and the original false positive alive for
  any launcher that scrubs the environment — roots now also come from the
  filesystem on darwin (containers and their T/ dirs);
- a relative TMPDIR resolved against the detector's own cwd — the project being
  judged — turning a real Apple project into a "temp root" and silently
  disabling Axiom; only absolute values are accepted;
- the scan-root guard ran before the repo-root exemption, so a repo rooted at a
  temp root (devcontainer or CI exporting TMPDIR to the workspace, or a clone in
  /tmp) read as non-Apple; the exemption now wins.

Mirrored into axiom-pi/src/session.ts with the same three tests; the parity
matrix gained a repo-rooted-at-a-temp-root case and TMPDIR control.

Verified: detector 54/54, axiom-pi 69/69 + typecheck, npm test PASS, test:unit
exit 0, check:cursor clean, docs build clean.
2026-09-15 10:52:34 -07:00
Charles Wiltgen 0fbb56821a fix(axiom-pi): port the temp-root fix, add a Python/TypeScript parity gate
axiom-pi/src/session.ts is a hand-maintained port of project_detect.py and had
the same defect the Python fix just closed: a marker in the shared temp root made
every cwd beneath it read as an Apple project, so Pi injected Axiom context in
non-Apple workspaces. Checked against the exported isAppleProject before the
change: temp dir true, temp root true.

- port the temp-root neutralization (TEMP_ROOTS Record + computed realpath set,
  guard in the upward walk, temp root as a vacuous scan root)
- mirror the three regression tests from project_detect_test.py
- add a cross-implementation parity gate: one fixture matrix (plain dir, marker
  at cwd, in an ancestor, above a git root; .swiftpm home; visible .swiftpm
  package; project inside the temp root; plain temp dir; temp root itself;
  missing path; oversized tree) run through both implementations, failing on any
  mismatch. It rides the axiom-pi suite, which the full pre-deploy runs.
2026-09-15 10:52:34 -07:00
Charles Wiltgen ba04043bf8 fix(hooks): stop reading a system temp root as an Apple project marker
Both cursor adapter contract tests failed on main: they assert that a temp
workspace is not an Apple project, and the detector said it was. Root cause is
not the tests and not the adapter — the upward walk found an Apple marker sitting
in the shared temp root itself ($TMPDIR/plan-test.swift, left by an unrelated
tool), so every cwd beneath $TMPDIR inherited it. Same class as GH #52's
~/.swiftpm: tool state, not project evidence.

- neutralize the temp root in the upward marker walk ($TMPDIR plus /tmp,
  /var/tmp, /private/tmp, abspath and realpath forms)
- treat the temp root as a vacuous scan root, so containment there never falls
  through to a whole-tree scan
- revert the failing tests to green with three new detector tests: a marker in
  the temp root is not evidence, the temp root is not a project, and a project
  that genuinely lives inside a temp dir is STILL detected (over-correction guard)

Regenerated axiom-codex/ and axiom-cursor/ mirrors.
2026-09-15 08:41:22 -07:00