Use when an F-block touches the product layer — framwork/.codeadd/, provider-map.json or cli/. Registration, build.js, the cli test suite and artefact structures.
Installs into .claude/skills of the current project.
Are you the author of Add Framework Product Layer?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/brabos-ai-add-framework-product-layer)
---
name: add-framework-product-layer
description: "Use when an F-block touches the product layer — framwork/.codeadd/, provider-map.json or cli/. Registration, build.js, the cli test suite and artefact structures."
---
# Product Layer Mechanics
<!-- uses:
- skill: building-commands
- skill: add-framework-development
- skill: add-product-artefact-renaming
- mention: add-build-ledger
- mention: add-framework--done
-->
Loaded on the first `[product]` F-block of a build. Everything here is about artefacts that ship to
users. WHAT gets recorded about execution is `add-build-ledger`'s job; the internal layer has its own
skill.
## When to Use
- An F-block naming `framwork/.codeadd/`, `framwork/provider-map.json`, `cli/`, or `mcp/`.
## When NOT to Use
- An F-block naming `workbench/`, `scripts/` or the repo root — **`mcp/` excepted**, which sits at
the root and is product because `scripts/build.js` copies it into the npm package.
**This skill does not write `AGENTS.md`.** Its inventory block is generated by
`node scripts/inventory.js` and kept current by `/add-framework--done`; everything else in the file
changes only through a plan. A build that edits it is a build doing someone else's job.
---
## Also Read, Before Writing
```
framwork/.codeadd/skills/add-resource-path-convention/SKILL.md # ALWAYS — path references
framwork/.codeadd/skills/add-ecosystem/SKILL.md # ALWAYS — ecosystem overview
framwork/.codeadd/skills/add-token-efficiency/SKILL.md # ALWAYS
framwork/.codeadd/skills/add-skill-creator/SKILL.md # IF the artefact is a skill
framwork/.codeadd/skills/add-doc-schemas/SKILL.md # IF generating docs
```
`add-framework-development` carries the artefact anatomies, the `<!-- uses: -->` syntax and the
build-pipeline internals. Read it when creating a new artefact type or changing how the build works.
## Source of Truth
| Type | Path | Built by `build.js`? |
|------|------|----------------------|
| Command | `framwork/.codeadd/commands/*.md` | Yes — provider files generated |
| Skill | `framwork/.codeadd/skills/*/SKILL.md` | Yes — provider files generated |
| Agent | `framwork/.codeadd/agents/*-agent.md` | Yes — only for providers declaring an `agents` pattern |
| Script | `framwork/.codeadd/scripts/*` | No — shipped verbatim |
| CLI source | `cli/src/*.js` + `cli/tests/` (suites and their `helpers/`) | No — published as the npm package |
```
IF TEMPTED TO EDIT A framwork/ PROVIDER DIRECTORY:
⛔ DO NOT USE: Write or Edit on framwork/.claude/, .codex/, .cursor/, .agents/, .opencode/, …
✅ DO: Edit framwork/.codeadd/ and let build.js generate the rest
```
## Three Rules That Bind Every Source Edit
- **HTML comments are stripped at build.** Use them for source-only notes. `<!-- uses: -->` and
injection markers depend on this.
- **Never write a raw `.codeadd/` path.** Use `{{cmd:NAME}}` / `{{skill:NAME/FILE}}`;
`lintResourcePaths()` warns otherwise. Scripts are the exception — always `.codeadd/scripts/`.
- **A change to the document model updates `mcp/reference.md` in the same F-block.** See below.
### The Document Model Answers to `mcp/reference.md`
`mcp/reference.md` is the source of truth for what a document is to the MCP — the frontmatter
contract, the three kinds, the type registry, the owner modes, the relation vocabulary and the
recorded divergences from OKF. Its two tables are generated from `mcp/types.mjs` by
`node mcp/generate-reference.mjs`, and `cli/tests/mcp-document-model.test.js` L4.1 asserts they match.
```
IF AN F-BLOCK CHANGES THE DOCUMENT MODEL, THE TYPE REGISTRY, THE RELATION
VOCABULARY, THE CORPUS RULES OR ANY ACTION'S RESULT SHAPE UNDER `mcp/`:
⛔ DO NOT: Commit the block without `mcp/reference.md` in the same diff
⛔ DO NOT: Leave the reference describing the previous model "until the next pass"
✅ DO: Regenerate the tables, update the text around them, and say in the commit what changed
```
**The trigger is narrow on purpose.** A bug fix, a new action, a performance change or a test that
touches `mcp/` does NOT fire it. A rule that fires on every edit is one people learn to ignore, and
then it protects nothing on the day it matters.
**Same F-block, not same delivery**, for the reason the ledger already gives about a rename: a
dependent updated in a later block leaves the tree inconsistent at a commit boundary, and a reference
that describes the previous model is worse than one that describes nothing — a reader trusts it.
**This rule exists because that failure is on the record.** A retirement note in
`add-doc-schemas/references/fix.md` claimed a work item's file list "lives now" in the graph index.
It did not, and had not for weeks. The code was right and the document was wrong, and every reader
believed the document — which is what a source of truth is for, and what makes a stale one expensive.
---
## Registration (MANDATORY for a new command, skill or agent)
For a product command or skill rename, load `add-product-artefact-renaming` first. Preview and apply the product source change under its own F-block tag; keep root docs and workbench text in an internal block.
An artefact absent from `framwork/provider-map.json` **fails the build** — it is built for no provider.
```json
"commands": { "<name>": { "description": "<from the command frontmatter>" } }
"skills": { "<name>": { "providers": ["claude","antigrav","cursor","opencode"] } }
```
Default providers = all. Omit the `providers` field to get all. Use `["antigrav"]` for a skill not
exposed to the end user.
**A provider entry may reuse another provider's `dir`, `commands` and `skills` strings verbatim,
rather than building its own tree.** ZCode does this with codex: it declares no independent
`commands`/`skills` output of its own conceptually, but its entry still names the SAME `dir`,
`commands` and `skills` values codex's entry does, so `buildResources` writes the identical bytes to
the identical path a second time rather than a new one. The result is one tree on disk shared by both
providers — no duplicate file, and a user with both installed never sees a skill listed twice. Only
what genuinely differs (ZCode's own agent dialect and `agentsDir`) gets a provider-specific pattern.
Reach for this when a new provider's own layout would otherwise duplicate an existing one exactly.
```
⛔ cli/ IS NOT IN THE REGISTRY:
⛔ DO NOT: Register a cli/ module in provider-map.json
⛔ DO NOT: Expect build.js to emit cli/ output — it never touches cli/
⛔ DO NOT: Apply the building-commands ruler to JavaScript — it governs .md only
⛔ DO NOT: Bump cli/package.json — that belongs to the release command
✅ DO: Edit cli/src/ and cli/tests/ directly; they ship as written
```
## Artefact Structures
**Command** — the mandatory skeleton, gates and checklist are `building-commands`. Create ONLY in
`framwork/.codeadd/commands/`.
**Skill** — frontmatter `name` + `description`, then When to Use / When NOT to Use / content /
validation checklist. Subdocs go in `references/`, never flat.
**Script** — header block naming usage, dependencies and exit codes; then detection, execution,
structured output. The header is the script's contract and its native suite
(`scripts/tests/<name>.test.cjs`) pins it.
**CLI module** — one concern per module, owning its registry, its state helpers and its
`export async function <name>(cwd, args, scope)` entry. Follow `cli/src/features.js` and
`cli/src/plugins.js`. **A new subcommand is unreachable until it is registered in the dispatch AND
the help text of `cli/src/cli.js`.**
---
## Validation
### Every product F-block
```bash
ADD_GRAPH_WARNINGS=1 node scripts/build.js
```
Exit 0, and no warning absent from the baseline measured before the first F-block. **Two different
classes of warning come out of this one run**: LINT warnings from the raw-path linter, and graph
warnings from the artefact graph. Both count, and **`build.js` summarises the graph ones as a count
unless `ADD_GRAPH_WARNINGS=1` is set**, so the bare form cannot support a "no new warning" claim.
The same run is where the three graph gates fire: a `uses:` target that does not exist fails, an
artefact missing from `provider-map.json` fails, and a name in prose with no declared relationship
fails.
### Markdown artefacts
Simulate execution against three scenarios: happy path, gate violation, edge case. Then:
- [ ] Can the agent skip a gate? Must be impossible
- [ ] Are prohibitions tool-specific, not generic?
- [ ] Is the order bypassable? Must not be
### CLI artefacts — a mental test is NOT evidence
```bash
npm test # at the repository root
```
```
IF type=cli AND THE SUITE HAS NOT BEEN RUN THROUGH `npm test`:
⛔ DO NOT: Report the F-block complete
⛔ DO NOT: Claim a validation level passed
✅ DO: Run it and read the result
```
**Root `npm test` goes through `scripts/run-tests.js`.** It selects the framework gate: CLI,
scripts and package smoke. `npm run test:all` adds board typecheck/unit and built-app E2E.
The runner owns execution, dependencies and isolation; use ordinary npm commands and read its
result. Its header owns transport mechanics. Generated output and sidecars must leave the
developer's checkout unchanged.
```
IF YOU WANT ONE FILE:
⛔ DO NOT: Bypass the dispatcher with a direct test tool
✅ DO: `npm run test:cli -- tests/<name>.test.js` at the root
```
**The suite runs in two projects, and a green run is proof.** Every file that does not spawn a
subprocess runs in parallel; the files that do run one at a time, after the rest. It used to be serial
or nothing, for two reasons that each now have their own fix: a test that rebuilt the real tree and
deleted the sidecars mid-run, and subprocesses timing out under a dozen workers. `cli/vitest.config.js`
carries the history and the numbers; if the two ever disagree, that file is the one with the numbers
in it.
```
IF THE RUN FAILS WITH "A test changed a build sidecar in the real tree":
⛔ DO NOT: Re-run until it passes, or treat it as flakiness
✅ DO: Find the test that wrote into framwork/.codeadd/ and make it work on a copy
```
**That message is the suite's own guard, not noise.** A test that writes into the real tree breaks
every test reading it afterwards, in whatever order the workers run.
```
IF ANY TEST FAILS:
⛔ DO NOT: Attribute it to flakiness without evidence
⛔ DO NOT: Report the raw failure count as this block's result
✅ DO: Re-run that file alone (`npm run test:cli -- <name>`), baseline against a clean tree, report the delta
```
**If stdout carries `Debugger listening on ws://…`**, an editor injected `NODE_OPTIONS`. Clear it
(`unset NODE_OPTIONS VSCODE_INSPECTOR_OPTIONS`) before trusting any assertion on stdout or stderr.
**Baseline before blaming the change.** What flakiness this suite has left is load-dependent: the
tests that drive a real subprocess stretch under a busy machine and cross their timeout. A quiet
checkout runs the whole suite green. Compare against a clean tree and report the delta, never the raw
count.
⛔ **Get that clean tree with a throwaway worktree, NEVER with `git stash`.**
```
IF YOU NEED A CLEAN-TREE BASELINE FOR THE SUITE:
⛔ DO NOT USE: Bash for git stash, git checkout, git reset, git clean or git restore
✅ DO: git worktree add <tmp> HEAD --detach, run the suite in <tmp>, then git worktree remove <tmp>
```
**A fresh worktree uses the same npm commands.** The runner owns dependency preparation;
do not copy or link another checkout's dependencies to manufacture evidence.
`git stash` empties the tree you are standing in. Your own edits come back with `git stash pop`, but
any sibling agent running against that same tree loses its uncommitted work for as long as the stash
is held — and a build dispatches several at once. A worktree gives you the clean checkout without
touching the tree anyone is working in.
**A path-scoped read is not the substitute here.** `git diff -- <path>` answers "what changed in this
file", which is the right tool when you are attributing one failure to one file. It cannot answer
"does the whole suite pass on a clean checkout", and that is the question this paragraph is about.
**TESTS ARE MANDATORY.** Every changed module needs coverage in `cli/tests/`. Where the plan specifies
a RED-first matrix, write each assertion and CONFIRM IT FAILS before the implementation — a test
authored after the fix proves nothing.
### Native scripts — the scripts suite runs before the block closes
An F-block touching `framwork/.codeadd/scripts/*.cjs` does not close until the suite has been run and
read:
```bash
npm run test:scripts
```
```
IF THE BLOCK CHANGED A .cjs FILE AND THE SUITE HAS NOT BEEN RUN:
⛔ DO NOT: Report the F-block complete
⛔ DO NOT: Append its `complete` line to the ledger
✅ DO: Run it and read the result
```
**This gate was unenforceable until recently, and that is why it did not exist.** The suite used to
run under Bats, which forked a process per case; on Windows it was slow enough that nobody paid it.
A gate nobody can afford to satisfy is a gate everybody rules their way past.
**Run the suite the change can reach, one file at a time:**
`node scripts/run-tests.js scripts scripts/tests/<name>.test.cjs`. It returns in seconds; the full
`test:scripts` runs every `scripts/tests/*.test.cjs`. **That scoped run is what closes the block**
when the change reaches only that entry — a `.cjs` whose own `.test.cjs` is the only suite that calls
it. When the change reaches a script other suites exercise, run each of those files too, or the whole
suite.
```
IF THE SCOPED RUN REPORTS A FAILURE:
⛔ DO NOT USE: Bash for a fork-per-case runner — none is needed; Node's own test runner is the route
✅ DO: node scripts/run-tests.js scripts <file> — then read its EXIT and count the `# pass`/`# fail`
lines; an empty grep is not a pass
```
**`npm run test:scripts` owns the runner decision.** Read the dispatcher's diagnostics to distinguish
assertion failures from refusal or unavailable preparation. An unavailable run is not passing evidence.
```
IF `npm run test:scripts` EXITED 2 BECAUSE THE RUNNER REFUSED:
⛔ DO NOT: Report the gate passed
⛔ DO NOT: Force a hand-picked path to manufacture a local verdict
✅ DO: Record a ruling in `add-build-ledger`'s three-part form — the gate was not run, why the
runner refused, and what it costs if the suite would have failed — then close the block
```
**The verdict then belongs to CI, which owns it regardless.** `/add-framework--done` reads the CI run
rather than re-running the suites for exactly this reason, and a local suite that cannot run here
changes nothing about that.
---
## Common Rationalizations (BLOCKED)
| Excuse | Reality |
|--------|---------|
| "I'll edit the provider file directly, it's faster" | build.js overwrites it. Edit `.codeadd/` |
| "The suite is flaky, this failure is noise" | Baseline against a clean tree, report the delta |
| "I ran the tool directly, it is the same gate" | Use the supported npm command; the dispatcher owns isolation and execution policy. Direct tools can mutate state before the gate protects it |
| "It's a small artefact, registration can wait" | Unregistered ships to nobody and fails the gate |
| "That test asserts the old rule, delete it" | Update it, and comment why |
## Rules
ALWAYS:
- Edit `framwork/.codeadd/`, never a generated provider directory
- Register a new command, skill or agent in `provider-map.json`
- Run `npm test` after any registry change
NEVER:
- Register a `cli/` artefact in `provider-map.json`
- Bump `cli/package.json`
- Report a cli F-block complete on a mental test alone
- Edit `AGENTS.md` — its inventory block is generated and the rest changes only through a plan