bundle assemble the export into an installable agent plugin¶
kac bundle [--no-color]
| Option | What it does |
|---|---|
--no-color |
Turn colour off. NO_COLOR in the environment does the same. |
What it does¶
bundle assembles what export wrote, plus the .plugin/ tree your corpus keeps beside its records, into
a Claude Code plugin directory under .dist/plugin/. A plugin is the corpus packaged so an agent can install it.
bundle also writes the marketplace that offers it into .dist/ above, so you can install the result.
Run export first. bundle reads that output, plus the plugin tree and one key of .corpus.yaml. It
never loads the corpus itself. The plugin tree is .plugin/ at the corpus root, unless
plugin.from points at a shared one.
What decides whether a component ships¶
A component is one skill or hook in the plugin tree. Each declares under metadata.components which record types it
reads, as requires, and ships only where the export contains every one of them.
requiresreads the export. An export includes what its corpus consumes, so a corpus with no glossary of its own ships the glossary skill where it consumes a corpus that has one.- An entry may name a shape, as
glossary@1. The component then reads that type's files at shape version 1. A bareglossaryasks only that the type is present. Both are trimmed where the export has no glossary. - A trimmed component's files leave with it. The trim matches the declared path and everything beneath it, on whole
segments, so
skills/adoes not takeskills/ab. - Every skill and hook needs a component. A run stops on a file under
skills/orhooks/that no component claims, because nothing would trim it. A file anywhere else, such as a README or a licence, ships whatever the corpus adopted. - A component naming no type supports the lookups, unless it is standalone.
ask-the-corpus,corpus-retrievalandrequest-deviationship wherever a component reading a type shipped, and leave with the last of them. The trim reports that asno component it supports survived.raise-findingdeclares"standalone": trueand always ships. - A run that trims every component warns and still builds. The empty plugin installs, and
bundle.jsonlists each component it dropped and the type that component needed.
Views¶
A view is a Markdown file bundle writes into the plugin from the export, shaped for one moment of the work. You
declare one as a component whose view key names it. bundle writes it at the component's path, which nothing in
the plugin tree may already use.
view |
What it lists, under each standard's id, title and status | Its component reads |
|---|---|---|
build-index |
each rule by title | standards@3 |
review |
each rule's obligations, and the controls checking it, with their mechanism | standards@3, and controls@3 where present |
A view is trimmed like any other component. A type under optional is read where the export has it, and its absence
trims nothing, so a corpus with no controls still gets the review view. The build index opens on how many rules the
corpus states, and names the lookup skill that reads standards where one survived the trim.
The export format says what a view reads.
Copying the review view says how another review tool
can read it.
The build-index hook¶
Where the hooks component and the build index both ship, bundle writes hooks/build-index.json. The hook runs
before every tool call. It prints the build index at the first call of the main session, at the first call of each
subagent, and again after the session compacts. Claude Code passes the build index to the agent with that call's
result, whichever tool the call used. An agent whose first call is an edit has the build index once that edit returns.
Claude Code passes a hook at most 10,000 characters of context. Over that, bundle writes the rule and standard counts
and the build index's path in its place, and warns.
A corpus with no tracker¶
raise-finding and request-deviation file an issue on the tracker the export
states. Where that tracker has no backlog to file on, bundle warns and still builds. The warning names each of the two
skills the plugin ships:
bundle: this corpus has no tracker to file on, so raise-finding and request-deviation will print each issue for you to file. Add a `tracker:` block to .corpus.yaml and export again to set one.
A corpus publishing to none has no tracker until you add the block. validate says nothing about it,
because a corpus that never bundles never files.
Examples¶
A built plugin¶
kac export
kac bundle
bundle prints each file as it writes it. The run closes with what shipped, what was trimmed and why, and how to
install it:
wrote .dist/plugin/hooks/breadcrumb
wrote .dist/plugin/hooks/breadcrumb.cmd
wrote .dist/plugin/hooks/breadcrumb.txt
wrote .dist/plugin/hooks/build-index
wrote .dist/plugin/hooks/hooks.json
wrote .dist/plugin/skills/corpus-retrieval/SKILL.md
wrote .dist/plugin/skills/glossary-lookup/SKILL.md
wrote .dist/plugin/skills/raise-finding/SKILL.md
wrote .dist/plugin/skills/raise-finding/azure-devops.md
wrote .dist/plugin/skills/raise-finding/github.md
wrote .dist/plugin/skills/request-deviation/SKILL.md
wrote .dist/plugin/skills/request-deviation/azure-devops.md
wrote .dist/plugin/skills/request-deviation/github.md
bundle: trimmed skills/controls-lookup: the export carries no controls.
bundle: trimmed skills/fix-lookup: the export carries no fixes.
bundle: trimmed skills/policy-lookup: the export carries no policies.
bundle: trimmed skills/process-lookup: the export carries no processes.
bundle: trimmed skills/standards-lookup: the export carries no standards.
bundle: trimmed skills/standards-review: the export carries no standards.
bundle: wrote 50 file(s) to .dist/plugin/ as example-libraries 0.31.0. 6 component(s) included, 6 trimmed.
bundle: .dist/ is a marketplace holding it. Install it from a path with: claude plugin marketplace add ./.dist
A trimmed component is not an error. Its record type is one this corpus does not export. This corpus exports no
standards, so the build-index hook ships with no hooks/build-index.json and prints nothing.
An install¶
claude plugin marketplace add ./.dist # register the marketplace bundle wrote
claude plugin install example-libraries@example-libraries # install the plugin from it
.dist/ and the plugin inside it are untracked, so nothing here needs a branch or a credential.
The install takes <plugin>@<marketplace>. bundle names both after corpus in .corpus.yaml, so write your
corpus's name on each side of the @. The names an install uses
says where each name comes from.
The components that shipped¶
example-engineering exports controls, glossary, policies and standards, so it ships a lookup skill for each, beside
the four skills that name no type of their own. Its export has standards, so it also ships both views and
standards-review, which reads the review view. It has no fixes and no processes, so fix-lookup and
process-lookup are trimmed. The closing line counts what survived:
bundle: wrote 82 file(s) to .dist/plugin/ as example-engineering 0.45.0. 12 component(s) included, 2 trimmed.
bundle.json lists them, and it travels inside the plugin:
jq -c '{kept: [.included[].path], trimmed: [.trimmed[].path]}' .dist/plugin/bundle.json
{"kept":["skills/corpus-retrieval","skills/glossary-lookup","skills/raise-finding","skills/request-deviation","skills/ask-the-corpus","skills/controls-lookup","skills/policy-lookup","skills/standards-lookup","skills/standards-review","views/build-index.md","views/review.md","hooks"],"trimmed":["skills/fix-lookup","skills/process-lookup"]}
To see which assembled skill reads the file one type exports, search for it:
grep -l clauses.jsonl .dist/plugin/skills/*/SKILL.md
.dist/plugin/skills/policy-lookup/SKILL.md
A pipeline check¶
npm install -g @anthropic-ai/claude-code
claude plugin validate ./.dist/plugin --strict
claude plugin validate ./.dist --strict
bundle validates nothing it assembles, so this is the layer that does.
The plugin manifest¶
.plugin/.claude-plugin/plugin.json declares what the plugin ships: metadata.corpusRoot, and the components under
metadata.components. bundle fills in who the plugin is from .corpus.yaml, through the
export:
| Manifest key | Comes from |
|---|---|
name |
corpus |
version |
content-version |
displayName, description, license, author |
the four keys beside them |
homepage, repository |
publishing.base |
keywords |
the types the export carried |
bundle removes a key the corpus declared nothing for. Three keys are the exceptions:
author: a corpus that named nobody is filed under its own name, becauseclaude plugin validate --strictfails a manifest with noauthor.description: a corpus with no description gets one built from its name.version: a corpus with nocontent-versionkeeps the version the manifest states.
Every other key in the manifest survives untouched.
Known limits¶
It refuses a run it cannot finish. Each of these prints a reason, writes nothing and exits 1:
- a plugin tree with no manifest
- a
plugin.fromnaming a folder that is not there - a manifest that is not JSON, or that states no
corpusRoot, or acorpusRootthe plugin tree already uses - an export with no manifest, or one naming no corpus because
.corpus.yamlstates nocorpus - an export whose
formatVersionis not the one this build reads - a component reading a type at a shape version the export does not carry, or at one that is not a whole number
- a file under
skills/orhooks/that no component claims - a view
bundledoes not write, or a view whosepaththe plugin tree orcorpusRootalready uses
Where the format versions differ, the message gives both. Rebuild an export that is behind the tool with
export. An export ahead of the tool means your kac is the older of the two. A shape mismatch means the
type's files have changed. The export format says what moves a shape version.
It validates nothing it assembles. Run claude plugin validate, as a pipeline check shows.
It does not publish. Pushing the result anywhere is a separate job.
The hook has been proved on macOS only. Whether Claude Code runs the .cmd half of the hook on Windows is not yet
known.
A component's requires is not checked against the schema. A misspelt type trims its component with the same
message as a type this corpus declined.
A view has no paths: and loads nowhere by itself. The build-index hook pushes the build index. The review view
reaches an agent only where a skill reads it.
The build-index hook marks each agent done in the plugin's data directory. It makes one directory per agent under
build-index/, and deletes any older than 7 days each time it makes one. A compaction deletes the session's
directories. A session whose id the hook cannot read gets the build index on every call.
The export is copied whole. A component that survives the trim brings the entire export, including types no surviving component requires.
export is what writes the data this assembles.