Starting a new domain package without inheriting somebody else's domain
Create the right starting point
People ask this as: generate a plugin skeleton that already passes the framework's own checks
Two files, an identity, five empty declarations and a README — a package that passes validate, inspect and the conformance harness with no manual edit, and that models nothing. It generates no record, action, policy, capability, provider, Admin section or MCP tool, on the explicit reasoning that a generated domain field is a claim about a business nobody described yet, and that an agent reads generated code as a decision already taken. It is dry-run by default, writes only with --apply, never overwrites and never silently renames.
Where this stops
Read this before the rest of the page. Every line below is a thing this does not do.
- It generates no domain semantics at all: no record, action, policy, provider, capability, Admin section, Solution Plan, MCP tool or test (NO_DOMAIN_SEMANTICS).
- It does not compose the package. Adding the one static import to packages/domains/generated/index.js is a deliberate human act, and that is the framework's security model rather than an oversight to automate (NO_COMPOSITION).
- It opens no database and runs no migration. A package owns a record only once a module manifest is applied through the module factory (NO_DATABASE_OR_MIGRATION).
- Nothing is downloaded, installed, signed, published or registered. There is no marketplace, no registry and no remote install — a package is source in your own repository (NO_INSTALL_OR_PUBLISH).
- Passing the conformance harness proves framework conformance and nothing about a domain the package does not yet model (CONFORMANCE_IS_NOT_CORRECTNESS).
- It checks the target directory, never the composed application, so a duplicate package identity is not caught here — the registry refuses it at startup instead (IDENTITY_UNIQUENESS_NOT_CHECKED).
- A plan reserves nothing and authorizes nothing; --apply re-derives the target and can still be refused (PLAN_IS_NOT_A_RESERVATION).
- The single-rename commit replaces a target directory that is empty at the instant of the rename. Nothing is lost, and it is recorded rather than left to be discovered (FINALIZATION_REPLACES_AN_EMPTY_DIRECTORY).
No authentication, tenancy or RBAC. The server is local-development-only. An actor header is an assertion, not an identity. Do not expose it to a network. Every claim and every limitation is on one page.
What question it answers
"Give me a new domain package that already conforms, and nothing else." Once a machine can tell you whether a package conforms, the expensive step is no longer checking — it is starting, because starting otherwise means opening an existing package, copying two thousand lines of somebody else's domain and deleting the parts that do not apply.
The command takes a name; its three siblings take a directory. `scaffold` gives you an empty conforming package, `validate` asks whether a declaration is structurally valid, `inspect` asks what a package declares, owns, offers and needs, and `test` asks whether it holds up when a real application composes it. It is the only one of the four that can write.
What it does
`npm run crm -- package scaffold <name> --json` plans `packages/<name>/src/index.js` and `packages/<name>/README.md`. Run here for a name of `field-service` it reported mode `plan`, `modeReason: "no --apply was given, so nothing was written"`, two files at 2,947 and 2,160 bytes with their content hashes, a package declaration of `{name, version: 1, packageContract: 1, resources: [], actions: [], requires: [], provides: [], policies: []}`, empty `problems`, empty `staleStaging`, and a content fingerprint of `fc924591…`. `git status` was unchanged afterwards.
It also carries a `conformance` block stating its own acceptance criterion in machine-readable form: expectation "passes `crm package validate`, `crm package inspect` and `crm package test` with no manual edit", the five not-applicable reasons the conformance run will legitimately produce (`NO_RESOURCES_DECLARED`, `NO_ACTIONS_DECLARED`, `NO_DEPENDENCIES_DECLARED`, `NO_CAPABILITIES_OFFERED`, `NO_POLICIES_DECLARED`), and `proves: "framework conformance only; it models no domain yet"`. A scaffold that cannot clear its own conformance bar has not saved anybody time — the plan says which bar, and which skips are honest.
Every declaration is present and empty: present so an author edits a list they can already see, empty so the framework never puts words in their mouth. The generated source is mostly comment explaining where a record, an action, a capability and a policy go.
Writing is atomic. Files are staged in a directory named uniquely per run and the package becomes visible through one `rename` — the commit point and the only race. Eight independent processes racing one target were measured producing exactly one `applied`, seven `TARGET_CLAIMED`, a complete package and zero leftover staging. An earlier draft staged in a fixed directory and refused with `SCAFFOLD_IN_PROGRESS` when it was occupied, which is a lock a crashed process holds forever; now each run stages under its own name, nothing is ever blocked, and leftovers are reported as `staleStaging` rather than deleted by a guess that could destroy a concurrent author's work.
The name rule is the framework's own `validatePackageDefinition`, not a second slugger. That matters because the name is interpolated into generated source: validating it against the real rule is what keeps a quote, a backtick or a newline from ever reaching a template. An author who asked for `Field Service` is refused with a suggestion rather than silently handed `field-service`.
What it refuses to claim
Eight codes, printed on every run including `--help`: `NO_DOMAIN_SEMANTICS` (the scaffold writes an identity and nothing else, because a guess about a business nobody described is worse than an empty file), `NO_COMPOSITION` (the package is not added to the composition file — composing it is one line a human adds deliberately, which is the framework's security model), `NO_DATABASE_OR_MIGRATION`, `NO_INSTALL_OR_PUBLISH` (nothing is downloaded, installed, signed, published or registered anywhere), `CONFORMANCE_IS_NOT_CORRECTNESS`, `IDENTITY_UNIQUENESS_NOT_CHECKED`, `PLAN_IS_NOT_A_RESERVATION` and `FINALIZATION_REPLACES_AN_EMPTY_DIRECTORY`.
The last three are the kind of honesty that is easier to omit. `IDENTITY_UNIQUENESS_NOT_CHECKED`: the target directory is checked, the composed application is not, because reading which package names are already registered means importing the composition, and this command runs no project code — a genuine duplicate is refused by the registry at startup, naming the collision. `PLAN_IS_NOT_A_RESERVATION`: a plan reserves nothing and authorizes nothing, and `--apply` re-derives the target from the filesystem as it is at that moment. `FINALIZATION_REPLACES_AN_EMPTY_DIRECTORY`: POSIX `rename` refuses a file, a symlink and a non-empty directory but replaces an empty one, so a directory created empty in the microseconds between the check and the rename would be replaced — nothing is lost, because an empty directory has no content, and Windows refuses every existing destination.
Because a plan and an apply both exit 0, every document carries a `modeReason` saying which flag won, and the human view prints NOTHING WAS WRITTEN rather than a polite suggestion. An agent reading only the exit code would otherwise conclude it wrote a package it did not write.
The scaffold does not close domain correctness (its output models nothing), record creation (a resource still needs a manifest and the module factory), composition (still one hand-written import), package-contributed HTTP routes (still open), or distribution (there is no marketplace, registry or remote install).
What it costs to run
Sub-second, and by default it writes nothing at all. Exit codes are `0` planned or applied and `1` refused — there is no third code, because unlike the conformance harness the scaffold reads no package and boots no application, so it has no unreadable case to distinguish.
Same name, byte-identical bytes. No timestamp, no random id, no absolute path and no environment value enters the fingerprint, so a scaffold in a checkout whose path contains a space produces the same bytes as one that does not.
Every hostile target is a refusal before any write: an occupied target (`TARGET_UNAVAILABLE`, via `lstat`, so a dangling symlink counts), `--into ../..` or an absolute path outside the project, a symlink escape resolved through `realpath` — including one whose leaf does not exist yet, because `mkdir -p` would otherwise follow the link on the way in — an invalid or reserved name (`PACKAGE_NAME_INVALID`), and a target that appears between the plan and the commit (`TARGET_CLAIMED`). A write that fails halfway removes this run's staging; no partial package is ever visible.
Where it fits
In the authoring flow it is the step after a plan and before an edit: inspect the application, write and check a Solution Plan, scaffold, edit, validate, inspect, run the conformance harness, write your own domain tests, then compose the package by hand.
The honest neighbour is worth naming. The jobs matrix still records "Scaffold a new package from a template" as not supported, and that row is not stale by accident: this command deliberately refuses to be a template. The rich-template option was considered and rejected because a generated `partner`, `score` or `status` field must first be noticed and then removed, and an agent is worse at that than a human. The teaching package in this repository is one resource, one action and one policy, and it still had to be designed — the bytes were never the hard part.
The evidence this page rests on
Claims and limitations are printed from site/claims.json word for word. Job statuses come from docs/benchmarks/jobs.json; a job with no page of its own is listed with its status rather than linked.
Claims
- C-13 A customer-authored domain package attaches and detaches with the kernel's fingerprint unchanged, and reaches another package only through a capability it declares.
LimitNo scaffold, no registry, no marketplace, and no sandboxing — package code runs with the host process's authority. Detaching leaves its data behind; there is no uninstall.
- C-18 The MCP server exposes project context and narrow write tools to a coding agent; anything that generates code or destroys state is dry-run unless you pass an explicit apply flag.
LimitStdio only, local only. There is no hosted or authenticated MCP endpoint, and the server inherits the local process's authority.
- C-17 Zero third-party runtime dependencies. Node 22 and a checkout — no build step, no bundler, no framework underneath your framework.
LimitDevelopment dependencies and the eventual PostgreSQL adapter are separate questions. Having no runtime dependencies is a property of the framework, not of whatever you add on top of it.
Limitations
- L-08 Ownership today means copying source, not installing a dependency. There is a project bootstrap and there is no published package, and the two are different facts. The repository's bootstrap command scaffolds a project that boots, reports `valid` from `app inspect` and exits 0 from `project doctor`, offline and with no install — run from a checkout of this repository. The package published under the reserved npm name is still an empty 0.0.1 placeholder, so the `npm create` route installs nothing until a human publishes it. Either way the framework is vendored into the project rather than depended on by version: you own the result outright, and upgrading means merging, not bumping.
- L-07 This is a framework, not a product you sign up for. There is no hosted CRM, no free tier and no account. The output is an application in your repository that you run.
Jobs it covers
- JTBD-PK-01 Create and compose a custom local domain package — validated end to end
- JTBD-PK-03 Validate a package before booting it — partially supported
- JTBD-PK-05 Scaffold a new package from a template — not supported
- JTBD-PK-06 Install a package from a registry or marketplace — not supported