feat(graph): compile apple_library through starlark rule impl#86
Merged
Conversation
When work units are independent, drive them concurrently rather than serializing. Sequential code should be a deliberate choice for data dependencies, not the default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The apple_library rule now carries an impl callable in the prelude that composes a real swiftc command line and emits it through native globals (xcrun_swiftc, apple_triple, declare_output, run_action). The analysis pass evaluates the impl per target with a ctx dict carrying the typed attrs, glob-expanded srcs, dep provider records, and the workspace build directory; the actions the impl declares are converted into cacheable RunCommand actions and executed through run_with_cache. The action's input digest folds in the swiftc identity, source content digests, and the dep action digests so a swap of Xcode or a change to any transitive source invalidates the parent cache slot. Recursive builds walk apple_library dependencies first and forward each dep's swiftmodule directory through -I so import statements resolve. The other apple rule kinds (apple_framework, apple_application, apple_test_bundle) keep their placeholder shell scripts; a small RULES_WITH_IMPL list gates the new path so unfinished rules don't trigger analysis. Fixture-backed shellspec coverage exercises the compile, the recursive build, the cache hit, and the dep-change invalidation paths, gated on xcrun availability. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Rust side now exposes only generic primitives: host_arch, host_os, host_which, host_command, glob, declare_output, run_action. Anything domain-specific (xcrun discovery, SDK name mapping, LLVM triple format, file-extension filtering) lives in apple.star and is implemented in starlark on top of these primitives. The Rust analysis layer becomes an executor for whatever the prelude declares. ctx["srcs"] now carries the raw glob patterns from the manifest; the impl calls glob(ctx["srcs"]) to expand them and filters by extension itself. The crates/once-cli/src/commands/graph/apple submodule and the once-frontend analysis_host helper are gone; the driver checks rule_has_impl against the prelude rather than a hardcoded list, so the set of impl-bearing kinds is owned by apple.star. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…escape test The canonicalize-and-strip-prefix check that rejects globs resolving outside the workspace was added without an exercise for the new analysis.rs location after the apple/srcs.rs module went away. Add a unix test that drops a symlink pointing at an external tempdir under the package and asserts expand_globs surfaces the "outside the workspace" error. Document on the function that the check is best-effort against on-disk state (the workspace is trusted) and that Windows junction coverage waits on Windows CI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lean on Rust's ownership model in the graph build pipeline: * `run_declared_actions` now takes `AnalysisResult` by value and destructures it, so the impl-returned provider record moves into `BuildOutcome` instead of being cloned. Each declared action is consumed by `into_iter`; its `env` moves into `Action::RunCommand` via a final destructure inside `declared_to_action`. * `compose_input_digest` borrows everywhere it can: sources sort as `Vec<&str>` instead of a cloned `Vec<String>`, dep digests sort by index into the caller's slice instead of `to_vec()`, and the env walk drops its sort buffer entirely (`BTreeMap` already iterates in key order). * `build_target` destructures the returned `BuildOutcome` so `outputs` moves into the run record rather than being cloned; `action_digest` is `Copy` and `cache_tag` is `&'static str`, so there's nothing left to clone. Net effect: the heaviest fields (provider `JsonValue` tree, output paths, env map) move from declaration through execution into the returned record without intermediate clones. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…holds Second ownership pass: * `BuildSession.targets` now stores `Arc<GraphTarget>` so the spawn path bumps a refcount instead of deep-cloning the row (and its `Vec<String>` srcs + `BTreeMap` attrs) twice per build task. * `BuildOutcome` drops its `Clone` derive: the type has exactly one owner at a time, and the producer→outcomes→consumer transitions are all moves. * `build_reachable` tracks `remaining_readers` per dep, so the last consumer moves a producer's `JsonValue` provider out of the session map via `outcomes.remove` instead of cloning it. * `reachable_impl_targets` checks membership before insert so the owned `target_id` popped off the stack moves straight into the HashSet. * `HostCache::which`/`command` release the mutex before the filesystem walk and `Command::output` so a slow `xcrun` spawn on one analysis task doesn't serialise sibling tasks asking about other binaries. Lock acquisition is encapsulated in small helpers so the slow path reads as plainly as the hot one. Also document the build pipeline (BuildSession, AnalysisEngine, HostCache, parallel scheduling, last-reader provider move, input digest composition) in `docs/guide/apple-graph.md`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two cache-related apple_spec examples seed the build with an unconditional `once --format json build` before the `When call`. On Linux runners `Skip if 'apple toolchain unavailable on this host'` correctly skips the assertions, but shellspec still executes the example body, so the priming run blows up with `xcrun not found on PATH` and the example aborts with exit 2. Mask the priming with `|| true` (and the `printf` followup with the same) so the preamble is a no-op when the toolchain is unavailable, while still doing useful work on macOS where the assertions run for real. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 1 of Buck2 / Bazel parity. The previous provider record
exposed only `{swiftmodule_dir, archive}` — enough for a sibling
library to find this module's interface, but nothing for a future
linker, bundler, or test rule to compose its own command line.
Provider expansion is the load-bearing piece every other Round
depends on; without it modulemap generation, mixed-language
compilation, and multi-arch linking each have to rediscover the
graph.
New provider fields (rules_swift `SwiftInfo` / `CcInfo` shape):
* `objc_header` — generated `<Module>-Swift.h` so downstream ObjC
consumers can `#import` Swift symbols
* `transitive_swiftmodule_dirs`, `transitive_archives` — for `-I` and
link inputs
* `transitive_sdk_frameworks`, `transitive_weak_sdk_frameworks`,
`transitive_sdk_dylibs` — frameworks/dylibs propagated to the link
* `transitive_linkopts`, `transitive_defines` — flags / `-D` macros
that flow with the module
A `_collect_transitive` helper keeps insertion order while deduping,
matching Bazel's depset(order=topological) semantics.
New schema attributes:
* `target_sdk_version` — distinct from `minimum_os` (Buck2 splits
deployment target from build SDK; we follow suit)
* `defines` — `-D` flags, propagated transitively
* `emit_dsym` — adds `-g` to swiftc so a future dsymutil step has DWARF
to extract a `.dSYM` from
`apple_library` now always emits the ObjC interop header alongside
the `.swiftmodule` / `.swiftdoc` / `.a`. The spec asserts the new
output exists.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 2 of Buck2 / Bazel parity for apple_library. * `exported_deps` (Buck2 parity): direct deps are private to this target's compile -I path; only `exported_deps` flow their transitive swiftmodule_dirs through to consumers. Link-affecting fields (archives, frameworks, linkopts, defines) still propagate unconditionally because the link line needs everything reachable. The provider now also surfaces `label_id` so the impl can match dep records against the exported_deps list. * `sdk_variant` (`simulator` | `device`): picks `iphoneos` vs `iphonesimulator`, `appletvos` vs `appletvsimulator`, etc., and strips the `-simulator` triple suffix for device builds. macOS ignores the variant. * `xcode_developer_dir`: pins a specific Xcode by overlaying `DEVELOPER_DIR` on the `xcrun` invocation. The dir is folded into `swiftc_identity` so different Xcodes partition the action cache. * `alwayslink`: provider hint a future linker rule will read to emit `-Wl,-force_load` for archives that need whole-archive linking (XCTest test discovery, ObjC categories). Each `transitive_alwayslink_archives` is accumulated alongside the normal `transitive_archives` list. Supporting changes: * `host_command(argv, env=...)` now takes an optional env dict on the starlark side. The Rust `HostCache` keys results on `(argv, env)` so a different `DEVELOPER_DIR` produces a different cache slot. * `run_action(..., env=...)` was already plumbed; the prelude now passes the xcrun env through so the swiftc compile honours the pinned Xcode. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 3 of Buck2 / Bazel parity. apple_library now compiles real mixed-language libraries: * Source filters route each glob match to its own driver: swiftc for `.swift`, clang for `.m`/`.mm`/`.c`, clang++ for `.cc`/`.cpp`/`.cxx`. * `bridging_header` attr threads `-import-objc-header <path>` into swiftc so Swift sources see ObjC symbols. The bridging header and every `exported_headers` entry feed the swiftc action as hash-tracked inputs so an edit to either invalidates the cache. * `_xcrun_clang` resolves `clang` + the SDK sysroot via `xcrun --show-sdk-path` and folds both plus DEVELOPER_DIR into a separate `once.apple.clang.v1` identity so clang and swiftc partition the cache independently. * Each non-Swift source becomes its own `clang -c` action keyed by a sanitised filename, so independent objects compile in parallel through the existing BuildSession scheduler. * `_xcrun_libtool` runs a static `libtool` merge that combines the Swift-only intermediate `<module>-swift.a` with every clang object into the final `<module>.a`. Swift-only libraries skip the merge and emit the archive directly from swiftc; Swift-less libraries libtool the clang objects on their own. * `exported_headers` now flow through the provider as workspace- relative paths plus parent-dir lists. Consumers' compile gets `-I` for each transitive header dir, and swiftc receives them as `-Xcc -I` so its underlying Clang invocation can locate dep headers via the bridging header. * New attr: `clang_flags` is honoured per-source. Fixture: `fixtures/apple_library_mixed` declares a `Mixed` library with a bridging header, an ObjC `.m`, and a Swift `.swift` that calls into the ObjC class. The new shellspec assertion verifies the swift+objc compile produces `Mixed-swift.a`, the per-source `.o`, and the libtool-merged `Mixed.a`. Deferred to Round 3b: modulemap generation, header maps, `enable_modules`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 3b — clang modules support for apple_library. * New `write_file(path, content)` starlark global emits a literal file as a cacheable action. Content is folded into the action's `toolchain_identity` so any edit to the rendered text invalidates the produced file. * `enable_modules = true` triggers modulemap generation from `exported_headers`. The modulemap is written to `<build_dir>/module.modulemap` and reference each exported header by a path relative to its own location. * Modulemaps propagate transitively via `transitive_modulemaps`. Consumers get `-fmodule-map-file=<path>` on the clang command line and `-Xcc -fmodule-map-file=<path>` on the swiftc command line so `import` of a clang-module dep resolves without manual flags. * `-fmodules` is added to clang and `-Xcc -fmodules` to swiftc when the attr is set. * The mixed fixture now declares `enable_modules = true` and the manual e2e run on macOS confirms the modulemap renders correctly alongside the libtool-merged archive. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document the new mixed-language compile flow (swiftc + clang + libtool), the provider record shape consumers read, and the full attribute reference (platform/triple, toolchain pin, sources, module flags, link inputs, dep edges). Also call out the still- open follow-ups (multi-arch + lipo, select() / transitions, Swift macros, header maps) so the parity gap with Buck2 / Bazel is explicit in the docs rather than buried in commit history. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`dep["label_id"]` carries the normalised target id (e.g. `apps/ios/AppCore`), but `exported_deps` entries come straight from `[target.attrs]` and may be written as `./Sibling`, `../web/Common`, or a root-relative `apps/ios/AppCore`. The old direct-membership check (`dep_label in exported_deps`) silently failed for every relative-style reference, so the compile-time privacy boundary defaulted to "nothing exported". Add `_resolve_dep_ref(ref, package)` to the prelude — same semantics as Once's Rust-side `normalize_manifest_target`, implemented in starlark so the impl can normalise on the fly. The exported_deps loop builds a normalised id set and tests dep label membership against it. Verified end-to-end by building Greeter with `exported_deps = ["./AppCore"]` under `[target.attrs]`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous `write_file` script inlined the shell-quoted path
directly inside `$(dirname ...)`. A path containing a single quote
serialises to the `'a'\''b'` form, which the shell tokenises
inside command substitution before `dirname` sees it; the result is
brittle and produces wrong output for paths with quotes.
Bind the path to `__once_path` first, then derive the parent
directory with the POSIX `${var%/*}` parameter expansion. The
content is still shell-quoted as before. Add focused unit tests
for `shell_quote` edge cases, structural assertions on the emitted
script, and an end-to-end check that runs the generated script on
a path with a quoted parent directory to lock in the fix. Also
cover the `CommandKey` env-aware caching with a test that proves a
different env value produces a distinct cache slot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a fixture whose Swift source uses `#error("...")` inside
`#if !ONCE_DEFINES_PRESENT`, so a successful build is positive
evidence that `defines = ["ONCE_DEFINES_PRESENT"]` made it to
swiftc. The same target also flips `emit_dsym = true`, exercising
the `-g` path. The new shellspec example asserts the build
completes and the canonical Swift outputs land in `.once/out`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Move `guide/apple-graph.md` → `guide/graph/apple.md` so the Apple rule deep-dive lives under a real Graph section instead of being a loose sibling. * Add `guide/graph/index.md`: the conceptual orientation for Once's graph — three-layer model (scripts → script targets → graph targets), what a target looks like in `once.toml`, the built-in rule set, capabilities, providers, action caching + `BuildSession`, the configurability story, and the agent workflow surface. * Reorder the sidebar so Scripts (the migration ramp) appears above Graph (where teams move when they need richer relationships). Graph now mirrors Scripts with an Overview entry above the rule deep-dive. Verified by `vitepress build`: both `guide/graph/index.html` and `guide/graph/apple.html` render and the sitemap picks them up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Convention is to keep em dashes out of user-facing docs (rewrite with a colon, comma, semicolon, or sentence break). Rewrite the dashes in guide/graph/index.md and the one I missed in guide/graph/apple.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The page had grown into a mid-depth tutorial: rule-impl mechanics, BuildSession internals, the configurability epic, agent workflows. Move all of that down to the per-domain deep dives where it belongs. The overview now keeps just the load-bearing concepts: * Where the graph fits in Once's three-layer model. * What a target looks like in `once.toml`. * The rule domains the prelude ships today (Apple), linked to the deep dive. * Capabilities the CLI dispatches on. About 50 lines instead of 150. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a `Reference` top-nav entry that points at `/reference/`, with a
dedicated sidebar listing every top-level subcommand. The CLI pages
under `/reference/cli/` are generated, not hand-written.
Generation lives in a hidden `once reference --out <dir>` subcommand
implemented in `crates/once-cli/src/reference.rs`. It walks the
`clap::Command` tree exposed by `Cli::command()`, skips hidden
commands and the auto-added `help`, and emits one markdown file per
node:
out/
index.md link list of top-level commands
build.md one file per top-level command
cache.md
cache/
stats.md nested commands live under their parent's dir
action.md
action/get.md
...
Each page renders the about line, a synopsis box, the long-about
description, a positional Arguments table, and an Options table with
flag/value/default/description columns. Intermediate commands list
their subcommand children as links.
Wired into `docs/package.json` as `build:reference`, which is run
before `vitepress build` and `vitepress dev` so the site never sees a
stale snapshot. Re-running it after touching `crates/once-cli/src/cli.rs`
keeps the docs in lockstep with the binary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `build:reference` script ran `cargo build --release -p once-cli` before `vitepress build`, but the GitHub Actions docs job has no `libcap-ng-dev` installed (it's only set up on the test/build jobs per AGENTS.md), so the link step failed with `rust-lld: error: unable to find library -lcap-ng` and `build docs` went red. The generated markdown is committed to the repo, so the docs build only needs to render what's on disk. Drop the pre-hook from both `build` and `dev` scripts; keep `build:reference` available as a manual command to refresh the markdown after touching `crates/once-cli/src/cli.rs`. The Reference overview now documents that workflow. While here, expand `long_about` on every top-level command (build, test, cache, auth, toolchain, query, runtime) so the generated pages render a real Description section instead of just the short about line, and teach the generator to drop the redundant leading summary that clap auto-prepends to `long_about` so the page doesn't print the same sentence twice. Reference markdown re-generated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Hoist the subcommand bullet's `parent` path to a local so every interpolation in the link line is an inline capture; the prior format string mixed an implicit positional with three named captures, which read like a bug even though the output was right. * Split `write_command_files` into `render_command_page` plus `build_synopsis`, `render_arguments`, `render_options`, and `render_subcommands` helpers. The original was a 100+ line function that tripped the clippy `too_many_lines` lint after the format tweaks; each piece is now small enough to read top to bottom. * Drop the "Generated from `crates/once-cli/src/cli.rs`. Re-run `npm run build:reference`" disclaimer from the rendered index page; that line was meant for contributors, not first-time readers, and it leaked an internal source path into the public docs. Replace with a one-line orientation aimed at the reader. * Add unit tests for `trim_leading_about` (about with and without trailing period, long that exactly matches about, missing about, empty input), `file_path_for` (top-level, two-level, three-level nesting), `walk` (skips hidden subs, descends, records about / long_about / children), and end-to-end `write_command_files` / `write_index` (asserts synopsis / description / subcommand rendering and that the index never leaks the internal source paths). 13 tests in the `reference::tests` module. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Link `build`, `run`, `test`, and `query` mentions in the Graph
overview to their CLI reference pages so the first time a reader
sees a verb they can pivot straight to the flag/argument detail.
* Rewrite the standalone Reference index page from a contributor
note ("Generated from clap definitions in
crates/once-cli/src/cli.rs. Run npm run build:reference …") into
a one-line orientation pointed at the reader. The "how it stays
in sync" detail is a maintainer concern; it doesn't need to
greet first-time visitors.
* Re-run the generator so `docs/reference/cli/index.md` picks up
the matching copy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A first-time reader landing on guide/graph/apple cares about what the Apple rule set does today, not about the RFC it implements or the Bazel and Buck2 rules we drew inspiration from. Rewrite the opening into a single load-bearing paragraph and move the RFC plus prior-art links to a "Prior Art and Tracking" footer at the bottom of the page as credits/references. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…copy * Rewrite `docs/guide/graph/apple.md`. The Commands walkthrough used to be a dense soup of `xcrun --sdk`, `swiftc -emit-library`, and `clang -c` flags interleaved with prose; split it into a short "what build/run/test produce" section and a focused "apple_library compile" subsection with three labelled bullets (Swift, C-family, mixed) plus a one-line note on modulemap propagation. Drop the "Built-In Rules" line that pointed at `crates/once-frontend/prelude/apple.star`, and remove the "Rule Implementations" and "Build Pipeline" sections wholesale (they exposed internal Rust types like `BuildSession`, `AnalysisEngine`, `Arc<GraphTarget>`, `tokio::task::JoinSet`, and `JsonValue` that the reader doesn't have to call). The provider table becomes a short prose paragraph that lists the field families instead of every name. * Stop referencing `crates/once/swift/Once.swift` in the Swift SDK guide; describe the vendoring step in user terms instead. * Add a "no source code paths in user-facing docs" rule under `AGENTS.md` Style. Source paths rot under refactors, mean nothing to a reader who isn't editing the repo, and leak implementation detail through the public surface. Describe behavior, link to the reference, or quote `once.toml` shapes instead. Verified by `vitepress build` (clean) and a final grep sweep for `crates/`, internal Rust type names, and em dashes (all zero) over `docs/guide/` and `docs/reference/index.md`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Define "rule schema" the first time the term appears. In the Graph
overview the third bullet of the three-layer model glosses it
("the contract for a kind: which attributes it accepts, which
providers each dep edge expects, which providers it emits, and
which capabilities it exposes"). In the Apple page a one-sentence
follow-up under the `once query schema` example carries the same
definition at point of use. Once the term is grounded the later
"Schema introspection" mention in the agent workflows section
reads naturally.
* Drop the "follows RFC 0001: Once Build Graph" link from the Apple
page's Prior Art section. Reader-facing docs don't need to
reference internal planning artifacts; what they do need (Bazel
rules_apple, Bazel apple_binary, Buck2 apple_library and friends)
stays.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`once mcp` runs an MCP 2024-11-05 server over stdio so a coding
agent (Claude Desktop, an IDE plug-in, an Anthropic SDK script) can
inspect the typed build graph without scraping CLI prose. The
server handles the initialize handshake, the `notifications/
initialized` post-handshake notification, `tools/list`, and
`tools/call`, then surfaces three read-only tools that wrap the
existing query verbs:
- once_query_targets: list every declared target, optionally
filtered by rule kind.
- once_query_capabilities: return the capabilities a target
exposes, with output groups and required inputs.
- once_query_schema: return the typed contract for a rule kind.
Each tool returns the same record shape the corresponding
`once query` command produces, serialised to JSON inside the MCP
`content` envelope. Tool-level failures land as `isError: true`
results so the agent sees the message instead of a protocol error.
Action invocation (call a graph action, return a digest, query the
cached outputs / logs / provider record by that digest later) stays
follow-up work; the action cache and CAS already key everything by
digest, so the read surface is ready to grow into that shape when
it lands.
Wiring:
- `Cmd::Mcp { workspace }` accepts an optional `--workspace` and
falls back to the global `-C/--directory`.
- `dispatch::run_command` now splits each multi-subcommand verb
(toolchain, query, runtime) into a small helper so it stays under
the clippy line-count limit.
- Reference docs regenerate to include `once mcp` and the sidebar
picks it up between `exec` and `query`.
- The Apple Graph page swaps its "agent workflows (planned)"
blurb for a real walk-through of the three tools.
Verification:
- 7 new unit tests in commands::mcp::tests (initialize handshake,
tools/list contents, unknown method, missing-argument tool error
surfacing through `isError`, parse-error null-id reply,
notifications suppressing the reply, schema tool returns the
apple_library contract). 197/197 lib tests green.
- Manual stdio smoke test against fixtures/apple_library: feeding
initialize + notifications/initialized + tools/call returns the
two-target list as JSON content (see commit body of follow-up
if regression).
- vitepress build clean; cargo clippy + fmt clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CLI reference at `/reference/cli/mcp` covers the binary flags, and the Apple Graph guide page walks through the tools in prose, but the protocol surface itself (transport, handshake, tool input schemas, return shapes, error model, Claude Desktop wiring) had no reference home. Add `docs/reference/mcp.md`. Documents: * Transport: newline-delimited JSON-RPC 2.0 over stdio, how to spawn the server with `--workspace` or `-C/--directory`. * Handshake: the MCP 2024-11-05 sequence (initialize, the server reply, notifications/initialized, tools/list, tools/call), with literal request/reply JSON for the first round-trip. * Tool catalog: each of the three tools (`once_query_targets`, `once_query_capabilities`, `once_query_schema`) gets its input schema and an example return shape that matches what `once query --format json` emits. * Error model: a table mapping each failure surface (parse error, unknown method, malformed params, unknown tool, missing argument, no-match lookup) to whether it lands as a JSON-RPC error or an `isError: true` tool result. * Worked example session showing a full client/server exchange. * Claude Desktop `mcpServers` config snippet so readers can wire the server into Claude Desktop without leaving the page. * "Not yet on the wire" note that the read surface is ready for the call-and-query-by-digest follow-up. Reference index page now lists the CLI and MCP references side by side. Sidebar gets a "Protocols" group under the CLI group so the MCP page is one click from anywhere in the reference. Apple Graph agent-workflows blurb now links the term "Model Context Protocol" to the new reference page so readers can pivot from the use-case to the spec. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…evel * Restructure the MCP reference into a top-level sidebar group with Overview + Tools sub-pages, sitting alongside CLI. The hand-written Overview at `/reference/mcp/` keeps the stable prose (transport, handshake, error model, example session, Claude Desktop wiring). The Tools page at `/reference/mcp/tools` is generated. * Drop the "Not yet on the wire" roadmap section from the overview; reference docs describe what's on the wire, not what isn't. * Refactor `commands::mcp::tool_definitions()` into a shared `tool_catalog()` returning structured `ToolDefinition` records (name, short description, long markdown description, JSON Schema input, and an example return). The runtime `tools/list` reply and the doc generator now read from the same record, so the names, descriptions, and schemas can't drift. * Teach `once reference --out <root>` to write both `cli/*.md` and `mcp/tools.md` under one root. `docs/package.json` `build:reference` switches from `--out docs/reference/cli` to `--out docs/reference` and pre-cleans both targets. `Cmd::Reference` is unchanged on the surface; only the layout under the target dir grows. * Regenerate the reference; the new `docs/reference/mcp/tools.md` carries each tool's section, input schema (pretty-printed from the same JSON Schema the server validates against), and example return. The reference index page and the Apple Graph agent- workflows blurb relink to `/reference/mcp/` (trailing slash for the directory route). Verified: cargo clippy + fmt clean, 197/197 lib tests green (commands::mcp::tests 7/7, reference::tests 13/13), vitepress build clean with both reference subtrees rendered. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
apple_librarybuild logic out of Rust shell-script placeholders into a starlark rule impl declared incrates/once-frontend/prelude/apple.star. The impl composes a realxcrun-drivenswiftc -emit-library -static -emit-modulecommand line through native globals (xcrun_swiftc,apple_triple,declare_output,run_action) and returns a provider dict.once-frontendthat evaluates a rule impl per target with actxdict (label, typed attrs, glob-expanded srcs, dep providers, build_dir), collects the declared actions through a thread-local store, and exposes them asDeclaredActions. State threading uses a thread-local instead ofEvaluator::extrabecause the workspace forbidsunsafe(the latter requires derivingProvidesStaticType).once-clithat walks deps for impl-bearing rules, converts eachDeclaredActioninto aonce_core::Actionwith aninput_digestcomposing the swiftc identity, source content digests, and dep action digests, then executes viarun_with_cache. The other apple rule kinds (apple_framework,apple_application,apple_test_bundle) keep their existing placeholder scripts; a smallRULES_WITH_IMPLlist gates the new path.fixtures/apple_library/workspace withAppCoreandGreeter(the latter imports the former), plus four new shellspec cases (compile, recursive dep build, cache hit, dep-change invalidation) gated onxcrunavailability. Updatedocs/guide/apple-graph.mdwith the rule-impl ctx surface and note the planned MCP-for-agents follow-up.Testing
mise exec -- cargo test --workspacemise exec -- cargo clippy --workspace --all-targets -- -D warningsmise exec -- cargo fmt --all -- --checkmise exec -- cargo build --releasemise exec -- shellspecapps/ios/AppCoreandapps/ios/Greeterfrom the fixture; confirmedAppCore.ais a realcurrent ar archive random library,Greeterresolvesimport AppCorethrough the-Isearch path on the dep's swiftmodule dir, cache hits on rebuild, and dep-source change invalidates the parent cache slot.