Commit Graph

1446 Commits

Author SHA1 Message Date
Charles Wiltgen 8a7086351a feat(cursor): port subagent skill awareness to subagentStart
The third and last capability the ledger recorded as downgraded, replaced by a
static preamble in each agent file. Cursor's subagentStart response accepts
additional_context and its query carries subagent_type, which is the only field
the canonical hook reads, so the port is a field rename.

This is additive, not a replacement. The generated preamble carries an agent's
own declared skills; the hook carries the general skill-awareness text gated by
agent type. Both now ship.

Verified as far as a free Cursor plan allows: the hook registers, fires, and
delivers a payload carrying subagent_type, task, and subagent_model — confirmed
against a live Cursor 3.17.8 delegation, which fires subagentStart three times
before refusing to start the subagent with "Named models unavailable. Free
plans can only use Auto." What a free plan cannot show is whether the returned
context reaches a subagent that never starts. The docs and the submission
matrix say so rather than implying full verification. If delivery does not
work, the preamble still carries the declared skills, so the failure mode is
the status quo rather than a regression.

The context guard needed widening: skill awareness is legitimately
multi-paragraph, where the router and crash hints are single-line. Newline and
tab are now permitted, every other control character is still rejected, and the
bound is separate at 8 KiB.
2026-08-23 13:18:08 -07:00
Charles Wiltgen 3bf999dfc3 feat(cursor): port crash-report read routing to preToolUse
The second capability recorded as "omitted" without rationale. Cursor supports
it: PreToolUseRequestQuery carries tool_name and tool_input, and
PreToolUseRequestResponse accepts additional_context, so the canonical
PreToolUse(Read) crash routing maps directly onto preToolUse with a Read
matcher. Verified against a live Cursor 3.17.8 session — asked to read a
Sample.ips, the model reported being advised to use the axiom_xcsym_crash MCP
tool, and the read was not gated.

The canonical hints name the bare `xcsym` binary, which a Cursor install cannot
run — the plugin routes xcsym through MCP. Shipping them verbatim would have
told the model to run something that is not there, which is the likeliest
reason this looked unportable. The adapter maps the four invocation forms onto
the MCP tool and fails closed if any backticked `xcsym` survives: no hint is
strictly better than a wrong one.

No `permission` field is emitted, so the plugin never gates a read, consistent
with the documented position that no Axiom hook is a permission boundary. The
hint interpolates the file path, so the adapter bounds its length and rejects
control characters before it reaches the model's turn.

Contract disposition moves from "omitted" to "preToolUse.additional_context".
2026-08-23 13:09:08 -07:00
Charles Wiltgen a0cea09a5c feat(cursor): port per-prompt router injection to beforeSubmitPrompt
The capability ledger recorded UserPromptSubmit as "omitted" with no stated
rationale, which dropped Axiom's core routing-enforcement mechanism and left
skill activation in Cursor resting entirely on description-based discovery.
Cursor supports the capability.

Its beforeSubmitPrompt event carries the prompt text and its response accepts
additional_context — the same mechanism user-prompt-submit.py already emits
through hookSpecificOutput.additionalContext. Verified against a live Cursor
3.17.8 session, not inferred: an injected marker token came back at the head of
the model's reply, and with this adapter mode in place the prompt "My SwiftUI
list scrolls badly on device" is routed to axiom-swiftui, which the model then
opens before answering.

The adapter gains a prompt-submit mode that translates the payload, runs the
canonical router as a child, and returns its guidance as additional_context.
The router child now ships with the distribution.

Two details worth noting:

_workspace_root centralises the fact that Cursor omits cwd on several events
but always sends workspace_roots. Children run their own project gate against
the directory they start in, so the router child is now started in the
workspace rather than the plugin directory. session_start uses the same helper.

Child output is not forwarded blindly. The router emits a fixed template naming
skills from its own table and never prompt text, so the adapter requires that
shape, bounds the length, and rejects control characters before the string
reaches the model's turn.

Also stops the hooks writing __pycache__ into the plugin directory Cursor
manages, via sys.dont_write_bytecode and -B on the child spawn.
2026-08-23 12:38:09 -07:00
Charles Wiltgen 53b9aca70e fix(cursor): reject a command whose frontmatter name diverges from its filename
transformCommand derives the emitted path from the filename (`axiom-<stem>.md`)
but the emitted `name:` from frontmatter (`axiom-<name>`), so a command whose
frontmatter name differs from its filename would ship with the two disagreeing.
Check 12r would then fail with its generic blanket message, naming neither the
command nor the cause.

Skills and agents already assert name-matches-filename in the same loader;
commands did not. They do now, with an error that names the file and the
offending value. No current command diverges.
2026-08-23 12:18:12 -07:00
Charles Wiltgen bac58974c9 fix(cursor): repair hooks against the live Cursor payload contract
Verified against payloads captured from a live Cursor 3.17.8 agent turn. Every
existing adapter test built its own payload, so they all agreed with the
adapter's assumptions rather than with Cursor.

postToolUse(Write) carries no cwd. _validated_post_write_context required it,
raised "unsafe write cwd", and failed open, so the Swift guardrails never ran
in Cursor at all. cwd now falls back to workspace_roots[0], which every
payload does carry; all existing path validation still applies to it.

duration arrives as a float (11554.364 ms). The int-only check dropped it, so
duration_ms never reached the hints child and no duration hint could fire.
Both int and float are accepted and coerced.

Three assumptions were checked and are correct, and are now pinned so they
cannot rot: tool_output is a JSON-encoded string, the Shell variant is keyed
exitCode/output, and workspace_roots is always present.

real-payload.test.ts runs against the generated distribution rather than the
source tree, because the guardrails child is only co-located with the adapter
in the shipped output. The fixture records the captured shape with identity
fields redacted.

Also documents two support boundaries found while verifying: Cursor Free
refuses subagent delegation, so the plugin's agents register but cannot run
there, and plugin hooks load behind Cursor's enable_cc_plugin_import gate,
which clears them silently when off. Both reach the install guide and the
submission runbook, whose smoke matrix could not otherwise be completed on a
free account.
2026-08-23 12:04:14 -07:00
Charles Wiltgen 22f33e57c2 fix(cursor): correct is_background emission and fail the build on rewrite rot
transformAgent computed releasedBackground before applying the
CURSOR_FORCED_FOREGROUND exclusion for the emitted frontmatter, but after it
for the capability report. The two disagreed. It is masked today because the
only forced-foreground agent is writable and so is never background-eligible,
but the moment that set includes a read-only agent the shipped frontmatter
would claim is_background: true while the report and pre-deploy assert
foreground. The exclusion is now applied once, before rendering.

rewriteCursorHostClaims strips Claude-host capability claims ("xclog is on
PATH as a bare command, just run it") from canonical prose. Each pattern is
pinned to current canonical wording, so rewording upstream silently stops the
rewrite and ships Cursor users instructions to run a binary the plugin does
not install. An output-side token gate cannot catch this: once the wording
changes the old string is gone and the new one passes through unmatched. The
rewrites are now a table with per-pattern hit tracking, and a render asserts
every pattern matched something. A dead pattern fails the build. All seven
fire on the current corpus.

post_write validated the payload, including reading up to 1 MiB, before
checking the .swift extension, so a large non-Swift write paid that cost and
could report "write file too large". The extension is checked first; a missing
or non-string path still falls through and keeps its diagnostic.

shipped-runtime.test.ts covers a gap: cursorRuntime applies blind string
substitutions over Python source, and every hook test staged the pre-transform
bytes, so the files users actually run were never executed. It compiles all
four shipped runtime scripts and checks the renamed environment variable
agrees between the adapter that sets it and the hints script that reads it.
2026-08-23 12:02:57 -07:00
Charles Wiltgen f7d2e9f339 refactor(cursor): derive plugin inventories from the canonical tree
The reviewed contract froze the Axiom inventory in three places: exact
router/agent/command name lists and counts in contract.ts, the agent class
distribution in report.ts, and both again in pre-deploy check 12r. Because
build:cursor is wired into `npm run build`, 12r into `npm test`, and
build-cursor into set-version.js, adding any skill, agent, or command broke
the build, test, and release paths until all three fixtures were hand-edited.

Adding one well-formed agent reproduced it: "Cursor agent inventory differs
from reviewed contract".

This is the drift trap build-codex.ts:412 already documents fixing, and that
set-version.js avoids by counting from directory scans.

Inventories and class counts are now derived. What stays hand-reviewed is
policy: the forced-foreground set, read-only and allowed tool sets, allowed
agent fields, hook dispositions, and hook event/entry counts. Check 12r
derives expected names from the canonical tree, and its per-agent hook warning
and advisory maps now come from CURSOR_AGENT_ADVISORIES instead of a third copy.

assertCursorCapabilityInventory validates shape rather than membership, so it
still fails closed on empty, duplicate, and non-slug names.

report.ts gains a real invariant in place of the 30/1/11 literals: a writable
background agent is downgraded to foreground by the authority rule whether or
not anyone decided so, and must therefore appear in the reviewed
forced-foreground set. Stale entries fail too, and the message names both
sides and the remedy.

source-fidelity.test.ts keeps the removal honest. Deriving inventories means
generation, check:cursor, and every other Cursor test read the same loader and
agree with each other, so a loader defect that silently drops an agent would be
invisible to CI — 12r catches it, but the Cursor workflow does not run
pre-deploy. It compares loadCursorSource against an independent filesystem and
manifest scan.

Generated output is byte-identical: this changes validation, not what ships.
2026-08-23 12:01:49 -07:00
Eude Kinsley Lesperance b42afaf49c docs(cursor): document installation and marketplace release 2026-08-20 19:29:30 -07:00
Eude Kinsley Lesperance d4c0b92cfb feat(cursor): add generated distribution and CI validation 2026-08-20 19:28:19 -07:00
Eude Kinsley Lesperance ca6f4842d4 feat(cursor): add deterministic renderer 2026-08-20 19:27:06 -07:00
Eude Kinsley Lesperance a4cd97cf16 feat(cursor): add advisory hook runtime 2026-08-20 19:25:33 -07:00
Eude Kinsley Lesperance 703861faee feat(cursor): add compiler and capability transforms 2026-08-20 19:24:34 -07:00
Charles Wiltgen 09ba80c9cd chore(axiom-codex): rebuild Codex plugin variant for v27.0.0-beta.48 v27.0.0-beta.48 2026-08-20 10:35:49 -07:00
Charles Wiltgen a409de2558 chore: bump version to 27.0.0-beta.48
- Lazy-container memory: measured iOS 26 vs 27 behaviour, the not-SDK-gated
  finding, and the lift-and-bound rule that covers both cycles
- New peak-vs-resting footprint measurement technique in memory-debugging
- swiftui-performance-analyzer rule 11 + Phase 3 scroll-away question
- Reference-page standards repair on swiftui-layout-ref
- Project-specific identifiers removed from two shipped skills

Counts unchanged: 273 skills (164 discipline, 82 reference, 27 diagnostic),
42 agents, 17 commands.
2026-08-20 10:35:17 -07:00
Charles Wiltgen fd1236ce32 chore(skills): replace project-specific identifiers in shipped examples
production-triage.md used real APP-3V / APP-7B issue IDs in its sample Sentry
payloads and named a specific project in guidance ("the ExampleApp lesson was..."),
and localization-research-ref.md shipped a branded glossary. Axiom is not written
for any one app, and its examples should not carry another project's namespace.

Sample IDs are now ACME-*, consistent with the organizations/acme URL already in
the payload. The triage lesson keeps its substance without the attribution.

No behavioural change: the xcsym noise-rule contract parity check (12n) still
passes, so the noise classes and standing notes are untouched.
2026-08-20 10:34:40 -07:00
Charles Wiltgen 4047c638db fix(skills): version the lazy-container memory claim, add peak-footprint measurement
layout-ref.md stated that lazy stacks "recycle them when off-screen" with no
version qualifier. That is true on iOS 27 and false on iOS 26 — inside the
supported floor — so a developer choosing LazyVStack for an unbounded feed on
26 believed memory was bounded when it was not.

Measured, one binary, same device, runtime the only variable (800 rows x 100 KB):
- 26.5 LazyVStack: 800 of 800 alive at the bottom, +95 MB; List is no better at
  +88 MB; onDisappear fires for 786 rows while zero are freed; 8s idle frees none
- 27.0 LazyVStack: 50 of 800 alive, +1.5 MB
- Not SDK-gated: a binary linked against the 26.5 SDK evicts identically on 27,
  so shipped apps change behaviour on OS update with no way to opt out
- 27 rebuilds rows on scroll-back, so per-row @State silently resets

One rule covers both cycles, both halves load-bearing: lift per-row state above
the row, and bound whatever holds the weight. Bounded-NSCache fix verified at
+6.5 MB against the same scroll.

memory-debugging.md gains "Measure Peak, Not Resting" — task_vm_info,
ledger_phys_footprint_peak, os_proc_available_memory headroom, which number each
termination is judged against, that the peak never resets (take a delta), and
that a zero headroom reading is ambiguous per <os/proc.h>. Its decision tree no
longer routes "memory grows while scrolling" to "Memory leak (fix retention)".

swiftui-performance-analyzer gains rule 11 (heavy payload in per-row state under
a lazy container) and a Phase 3 question on state that must survive a scroll-away.

docs/reference/swiftui-layout-ref.md also gains the two required Reference-page
sections it was missing (When to Use, Example Prompts) plus Documentation Scope.

Published Swift extracted from the markdown and compiled with
swiftc -emit-sil -swift-version 6 at ios26.0 and ios27.0.
2026-08-20 10:34:04 -07:00
Charles Wiltgen 4741f4cbd0 chore(axiom-codex): rebuild Codex plugin variant for v27.0.0-beta.47 v27.0.0-beta.47 2026-08-17 11:56:22 -07:00
Charles Wiltgen c90e675786 chore: bump version to 27.0.0-beta.47
Ships List section index and system-spacing coverage. Axiom had zero coverage of
the section index — sectionIndexLabel appeared nowhere in the suite — and no
answer to "what spacing value should I use", while its own layout references
taught 30 hardcoded literals as canonical.

Adds sectionIndexLabel, listSectionIndexVisibility and listSectionMargins with
compile-proved availability (including the macOS/tvOS asymmetry), the HIG rule
against pairing an index with trailing row controls, and the system-spacing rule
grounded in the SDK's own optional-length signatures.

Also removes sub-floor content from the SwiftUI layout references — an
iOS 13-16 safe-area fallback and 23 dead availability markers — and ships the
in-flight sfsymbols CLI documentation.

Counts unchanged: 273 skills, 42 agents, 17 commands.
2026-08-17 11:53:04 -07:00
Charles Wiltgen b4ce8d0400 feat(skills): add List section index + system-spacing coverage; strip sub-floor content
Axiom had zero coverage of the List section index — a suite-wide grep for
sectionIndexLabel returned nothing — and no guidance on what spacing or margin
value to use, while axiom-swiftui's layout references taught 30 hardcoded
literals as canonical.

axiom-swiftui (skills/26-ref.md)
  Section Index and Section Margins. Research surfaced a third API that neither
  the report nor the issue knew about: listSectionIndexVisibility(_:), named by
  Apple's own doc comment on sectionIndexLabel. Two behavioral facts come from
  the SDK doc comments and appear nowhere in the web docs — the index is visible
  by default once any section carries a label, and an empty labeled section with
  a hidden header is how you show a letter that has no content. Also the i18n
  trap: a naive first.uppercased() collapses Japanese, Korean, Chinese, Greek
  and Cyrillic into one bucket, where UILocalizedIndexedCollation is correct.

axiom-design (skills/hig.md)
  The system-spacing rule. Apple's HIG publishes no iOS spacing scale, and the
  SDK encodes that: padding and safeAreaPadding default the length to nil,
  meaning system-determined. contentMargins, listRowInsets and listSectionMargins
  type it CGFloat? but give it no default, so the argument is mandatory while a
  number is not — .contentMargins(.horizontal) does not compile.
  Plus the section-index design rule: never pair an index with trailing row
  controls, and navigationLinkIndicatorVisibility(.hidden) as the way to keep
  push navigation regardless.

Version floor
  layout-ref.md and containers-ref.md are now free of sub-floor content. An
  iOS 13-16 manual safe-area fallback section is deleted outright, 23 dead
  availability markers removed, and the hardcoded spacing literals reduced to
  only the deliberate ones — spacing: 0, a photo grid's tight gutters, and
  containerRelativeFrame's required argument.

Prompt router
  Routes "index strip" and "alphabet scrubber" plus the three API tokens, with
  the English phrases behind the non_ios gate because Android's SectionIndexer
  is literally this feature. Adds a meta gate so prompts about Axiom's own
  internals stop routing to Axiom skills. Measured against the published hook
  across 372 prompts: 0 regressions, 5 new routes, false positives unchanged
  at zero.

Also ships the in-flight sfsymbols CLI documentation in axiom-design
(skills/sf-symbols.md, sf-symbols-ref.md): binary discovery, per-symbol and
per-rendering-mode availability via --json, deployment-target filtering, export,
and the trap that a zero-result search still exits 0.

Every API claim verified against iPhoneOS27.0.sdk; every published example
compile-tested with swiftc at ios26.0.
2026-08-17 11:52:13 -07:00
Charles Wiltgen e6631526ce chore(axiom-codex): rebuild Codex plugin variant for v27.0.0-beta.46 v27.0.0-beta.46 2026-08-16 14:12:43 -07:00
Charles Wiltgen e9cc221c8f chore: bump version to 27.0.0-beta.46
Ships status-bar-for-screenshots coverage in the axiom-tools device-control
reference: the devicectl `statusBar` family and its `screenshot` preset, the
override store it shares with `simctl status_bar`, and the silent exit-0
failures that make a stale status bar reach a shipped screenshot.

Counts unchanged: 273 skills, 42 agents, 17 commands.
2026-08-16 14:12:13 -07:00
Charles Wiltgen f1024d495a docs(axiom-tools): document status-bar setup for screenshots on Device Hub and Simulator
Axiom had no coverage of preparing a status bar for App Store screenshots. The
tool map also listed `status_bar` as a simctl sim-only feature, which stopped
being the whole story: devicectl owns a `device simulate statusBar` family whose
`preset screenshot` sets Apple's classic 9:41 / full-bars / 100% look in one
command, and both tools write the same override store.

Every claim was verified against a booted iPhone 17 simulator on Xcode 27.0 /
CoreDevice 642.9.1 — readbacks, exit codes, and a visual capture — rather than
from documentation, which is what surfaced the traps below.

- `override` merges into existing state, and several values silently fail while
  still exiting 0: `--operatorName` will not overwrite an existing carrier name,
  and `--dataNetwork wifi` stores `5G` if any `--cellularMode` is in the same
  call. Guidance is now clear-first, then assert on the readback — never on the
  exit code. The silent-failure table carries a "changes the capture?" column so
  a reader can tell which rows actually alter pixels.
- The `screenshot` preset writes the literal carrier string `Carrier` — the same
  placeholder screenshot-validator flags as a defect — and nothing can overwrite
  it afterward. Both sides now cross-reference: the skill says to skip the preset
  on carrier-rendering devices, and the agent explains where a flagged `Carrier`
  came from and how to re-capture.
- The two tools' flag vocabularies are not interchangeable (`draining` vs
  `discharging`, `5G` vs `5g`); cross-spellings hard-fail. Documented side by side
  with the observed exit codes.
- devicectl's `--cellular-strength` help advertises 1-5; a simulator rejects 5.
- `dataNetworkType` in `--json-output` comes back backtick-wrapped for values that
  are not valid Swift identifiers (`5G`, `4G`, `1x`, `5GUC`). Strip before parsing.
- devicectl's subcommand set follows a machine-wide CoreDevice, not xcode-select,
  so a version cannot be inferred from the selected Xcode. Documented as a
  capability probe the reader runs instead of a version claim.
2026-08-16 14:11:36 -07:00
Charles Wiltgen 5b165fdeaf chore: bump version to 27.0.0-beta.45
Hotfix release. xcui assert/wait/dialog/voiceover failed to parse any
accessibility tree containing a non-string AXValue (slider, page control,
toggle) — a defect present in every prior release, reported against beta.44.
v27.0.0-beta.45
2026-08-15 13:17:31 -07:00
Charles Wiltgen 42540c2801 fix(xcui): tolerate non-string AXValue so one slider stops breaking the tree
Reported from ExampleApp: `xcui assert` died with "cannot unmarshal number into Go
struct field AXElement.AXValue of type string".

AXValue is not consistently a string — a slider reports 0.5, a page control an
integer, a toggle a bool. Typed as *string, encoding/json failed the WHOLE
document, so a single numeric value anywhere on screen took down every command
that reads the accessibility tree: assert, wait, dialog, and voiceover together.
Measured on a stock Settings screen: 258 null, 10 string, 5 number — not an
exotic control.

Pre-existing, not a regression: axtree.go was last touched in aefd0240 and every
release since has shipped it. What is new is that it was found.

New AXText type accepts string, number, bool, or null and keeps the literal text
(0.5, 3, true) — which is what a user comparing against `--value 0.5` would type.
Applied to every text-ish field, not just the reported one: the cost of guessing
wrong about any of them is total parse failure, and there is no upside to
strictness when xcui compares them as text either way.

Also fixed a nil-ordering bug introduced while converting findByID — an empty
--id would have matched every element lacking an identifier.

9 regression tests: all six JSON shapes for AXValue, every text field carrying a
number, and numeric-identifier lookup.
2026-08-15 13:16:45 -07:00
Charles Wiltgen 7c03e0636e chore: bump version to 27.0.0-beta.44 v27.0.0-beta.44 2026-08-15 12:41:36 -07:00
Charles Wiltgen eb943ea5c5 chore: rebuild generated artifacts (Codex variant, MCP bundle, xcui binary)
Derived output only — regenerated from the source changes in this series via
`npm run build:codex`, `pnpm run build:bundle`, and `make install` in tools/xcui.

The Codex tree is the largest delta: it now carries three previously excluded
suites, translated agent/command invokes, policy blocks in every agent-skill's
openai.yaml, and no longer carries the frontmatter key Codex's own validator
rejects. Verified against that validator directly — "Plugin validation passed",
exit 0, zero errors, which is the first time this build has passed it.
2026-08-15 12:31:57 -07:00
Charles Wiltgen df9e81a0b4 docs: document how to control Axiom activation, and fix the Codex parity table
install.md gains a "Controlling When Axiom Activates" section. Axiom shipped zero
guidance on this, so users were reaching for Claude Code's per-skill
skillOverrides — which its documentation states plainly does not apply:
"Plugin skills are not affected by skillOverrides. Manage those through /plugin
instead." The supported levers are the whole-plugin toggle and the fact that
every /axiom:* command is user-invoked only.

codex-install.md's differences table advertised the abandoned blanket-router
policy as a feature — "175 specialized (Codex has native routing)" — and claimed
"Agents: Not supported in Codex plugins", which was never true: all 42 ship as
skills. Corrected to real counts, with the 31 withheld files accounted for
(30 inlined auditor procedures Codex receives as first-class skills instead, plus
getting-started).
2026-08-15 12:31:20 -07:00
Charles Wiltgen a28ef21c72 feat(pre-deploy): gate the failure classes that let this session's bugs ship
Four new checks, each closing a class where both halves looked locally correct
and only the built artifact showed the break.

- 11z runs Codex's OWN validate_plugin.py against axiom-codex, from the copy
  Codex extracts to ~/.codex/skills/.system/. 12p asserts one rule transcribed by
  hand, which catches the rule we know about; running the validator catches the
  next one. Skips cleanly when Codex or PyYAML is absent.
- 12o resolves every pointer in the EMITTED Codex tree — sub-skill paths, bare
  agent names, and /axiom: commands. Source cross-references were valid and the
  exclusion list was valid; only the emitted tree showed 41 dangling pointers.
  Check the artifact, not the inputs.
- 12p asserts Codex invocation policy in BOTH directions: agent-skills carry
  policy.allow_implicit_invocation: false with a $-named default_prompt, routers
  stay discoverable, no emitted skill carries the frontmatter key Codex's
  validator rejects, and no description was truncated at a file-extension period.
- 12q asserts all 17 commands and the ask.md template are user-invoked only.

Also corrected 12n's comment, which justified inlining crash-triage safety text
by citing the axiom-shipping Codex exclusion that no longer exists. Axiom-dtq
treated that symptom; the root cause was the inherited exclusion. The inlining
stays — safety text is worth carrying at the point of use.
2026-08-15 12:30:39 -07:00
Charles Wiltgen 7482a0273d fix(codex): ship the suites Codex skills already point at, and fix the pointers
Three suites (axiom-tools, axiom-shipping, axiom-apple-docs) were excluded from
the Codex build. That was not a decision — the original list (2b5d6adb) was a
blanket "Router skills — Codex has native progressive disclosure, so these are
unnecessary". The premise was abandoned and the list curated, but these three
survived: two still annotated "Codex fit not yet assessed", and axiom-tools
carried a reason written when it was named axiom-using-axiom and held ONLY
discipline text. It then absorbed six sub-skills, five of them harness-neutral
tool references, all inheriting an exclusion that predated them.

Measured cost: 41 dangling "see X" pointers plus ~94 name citations across ~40
emitted Codex skills, aimed at files the build never wrote.

- CODEX_EXCLUDED_SUITES is now empty; per-FILE exclusion replaces it, holding one
  entry — getting-started.md, which drives AskUserQuestion and cites 15 /axiom:
  commands, and has zero inbound references.
- dropExcludedSubSkillRows strips a router's table row for a withheld sub-skill,
  so per-file exclusion cannot recreate the defect it exists to avoid.
- rewriteInlineAuditorBlockForCodex translates auditor file paths to Codex skill
  names; that block previously told Codex users to read files this build skips.
- rewriteAgentInvokesForCodex fixes the layer under that: routers said "Launch
  `build-fixer` agent or `/axiom:fix-build`" while `axiom-fix-build` — the thing
  that exists there — appeared zero times. Neither agents nor /axiom: commands
  exist on Codex, and the mapping is not derivable (spm-conflict-resolver →
  axiom-resolve-spm). Applied to routers, agent-skills, and sub-skills, since a
  verbatim copy reintroduces it. Two commands have no Codex equivalent at all
  (console, compare-traces) and are labelled Claude Code-only rather than
  redirected somewhere that does a different job.
- agentToSkillName moved into the shared module so the pre-deploy gate derives
  the agent-skill set instead of guessing from names — a name heuristic
  misclassifies both ways (axiom-health and axiom-testing are routers that read
  verb-first; axiom-swift-simplifier is an agent-skill that does not).
- The count test now derives the agent total from the agents directory; the
  hardcoded 41/68 had drifted from a real 42/69 and passed anyway by being
  self-consistent.
2026-08-15 12:30:00 -07:00
Charles Wiltgen 3a47a8b3d2 fix(commands): stop Claude auto-invoking /axiom:ask and /axiom:status
15 of 17 commands carried `disable-model-invocation: true`; ask.md and status.md
did not, so Claude could invoke both unprompted.

/axiom:ask is the worst case. It is 7.5 KB listing all 26 routers, and it exists
as the MANUAL escape hatch for when auto-routing misses — so making it
auto-invocable loaded the entire routing table to redo routing the routers had
already done, and put it in competition with the routers it backstops.
/axiom:status ran shell checks nobody asked for.

ask.md is generated from scripts/templates/ask.md.template by set-version.js, so
the template is fixed too: patching only the output would have regressed on the
next version bump, which is plausibly how this was lost in the first place.

Added pre-deploy gate 12q, which asserts every commands/*.md AND the template
carry the key.
2026-08-15 12:29:17 -07:00
Charles Wiltgen 39d7a8435f docs(xcui): make xcui the front door for simulator input and resize testing
Repoints guidance from bare `axe tap` to `xcui tap` so the SimulatorKit handling
comes along automatically, and routes breakpoint testing at the new
`xcui resize sweep` instead of a hand-rolled devicectl loop.

- The axiom-tools router's resize row now points at the sweep; the raw
  appResize section is kept for driving it by hand.
- resize-auditor's final validation step is one command rather than
  start/set/kill plus a separate assert — which is what made that step
  agent-executable.
- The docs agent page still described the old two-tool flow; found by preflight's
  companion-docs drift check and fixed in the same pass.

Guarded against a regression this repoint introduced: xcui is on PATH
automatically ONLY on Claude Code. Codex, Pi, and MCP installs ship skills with
no binaries, so guidance telling those users to run `xcui tap` pointed at
something they do not have, where the old `axe tap` worked. xcui-ref even
asserted "xcui is on PATH as a bare command". The invocation section is now
harness-aware, and the guidance says `command -v xcui` first with the bare-`axe`
fallback spelled out.
2026-08-15 12:28:42 -07:00
Charles Wiltgen 25fcc33bf8 fix(skills): correct Xcode 27 guidance that silently does nothing
`killall -9 Simulator` was prescribed as the stuck-simulator nuclear option in
six shipped places. Xcode 27 ships Contents/Applications/DeviceHub.app and no
Simulator.app at any path, so on a 27-only Mac the command kills nothing while
an agent reports the fix as done. Verified: `find` for Simulator.app under
Xcode-beta.app returns nothing; the live processes are DeviceHub (27) and
Simulator (26.6).

Now `killall -9 Simulator DeviceHub`, verified against the PROCESS rather than
`$?`. This second half came out of behavioural testing: killall exits 0 when
EITHER name matched, so on a machine carrying both Xcodes a 0 can mean "killed
Simulator, DeviceHub still running". Every site now confirms with
`pgrep -l Simulator DeviceHub`.

Also corrected, all verified against the installed toolchains:

- Four GUI paths now branch on Xcode version instead of naming only the pre-27
  surface (Devices and Simulators, the Simulator I/O menu). Previously only
  watch-device-diag did this.
- device-control-ref gains the bundle path / process name / bundle id table per
  Xcode version — the facts the killall fix depends on.
- CarPlay: the Simulator menu path AND `defaults write com.apple.iphonesimulator
  CarPlayExtraOptions` are Xcode 26-only. The key appears nowhere in the 27
  toolchain, and on 27 that `defaults write` FAILS SILENTLY — it validates no key
  names, so it exits 0 writing into the domain of an app the toolchain no longer
  ships. That silence is why the recipe keeps propagating from stale notes.
- CarPlay Simulator requires a physical iPhone over USB and cannot drive a booted
  simulator (otool: links MobileDevice, not CoreSimulator; its own strings say
  so). Load-bearing now that the Simulator path is dead on 27 — it is the only
  CarPlay route, so a reader could otherwise plan a device-free session that
  cannot work.
- axe-ref dropped "AXe 1.7.1 has no fix yet" and the universal "bare axe fails";
  both were false at AXe 1.8.0. Guidance is now conditional on what `xcui doctor`
  observed.
2026-08-15 12:28:00 -07:00
Charles Wiltgen 8e61d9df1c feat(xcui): add a VoiceOver toggle and a resize sweep primitive
Two capabilities that had no scriptable path before.

VoiceOver toggle — closes a documented v1 gap. `a11y set --toggle voiceover`
maps to `devicectl device settings voiceover`, the mechanism that did not exist
when the toggle was omitted. It is the only toggle that leaves the
simctl/defaults world, since simctl has no VoiceOver setter. `a11y reset` now
disables it too: every other toggle tears down through simctl, so a VoiceOver
left running would have been the worst leak of the set — a screen reader that
survives the run changes what every later run sees.

differentiate-without-color and bold-text stay unsupported; `devicectl device
settings` covers only appearance, audio, biometrics, reset, and voiceover.

resize sweep — drives breakpoints and checks each in one JSON envelope, wrapping
the three tedious parts of `devicectl device appResize`:

- Session lifecycle. `appResize start` holds the session only while it runs, so
  it is backgrounded and reaped on every exit path, including SIGINT/SIGTERM. A
  bare defer covered only normal returns; a harness signalling just this PID
  would have stranded the app on the Resizable display.
- Actual vs requested size, read from the field devicectl labels "Actual" rather
  than inferred from info appResize's preferredSize, which names the REQUEST. A
  device that clamps (an iPhone 17 turned 1100x500 into 1100x550) is exactly the
  case a sweep exists to catch, and reading the proxy would report a clean pass
  for a width never exercised. Readiness waits for BOTH the info query and the
  geometry flush — two unordered processes, and keying off one alone reported
  honored:false for sizes the device honored.
- Post-resize flakiness: the automation session can time out once while the
  display transitions, so each assertion retries before failing.

--screenshot-dir writes a PNG per breakpoint from the Resizable display. An
id-presence assertion catches "the element vanished", but resizing breaks layouts
by overlap and truncation, which only a picture shows.

--strict fails a clamped step. Without it a fully clamped sweep still exited 0
when assertions passed, so an exit-code-only consumer read "all breakpoints
validated" for breakpoints the device never produced.

Three CoreDeviceErrors are separated because each needs a different fix: 1001
(device has no Resizable App Management), 24001 (no foreground app to move),
24004 (no session — not terminal; the startup poll retries through it).

The output buffer is mutex-guarded: os/exec writes the child's output from its
own goroutine while the parser reads it, which -race catches.
2026-08-15 12:27:41 -07:00
Charles Wiltgen 9abd175aa9 feat(xcui): forward AXe input verbs so guidance cannot drift out of them
Adds twelve passthrough verbs (tap, slider, type, swipe, drag, touch, gesture,
button, key, key-sequence, key-combo, screenshot) that forward argv to AXe
verbatim — same flags, same output, same exit code. xcui parses none of AXe's
flags, so they stay flag-compatible as AXe evolves.

The point is that input goes through runAxe, which owns the SimulatorKit
fallback. Guidance that says "run `xcui tap`" cannot drift out of that handling
the way "run `axe tap`, and also remember a DEVELOPER_DIR prefix when doctor
tells you to" can.

- --udid is injected when omitted; hasFlag handles both --udid X and --udid=X and
  does not false-match a longer flag sharing the prefix.
- AXe's exit code is propagated via errors.As on an ExitCode() interface rather
  than *exec.ExitError concretely — broader, and constructible in a test without
  spawning a process. A passthrough that swallows the failure code is not
  transparent.
- Only a LEADING --help is a help request. Scanning all of argv hijacked a
  literal one (`xcui type -- --help`) and swallowed the error, so `xcui tap
  --help` exited 0 printing nothing when AXe was absent.
- Dispatched after xcui's own verbs so they always win a name collision with a
  future AXe subcommand; a test asserts the two sets stay disjoint.
- describe-ui / list-simulators / init are not forwarded (xcui owns those jobs);
  stream-video / record-video stay bare because they outlive any request timeout.
- doctor now reports AXe verbs xcui does not forward, so the static allowlist
  cannot silently lag the tool it fronts. Anchored to the exact two-space indent
  of the SUBCOMMANDS block: a looser pattern was terminal-width dependent and
  matched a wrapped description word while missing a real verb.
2026-08-15 12:27:26 -07:00
Charles Wiltgen 9d2b1cba6d fix(xcui): react to AXe SimulatorKit failures instead of predicting them
The DEVELOPER_DIR override was predictive: stat the selected Xcode for
SimulatorKit.framework at the legacy path and pre-emptively pin an older Xcode
for every AXe call. Correct when written (2026-07-06), wrong by now — AXe 1.8.0
finds the framework at its Xcode 27 home unaided, so the override fired when
nothing needed fixing, silently downgraded the toolchain AXe ran under, and made
`xcui doctor` assert that bare `axe` fails when it demonstrably does not. It had
no path that ever un-set itself.

Now reactive: run AXe bare, and only on an observed SimulatorKit load failure
find a fallback DEVELOPER_DIR and retry. It retires itself when upstream fixes
things, with no version table to maintain.

- Record the override only AFTER the retry is observed to work.
  axeFallbackDeveloperDir only stats a path, so a candidate can carry the legacy
  directory and still fail to run AXe. Recording first pinned a known-bad
  toolchain for the whole process and made doctor report "no fallback was found"
  when one was found and tried.
- Memoize only conclusive outcomes, so an unrelated failure (no booted sim, bad
  args) leaves the question open rather than pinning a verdict off noise.
- currentDeveloperDir() honours $DEVELOPER_DIR before xcode-select, matching
  Apple's precedence and giving callers an escape hatch they did not have.
- doctor's note now reports what xcui observed rather than asserting third-party
  behaviour, and only appears when the override actually engaged.
- isSimulatorKitLoadError tracks AXe 1.8.0's rewording ("could not load simulator
  support" replaced "Failed to load essential private frameworks"); only the
  shared "does not exist" tail kept the detector alive across that change.
- Added an axeExec seam: on a healthy machine the reactive path is unreachable
  end-to-end, and an untested retry is how the last workaround rotted unnoticed.
2026-08-15 12:27:13 -07:00
Charles Wiltgen 5dff7e53ca chore: gitignore .beads.gate.lock
A stale zero-byte gate lock sat untracked in the repo root and blocked
`set-version.js --tag`, which refuses on any unrelated working-tree change.
Beads artifacts are never committed (`.beads/` and `.beads-credential-key`
are already ignored); this one was missed.
v27.0.0-beta.43
2026-08-14 14:14:31 -07:00
Charles Wiltgen 6cf69468d2 chore(axiom-codex): rebuild Codex plugin variant for v27.0.0-beta.43 2026-08-14 14:13:57 -07:00
Charles Wiltgen 7337b3cc5c chore: bump version to 27.0.0-beta.43
- axiom-xcode-mcp rewritten for the Xcode 27 headless MCP server (mcp-server),
  reference regenerated from a live tools/list capture (20 -> 54 tools)
- devicectl appResize documented; the "resize is GUI-only" claim corrected in
  five files, making resize-auditor's final validation step scriptable
- Manifest routing description updated: it still advertised the nonexistent
  RunTests tool and had no headless trigger, so Layer-1 routing could not
  surface the rewritten content
2026-08-14 14:13:23 -07:00
Charles Wiltgen 82e403421b fix(axiom-tools): free resize is scriptable — document devicectl appResize
Axiom asserted "GUI only — no public window-resize API in XCUITest, xcui,
simctl, or devicectl". `devicectl device appResize` is exactly that API.

Verified end to end on an iOS 27 simulator against Xcode 27 beta 5: a purpose-
built SwiftUI probe was driven 500x800 -> 900x600 -> 1100x500 from the CLI with
the accessibility tree asserted at each step, confirming children re-center.

- Document start/set/observe + `info appResize`, --preferred-size, --corner-radius
- Note actual size can differ from requested (1100x500 yields 1100x550) — read
  it back rather than trusting the request
- Correct the four files carrying the GUI-only claim; resize-auditor's final
  validation step is now scriptable instead of a human handoff
- Add `devicectl device settings voiceover` to the verified matrix — one of the
  three toggles xcui v1 omitted for lack of a mechanism (Axiom-h8m)
- Reframe device-control-ref from uptime to capability-and-privilege: the CLI
  tools run unprivileged, which is what matters in CI
2026-08-14 14:12:47 -07:00
Charles Wiltgen 8e30700973 fix(axiom-xcode-mcp): rewrite for the Xcode 27 headless MCP server
The suite's premise — "mcpbridge requires a running Xcode with a project
open" — is false on Xcode 27. `xcrun mcp-server` runs the tool service with
Xcode.app closed. Verified against 27A5237l by driving raw JSON-RPC over
mcpbridge with no Xcode process running.

- Split the running-Xcode requirement into an OS26 paragraph and an OS27 one;
  clients still register `xcrun mcpbridge` (transport), mcp-server is the service
- Add the headless lifecycle (enable/start/open/status/stop/disable) and the
  sudo approval model (approve/allow-folder/deny/clear-permissions)
- Document the top headless hazard: an unapproved agent blocks forever on an
  XcodeService dialog while initialize succeeds and status reports running:true
- Regenerate xcode-mcp-ref from a live tools/list capture: 20 -> 54 tools across
  12 categories, params from inputSchema, returns from outputSchema, 15 notes
- Replace tabIdentifier (absent from all 54) with workspaceIdentifier, which is
  required in practice despite appearing in no tool's `required` list
- Remove XcodeListWindows, XcodeListNavigatorIssues, XcodeGetCurrentFile, RunTests
- Add the workspace-management family the headless bootstrap depends on
- Keep DocumentationSearch: it is workspace-gated, not dead (53 tools without a
  workspace open, 54 with; the server advertises listChanged:true)
- Retire the XcodeMCPWrapper proxy advice — structuredContent is fixed on 27
2026-08-14 14:12:00 -07:00
Charles Wiltgen 18fff8c3f7 chore(axiom-codex): rebuild Codex plugin variant for v27.0.0-beta.42 v27.0.0-beta.42 2026-08-12 13:20:24 -07:00
Charles Wiltgen 4ed286b847 chore: bump version to 27.0.0-beta.42
- axiom-build/build-performance: OS27 ContentBuilder type-check restructure
  (two-tier availability with compile measurements, TN3211 migration fixes,
  WWDC 2026-269), with companion docs page updated

Counts unchanged: 27 suites, 273 skills, 42 agents, 17 commands.
2026-08-12 13:19:29 -07:00
Charles Wiltgen 90ce821a86 docs(axiom-build): fold OS27 ContentBuilder type-check restructure into build-performance
- New Pattern 1 subsection: Xcode 27 unifies Group/Section/ForEach under
  ContentBuilder (a ViewBuilder typealias) with TupleContent conditional
  conformances; Scene/Tab/Table keep dedicated builders
- Two-tier availability, compile-measured: mixed Section/Group/ForEach nest
  7.2s (Xcode 26) -> 1.7s at any deployment target on Xcode 27; homogeneous
  11-level Group collapses fully only at iOS 27 targets (1.8s -> 0.15s)
- TN3211 source-incompatibility shapes and fixes folded; WWDC 2026-269 and
  /technotes/tn3211 added to Resources
- Companion docs page gains matching When to Use bullet, example prompts,
  and an Xcode 27 Features section
2026-08-12 13:18:49 -07:00
Charles Wiltgen 1ed98a943e chore(axiom-codex): rebuild Codex plugin variant for v27.0.0-beta.41 v27.0.0-beta.41 2026-08-11 17:48:17 -07:00
Charles Wiltgen 13a84dd4c5 chore: bump version to 27.0.0-beta.41
- axiom-uikit hook now routes resize/resizable/foldable prompts, so
  resize-auditor reaches the suite that owns it (2 of 5 canonical prompts
  routed before; 5 of 5 now)
- skill-annotations.json regenerated after 2.5 months of drift: 106 previously
  unannotated skills gain category faceting and the 2x tags search boost
- generate-annotations wired into build:bundle so coverage cannot drift again
- MCP SDK >=1.30 transport errors are logged instead of exiting silently

Counts unchanged: 27 suites, 273 skills, 42 agents, 17 commands.
2026-08-11 17:47:40 -07:00
Charles Wiltgen c6960167f0 fix(axiom-mcp): stop skill-annotations.json drifting, and log transport errors
skill-annotations.json supplies category and tags to the search bundle, where
tags carry a 2x BM25 boost. It had not been regenerated since 2026-05-28, so
106 of 300 skills had no entry — the Foundation Models adapters/evaluations
families, the CoreML trio, watchos-a11y, and every sub-skill added since.
Affected skills still loaded and were findable by body text; they ranked lower
and could not be filtered by category.

Three properties compounded: the generator had no npm script, CI step, or hook;
it is additive-only, so it never reported what was missing or failed a build;
and its last run was bundled incidentally into an unrelated feature commit.

- generate-annotations now runs inside build:bundle, so coverage cannot drift
- it writes only when content changes, so a standalone run does not report a
  stale bundle over identical bytes (staleness.ts only forgives git-clean skew)
- adds a non-destructive `annotations` script for regenerating without a bundle

Fixing that exposed a second defect. The bundler excludes generated
inline-auditor sub-skills — MCP ships each auditor as an agent, so bundling the
inlined copy double-counts — but the generator had no such exclusion and
produced 30 entries for files the bundle drops. The marker was a bare literal
duplicated across consumers behind "keep in sync" comments. It is now one
exported predicate, isGeneratedSubSkill, shared by bundler and generator, named
to match the existing root-side definition.

Result: 299 of 300 bundled skills annotated, 0 orphans, generator idempotent.

Also handles MCP SDK >=1.30 transport errors, which route to onerror and then
close; unhandled, the server exited silently with nothing in the log.

Known gaps, filed not fixed: dev-loader.ts still lacks the exclusion, so dev
mode serves ~30 skills production drops (Axiom-71h); the marker is still two
copies rather than one, across a package boundary (Axiom-ris).
2026-08-11 17:47:31 -07:00
Charles Wiltgen c3bbfab55c fix(axiom-uikit): route resize and foldable prompts to the UIKit suite
The axiom-uikit branch of user-prompt-submit.py contained no `resiz` token,
so resize-auditor's own canonical example prompts did not reach the suite that
owns it — 2 of 5 routed. "Audit my app for screen resizing support" matched
nothing; "Is my app ready for resizable windows on iOS 27?" went to
axiom-apple-docs.

The token was already present in the swiftui, testing, and games branches; only
the owning suite was missed. Adds bidirectional resize proximity matching plus
foldable and folding-device phrasings, so word order does not matter. Uses
`apps?\b` rather than bare `app` so "Apple" and "happy" do not trigger it.

All 5 canonical prompts now route. New test_uikit_resize covers them, with
negatives for "resize an image" and "create a folder" guarding over-match.
2026-08-11 17:46:40 -07:00
Charles Wiltgen 59b64d6b3e chore(axiom-codex): rebuild Codex plugin variant for v27.0.0-beta.40 v27.0.0-beta.40 2026-08-11 08:59:13 -07:00
Charles Wiltgen 0e5c9e7902 chore: bump version to 27.0.0-beta.40
Xcode 27 beta 5 (27A5237l) sweep plus a camera-reliability repair.

- HealthKit: beta 5 ships the earliestAuthorizedSampleDate doc comment;
  the dictionary is sparse and a floor may only clamp forward
- CoreLocation: LiveConfiguration.maritime (OS27)
- CryptoKit: Insecure.UnauthenticatedAES / UnauthenticatedChaCha20 (OS27)
- WebKit: allowsJSHandleCreationInPageWorld; 9999 sentinels resolved to 27.0
- StoreKit: three standing re-checks verified unchanged, stamps refreshed
- Xcode MCP: ExecuteSnippet renamed to RunCodeSnippet; 53-tool surface scoped
- Camera: error-channel + controller-dealloc diagnostics, iOS 18 notification
  migration across four files, .systemStress corrected to .batteryStress

Swift 6.4 probe was a no-op this beta: mapKeyedValues, stdlib FilePath, and
BorrowingSequence all still absent.

Counts unchanged: 273 skills, 42 agents, 17 commands.
2026-08-11 08:58:41 -07:00
Charles Wiltgen 11f2d81498 fix(axiom-media): camera error-channel and dealloc diagnostics, notification migration, .batteryStress
Three defects and two new failure modes, all verified against the iOS 27 SDK.

An invented symbol was shipping. Both camera skills named .systemStress as an
OS27 system-pressure factor. The SDK defines exactly six factors and that is
not one of them — the real symbol is .batteryStress. The surrounding prose was
accurate; only the identifier was wrong. It survived every gate because it
lived in a markdown table row rather than a swift fence, and the
compile-the-published-text discipline only extracts fenced blocks.

The auditor would have failed correct code. camera-auditor searched only the
legacy notification constant names, which are not substrings of the current
ones — so an app correctly observing AVCaptureSession.runtimeErrorNotification
was reported as missing a runtime-error observer. It now searches both.

Ten uses of the iOS 18-deprecated .AVCaptureSessionWasInterrupted family
across four files migrated to the current AVCaptureSession.<name>Notification
forms, each mapping read from NS_SWIFT_NAME in the SDK header.

New in camera-capture-diag:
- Step 5, Turn On the Error Channel. A clean console is not evidence of
  health: AVCaptureSession reports runtime failures through NotificationCenter,
  not by throwing, and a runtime error leaves the session stopped without
  restarting itself.
- Pattern 18, Controller Deallocated. A controller that falls out of scope
  tears down the session and the preview goes black with no error, no
  exception, no log.
- DiscoverySession named as an explicit non-fix for a nil device: it queries
  the same enumeration, so nil merely becomes [].
2026-08-11 08:57:35 -07:00