Clients and plugins
Expose a small factory-based client that works without plugins. Prebuilt interpretation plugins are optional and compose through the same contracts as third-party plugins.
The granary-ai package exposes factories that return clients directly; operations return Granary-owned plain typed outcomes. See the API reference for complete contracts.
Base and composed clients
createGranary() constructs the base client: pack installation, lock inspection, reviewed updates, and plugin composition. It has no document interpretation by default.
granary() is the SDK entrypoint. It defaults to the current directory and no plugins; explicit registrations determine its additional capabilities. Neither factory changes persistent project configuration merely by constructing a client.
All effectful operations return plain Outcome<Value, Failure> objects discriminated by ok; consumers need no massaman dependency. Invalid construction options and plugin setup throw errors with kind: 'invalid'. Standards conversions read, parse, and validate internally, returning content in memory without writing files. JSON returns JSON-compatible data; Markdown and YAML return strings. YAML uses JSON-compatible flow syntax, which is valid YAML 1.2. Markdown is an export, not a promise of lossless source reconstruction.
Structured source inputs
Git revisions accept exactly one of branch, tag, or commit; omission selects the remote default branch. Generic Git sources use { provider: 'git', repository, pack?, revision? }. Local sources use { provider: 'local', path, pack? } and reject Git revision selectors. Selection fields such as files and output belong beside source, not inside it. The client owns locator encoding; manifests and the CLI retain their compact syntax.
client.updates.prepare() prepares a review from declared intent without applying it. client.updates.apply({ review }) applies resolved decisions through existing provenance, stale-state, and transactional checks. No separate client diff() or user-facing standards setup() is needed.
Plugin definition
definePlugin() always returns a callable plugin factory. Its input is either a definition object for an unconfigured plugin or an arrow callback that receives options and returns a configured definition.
Configured plugins use the same definition shape:
Use an arrow for the configuration callback and method syntax for setup. Standalone implementation functions retain named function declarations. Plugin authoring modules may default-export the definePlugin() result.
Registration and lifecycle
pluginsis an array, never a keyed registration object.- Calling the plugin factory captures options and returns its definition. It does not initialize project resources.
- Core invokes synchronous
setup(context)once for each client construction. Setup wires capabilities; file reads, parsing, and other async work happen in their methods. - The returned readonly API is mounted under the plugin's literal
name, with its type inferred from the supplied plugins. There is no intermediate.documentsnamespace. - Duplicate plugin names and collisions with core capability names are errors. There are no implicit registrations to replace or collide with.
- Plugins compose capabilities without mutating the client or using
this, inheritance, or global registration. Reusing a plugin registration must not share mutable client state.
Content interpretation
The standards plugin owns its parsing, validation, enforcement interpretation, and conversions. Custom plugins can compose reusable parser utilities or use another parser entirely; there is no second parser registry to configure.
Ordinary content stays readable without plugins. XML in a file does not automatically classify or execute it. Preserve original content and provenance; ignore tags in code examples when interpreting authored markup. Content-pack installation never automatically loads executable plugin code.
Plugins are trusted application code, not a security sandbox. Core supplies narrow content-reading capabilities rather than installation transaction internals. Locking, ownership checks, and reviewed writes remain core responsibilities.
Package ownership
@granary/core owns the installation engine and definePlugin() contract. @granary/sdk owns granary() and depends only on core. @granary/plugins owns standards and the opt-in defaultPlugins() preset, and re-exports the core plugin definition helper. @granary/cli owns terminal commands. granary-ai combines SDK exports, plugin subpaths, and the executable without starting the CLI on import. Component packages are workspace-private until separately approved for publishing.
Core never imports a prebuilt plugin. Namespaced plugins.<name> manifest settings are JSON data; plugin registration remains explicit application code. Each plugin validates its own configuration. Standards uses plugins.standards, not a special top-level field.
Registered definitions may implement validateUpdate to inspect detached upstream and effective document views before core prepares a transaction. The standards plugin uses it to validate classified markup and preserve configured rule identities. A rejected validation prevents writes; the engine's provenance and stale-state checks remain mandatory even without plugins.
Writing authored XML or synchronizing a typed model back into source is outside this shipping cut. Humans and agents author the markup; plugins read and export it.