# IntentCall — DX FAQ

> Published copy for [docs.page](https://docs.page/Arenukvern/intentcall): [docs/DX_FAQ.mdx](docs/DX_FAQ.mdx)

*How to use and extend IntentCall.* For architectural rationale, see [Design FAQ](/DESIGN_FAQ).

---

## 📚 Documentation site

**Q: Where is the published documentation?**

[docs.page/Arenukvern/intentcall](https://docs.page/Arenukvern/intentcall) — site config in `docs.json`, pages under `docs/`. Maintainer guide: [Enable docs.page](/contributing/enable_docs_page).

**Q: How do I validate docs before a PR?**

```bash
pnpm install
just docs-check
```

Run this when you change `docs/` or `docs.json`.

---

## 🏠 Getting started

**Q: How do I set up the workspace locally?**

```bash
git clone https://github.com/Arenukvern/intentcall.git
cd intentcall
dart pub get
just test
just analyze
```

**Q: What should a coding agent or operator run first?**

Start from the declared Steward contract before choosing ad hoc commands:

```bash
steward doctor --json
steward actions list --json
steward action inspect intentcall.validate --json
steward probe --json --profile quick
```

For adapter, platform bridge, or registry contract work, also run:

```bash
steward benchmark --scenario intentcall.adapter-contract --json
```

For manifest export, emitter, and platform projection alignment, run:

```bash
steward benchmark --scenario intentcall.projection-pipeline --json
```

The quick probe validates package synchronization, internal dependency floors,
path dependency hygiene, active-plan hygiene, and the adapter/platform contract
lane selected by `steward.yaml`.

**Q: How do I run only the tests for one package?**

```bash
dart test packages/intentcall_core
```

**Q: How do I run static analysis?**

```bash
just analyze
# equivalent to: dart analyze .
```

**Q: How do I validate all packages are publishable without publishing?**

```bash
just publish-dry-run
# equivalent to: dart run tool/intentcall/bin/intentcall.dart publish-all
```

**Q: How are IntentCall packages released automatically?**

Release Please opens a release PR from `release-please-config.json` and
`.release-please-manifest.json`. The packages are linked as one synchronized
`intentcall_*` train, so merging the release PR creates component tags such as
`intentcall_core-v<version>`. Each tag triggers `.github/workflows/pub_publish.yml`,
which publishes the package named by the tag through pub.dev automated
publishing.

**Q: How do I dry-run the package selected by a release tag?**

```bash
just publish-tag-dry-run intentcall_session-v<version>
# equivalent to: dart run tool/intentcall/bin/intentcall.dart publish-tag --tag intentcall_session-v<version> --skip-existing
```

Pub.dev automated publishing must be enabled for every package with tag pattern
`<package>-v{{version}}` and GitHub Actions environment `pub.dev`. The release
workflow should use `RELEASE_PLEASE_TOKEN`; using only `GITHUB_TOKEN` can create
tags without triggering the downstream publish workflow.

---

## 🏭 Adding a dependency

**Q: How do I add a dependency to a package?**

Edit the target package's `pubspec.yaml`, then run `dart pub get` from the workspace root. Workspace resolution propagates automatically.

**Q: How do I print hosted dependency blocks without hardcoding versions?**

```bash
just print-hosted-deps
```

The command derives the synchronized package train from package `pubspec.yaml`
files. Use it for migration notes or local override cleanup instead of copying
literal IntentCall package versions into durable docs.

**Q: How do I add a path dependency on a sibling package within this monorepo?**

```yaml
# in packages/intentcall_platform/pubspec.yaml
dependencies:
  intentcall_core:
    path: ../intentcall_core
```

The workspace resolver handles this; no extra steps needed.

---

## 🔧 Adding a new package

**Q: How do I add a new `intentcall_*` package to the workspace?**

1. Create `packages/intentcall_foo/` with a valid `pubspec.yaml`.
2. Add `- packages/intentcall_foo` to the `workspace:` list in the root `pubspec.yaml`.
3. Add the package to the `test` target in `justfile` and the `publishOrder` list in `tool/intentcall/bin/intentcall.dart`.
4. Add a row to the package table in `README.md`.
5. Add the package to `release-please-config.json` if it will be published to pub.dev.
6. Run `dart pub get` to re-resolve.

---

## 🔌 Writing a new adapter

**Q: What is the minimal interface a new adapter must implement?**

An adapter imports `intentcall_core`, reads entries from `AgentRegistry`, and bridges them to the target transport. Look at `intentcall_mcp` as the contract-tested reference adapter.

**Q: What is the adapter lifecycle checklist?**

1. Attach to an `AgentRegistry`.
2. Publish every current `registry.listEntries()` entry by `AgentRegistryEntry.key`.
3. Subscribe to `registry.events` when the target transport supports hot-sync.
4. On registration events, publish the stored registry key and descriptor.
5. On unregistration events, remove the target transport surface.
6. On invocation, translate target arguments and call `registry.invoke(entry.key, arguments)`.
7. Detach by cancelling subscriptions and removing any transport resources the adapter owns.
8. Add or extend a `verifyNativeAdapterContract(...)` test.

Run `steward benchmark --scenario intentcall.adapter-contract --json` before
claiming the adapter satisfies the shared contract.

**Q: How does an adapter discover registered intents?**

```dart
import 'package:intentcall_core/intentcall_core.dart';

final registry = InMemoryAgentRegistry();
final entries = registry.listEntries();
```

Subscribe to `registry.events` when the target transport supports hot-sync.
Adapters should publish and invoke by `AgentRegistryEntry.key`; descriptors are
metadata, not a safe substitute for the stored registry key. `listDescriptors()`
is fine for display-only catalog reads.

**Q: Where do wire types live?**

Wire types (`AgentResult`, validation helpers, resource input schemas) live in `intentcall_schema`. Registry and authoring types (`AgentRegistry`, `AgentCallEntry`, `RegisteredAgentIntent`) live in `intentcall_core`.

Full wire-contract guide: [intentcall_schema](/packages/intentcall_schema) — package map, validate/coerce pipeline, entity JSON shape (AX), and envelope examples.

**Q: Where do tool/resource registration value objects live?**

Use `intentcall_core` for neutral registration values:
`ToolRegistration`, `ToolHandler`, `ResourceRegistration`,
`ResourceTemplateRegistration`, and `ResourceHandler`. `intentcall_mcp`
re-exports them for compatibility with existing MCP adapter imports, but MCP
does not own the vocabulary.

---

## 🧭 Runtime sessions

**Q: How do I add reusable runtime session lifecycle to a CLI or tool?**

Use `intentcall_session` with a connector that knows how to attach to your runtime. The connector owns target discovery and transport details; `IntentSessionManager` owns persisted session state.

```dart
import 'package:intentcall_session/intentcall_session.dart';

final sessions = IntentSessionManager(
  connector: myConnector,
  stateStore: StateStore(path: '.intentcall/session_state.json'),
);

await sessions.load();
final start = await sessions.startSession(
  const IntentSessionStartRequest(
    mode: IntentSessionConnectionMode.uri,
    uri: 'wss://runtime.example.com/ws',
    sessionId: 'debug',
  ),
);
```

**Q: What does my connector need to implement?**

Implement `IntentSessionConnector`. Return a stable `activeEndpointDisplay` after `connect`; throw `IntentSessionConnectionException` with `multipleTargets`, `targetNotFound`, `noTargets`, `invalidUri`, or `invalidTargetId` when selection fails.

```dart
final class MyConnector implements IntentSessionConnector {
  @override
  String? activeEndpointDisplay;

  @override
  Map<String, Object?> get lastSelectionDiagnostics => const {};

  @override
  Future<Map<String, Object?>> connect({
    IntentSessionConnectionMode mode = IntentSessionConnectionMode.auto,
    String? targetId,
    String? uri,
    String? host,
    int? port,
    bool forceReconnect = false,
  }) async {
    activeEndpointDisplay = uri ?? 'runtime://current';
    return {'connected': true, 'reusedConnection': false};
  }

  @override
  Future<void> disconnect() async {
    activeEndpointDisplay = null;
  }
}
```

**Q: How do I invoke an intent inside the active session?**

Use `IntentSessionExecutor`. It attaches first, invokes the registry, and marks the session used after successful calls.

```dart
import 'package:intentcall_core/intentcall_core.dart';
import 'package:intentcall_session/intentcall_session.dart';

final executor = IntentSessionExecutor(
  sessions: sessions,
  registry: registry,
);

final result = await executor.invoke(
  qualifiedName: 'debug_select',
  arguments: const {'id': 'node-7'},
  sessionId: 'debug',
);
```

**Q: Should I build a broker package on top of this?**

Usually no. Compose `IntentSessionManager`, `IntentSessionExecutor`, `AgentRegistry`, and your transport adapter directly; introduce a named broker only when it owns real product behavior such as routing policy, artifact storage, or multi-runtime coordination.

**Q: How do I persist and diff JSON runtime snapshots?**

Use `IntentSnapshotStore` for storage only. Build the snapshot payload in your host, then let the store handle safe writes, listing, loading, and structural diffs.

```dart
final snapshots = IntentSnapshotStore(
  snapshotsDir: '.intentcall/snapshots',
);

await snapshots.saveSnapshot(
  id: 'before',
  snapshot: const {
    'id': 'before',
    'createdAt': '2026-06-22T00:00:00.000Z',
    'result': {'selected': 'node-7'},
  },
);

final diff = await snapshots.diffSnapshots(fromId: 'before', toId: 'after');
```

---

## 🧰 Direct IntentCall Usage

**Q: Can I use IntentCall in a CLI, server, or non-Flutter host?**

Yes. Use `intentcall_core` + `intentcall_schema` for a pure Dart registry and
wire contract. Add `intentcall_mcp`, `intentcall_webmcp`, `intentcall_session`,
or platform packages only when you need those projection or lifecycle surfaces.
Keep host-specific discovery, process management, permissions, and UI policy in
your product.

**Q: What is the smallest non-Flutter path?**

```dart
final registry = InMemoryAgentRegistry();
registry.register(myEntry.toRegistration());
final result = await registry.invoke('namespace.name', arguments);
```

Custom adapters should stay thin: publish registry metadata, translate incoming
calls, then route execution back through `AgentRegistry.invoke(...)`.

**Q: When should I choose mcp_flutter?**

Choose `mcp_flutter` / `mcp_toolkit` when you are building a Flutter app and
want the product harness: CLI workflows, runtime discovery, Flutter VM
integration, app-side bootstrap, screenshots, or inspector-oriented tooling.

---

## 🧭 IntentPack direction

**Q: Should I use `IntentPack` today?**

Not as a stable public API yet. `IntentPack` is the direction for packaging an app's agent surface as one portable unit, but current authoring should still use `AgentRegistry`, `AgentCallEntry`, and `RegisteredAgentIntent`.

**Q: What should an IntentPack eventually contain?**

An IntentPack should carry a stable pack id, name, description, entries/intents, input and output schemas, examples, side-effect metadata, confirmation and safety policy, platform projection hints, runtime adapter hints, and version/compatibility metadata.

**Q: What projections should one pack support?**

The target is to project one pack into MCP tool registrations, WebMCP manifest material, Flutter runtime registry entries, CLI command bindings, deep-link routing metadata, and future native-platform artifacts where supported.

**Q: Will code generation be required?**

No. Handwritten entries remain first-class. Code generation should reduce repetition for stable surfaces, especially build-time artifacts such as manifests, web assets, native metadata, or generated skill snapshots; it should not become mandatory ceremony for local development, tests, CLI tools, or dynamic hosts.

**Q: How should I think about typed app entities and indexing?**

Treat them as additive projection metadata, not as a replacement for your app
model. Your Dart app owns the real data and emits snapshots with stable ids,
display fields, search/index fields, and enough routing payload to invoke later
actions. Platform code may cache that projection so native query or indexing
callbacks can answer while Flutter is cold.

Generated entity schemas, App Intents artifacts, native cache rows, and indexing
or donation helpers are not live Spotlight, Siri, Shortcuts, or product proof.
For Apple, claim donation/indexing only after a signed consuming app or
AppIntentsTesting proves the system path you are describing. Treat
AppIntentsTesting as the primary automated regression lane for Apple App Intents
behavior: actions, supported entity queries, and Spotlight query paths where the
API covers them. Manual Siri, Shortcuts, and Spotlight runs remain smoke/product
UX checks.

---

## 🧬 Codegen (`@AgentTool`)

**Q: How do I opt in to codegen?**

Add `intentcall_codegen` as a `dev_dependency` and `build_runner` to run the generator:

```yaml
dev_dependencies:
  intentcall_codegen:
    path: ../../packages/intentcall_codegen
  build_runner: ^2.0.0
```

Then annotate your intent class with `@AgentTool` and run:

```bash
dart run build_runner build --delete-conflicting-outputs
intentcall manifest export --check
```

`build_runner` writes `lib/generated/agent_catalog.g.dart`. `manifest export`
merges the catalog with `intentcall.yaml` defaults (and optional `@AgentProjection`
per tool) into committed `web/agent_manifest.json`.

Per-tool projection is **Dart-only** (`@AgentProjection` or `EntryProjection` on
catalog rows). `intentcall.yaml` carries host wiring and global `defaults` only.

**Q: Is codegen required?**

No. You can register `AgentCallEntry` objects manually. Codegen is a convenience layer.

**Q: How do instance-bound or handwritten tools join the generated catalog and manifest?**

Catalog sources:

```text
@AgentTool          →  tool implementation + (usually) catalog row
handwritten getter  →  tool implementation only
catalog row         →  @AgentCatalog list
agent_catalog.g.dart →  merge of all three sources
```

Put instance methods and `AgentCallEntry` getters on a host class (typically
`static final shared`), then co-locate a `List<AgentRegistryCatalogEntry>`
annotated with **`@AgentCatalog`** — the catalog builder discovers it via
`tool_globs`.

**Placement:**

| Where | Generated spread | Supported |
|-------|------------------|-----------|
| **Static field on host class** (recommended) | `...HostClass.catalogEntries` | Yes |
| Top-level variable | `...catalogEntries` | Yes |
| Instance field on host | — | No |

```dart
final class DemoHostTools {
  static final DemoHostTools shared = DemoHostTools();

  AgentCallEntry get inboxCallEntry => AgentCallEntry.tool(/* … */);

  @AgentCatalog()
  static final List<AgentRegistryCatalogEntry> demoHostCatalogEntries =
      <AgentRegistryCatalogEntry>[
    AgentRegistryCatalogEntry(
      registryKey: 'app_demo_inbox',
      entry: shared.inboxCallEntry,
      projection: const EntryProjection(
        surfaces: {AgentManifestSurface.webMcp: true},
      ),
    ),
  ];
}
```

**Do not duplicate `@AgentTool` rows** in `@AgentCatalog` — e.g. `app_demo_host_status`
is merged from `@AgentTool` codegen on the instance method, not from the host catalog
list. Duplicate `registryKey` values fail the build.

Run `build_runner`. `@AgentTool` rows are collected from generated `*.g.dart`
parts (`tool_part_globs`, default `lib/**.g.dart`), including tools under
`lib/src/` when `agent_tool` emits their parts. The aggregate catalog at
`lib/generated/agent_catalog.g.dart` spreads `@AgentCatalog` lists next to
codegen rows; `intentcall manifest export --check` merges catalog +
projection into committed `agent_manifest.json`. Per-row projection uses
`EntryProjection` on catalog rows — same policy surface as `@AgentProjection`
on annotated tools. See [ADR 0019](decisions/0019-framework-neutral-intentcall-cli.md),
[ADR 0021](decisions/0021-agent-catalog-annotation.md), and the
[`intentcall_codegen` example](../packages/intentcall_codegen/example/lib/tools/demo_host_tools.dart).

**Catalog builder options** (`intentcall_codegen|agent_catalog` in `build.yaml`):
`tool_part_globs`, `tool_globs` (`@AgentCatalog` scan only), `tool_exclude_globs`,
`host_binding_field`.
Defaults and a commented template: [`example/build.yaml`](../packages/intentcall_codegen/example/build.yaml).

**Probe anchor:** `intentcall manifest export` runs a subprocess that evaluates
each row's `entry:` and calls `resolveDescriptor()` — handlers are stripped
before merge. Use `Host.shared.<getter>CallEntry` or top-level `*CallEntry`
getters so the catalog compiles at export time. Runtime may register a different
instance when descriptors match.

**Descriptor-only rows:** When a tool should appear in the manifest but should
not materialize an `AgentCallEntry` at probe time, set `descriptor:` on
`AgentRegistryCatalogEntry` and register the handler in app setup; keep
`qualifiedName` aligned for parity gates.

**Optional instance `@AgentTool` codegen:** Annotate instance methods on a host
class. Codegen emits an extension with `AgentCallEntry` getters whose handlers call
instance methods on `this`. Catalog rows use `descriptor:` when no static binding
field exists, or `Host.shared.<getter>CallEntry` when a probe anchor is present.
Handwritten getters remain the canonical path when you need full control.

**Typed projection:** `@AgentProjection` and `EntryProjection` use
`AgentManifestSurface` enum keys. See [ADR 0022](decisions/0022-projection-pipeline-alignment.md)
for the full surface table and Apple sub-channel defaults.

| Enum | Manifest key | Default when platform enabled |
|------|--------------|------------------------------|
| `appleAppIntents` | `apple.appIntents` | `true` on `ios`/`macos` |
| `appleAppShortcuts` | `apple.appShortcuts` | `false` (opt-in) |
| `appleSpotlight` | `apple.spotlight` | `false` |
| `appleEntities` | `apple.entities` | `false` |
| `androidShortcuts` | `android.shortcuts` | `true` on `android` |
| `webManifestShortcuts` | `web.manifestShortcuts` | `true` on `web` |
| `webProtocolHandlers` | `web.protocolHandlers` | `true` on `web` |
| `webMcp` | `web.webMcp` | `true` on `web` |
| `windowsProtocolActivation` | `windows.protocolActivation` | `true` on `windows` |
| `windowsMsixProtocol` | `windows.msixProtocol` | `true` on `windows` |
| `windowsAppActions` | `windows.appActions` | `false` (opt-in). Requires an MSIX package. See [ADR 0027](/decisions/0027-agent-usable-projection-results). |
| `linuxSchemeHandler` | `linux.schemeHandler` | `true` on `linux` |

Apple sub-channels (Siri phrases, Spotlight donation hints) use
`AgentManifestSurfaceExposure.options` on handwritten rows until emitters define
them — see [ADR 0020](decisions/0020-platform-scoped-manifest-surfaces.md).

**Platform-scoped surfaces:** `platforms.enabled` in `intentcall.yaml` scopes
default manifest surface families at export time (not just platform sync targets).
Explicit `defaults.surfaces` in yaml overrides platform-scoped defaults.

**Platform subset author matrix** ([ADR 0025](decisions/0025-platform-subset-federated-plugins.md)):

| App profile | `platforms.enabled` | `pubspec` deps | Hooks |
|-------------|---------------------|----------------|-------|
| iOS + Android mobile | `[android, ios]` | `intentcall_platform` (+ codegen stack) | Gradle + Xcode from spine |
| macOS + Windows desktop | `[macos, windows]` | `intentcall_platform` | CI sync for windows/linux |
| Android + Huawei stores | `[android]` | `intentcall_platform` | Gradle only |
| Web Jaspr | `[web]` | `intentcall_hooks` as a dev dep; no Flutter platform plugin | Dart SDK hook |
| iOS-only | `[ios]` | `intentcall_platform` (federated; android impl not compiled into the app binary) | Xcode only |

Apps list the `intentcall_platform` umbrella only. Endorsed federated impls
(`intentcall_platform_apple`, `intentcall_platform_android`) come in via
`default_package`. Huawei/HyperOS use the `android` token — OEM packaging is an
app concern.

---

## Flutter in-app host {#flutter-in-app-host}

**Q: How do I wire `IntentCallFlutterHost` in a Flutter app?**

Audience overview: [Who is this for? §1](/start_here/audiences#1-in-app--internal-flutter-host).

1. Depend on `intentcall_platform` (umbrella) + `intentcall_core` — not deleted
   `intentcall_apple` / `intentcall_android`.
2. Add `intentcall.yaml` with `host: flutter`, `protocolScheme`, and explicit
   `platforms.enabled` (required — `intentcall config validate` errors if empty).
3. Register tools once; bind the host:

```dart
final host = IntentCallFlutterHost.bindRegistry(
  registry: registry,
  policy: productionPolicy, // never ship debugAllowAll in release
  registerWebMcp: kIsWeb,
  listenForDeepLinks: !kIsWeb,
  protocolScheme: 'myapp',
);
await host.start();
```

4. Run hooks init + three-gate spine (`manifest export --check`,
   `platform sync --check`) or the mcp_flutter `init intentcall-platform` /
   `codegen sync` wrappers.

**Q: What should a Flutter host check after moving to the current IntentCall train?**

Use the same train for all `intentcall_*` packages. Pass an explicit production
policy, enable `registerWebMcp` only for pages that expose a compatible WebMCP
host, enable `listenForDeepLinks` only with an app-owned `protocolScheme`, and
drain on startup plus foreground/resume for open-app native handoff.

For iOS and macOS open-app App Intents, the readiness checklist is:

1. `agent_manifest.json` / export has the app-owned `protocolScheme`.
2. Apple entries that should appear in Shortcuts use `"dispatchMode": "openApp"`
   and curated `"surfaces": { "apple.appShortcuts": { "include": true } }`.
3. `intentcall platform sync --platform ios,macos --check` passes, proving generated Swift,
   Runner target membership, and `Runner/Info.plist` `CFBundleURLTypes` drift.
   For module visibility (generated Runner code vs federated plugin), also run
   the mcp_flutter compile gate: `bash tool/contracts/check_apple_runner_compile.sh`
   (or `just apple-runner-compile-check` from agentkit when `../mcp_flutter` exists).
4. The app is signed and installed before claiming manual Shortcuts or Spotlight
   product proof.
5. Automated Apple App Intents regression proof uses AppIntentsTesting where the
   API covers the behavior, and verifies that Flutter consumed the pending
   invocation, not merely that Swift compiled.
6. Any typed entity or indexing path refreshes a Dart-owned snapshot into a
   durable native cache, and Apple query/indexing code reads that cache instead
   of assuming Flutter is already running.

Use the exact proof label in release notes and docs: generated Swift compile
proof, AppIntentsTesting runtime proof, entity query proof, Spotlight
indexing/query proof, or manual smoke. The current local repo proof can stop at
scaffold/API-shape tests plus Steward gates when the active toolchain cannot
import `AppIntentsTesting`.

If `xcode-select -p` points at `/Library/Developer/CommandLineTools`, raw
`xcrun swiftc -typecheck` may still typecheck `import AppIntents` but fail on
`import AppIntentsTesting` with `no such module`. That is expected: the testing
framework is shipped with full Xcode under the platform Developer framework
directory. Use the Xcode-backed probe before claiming local AppIntentsTesting
compile support:

```bash
just apple-appintents-testing-typecheck /Applications/Xcode-beta.app
```

Generate the XCTest UI-test source from the same `agent_manifest.json` that
drives platform sync:

```bash
dart run intentcall_cli:intentcall apple-appintents-testing generate-fixtures \
  --manifest path/to/agent_manifest.json \
  --sample-arguments-output path/to/appintents_arguments.json \
  --entity-fixtures-output path/to/appintents_entities.json

dart run intentcall_cli:intentcall apple-appintents-testing generate-tests \
  --manifest path/to/agent_manifest.json \
  --bundle-id com.example.app \
  --sample-arguments path/to/appintents_arguments.json \
  --entity-fixtures path/to/appintents_entities.json \
  --output ios/RunnerUITests/IntentCallAppIntentsTests.swift
```

`appintents_arguments.json` is keyed by action `qualifiedName`; each value is
an object of primitive App Intent parameter fixtures. `appintents_entities.json`
is keyed by entity `qualifiedName`; each value contains `identifier`, `search`,
and `expectedTitle`. Treat generated fixture files as editable templates:
replace placeholder argument values with data seeded by the consuming app's
UI-test setup before claiming runtime proof. The generated source is still
scaffold proof until it is added to a signed consuming app's XCTest UI-test
target and run with `xcodebuild test`.

The command emits a proof-label reminder because these claims are different:
typechecking `import AppIntentsTesting` proves full-Xcode framework visibility,
generated XCTest source proves scaffold/API shape, and a passing generated UI
test in the app target is the AppIntentsTesting runtime proof.

For web runtime checks, prefer local Flutter web resources when the consumer
fixture does not check in `web/canvaskit/`. In Flutter CLI terms, launch with
`--no-web-resources-cdn` rather than pointing at a missing local CanvasKit
override.

**mcp_flutter dogfood:** web and macOS have the strongest runtime lanes today;
iOS shares Apple emitters and is proven at sync/scaffold level until signed
AppIntentsTesting runs. See [Platform support — mcp_flutter dogfood](/start_here/platform_support#mcp_flutter-dogfood-sibling-consumer).

---

## Surface link

**Q: How do I let a page and a coding agent call one registry without WebMCP?**
A: Bind `AgentInvocationHub` to the registry. Serve `AgentWebSocketSurface` from `package:intentcall_platform_sync/io.dart` if another process must connect. Point `AgentHttpInvoke.through` at the hub for `POST /agent/invoke?name=`. Pass `McpPublishAdapter.onToolCall` through to `hub.observe` so MCP calls show up on `watch`. Do not rebuild the UI in that callback. Update state inside the tool handler. See [ADR 0028](/decisions/0028-surface-link-no-redraw).

**Q: How do CLI, macOS, web, and MCP find that server?**
A: Pass `protocolScheme` to `AgentWebSocketSurface.serve`. It writes `$HOME/.intentcall/links/<scheme>.json` and answers `GET /agent/discover` on the same port. Dart peers use `AgentLinkDirectory.find`. A browser uses the discover URL. `intentcall link discover` and `intentcall link call` read the directory. `intentcall mcp serve --auto --scheme <scheme>` prefers a Flutter debug VM (`--vm-service-uri` or a short `flutter attach --machine` probe) and otherwise proxies the published link. If neither door is open, the server stays up with `intentcall_app_status` instead of an empty tool list. A Flutter app publishes only with `publishSurfaceLink: true`. See [ADR 0029](/decisions/0029-surface-discovery.md).

---

## 🧪 Testing

**Q: How do I test an adapter against the contract?**

Add `intentcall_testing` as a `dev_dependency` and call `verifyNativeAdapterContract(...)` from a package test. For example:

```dart
test('McpPublishAdapter satisfies the shared native contract', () async {
  await verifyNativeAdapterContract(
    attach: (registry, transport) async {
      final adapter = McpPublishAdapter(registry: registry, transport: transport);
      await adapter.attach();
      return adapter.detach;
    },
  );
});
```

See `packages/intentcall_mcp/test/mcp_adapter_contract_test.dart` for the current reference shape.

**Q: How do I test session lifecycle without Flutter or MCP?**

Use an in-memory fake connector and a temporary `StateStore` path. This proves persistence, lock handling, and session executor behavior without importing adapter internals.

```dart
final manager = IntentSessionManager(
  connector: FakeConnector(endpoint: 'runtime://test'),
  stateStore: StateStore(path: '${tempDir.path}/state.json'),
);
```

**Q: How do I run consumer regression checks against mcp_flutter?**

```bash
# from the mcp_flutter repo root:
make check-contracts
make dogfood-eval-static
# web / macOS runtime dogfood:
make web-showcase
make showcase
make macos-validate-runtime
# (defaults INTENTCALL_ROOT=../agentkit when cloned as siblings)
```

**Q: Which app actions should become Apple App Shortcuts?**

Publish product verbs, not bridge diagnostics. A Flutter showcase can expose
normal registry entries such as `mcpfluttertest_enable_switch`,
`mcpfluttertest_set_greeting`, `mcpfluttertest_fill_form`, and
`mcpfluttertest_write_text` with `"dispatchMode": "openApp"` plus curated
`apple.appShortcuts` opt-ins. Human phrases should describe the app action, for
example "Set greeting in \(.applicationName)", rather than exposing every tool
or using transport-oriented wording.

Current generated shortcuts still use default title, phrase, and system-image
behavior. Manifest surface options are preserved for future expansion, but
custom Apple `phrases`, `title`, `systemImage`, and `parameterSummary` are not a
supported claim yet.

---

## 📦 Publishing to pub.dev

**Q: What is the correct publish order?**

1. `intentcall_schema`
2. `intentcall_core`
3. `intentcall_session`
4. `intentcall_mcp`, `intentcall_webmcp`, `intentcall_codegen` (parallel)
5. `intentcall_platform_sync`, `intentcall_hooks`, `intentcall_bridge`, `intentcall_cli`
6. `intentcall_platform`, `intentcall_platform_apple`, `intentcall_platform_android`
7. `intentcall_testing`

**Q: How do I publish?**

See [PUBLISHING.md](https://github.com/Arenukvern/intentcall/blob/main/PUBLISHING.md) for full instructions. In short:

```bash
# historical first-publish check: only for brand-new package names
just publish-preflight-first

# later releases: release cleanliness and token
just publish-preflight

# archive validation, no publish
just publish-dry-run

# recovery-only when automated tag publishing is unavailable
just publish-execute
```

**Q: How do I cut a release?**

This repo uses [release-please](https://github.com/googleapis/release-please). Merge a release PR generated by Release Please; the resulting package tags trigger GitHub Actions publishing. Manual publish commands are recovery-only.

`intentcall_gemma` stays in the workspace as an example-only adapter package. It
is marked `publish_to: none` and is not part of the pub.dev release train.

---

## 🔗 Local sibling repo path overrides

**Q: When should the mcp_flutter sibling use local IntentCall path overrides?**

Normal consumers should use hosted `intentcall_*` packages from pub.dev. Use
path overrides only for local IntentCall development before a release is
published. When cloned as sibling repositories (`agentkit` and `mcp_flutter`),
a temporary local-development override looks like:

```yaml
dependency_overrides:
  intentcall_core:
    path: ../agentkit/packages/intentcall_core
  # … etc
```

If your checkout uses a different local folder name, update the path or use
`INTENTCALL_ROOT` for local source validation:

```bash
INTENTCALL_ROOT="$(pwd)/../agentkit" make check-intentcall-integration
```
