Turn a semantic-model markdown file (the artifact produced by
semantic-model-analyst) into one task-aware Agent Skill that
captures the domain knowledge needed to act on that model efficiently.
The generated skill does not duplicate use-semantius, it sits on top
of it, focusing exclusively on the domain (entities, lifecycles, label
rules, cross-FK invariants) that a generic platform skill cannot know.
The generated skill is plain Agent Skills format, a SKILL.md with
YAML frontmatter under a folder named after the model, and works in
any agent harness that loads Agent Skills, including Claude Code.
Writing conventions (apply to every file and message this skill produces)
These rules apply to chat output, the generated SKILL.md, the generated README.mdx, every reference file, and every script this skill writes.
1. US English spellings, always. Never British English. Examples (left = correct US form, right in backticks = banned British form): optimize (not optimise), behavior (not behaviour), modeling (not modelling), customize (not customise), recognize (not recognise), labeled (not labelled), materialize (not materialise), organization (not organisation), summarize (not summarise), categorize (not categorise), uncategorized (not uncategorised), normalize (not normalise), harmonize (not harmonise), analyze (not analyse). When in doubt between two spellings, pick the -ize / -or / -er form.
2. No em-dashes (—, U+2014). Banned everywhere this skill writes: SKILL.md, README.mdx, semantic-model citations, references, scripts, and chat output. This propagates. LLMs mirror the style of their context, so an em-dash inside generator prose causes generated skills to emit em-dashes too. Replace with: X — Y parenthetical → X (Y) or X, Y; X — but Y contrast → X. But Y. or X; Y; A — B — C triplet → split into two sentences. The en-dash (–) and hyphen (-) are fine in number ranges and compound words; the ban is on — used as punctuation. Before writing any file, literal-search for — and convert each instance.
3. Singular-subject grammar in confirmation prompts. "Looks good?" not "Look good?"; "Sounds right?" not "Sound right?". Use the form that agrees with the singular implicit subject.
4. README.mdx catalog-description rules. The README.mdx this skill generates is rendered side-by-side with other skills in a catalog gallery. These are hard constraints for catalog readability:
- Front-matter
description: one sentence, ≤140 characters, unique value-prop for this skill. Never starts with "This skill" and never contains the substring "this skill". Do not just restate the domain. Shape: verb-led, names actual capabilities (e.g. "Manages the recruiting funnel from requisition through application, interview, offer, and hire."). Do not use the "[verb] [object] so [outcome]" shape; the "so..." clause reads as trigger-fodder, not a capability list. - Front-matter
titleand the#heading: must always contain the word "Skill", and must read as natural English. If the sourcesystem_nameends in "System" (e.g. "Applicant Tracking System"), drop "System" and append "Skill" → "Applicant Tracking Skill". Otherwise append "Skill" directly: "Workforce Planning Skill", "Product Roadmap Skill", "Customer Relations Skill". - Body, immediately after heading: repeat the model's domain description verbatim (or near-verbatim), pulled from the model file's
domainfront-matter or §1 narrative. The body describes the SYSTEM, not the skill. The skill's unique angle lives only in the front-matterdescription. - No
generated_fromkey in README front-matter. - Validate before declaring done: literal-search for "this skill" (must be zero hits) and
—(must be zero hits), and check the heading contains "Skill".
5. Semantius entity-label symmetry (when the generated SKILL.md cites entity labels). singular_label is the bare singular noun matching plural_label. ✅ Product / Products. ❌ Product Name / Products. The skill-maker reads these from the source model file; if the source has an asymmetric pair, treat the source as buggy and ask the user to fix the model via the analyst before re-running the skill-maker (do not paper over it in generated output).
Schema compatibility: EXPECTED_MAJOR = 3
This skill expects model files written by semantic-model-analyst major 3. The model file's front-matter version: "MAJOR.MINOR" is checked at the start of Step 2. Major must equal EXPECTED_MAJOR, minor is informational and not compared. Files with a different major are rejected; the resulting per-domain skill would bake in stale recipes. Three cases:
- Older major, the file was written using a structure this skill no longer understands (different section numbering, different table shapes, missing fields). Tell the user to run
semantic-model-analyst; its archived-knowledge mode reads the older file and re-authors a current-major file from the same semantic content. Re-run skill-maker against the new file. - Newer major, the file was written by a newer analyst than this skill knows. Tell the user to update
semantius-skill-makerbefore retrying. - Missing
versionkey (legacy, pre-versioning), treat as major0; same response as older-major.
When the analyst's major bumps, this skill's EXPECTED_MAJOR must be bumped in lock-step (same commit when feasible). The trio of analyst, deployer, and skill-maker share the major.
The history of the skill-maker's contract changes lives in CHANGELOG.md — what each analyst-lockstep bump changed in the parser, indices, generated-skill preambles, and self-review principles. That file is not loaded at runtime; the body of this SKILL.md is the current contract, the CHANGELOG is the history.
Inputs
MODEL_PATH: absolute path to a*-semantic-model.mdfile with valid frontmatter (system_slug,system_name,entities, etc.) and §3 entity definitions.
Output
A single folder under the user's Claude skills root. Two top-level
files are always written; references/ and scripts/ subfolders are
written only when the JTBD classifier in Step 3 (Pass 3) puts work
there.
<skills-root>/<modelslug>/
├── SKILL.md # for the calling agent: glossary, JTBD outlines, guardrails
├── README.mdx # for humans browsing a catalog: narrative + diagram
├── references/ # optional, per-JTBD detail, loaded on demand
│ └── <jtbd-slug>.md
└── scripts/ # optional, deterministic ops, invoked not loaded
└── <op-slug>.sh
SKILL.md is what an agent harness loads at runtime. README.mdx is a
human-facing catalog entry: a downstream system renders these into a
gallery so a person can browse available skills, understand each one's
purpose at a glance, and decide whether to install it. The two files
share a folder, but their audiences and formats are different, do not
collapse them, and do not skip the README.
The references/ and scripts/ subfolders exist to keep SKILL.md
small. SKILL.md is loaded into context every time the skill triggers;
a reference file is loaded only when the agent enters that specific
JTBD; a script is never loaded, the agent invokes it. A 400-line
SKILL.md plus a handful of focused 80-line reference files costs less
per trigger than a 900-line monolith, because the agent typically
engages one JTBD at a time. The classifier in Step 3 (Pass 3) decides
which JTBD belongs in which file.
<modelslug> is the model's system_slug converted to kebab-case ,
underscores become dashes (e.g. customer_relations →
customer-relations, product_roadmap → product-roadmap). Every
multi-word skill in Claude Code uses kebab-case (use-semantius,
semantic-model-analyst, skill-creator); mashing words together
(customerrelations) breaks the convention and makes the description
text harder to read. The folder name and the SKILL.md name
frontmatter match exactly.
<skills-root> resolution order:
- Project skills root, the nearest
.claude/skills/directory walking up from the model file's location, or from the current working directory. Prefer this if found. - User skills root,
~/.claude/skills/(on Windows:%USERPROFILE%\.claude\skills\).
If both exist, ask the user which to use; default to the project root. If neither exists, ask before creating one.
Source-of-truth and the model reference
The generated SKILL.md is self-contained at runtime for the cross-cutting facts (glossary, enums, recipes resolve every value at generation time so the calling agent never needs to open the model file to act). FK shape and audit-logging detail live with the JTBD that uses them, not in SKILL.md, see "Where FK detail lives" below.
The model file is still referenced, but only as provenance metadata in
frontmatter (semantic_model: key, see the template below), not as a
clickable link in the body. Two reasons:
- A body link invites the agent to fetch a 400+ line file the SKILL.md has already condensed. That defeats the point of generating the skill.
- Provenance still needs to live somewhere, for re-generation, audit, and drift detection, and frontmatter is the right place because it's machine-readable and the agent doesn't render it as a follow-up action.
Set semantic_model to the model's system_slug value (e.g.
product_roadmap), not a path. The slug is stable across machines
and re-locations of the model file; an absolute path bakes in the
generator's working directory and breaks the moment the file moves
or the skill is shared with another user. The downstream tools that
consume this provenance (re-generation, drift detection) resolve the
slug against the current *-semantic-model.md files in the working
directory, so the slug is sufficient.
Workflow
Step 1, Load the Semantius reference
Before writing recipes, read the use-semantius skill so the JTBD recipes
use the right CLI patterns:
Read: <skills-root>/use-semantius/SKILL.md
Read: <skills-root>/use-semantius/references/data-modeling.md
Read: <skills-root>/use-semantius/references/crud-tools.md
Read: <skills-root>/use-semantius/references/cube-queries.md
You will not run semantius yourself in this skill, but the recipes you
bake in must be valid CLI invocations. If use-semantius cannot be located,
stop and ask the user.
Step 2, Parse the model
Read MODEL_PATH and gate on the version first before extracting anything else. Compare the file's front-matter version major against this skill's EXPECTED_MAJOR (see "Schema compatibility" near the top). Major equal → continue. Major older or missing → stop with a message naming the file's version, this skill's expected major, and the recommended fix (run the analyst's audit to migrate). Major newer → stop and ask the user to update this skill. Do not parse anything else when the gate fails; the file's structure may not match what the rest of Step 2 assumes.
Once the version gate passes, extract:
-
system_slug,system_name,domain, andmodule_type("domain"or"master"; absent → treat as"domain") from frontmatter.module_typecontrols two things downstream: the generated SKILL.md's "Module type" preamble line (Step 5), and the JSON-array merge note (master-module entities'computed_fields/validation_rulesentries carry an extrasource_modulereconciliation tag — see the per-entity extraction note below). -
Entity list with
singular_label,label_column, fields (name, format, required), enum values (§5), FK relationships (§4), parent/cascade-child flags,audit_log,computed_fieldsarray,validation_rulesarray, and (analyst v3.0+) any**Shared master cluster:** <name>annotation under the §3 entity heading. The two latter JSON blocks are platform-enforced JsonLogic — they change what the calling agent must do and what the calling agent must NOT do. See "Pass 2" merit-test adjustments below.When the entity belongs to a master module (
module_type: masterin frontmatter), itscomputed_fieldsandvalidation_rulesentries may each carry asource_modulekey naming the consuming module that contributed the entry. Treatsource_moduleas opaque reconciliation metadata: surface its existence in the preamble note but never use it to filter entries (every entry applies regardless of which module contributed it). Domain-module entities never carrysource_module.Record the cluster annotation per-entity into a
master_cluster_by_entityindex. The annotation is informational for skill-maker: it signals that the entity is a classic master concept (vendors →parties, cost_centers →finance, etc.) and that writes to it may have cross-domain visibility. Use it to surface a "shared across domains" callout in the generated skill's preamble (Step 5) and as a guardrail on every write recipe touching the entity (Step 6).Treat each entry's JsonLogic body as opaque and read only the human-readable metadata (
nameanddescriptionforcomputed_fields;code,message, anddescriptionforvalidation_rules). All matching downstream, Pass 2 suppression, gap-tracking, SKILL.md preamble copy, JTBD failure-mode wiring, uses that metadata. The analyst's job is to writedescription/messagetext precise enough that a candidate transition or derivation can be matched against it by reading. If the analyst's text is too vague to match, that is a model defect to send back; do not try to evaluate the JsonLogic to recover.One exception: scan each rule's JsonLogic body literally (string-match only) for the two platform-extension operators. Both are first-class JTBD signals:
require_permission, i.e.{"require_permission": "<code>"}somewhere inside the rule body. Extract(entity, rule_code, rule_description, permission_code)for every match into aconditional_permissionsindex. This index drives the new "Conditional-permission gate" merit signal in Pass 2 and thePlatform-enforced permissionsSKILL.md preamble (Step 5). The match is purely literal; which condition gates the permission still comes from the rule'sdescriptiontext, not from interpreting the JsonLogic.value_changed, i.e.{"value_changed": "<field>"}somewhere inside the rule body. Note(entity, field)pairs into atransition_gated_fieldsindex. This is informational, not a merit signal on its own; it helps Pass 2 confirm that arequire_permissionrule actually fires on transition rather than on every write (a quality check that the analyst's audit also runs).
In analyst v2.0+ files, the §2 Permissions summary table is the canonical source for the module's full permission catalog (not §8 step 1). Parse the table verbatim — five columns:
Permission | Type | Description | Used by | Hierarchy parent. Build apermissions_catalogindex from the rows. Use this index for everything: the SKILL.md "Platform-enforced permissions" preamble (one row per workflow permission), the role-hint lookup for eachconditional_permissionsentry (theDescriptionandUsed bycells give the role-hint richer than v1.11's §8 prose), and the cross-check that everyconditional_permissions[].permission_codeappears in the table. A mismatch is a model defect; refuse to generate and route back to the analyst. §8 step 1 in a v2 file is a procedural pointer to the table and no longer enumerates permissions itself; do not parse §8 for the permission list.v3.0 cross-module hierarchy rows. The
Hierarchy parentcell may reference a permission in a different module (e.g. an ITSM module'sitsm:readrow carryingHierarchy parent: parties:read); this is a cross-module bridge to a master module that the deployer materializes viapermission_hierarchyrows taggedorigin = "model_master". Recognize these by the presence of a<other_slug>:<suffix>value in the cell where<other_slug>is not this module'ssystem_slug. Record cross-module parents into amaster_inclusionsindex keyed by(this_permission, master_permission). The index drives a one-line note in the SKILL.md "Platform-enforced permissions" preamble: every permission with at least one cross-module parent gets a "includes : from the<master>master module" sub-note so the calling agent knows that holding it grants visibility into shared data. Same-module hierarchy rows still surface as the regular rollup chain; the index split only matters for the preamble note. -
Inbound-FK delete-mode index). Walk the §4 relationship table once more and build a
restrict_inboundindex per entity: for every row whoseKindisreferenceandDeleteisrestrict, record(child_entity, fk_field)against the target (the entity on the right side of the relationship). This index drives the new "Restrict-chained cleanup" merit signal in Pass 2 and is consumed by Pattern J (delete / archive of a parent entity that has restrict-children). The pattern matters because the platform refuses to delete a row that has livereference + restrictchildren; the calling agent must clean them up first, in dependency order, and a genericuse-semantiusDELETEwill surface only a single per-row constraint failure rather than the full chain. Also note inboundreference + cascadeandreference + clearrows separately; they don't trigger Pattern J but the SKILL.md preamble should still call out the cascade-delete / orphan-clear behavior for downstream awareness. -
Read-side rule indices). Two new optional §3 sub-blocks per entity. Build one index for each; both stay opaque on the JsonLogic body and read only the human-readable metadata.
row_visibility_rules— for every entity that declares a non-emptySelect rulesub-block, record(entity, jsonlogic_object, description?).descriptionis the entity-level prose that explains "what the rule restricts" — if the analyst didn't write one, walk §3 prose for a sentence that names the per-row predicate (e.g. "a note is visible to its author, or to anyone when itsvisibilityfield ispublic"). Surface a defect to the user if neither the sub-block carries adescriptionnor §3 prose names the scope; the SKILL.md preamble and per-JTBD guardrails need readable scope text. Critical (v2.2): the rule applies uniformly to every caller withview_permission. The platform evaluates the JsonLogic body per row with$today/$now/$user_idas reserved variables; there is no documented mechanism by which holding a specific permission causes the rule to be skipped for that caller. If the analyst's model contains a §7 architectural decision naming a documented broadening mechanism (Stage 12 option C separate-entity surface, option D PostgresBYPASSRLSrole attribute), record it underrow_visibility_broadeningkeyed by entity so the SKILL.md preamble can name the actual mechanism the user resolved. Never invent a "view_all bypass" the model file does not explicitly resolve in §7. This index drives the Row-level read scope SKILL.md preamble (Step 5), the Row-level read scope cross-cutting guardrail attached to every read recipe against a scoped entity (Step 6 / Step 7), and a new severity row in Step 9 self-review.dynamic_input_types— for everyInput type rulesentry across every entity, record(entity, field, jsonlogic_object, description?). Thedescriptionfield describes when the rule changes the field's UI mode —"hidden until status=approved, then readonly"is the canonical shape. Walk the JsonLogic body literally (string-match only, same posture asrequire_permission/value_changed) for one pattern that bears on JTBD shaping: a return value of"required"triggered by a sibling-field comparison (e.g.status=approved → "required") flags the field as a conditional-required side-effect on the same transition, which co-fires with Pattern A's side-effect-fields-on-transition merit signal. Record those into a sub-indexconditional_required_fieldskeyed by(entity, trigger_field, trigger_value, dependent_field). This index drives the Conditional field UI states SKILL.md preamble (Step 5), additional side-effect-fields-on-transition coverage in Pattern A recipes (Step 6 / Step 7), and a new severity row in Step 9 self-review.
Skip both indices entirely when the model declares no entity-level
Select ruleand no field-levelInput type rules. v2.1 and earlier files have neither.
Compute modelslug = system_slug.replace(/_/g, "-").
Refuse if §7.1 lists open blockers, the model is not finished and the skill would bake in wrong recipes.
Step 3, Plan jobs to be done
JTBD discovery is a two-pass process: nominate broadly with the pattern catalog below, then filter with the merit test. The merit test matters because every section sits in one file, slack adds noise to every load.
Pass 1, Nominate with the JTBD pattern catalog
The catalog below is generic across domains, not just transactional business models. Walk all nine patterns every time, regardless of what the model "looks like" at first glance.
Pattern A, Lifecycle transitions on status enums.
Shape test: entity has a *_status enum + side-effect fields like
approved_at, committed_at, rejected_at, closed_at. Each
non-trivial transition (more than a status flip) is a candidate. Pure
status flips with no side effects collapse into the entity's primary
lifecycle JTBD.
Sub-shape: approval / sign-off / workflow gate), broadened in v1.12). Any
transition whose validation_rules entry invokes {"require_permission": "..."} (look up the entity's conditional_permissions index from Step
2) is automatically a strong JTBD candidate. The agent calling this
skill is the one that will trip the permission check, so it needs an
explicit recipe: name the permission, name the role(s) that typically
hold it (read from §8 step 1 description), and tell the agent how to
recover from the platform's throw (surface the rule's message,
propose escalation paths like "ask an offer-approver to confirm" or
"defer to the user who is signed in as the approver"). This sub-shape
does not get suppressed by the merit-test "platform-enforced" rule,
because the platform enforces the check but the calling agent still
owns the workflow around it (the read of who-approves-what, the
confirmation prompt, the failure-message phrasing). Treat every entry
in conditional_permissions as one JTBD candidate (or a branch inside
the entity's primary lifecycle JTBD), even if Pattern A would otherwise
collapse the transition.
The sub-shape covers three distinct trigger fields, all flagged the
same way once value_changed + require_permission co-occur in the
rule body:
- Status-transition gates:
value_changed: "status"→ one ofapproved/signed/released/ etc. Recipe: confirm with the user, then PATCHstatus. - Submit-then-lock gates:
value_changed: "is_submitted"(oris_locked/is_final/is_complete) →true. Recipe: confirm the user is the row's owner (or holds the override permission); compose the PATCH that flips the lock; after the flip, treat the row as immutable except via the override. - Closure / cancellation gates:
value_changed: "status"→ one ofclosed/cancelled/void/hired(when §3 prose names weight). Same recipe shape as (1).
Sub-shape: housekeeping field appears on transition). For each entry in the conditional_required_fields sub-index (built in Step 2 from Input type rules), the transition JTBD that fires the trigger condition must include the dependent field as a same-PATCH side-effect. Concretely: when dynamic_input_types says "offers.approved_at hidden until status=approved, then readonly", the "approve offer" JTBD's PATCH body MUST set approved_at = $now in the same call as status = "approved" — the UI rule controls when the form renders the field, but a recipe that drives the transition through the API has no form to render and would silently leave approved_at empty. This is the same merit signal as the existing "Side-effect fields on transition" row (the *_at field that must be set in the same PATCH as the status flip), but the input_type_rule makes the pairing mechanical and detectable instead of relying on the analyst remembering to mention it in prose. Cross-reference with the entity's validation_rules: a paired family-5 conditional-required rule (server-side must be non-null when status='approved') confirms the intent and gives the JTBD's Failure modes block the platform code to surface on bad writes. When the UI rule fires but no server-side family-5 rule exists, surface as a platform_enforceable_gaps entry of kind validation_rule (the UI says the field must be set; the server doesn't enforce it; the recipe enforces it client-side as a stopgap).
Pattern B, Polymorphic action / event staging.
Shape test: entity named *_actions, *_events, *_transactions,
*_movements, with a polymorphic *_type enum that fans out
behavior. Nominate one JTBD with branches per type value, do not
split per enum value (that produces overlapping recipes).
Pattern C, Materialization / handoff.
Shape test: entity exists so another entity can be created from it;
look for originated_from_*_id back-pointers, or "approved → real"
flows where a staging row spawns rows in a different table. These are
the highest-value candidates because they touch multiple tables and
are the most error-prone for a calling agent to derive from the schema
alone.
Pattern D, Hierarchy operations.
Shape test: self-referencing FK like parent_*_id, manager_*_id,
backfill_for_*_id; tree-shaped data. Candidates: reparent without
orphaning, roll-up, cycle prevention. Skip if the self-FK is purely
informational ("previous version of this row") with no operation that
restructures the tree.
Pattern E, Ownership / sharing.
Shape test: owner_*_id field, sharing tables. Candidates: transfer
ownership of X, share X with, revoke access to X. (Multi-tenant
scoping is not a Semantius concern: each customer organization runs
its own Semantius deployment against its own database, so a single
instance never holds rows for two different tenants.)
Sub-shape: owner-or-manager edit gate). Entity
carries a created_by / author_id / owner_id / assignee_id
field AND its conditional_permissions index includes a rule that
references the owner field against $user_id plus an elevated
<slug>:manage_all_<plural> permission. The agent calling this skill
is often not the original owner (it acts on behalf of users who may
be different from the record's creator), so the recipe must surface:
"editing this record requires that the caller is the original
<owner_field> OR holds <slug>:manage_all_<plural>; if neither
holds, the platform will reject with <rule.code>: <rule.message>,
propose handing off to the original owner or to a user who holds the
override permission." Treat as a JTBD candidate even when no other
Pattern-E shape fires.
Pattern F, Publication / versioning.
Shape test: draft/published states distinct from approval; version
chains (*_version, previous_version_id); published_at separate
from created_at. Candidates: publish X, unpublish X, create new
version of X. Distinct from Pattern A approval, publication is about
distribution, not sign-off, and the side effects differ (cache
invalidation, notification, visibility scope).
Pattern G, External-system handoff.
Shape test: fields like external_*_url, *_external_id,
webhook_* entities, or a status value such as synced / failed.
Candidates: sync X to external, replay failed X, reconcile X.
Pattern H, Bulk ingest. Shape test: model declares webhook receivers explicitly, or has an entity whose typical population unit is a batch (a CSV-shaped table with no parent beyond the batch). See "pattern-level adjustments" below, in this skill, bulk ingest usually becomes a one-line pointer rather than a JTBD.
Pattern I, Cross-entity reporting.
Shape test: 3+ entities joined by FKs and at least one numeric measure
(cost, count, duration, FTE, amount). See "pattern-level adjustments"
below, in this skill, reporting becomes a ## Common queries
appendix, not a JTBD section.
Pattern J, Restrict-chained cleanup).
Shape test: an entity that appears as the target of at least one
inbound reference + restrict FK from another entity in the model
(read the restrict_inbound index from Step 2). The platform refuses
to delete a row that has live restrict-children; the calling agent
needs an explicit cleanup recipe naming each restrict-child entity
in dependency order, with a per-child query shape ("find all
<child> where <fk_field> = <parent_id>") and a delete loop
before the parent delete itself. This pattern is the v1.13
parent-vs-reference rule's direct downstream consequence: when the
analyst flips a parent (cascade) FK to reference (restrict)
because the child has divergent permission scope, the analyst is
deliberately moving the cleanup decision into the application
layer rather than letting the platform silently cascade-delete
permission-scoped rows. The skill-maker materialises that decision
as a recipe. Candidates: "delete <parent>", "archive
<parent>", "merge two <parent> records" (where merge ends with
a delete of one). Skip when the entity has zero inbound
restrict-FKs (the platform's own DELETE is enough) or when the
restrict-children themselves carry no permission-scope divergence
(then the analyst's choice of restrict was overly cautious; flag
to the user but still emit the recipe).
Skip rules
Do not nominate any of the following, they fall outside the "job to be done" frame:
- Single-row CRUD on master-data tables with no lifecycle (insert
one department, edit a job code). The calling agent uses
use-semantiusdirectly; an extra section is just noise. - Seed / sample / test-data population. One-off developer work, not a recurring job. If the user wants a seed script, ask separately.
- Entities listed in §7.2 "Future considerations", they don't exist yet.
- Pure read-by-id lookups, the calling agent uses
postgrestRequestdirectly.
When none of the patterns fire
If a model has shapes none of the patterns recognize (rare, but possible for unusual domains), name the unmatched shape explicitly in the Pass-2 confirmation step and ask the user whether it warrants a custom JTBD. Do not invent a job to fill space.
Pattern-level adjustments specific to this skill
- Pattern I (cross-entity reporting) does not become a JTBD section
here. Promote it instead to a
## Common queriesappendix at the end of the SKILL.md, 3–5 pre-shaped cube queries the calling agent can adapt. Reporting is largelyuse-semantiusterritory once the schema is known; baking in example queries is useful, but framing it as a "job" misleads the calling agent into routing every analytic question through this skill. - Pattern H (bulk ingest) becomes a one-line pointer, not a recipe,
unless the model declares webhook receivers explicitly. If it does,
write a JTBD; otherwise the SKILL.md just notes "for CSV import, see
use-semantiusreferences/webhook-import.md".
Pass 2, Apply the merit test (earn-its-place filter)
For each candidate from Pass 1, ask: would the calling agent get this
right with use-semantius alone? If yes, drop the candidate, an extra
section is just noise, the calling agent should call use-semantius
directly. A candidate earns a section only if it answers YES to ≥1
of the following:
| Merit signal | What to check in the model |
|---|---|
| Caller-populated label | Junction or sub-entity has a required *_label column distinct from any label_column, with no DB-level default. The recipe must compose the label client-side, not obvious from the schema alone. |
| Computed field | A stored derived field (e.g. rice_score, total_amount, days_open) whose value depends on sibling fields. Match by exact name against the entity's computed_fields[].name array, then resolve to one of three cases. (a) Field is in computed_fields → signal is suppressed; does NOT earn a section on its own. The platform overwrites caller payloads on every write, so the calling agent must not include the derived field in any POST/PATCH body and must not recompute it client-side. That rule is documented globally in the SKILL.md Platform-derived fields preamble (resolved at generation time); no per-JTBD recipe is needed. A candidate whose only merit signal is this row, in case (a), is dropped and listed under skipped. (b) §3 prose names a derivation but no matching computed_fields[].name entry exists → gap-tracked (record under platform_enforceable_gaps; see below). The signal earns a section ONLY when the candidate also passes one of the other merit signals (cascade, side-effect, junction without uniqueness, etc.); the recipe enforces the derivation client-side as a stopgap until the analyst adds the rule to the model. (c) Neither field-name match nor §3 prose names a derivation → signal does not fire. |
| DB-unguarded lifecycle gate | Status enum where some transitions are valid and others aren't, but the DB accepts any value. Suppression here is per-transition, matched by description / message text, not per-block: walk the entity's validation_rules[] and ask "does any entry's description or message text name this specific transition, or the invariant it gates?" Then resolve. (a) A validation_rules[] entry covers the transition → signal is suppressed for that transition; does NOT earn a section on its own. The platform rejects the bad write with { "errors": [{ "code", "message" }, ...] }; the recipe just surfaces that. The code is documented globally in the SKILL.md Platform-enforced invariants preamble; no per-JTBD recipe is needed for the gate. A candidate whose only merit is this row, in case (a), is dropped and listed under skipped. (Scripts that exist for other merit reasons may name the platform code in their stderr diagnostics so the agent surfaces a clean recovery hint; they must not duplicate the JsonLogic check.) (b) §3 prose names the invariant but no matching validation_rules[] description does → gap-tracked. The signal earns a section ONLY when the candidate also passes another merit signal (cascade, side-effect, etc.); the recipe enforces the gate client-side as a stopgap. (c) Neither prose nor validation_rules names the invariant → signal does not fire. |
| DB-unguarded invariant across FKs | E.g. features.release_id and features.product_id must agree on product. The recipe must read both rows and check before patching. Cross-row constraints are out of scope for validation_rules (entity-level only), so this signal is not suppressed by the platform. |
| Cascade flow | Flipping one parent row should flip a filtered set of children in the same logical operation (e.g. release-shipped → its planned/in-progress features → shipped). |
| Junction without uniqueness | M:N junction without a DB-level unique constraint on the natural key. The recipe must dedupe-before-insert. |
| Materialization / handoff | One entity row spawns rows in a different table (Pattern C). The order, FK back-pointers, and source-status flip are easy to get wrong. |
| Side-effect fields on transition | approved_at, committed_at, actual_release_date, etc. that must be set in the same PATCH as the status flip, easy to forget. |
| Audit-trail read | Audit-logged entity (audit_log: true) where "who/when changed X" is a likely user question. Worth a short recipe even though writes need no special handling. |
| Conditional-permission gate | The entity's conditional_permissions index from Step 2 carries one or more entries (i.e. at least one validation_rules rule invokes {"require_permission": "<code>"}). Unlike the basic "DB-unguarded lifecycle gate" row, this signal is never suppressed by the platform-enforces-it rule, the platform enforces the check but the calling agent still owns the workflow: detecting that the caller lacks the permission before the write attempt (cheap UX win), surfacing the rule's message cleanly on the throw, and proposing the right escalation path (which role typically holds the permission, who to hand off to). One JTBD per (entity, permission_code) pair, or one combined JTBD per entity when several conditional gates share the same approver role. Cross-reference the §8 step 1 description for the permission to know what role typically holds it. |
| Restrict-chained cleanup | The entity appears in at least one other entity's restrict_inbound index (i.e. is the target of at least one reference + restrict inbound FK). Pattern J fires. The recipe walks the dependency tree of restrict-children, naming each one and providing the find-and-delete query shape, then performs the parent delete. The merit signal is the chain itself: a generic use-semantius DELETE against the parent will surface only one row's constraint failure per attempt, never the full chain; the recipe makes the order explicit. |
| Row-scoped read | The entity appears in the row_visibility_rules index from Step 2 (i.e. carries a non-empty Select rule). Like the conditional-permission gate above, this signal is never suppressed — the platform filters rows from the result set, but the calling agent still owns the workflow of surfacing the limit to the user. Critical (v2.2): the rule applies uniformly to every caller with view_permission. A read recipe against a scoped entity must surface that as a uniform limit ("every caller sees only <predicate>"), not as a tiered limit. If the analyst's model has resolved an architectural-decision §7 entry that names a documented broadening mechanism (option C separate-entity surface, option D Postgres BYPASSRLS role), the recipe can name it as the path to broader access; otherwise the broader-access path is "talk to a user with that role" and the recipe says so. Never invent a view_all_<plural> permission bypass. The signal earns a section when the entity is a target of meaningful read recipes (list, search, look up by non-id) — those recipes need a preamble noting the scope. Pure write recipes on the entity (insert, update with the id already known) are not affected and don't need a separate JTBD; the cross-cutting Row-level read scope preamble in the SKILL.md body covers them. Entities whose only operations against them are writes-by-known-id get the preamble entry but no per-JTBD section. |
| Conditional field UI state | The entity has at least one field in the dynamic_input_types index. By itself, this is not a merit signal that earns a section: an input_type_rule is UI control only, the platform doesn't gate writes on it, and recipes that POST/PATCH bypass the UI entirely. The exception is the conditional_required_fields sub-index (UI rules that go default → required on a transition): those compose with Pattern A's existing "Side-effect fields on transition" signal and the housekeeping-field-appears-on-transition sub-shape above, and earn coverage inside the transition JTBD that fires the trigger. Cross-cutting documentation of every input_type_rule lives in the Conditional field UI states SKILL.md preamble (Step 5), not in per-JTBD sections — same posture as Platform-derived fields. |
Trap, the most common defect: when a candidate's only merit signal is Computed field and the field is in
computed_fields, OR only DB-unguarded lifecycle gate and avalidation_rules[]entry covers the transition (matched by description), the candidate is dropped, not earned. The platform owns the rule end-to-end; a JTBD section would just restate what the SKILL.md preamble already documents globally. List under skipped with a reason likepure CRUD; <field> is platform-derivedorpure CRUD; transition is platform-enforced via <code>. The "Score with RICE" anti-pattern is the canonical example: every input has avalidation_rulesrange check andrice_scoreis incomputed_fields, so the recipe is one PATCH and the platform handles the rest, no JTBD section earns a place.
If the only thing a candidate does is single-table CRUD with the platform
defaults (no merit signals), drop it. List dropped candidates in the Step
4 summary as skipped: pure CRUD against <table>, calling agent uses use-semantius directly. This is not a failure; it is the design.
Track platform-enforceable gaps
While walking the merit table, maintain a side list platform_enforceable_gaps
keyed by entity. Append an entry whenever the Computed field or
DB-unguarded lifecycle gate signal fires and the entity's model
block does not already capture the rule:
- Computed-field gap. A field's §3 prose names a derivation
(
(reach × impact × confidence) / effort, "subtotal of line amounts", "days between open_date and close_date") and the entity'scomputed_fieldsarray carries no entry whosenamematches that field. Record{entity, field, kind: "computed_field", evidence: "<the §3 prose snippet>"}. - Lifecycle-gate gap. The §3 prose for a status enum (or any
field-level invariant) names a constraint ("only set X once Y is
committed", "X cannot decrease", "X required when Y is
paid") and novalidation_rulesentry on the entity encodes it. Record{entity, field, kind: "validation_rule", evidence: "<the §3 prose snippet>"}.
The detection is conservative: only fire when the §3 prose names the derivation or constraint explicitly. Do not infer rules the analyst did not write down — that is the analyst's job, not the skill-maker's.
A gap by itself does not earn a JTBD section. A candidate still has
to pass the merit test on at least one other signal (cascade,
side-effect on transition, junction without uniqueness, materialization
handoff, caller-populated label, etc.). When it does, the gap-tracked
rule earns a stopgap recipe inside that JTBD until the analyst adds the
matching computed_fields / validation_rules entry to the model. The
gaps list itself is purely the Step 3 confirmation checkpoint, so the
human can decide whether to pause and add the rule via the analyst
skill, or accept the stopgap recipe.
Pass 3, Classify each surviving JTBD into a file
For each JTBD that passed the merit test, decide where its body lives. The default is reference. Inline and script are exceptions earned by specific shape.
Inline (kept in SKILL.md body). Reserve for JTBDs whose entire recipe is one POST or PATCH with no read-first, no client-side composition, no branching. Rare; most JTBDs that earned a merit signal have at least one of these. If the inline body would exceed ~5 lines of recipe, promote to reference.
Script (scripts/<op-slug>.sh). The decision rule for "script vs
reference" is one test, not a checklist:
Does any branch in this recipe require the agent to ask the user something before continuing?
If no, it's a script. The script reads its parents, composes any labels internally, recomputes any stored values internally, validates preconditions, and either succeeds (exit 0) or refuses with a diagnostic message and non-zero exit. The agent invokes it with a small set of arguments (titles, emails, codes, dates) and checks the exit code; the script body never loads into context.
What is not a judgment branch (these all stay in scripts):
- Label composition.
feature_vote_label = "{user_full_name} -> {feature_title}"is mechanical: read the parents, format the string, no agent involvement. The script reads the rows it composes from. - Computed values.
rice_score = (reach * impact * confidence) / effortis mechanical: read current values, overlay caller's deltas, arithmetic, PATCH the result. Round to the column scale; if the inputs make the result undefined (effort null/zero), set the computed field to null and exit 0 with a diagnostic, do not exit non-zero. - Mechanical preconditions ("refuse if release_status is
releasedorcancelled", "refuse if effort_score is null"). The script checks, exits 1 with a clear message, and the agent surfaces the message to the user. That is not the agent asking the user something; that is the script telling the agent it cannot proceed. - Dedupe-on-junction. "PATCH if the row exists, POST if not" is a fixed branch on what the read returned; the agent does not intervene.
What is a judgment branch (these belong in references):
- "Ask the user before rewriting a committed row's history."
- "Ask the user before charging this work to an inactive cost center."
- "Ask the user whether to abort or recreate when the parent was deleted."
Anything that needs a yes/no from the user mid-operation is a reference. Everything else is a script.
The script must also satisfy two structural requirements that follow from being callable by the agent:
- Idempotent: re-running with the same inputs is safe. Filter writes to "rows that still need the change", not "all rows in the parent set"; running twice is then a deterministic no-op on the second run.
- Failure messages are diagnostic: name the step that failed and what the agent should tell the user (e.g. "step 1: feature '' not found, ask the user for the correct title"), so the agent can recover or escalate without re-reading the script body.
Reference (references/<jtbd-slug>.md). Use this when the recipe
has a judgment branch that needs the agent to mediate a user
confirmation (the test above). The reference file owns the
read-first calls, the branching prose, the user-prompt phrasing, and
the long failure-mode discussion. The agent loads the reference only
when it enters the JTBD; reading it is part of the operation.
A JTBD's classification controls the SKILL.md template body for that section: inline carries the full recipe; reference and script carry only Triggers, Inputs, a one-line Recipe pointer, Validation, and a terse Failure-modes summary, with the long body living in the linked file. The classification belongs in the Step 10 summary so the user can sanity-check it.
Pass 3 is mandatory. "Inline" is a rare exception, not a default.
Every merit signal that earned a JTBD a section in Pass 2
(caller-populated label, computed field, DB-unguarded lifecycle gate,
cascade flow, junction without uniqueness, materialization handoff,
side-effect fields on transition) is also a reason the JTBD belongs
in references/ rather than inline. If you find yourself classifying
every surviving JTBD as inline, you have not classified, you have
skipped Pass 3. Re-read the merit table: each signal's recipe needs
read-first, branch, compose, recompute, or cascade logic that runs to
~30+ lines including comments. None of that fits the inline criterion
(≤5 lines, no branching, no composition).
Concrete shape of a healthy classification, derived from prior generations applied with the sharpened "user-prompt branch" rule:
- A skill with 5–10 JTBDs typically produces 3–5 reference files, 3–5 script files, and 0–2 inline JTBDs. A run that produces zero reference files is suspicious (most domains have at least one user-confirmation branch); a run that produces zero script files is almost always a misclassification (most domains have at least one cascade or pure-mechanical operation).
- The ratio of reference + script to inline should be at least 3:1. If your classification gives you 7 inline + 1 reference, walk back through the merit table and force yourself to name, for each inline JTBD, why it has zero of: read-first, label composition, computed recompute, cascade, branching. If you cannot, the JTBD is at least a reference, possibly a script.
- Cascade-shaped JTBDs (Pattern A side effects + Pattern C materialization, e.g. "ship the release" with a feature-status sweep) are scripts, unless they carry a user-confirmation branch.
- Dedupe-on-junction JTBDs (Pattern junction-without-uniqueness, e.g. "vote on a feature", "tag a feature") are scripts. The label composition is mechanical and the script reads the parents to compose; do not promote to reference just because the label is caller-populated.
- Computed-field recompute JTBDs (e.g. "score with RICE", "recalculate total amount") are scripts. The arithmetic is mechanical; the script reads current values and writes the new ones in one PATCH.
- Lifecycle-gate JTBDs (Pattern A, e.g. "triage", "schedule", "approve") are references when at least one transition needs a user confirmation, scripts otherwise.
Sizing
After filtering, aim for 5–10 sections plus the optional Common queries appendix.
- Fewer than 5 sections after filtering: the model may be too thin to
justify a domain skill. Tell the user; ask whether to ship it anyway
or extend
use-semantiuswith a glossary file instead. - More than ~10 sections after filtering: a single skill that long under-triggers, the description gets diluted and the matcher loses signal. Run Step 3.5 (Cluster check) below; it inspects the entity graph for natural cut points and proposes a split when one exists. If no clean split surfaces, push back to the user: drop the lower-merit candidates, or split the model file itself. Proceed with one oversized skill only if the user confirms.
Step 3.5, Cluster check
After Pass 3 classifies every JTBD, before presenting the confirmation checkpoint, inspect the model for natural sub-domain cuts. Most models generate cleanly as one skill; a minority (typically ITIL-shaped or multi-process domains) read as two or three loosely coupled sub-domains stitched into one model file. A skill that spans loose sub-domains under-triggers for the same reason an oversized skill does, the description has to cover too many vocabularies and the matcher loses signal.
Run this step on every generation, regardless of JTBD count. The size-based threshold in Sizing is one trigger for splitting, not the only one; a 7-JTBD model split into two clear clusters of 4 and 3 still benefits from being two skills.
Compute the cluster signal
- Build an undirected graph. Nodes are the model's entities
(§3). Edges are FK relationships from §4, weighted by count
(multi-FK pairs get higher weight). Skip edges to ubiquitous
hub entities,
users,departments, and any §6 cross-model targets, they connect to almost everything and obscure the real clustering. - Find candidate clusters. A simple greedy modularity walk is enough; you do not need a full community-detection library here. Start with each entity in its own cluster and merge the pair whose merger most improves the within/cross edge ratio until further merges stop improving it. Aim for 2–4 clusters; stop merging when only one cluster remains or when the next merge would drop the ratio.
- Assign each JTBD to a cluster. A JTBD lives in the cluster that owns the majority of its touched entities (parents, children, joined targets in lookups). JTBDs that span clusters (e.g. one that joins entities from both) count as cross-cluster and are flagged separately.
- Compute the cut score.
cross_edges / (within_edges + cross_edges). Lower is better, a model with two genuine sub-domains scores < 0.20; a tightly interlocked single-domain model scores > 0.40.
Surface a split proposal only when all of these hold
- ≥ 2 clusters with ≥ 3 JTBDs each. (Smaller clusters are not skill-shaped; fold them into the larger neighbor.)
- Cut score < 0.25.
- ≤ 2 cross-cluster JTBDs. More than that means the recipes routinely span both halves and splitting forces the calling agent to load both skills anyway, defeating the point.
- Each cluster has its own distinct trigger vocabulary (the
cluster's JTBD verb-phrases would not naturally appear in the
other cluster's description). If the same verbs (
approve,assign,close) dominate both clusters, the matcher will thrash even with a split.
If any condition fails, the model generates as one skill and Step 3.5 produces no extra prompt.
When all conditions hold, propose the split
Add this to the Step 3 confirmation checkpoint as a fourth list, before writing files. Example shape:
The entity graph splits cleanly into two clusters:
Cluster A:
<slug>-incidents(entities: incidents, incident_comments, incident_categories; JTBDs: log incident, resolve incident, link to problem, post comment).Cluster B:
<slug>-changes(entities: changes, change_tasks, change_approvals; JTBDs: submit change, approve change, schedule change, execute change).Cross-cluster: 1 JTBD (problem-resolved-by-change) would need both skills loaded.
Cut score: 0.12. Each half has its own trigger vocabulary ("incident / ticket / outage" vs "change / deployment / maintenance window").
Generate as one skill
<slug>(default), or split into<slug>-incidentsand<slug>-changes?
The default is one skill even when the proposal fires. Splitting fragments shared cross-cutting rules, doubles the README/glossary maintenance burden, and surprises users who installed "the X skill" and find half their work routes elsewhere. Only split when the user explicitly confirms.
What changes when the user accepts a split
- Each cluster becomes its own folder under
<skills-root>/, with its own SKILL.md, README.mdx, references/, and scripts/. - Each SKILL.md's description mentions the sibling skill ("loads
alongside
<sibling-slug>for cross-process work like X") so the matcher knows the pair composes. - Cross-cluster JTBDs go in whichever cluster owns the action's
primary verb, not duplicated. If "resolve a problem with a
change" lives in
<slug>-problems(because the verb is "resolve"),<slug>-changesonly needs a one-line note that problem-resolution writes to changes; the recipe lives in the problems skill. - The
semantic_model:provenance key on every generated SKILL.md still points to the same source slug (e.g.itsm); the model file is shared, the skills are split.
Confirmation checkpoint
Present three lists to the user (four when Step 3.5 fired a split
proposal; five when platform_enforceable_gaps is non-empty):
-
Sections, the JTBDs that earned a place (one bullet each, with the merit signals that justified them).
-
Common queries, the cube queries that go in the appendix.
-
Skipped, Pass-1 candidates that failed the merit test, with the reason. The user may disagree and ask to add some back.
-
Split proposal (only when Step 3.5's conditions all held), the cluster breakdown and the one-skill-vs-split question. Default to one skill; only split when the user explicitly confirms.
-
⚠️ Platform-enforceable gaps detected (only when
platform_enforceable_gapsis non-empty), one bullet per gap listing the entity, the field, the kind (computed_fieldorvalidation_rule), and a short quote of the §3 prose evidence. Frame as a question, not a command:The model documents these rules in §3 prose, but the entity's
computed_fields/validation_rulesblocks do not encode them. A recipe-level stopgap will be baked into the generated skill, but the platform will not enforce the rule on writes that bypass the skill (direct CLI calls, other skills, future migrations).features.rice_score(computed_field): §3 says "(reach × impact × confidence) / effort". Nocomputed_fieldsentry derivesrice_score.features.release_id(validation_rule): §3 says "null until scheduled, …commitment is derived (status ∈ {planned, in_progress, shipped} ⇒ committed)". Novalidation_rulesentry gatesrelease_idagainst status.
Recommended: pause this skill-maker run, route to the
semantic-model-analystskill (Audit / Extend mode) to add the matchingcomputed_fields/validation_rulesblocks, redeploy viasemantic-model-deployer, then re-run skill-maker. The regenerated skill will surface the platform-derived fields and error codes instead of baking client-side recipes for them.Or: proceed and accept the stopgap recipes. Pick a route.
Default is to surface the gap and let the user decide; do not silently proceed past the checkpoint when this list is non-empty. If the user picks "proceed", the run continues to Step 4 with the JTBDs intact; if they pick "pause", stop the run and tell them which skill to invoke next.
Wait for confirmation before writing files. This is the only human checkpoint.
Step 4, Audit existing artifacts
If <skills-root>/<modelslug>/ already exists from a prior generation,
read every file under it and check for drift against (1) the
current source model, (2) the current generation's plan, and (3) the
current platform conventions. The audit is read-only; nothing is
rewritten in this step. Findings go into the Step 10 summary, and the
user decides whether to regenerate flagged files in Step 5–7.
Skip this step entirely on a fresh generation (target folder does not
exist or contains only a stale SKILL.md with no references/ or
scripts/). On a fresh run, there is nothing to audit; everything
gets written in Steps 5–8.
Model drift is the first thing to check
The generated skill is a frozen snapshot of the source model at
generation time. The model can move between generations, an analyst
can rename a singular_label, swap an enum value, add a
computed_field, simplify the §1 narrative, redraw the mermaid
diagram, or close out a §7.2 future-consideration. Any of those
changes leaves baked content stale. Detect drift before running
the artifact-shape checks below; a stale glossary or recipe is
worse than a non-canonical script.
The audit re-parses the source model file at MODEL_PATH (same
parse Step 2 does) and diffs the resulting facts against what is
baked into the generated files:
- README
## Semantic modelmermaid block: byte-diff against the model's §2 mermaid block. Any difference is a defect. - README body §1 paragraph(s): byte-diff against the model's §1 narrative. The README copies §1 verbatim; a mismatch means the analyst rewrote §1 (or the generator paraphrased instead of copying). Defect.
- SKILL.md domain glossary: every entity in §2 / §3 should
appear in the glossary table with its current
singular_label, and no extra entities should appear. Add / remove / rename = defect. - SKILL.md "Key enums": every enum the model declares
(§5 plus per-entity
enumfields) and every value within each enum should appear; no extras. Defect on any add / remove / rename / reordering of values that recipes branch on. - SKILL.md "Platform-derived fields" preamble block: must
match the union of
computed_fields[].nameacross every entity, with the model'sdescriptiontext intact. Defect on any add / remove / rename. - SKILL.md "Platform-enforced invariants" preamble block:
must match the union of
validation_rules[].codeacross every entity, with the model'smessageanddescriptionintact. Defect on any add / remove / rename. - SKILL.md "Platform-enforced permissions" preamble block
): must match the
conditional_permissionsindex built in Step 2 (i.e. everyvalidation_rulesrule whose JsonLogic invokes{"require_permission": "<code>"}). Every(entity, rule_code, permission_code)tuple in the index must appear in the preamble with the rule'smessageand the §8 step 1 description of the permission. Defect on any add / remove / rename. Skip the section entirely when the index is empty. - SKILL.md "Restrict-cleanup chains" preamble block
): must match the
restrict_inboundindex built in Step 2. Every entity that is the target of at least onereference + restrictinbound FK must appear with its cleanup-children listed in dependency order. Drift signals: an entity that the model now declares with a flippedparent ↔ referenceFK (per the v1.13 permission-scope override rule) but whose preamble entry hasn't been regenerated; or a restrict-inbound row in the model that the preamble fails to list. Skip the section entirely when no entity has restrict-inbound edges. - SKILL.md "Row-level read scope" preamble block:
must match the
row_visibility_rulesindex built in Step 2. Every entity that declares a non-emptySelect rulemust appear with the scope's plain-English gist (from the rule'sdescription, the entity's §3 prose, or a fallback paraphrase of the JsonLogic intent). The gist must describe the uniform per-row predicate that the platform applies to every caller withview_permission; it must NOT promise aview_all_<plural>permission bypass unless the model file's §7 architectural-decision entry explicitly resolves the broadening mechanism (option C separate-entity surface, option D PostgresBYPASSRLSrole attribute) and the preamble names that mechanism. Drift signals (defects): (a) an entity that the model now declares with a new / removed / modifiedSelect rulewhose preamble entry hasn't been regenerated; (b) a preamble entry that names a permission bypass the model file does not resolve in §7 (the canonical v2.2 defect — the prose claims a bypass the platform cannot honor); (c) a preamble entry whose gist describes a tiered read pattern but whose underlyingSelect ruleJsonLogic encodes a uniform filter (drift between intent and rule). Skip the section entirely whenrow_visibility_rulesis empty. - SKILL.md "Conditional field UI states" preamble block (analyst
v2.2+): must match the
dynamic_input_typesindex built in Step 2. Every field that declares a non-emptyinput_type_rulemust appear with the rule'sdescription(or the JsonLogic's plain-English gist), grouped by entity. Drift signals: anInput type rulesentry that the model adds, removes, or whose trigger condition the model rewrites without a matching preamble change; a preamble entry whose field no longer exists in §3 (the analyst dropped the field but the preamble lingers). Skip the section entirely whendynamic_input_typesis empty. - Per-JTBD coverage of
conditional_required_fields: for every entry in theconditional_required_fieldssub-index built in Step 2 (a field whoseinput_type_ruleflipsdefault → requiredon a transition), the transition JTBD that fires the trigger condition must include the dependent field in its PATCH body as a side-effect of the trigger. A transition recipe that sets the trigger field without the paired side-effect field is a drift defect — the rule was added to the model after the recipe was written, or the generator missed it. Severity: defect (regenerate the JTBD's reference / script). - SKILL.md "What this skill does NOT do" inlined §7.2 list: must match the model's current §7.2 bullets. Defect on any add / remove / rename. (Bullets the user has paraphrased for readability are non-canonical, not defect; flag separately.)
- JTBD recipe field references: every column a recipe writes (in inline bodies, references' Recipe blocks, or scripts) must exist on the entity in the current §3. A recipe writing a removed or renamed column is a defect.
- Audit-log mentions: every entity SKILL.md / a reference
claims is
audit_log: yes(or: no) must agree with the current §3 entity declaration. Defect on mismatch. - §6 cross-model hints: every cross-model hint named in generated content (Guardrails, "What this skill does NOT do") should still be present in the model's §6. Defect on stale references.
The drift check has its own severity row in "What to report" below. It runs regardless of whether the user invoked the skill-maker for a regeneration or a review, the model is the contract, and a review that does not re-resolve to the contract is incomplete. (The "review skill" call is exactly Step 4.)
Artifact-shape checks (post-drift)
After the drift check, walk every file in the skill folder
(SKILL.md, README.mdx, references/*.md, scripts/*.sh) and
apply the same checks the self-review (Step 9, Principle 0)
applies to freshly written files. The point is symmetry: if
Principle 0 would reject a file the generator just wrote, it
should also flag the same file sitting on disk from a prior run.
Concretely:
- Read patterns. Every
semantius call ... GET ...is either--single(one row required) or array-default (zero or many rows acceptable). The flag must be present iff the lookup is byid, unique column, or composite-unique key. A unique-key read missing--singleis non-canonical (still works, but uses the legacy two-guard pattern). - Guard patterns (scripts only).
--singlereads need only the exit-code guard; array reads need exit-code AND body inspection. A--singleread with a redundantgrep -q '"id"'follow-up is non-canonical. An array read with no body inspection is a defect. - Response parsing (scripts only).
--singleresponses are bare objects (grep -oE '"id":"..."', nohead -n1, no[0]); array responses needhead -n1or[0]indexing. A mismatch silently produces empty strings; flag it. # expect:annotations (references only). Every GET, POST, PATCH, DELETE in the recipe carries an# expect:line naming the pattern (--singleor array) and the action on failure. Missing annotations are defects.- Cross-reference integrity. Every
references/<slug>.mdlinked from SKILL.md must exist on disk; everyscripts/<slug>.shmentioned in a SKILL.mdRecipe:line must exist. Files on disk that aren't linked from SKILL.md are orphans (left over from a JTBD that no longer exists or was reclassified). - Pass 3 alignment. For each JTBD in the current generation's
plan, the file on disk should match the new classification. A
JTBD now classified
scriptwhose existing artifact isreferences/<slug>.md(or vice versa) needs migration; the audit flags the pair, the user picks the direction. - Hardcoded literals. Search every file for ISO timestamps, ISO dates, baked-in ids, and the generation year. These rot and should be placeholders. Same scan the self-review's Principle 1 runs on a fresh write.
What to report (handed to Step 10)
For each file with findings, produce one bullet:
README.mdx: drift, body §1 paragraph mismatches the model's current §1 narrative. The analyst rewrote §1 to drop backticked identifiers; the README still has the old verbatim copy.SKILL.md: drift, "Platform-derived fields" preamble lists 1 field; model's computed_fields union now has 2 (added features.days_open). Recipes will not warn against writing the new field.SKILL.md: drift, "Key enums" lists feature_status values including 'parked'; model's §5 no longer declares 'parked'. Triage's Inputs table accepts a value that no longer exists.cast-vote.sh: 4 reads use legacy two-guard pattern; --single would collapse each to one guard. Working but non-canonical.triage-feature.md: 2 GETs missing # expect: annotations.score-rice.md: classifiedscriptin current plan but exists as reference; needs migration.tag-feature.md: orphan; no JTBD with this slug in current plan.ship-release.sh: contains hardcoded date "2026-05-04"; should be a placeholder.
Group findings by file and by severity:
- "drift" = the file no longer matches the source model; baked content is stale. Always recommend regenerating.
- "defect" = the file is broken on its own terms (missing link target, structural shape failure, recipe writes a removed column). Recommend regenerating.
- "non-canonical" = the file works but uses an outdated pattern; user decides whether to take the upgrade.
- "orphan" = the file is no longer referenced from SKILL.md (its JTBD was reclassified or removed). User decides whether to delete or restore.
The Step 10 summary surfaces these and asks the user how to proceed; do not auto-rewrite in Step 4.
What the user can choose in Step 10
Three options the summary should offer:
- Regenerate flagged files (Steps 5–7 rewrite them). Default for defects.
- Leave as-is. Default for non-canonical findings on a file the user may have hand-edited.
- Per-file decision. The user accepts some, rejects others.
If the user opts to regenerate, the affected files are rewritten in Steps 6 (references) and 7 (scripts). Files not flagged for regeneration stay untouched even if their JTBD is in the current plan; the audit is what gates the rewrite, not the plan alone.
Hand-edits
The audit is conservative about hand-edited prose. References in particular get hand-edited (failure-mode wording, user-prompt phrasing). The audit flags non-canonical patterns but does NOT mark the file for forced rewrite; the user keeps custody. The defect categories that DO warrant a forced rewrite recommendation (prefixed "defect" in the summary) are:
- A reference linked from SKILL.md that doesn't exist on disk
- A script file that fails the structural shape check (empty,
wrong shebang, missing
set -euo pipefail) - Cross-FK invariants that contradict the current model (e.g. a reference recipe writes a column that no longer exists)
Everything else is "non-canonical" and the user decides.
Step 5, Write the consolidated SKILL.md
The folder gets two files written in sequence: the agent-facing
SKILL.md (this step) and the human-facing README.mdx (Step 8).
Both are required output. Write the SKILL.md first because the README
pulls its trigger phrases and JTBD titles from it.
Use the template below. Resolve every reference at generation time (enum values, FK target tables, required-on-create field sets), the calling agent must not need to consult the semantic-model file to fill in fields.
SKILL.md template
---
name: <modelslug>
description: >-
<One paragraph. Lead with the domain ("Use this skill for anything
involving <system_name>, <one-line domain summary>"). List 4–6
realistic trigger phrases users might say, mixing entity names and
task verbs (e.g. "create a lead", "convert opportunity to account",
"report pipeline by stage"). Be slightly pushy, skills under-trigger
by default.>
semantic_model: <system_slug, e.g. product_roadmap (no path, no .md extension)>
---
# <system_name>
The H1 here is **`system_name` verbatim**. No "Skill" suffix, no
"domain" prefix, no rewording. The file is named `SKILL.md`, so the
agent loading it already knows it is a skill, repeating "Skill" in
the heading is noise. Examples: `# Applicant Tracking System`,
`# Workforce Planning`, `# Customer Relations`.
(The README.mdx H1 follows a different rule, the Title grammar in
Step 8, because the catalog needs the "Skill" suffix to
disambiguate cards. Do not confuse the two.)
This skill carries the domain map and the jobs-to-be-done for
<system_name>. Platform mechanics, CLI install, env vars, PostgREST
URL-encoding, `sqlToRest`, cube `discover`/`validate`/`load`, and
schema-management tools, live in `use-semantius`. Assume it loads
alongside; do not re-explain CLI basics here.
If a task is purely about defining schema, managing permissions, or
running ad-hoc queries against tables you already know, call
`use-semantius` directly, going through this skill adds nothing.
**Module type**: <`domain` or `master`, copied from frontmatter
`module_type` (default `domain` when absent)>. A `master` module is a
neutral host for shared / master data (e.g. vendors, currencies,
departments) consumed by multiple domain modules via cross-module
`permission_hierarchy` rows tagged `origin = "model_master"`. Writes to
entities in a master module have cross-domain visibility: every
consuming domain module sees the write through the bridge. The recipes
in this skill treat master entities the same as domain entities at the
CLI level; the only practical difference is the "shared across domains"
guardrail surfaced on each write recipe touching one. Skip this line
entirely when the model is a plain domain module.
**Shared master entities** (entities authored under this domain module
that the analyst flagged as classic master concepts via the
`**Shared master cluster:** <cluster>` annotation; writes to these
entities are likely candidates for promotion to a master module on
re-deploy, and the live module may already have routed them through a
master via the deployer's Stage 2d Branch B path. Recipes that touch
them include a one-line note that the data is shared across the named
cluster):
<list each entity in the `master_cluster_by_entity` index from Step 2.
Skip the section entirely when no entity in the model carries the
annotation AND the module itself is not `module_type: master`.>
- `<table>` is part of the `<cluster>` shared master cluster.
**Auto-managed fields** (set by Semantius on every table; never include
in POST/PATCH bodies): `id`, `created_at`, `updated_at`. The
`label_column` field is **required on insert and caller-populated** on
every entity unless the model explicitly says it is auto-derived ,
this includes junction tables like `<junction>` and sub-entities like
`<sub-entity>`, where the recipe must compose the value (see each
JTBD for the composition rule). Do not omit `*_label` from POST bodies.
**Platform-derived fields** (set by the platform's per-entity
`computed_fields` triggers on every INSERT/UPDATE; **never include in
POST/PATCH bodies, the platform overwrites caller payloads**:
<list each `computed_fields[].name` from the model, grouped by table
and one-line description copied from `computed_fields[].description`.
Skip the section entirely when no entity in the model declares
`computed_fields`.>
- `<table>.<field>`: <description from the model's `computed_fields[].description`>
**Platform-enforced invariants** (entity-level `validation_rules`
triggered on every INSERT/UPDATE; the platform rejects writes that
violate them with `{ "errors": [{ "code", "message" }, ...] }`. The
recipes here do NOT pre-validate these; they surface the platform's
error to the user verbatim if the write fails):
<list each `validation_rules[].code` from the model, grouped by table,
with the `message` and (if present) `description`. Skip the section
entirely when no entity in the model declares `validation_rules`.>
- `<table>` rule `<code>`: <message>. Why: <description, when present>.
**Platform-enforced permissions** (rules whose JsonLogic invokes
`{"require_permission": "<code>"}`; the platform throws when the caller
lacks the named permission, surfacing the rule's `code` and `message`
as a validation failure. The recipes that hit these gates name the
permission up-front so the calling agent can either (a) confirm the
caller holds it before attempting the write, or (b) propose handing
off to a user who holds it instead of hitting the throw blind):
<list each `(entity, rule_code, permission_code, role-hint)` tuple
from the `conditional_permissions` index built in Step 2. Pull the
`role-hint` from §8 step 1's description of the permission. Skip the
section entirely when `conditional_permissions` is empty.>
- `<table>` rule `<code>` requires `<permission_code>` (`<role-hint
from §8>`): <message>. Why: <description, when present>.
**Restrict-cleanup chains**; inbound `reference + restrict`
FKs that block deletion of the target entity until children are
explicitly cleaned up first. The calling agent attempting to delete a
listed entity must walk the named children first, in the order given,
or the platform will reject the DELETE with a foreign-key constraint
error. Every listed chain corresponds to a Pattern J "delete /
archive" JTBD in this skill):
<list each entity that has at least one inbound `reference + restrict`
edge per the `restrict_inbound` index from Step 2. For each entity,
list the restrict-children in dependency order (children of children
first if any chain deeper). Skip the section entirely when no entity
in the model has restrict-inbound FKs.>
- Deleting `<table>` requires cleaning up first: `<child_table>` (via
`<fk_field>`), `<child_table>` (via `<fk_field>`), … See the
`delete-<table>` JTBD for the recipe.
**Row-level read scope**; per-row visibility filters set on the
entity via `select_rule`. The platform applies an RLS policy on every read.
**Critical: the rule applies uniformly to every caller with `view_permission`** —
the platform evaluates the JsonLogic body per row with `$today` / `$now` /
`$user_id` as reserved variables; there is no documented mechanism by which
holding a specific permission causes the rule to be skipped for that caller.
Recipes against a scoped entity must surface the per-row predicate as a
**uniform** limit, not a tiered one. If the model file's §7 carries an
explicit architectural-decision entry naming a documented broadening
mechanism (a separate cube view / entity surface admins read, or a
Postgres role with `BYPASSRLS` provisioned outside Semantius), name that
mechanism here so the recipe can route to it; otherwise the broader-access
path is human escalation, not a magic permission grant.):
<list each entity in the `row_visibility_rules` index from Step 2. For
each entity, give the scope in plain English (from the rule's
`description`, the entity's §3 prose, or a paraphrase). Name the
documented broadening mechanism ONLY when the model file's §7 explicitly
resolves it; otherwise omit the broadening line. Skip the section
entirely when `row_visibility_rules` is empty.>
- `<table>`: every caller with `view_permission` sees only rows where
<plain-English uniform predicate>. <Optional: "Broader read access for
`<role>` is provisioned via `<mechanism named in model §7>`" — include
only when the model's §7 names the mechanism.>
> **Never write a line like "callers holding `<slug>:view_all_X` bypass the
> filter".** That mechanism is not documented in the current Semantius
> platform spec. A preamble that promises it generates recipes that look
> right but silently leak access OR silently deny access (depending on
> how the calling agent reasons). The audit catches this as the canonical
> v2.2 defect.
**Conditional field UI states**; per-field UI mode overrides
set via `input_type_rule`. The platform evaluates the rule client-side at
form-render time and overrides the field's static `input_type` for the
current record. The rule does NOT gate writes — recipes that POST/PATCH
the entity bypass the form entirely and can set any field at any
time — but two patterns matter for recipe shape: (a) when a rule flips
a field's effective mode to `required` on a sibling-field transition,
the transition's recipe must set that field in the same PATCH (else the
form would have rendered it `required` but the API call leaves it null);
(b) when a rule flips a field's effective mode to `readonly` after a
terminal state, recipes that update the entity after that state should
not write the field. Cross-reference with `validation_rules` — when the
server-side rule enforces the same invariant, the platform rejects the
bad write with a structured `code` / `message`; surface that in the
JTBD's Failure modes block):
<list each field in the `dynamic_input_types` index from Step 2,
grouped by entity. For each, name the field, the trigger gist (from the
rule's `description` or the JsonLogic's plain-English summary), and the
resulting effective `input_type`. Skip the section entirely when
`dynamic_input_types` is empty.>
- `<table>.<field>`: <trigger gist, e.g. "hidden until status=approved,
then readonly"> (paired server-side rule: `<validation_rules.code>` —
or *"no paired server-side rule; UI-only behavior"*).
---
## Domain glossary
<One short table. Pull `singular_label`s, table names, and a one-line
"what it represents" for each entity. Group related entities together
(e.g. "Pipeline: leads, opportunities, accounts"). Skip junction tables
unless a job touches them directly. Do not duplicate FK targets here ,
each JTBD's reference file owns its own FK assumptions.>
| Concept | Table | Notes |
|---|---|---|
| Lead | `leads` | Inbound or sourced contact, not yet qualified |
| Opportunity | `opportunities` | Qualified deal in the pipeline |
| Account | `accounts` | Closed-won opportunity or imported customer |
## Key enums
<Only enums that gate JTBDs. Skip purely informational ones. Format:
table.column → values, with the typical lifecycle path marked.>
- `leads.lead_status`: `new` → `contacted` → `qualified` | `disqualified`
- `opportunities.stage`: `prospecting` → `proposal` → `negotiation` →
`closed_won` | `closed_lost`
## Cross-cutting data rules
<Only facts that span 2+ JTBDs in load-bearing ways belong here.
Per-FK shape, per-junction uniqueness, and audit-logging notes go
in the JTBD's reference file, not here. This section is often empty
or just 1–2 lines; that is correct, not a sign of incompleteness.
Examples of facts that *do* belong here:
- A naming convention several recipes rely on (e.g. "all `*_at`
fields are server-set on insert; never include them in POST bodies").
- A platform-wide constraint that constrains every recipe's write
pattern (e.g. "rows soft-deleted via `is_active=false` are still
visible to `read_*`; recipes filter on `is_active=eq.true` unless
the JTBD explicitly wants the full history").
If you have nothing to put here, omit the section.>
## When the runtime disagrees with the recipe
The FK shape and audit-logging facts in each JTBD's reference file
are baked in at skill-generation time. The live schema can drift,
admins can add a unique index, drop an FK, or toggle audit-logging
on a table without regenerating this skill. The recipes are not
self-correcting on their own, but the agent has an escape hatch.
When a recipe gets a `409 Conflict`, `422 Unprocessable Entity`, or
any other write failure the JTBD's reference file did not predict,
the recovery move is **read the live schema, then decide**:
```bash
# What FKs does this entity actually have right now?
semantius call crud read_field '{"filters": "entity=eq.<entity_id>"}'
# Or, more targeted, what does field <name> reference today?
semantius call crud read_field '{"filters": "entity=eq.<entity_id>,name=eq.<field_name>"}'
```
If the live shape contradicts the recipe's assumption (e.g. a unique
constraint exists where the recipe expected a free-form junction),
abort with a clear stderr message naming the drift, do not silently
"fix it up" with extra writes. Then surface to the user that the
skill is out of date and recommend regenerating via
`semantius-skill-maker`. Drift recovery is the user's call, not
the agent's.
## Lookup convention
<This block lives at the SKILL.md top level, not per-JTBD, so the
agent reads it once per trigger and applies it everywhere. The recipe
files under `references/` may name the column they resolve, but they
do not re-explain `wfts` vs `eq`.>
Semantius adds a `search_vector` column to searchable entities for
full-text search across all text fields. Use it whenever the user
passes a name, title, email, or description, not a UUID:
```bash
semantius call crud postgrestRequest '{"method":"GET","path":"/<table>?search_vector=wfts(simple).<term>&select=id,<label_column>"}'
```
Use `wfts(simple).<term>` for fuzzy text searches; never `ilike` and
never `fts`, they bypass the search index and mismatch the platform
convention.
Field-equality (`<column>=eq.<value>`) is the right tool for a
*different* job: filtering on a known-exact value. Use it for UUIDs,
FK ids, status enums, and unique columns whose values the caller
already knows verbatim (e.g. `tag_name`, `user_email`, `release_name`,
`cost_center_code`). The two patterns are not in competition:
`wfts(simple)` resolves a fuzzy human input to a row; `eq` selects
rows whose column exactly equals a known value.
If a lookup returns more than one row, present the candidates and
ask. If zero, ask the user to clarify rather than guessing.
## Timestamps in recipe bodies
<Top-level rule, named once here, never restated per JTBD.>
Every `*_at` field, `*_date` field, or other moment-of-action value in
a recipe body is a placeholder the calling agent fills at call time,
not a literal copied from the example. The Recipe templates use
`<current ISO timestamp>` and `<today's date, YYYY-MM-DD>`; do not
copy those strings into a real call. This applies in SKILL.md, in
every reference file, in the Common queries appendix, and in any
script the calling agent invokes.
---
## Jobs to be done
<One H2 per JTBD. Each section follows the structure below. Order
sections by typical lifecycle (create → progress → close → report),
not alphabetically.>
### <Job title, verb phrase>
**Triggers:** `<phrase 1>`, `<phrase 2>`, `<phrase 3>`
**Inputs:**
| Name | Required | Notes |
|---|---|---|
| `<input>` | yes/no | <where it comes from; resolved by `<column>=eq.<value>` if exact, `search_vector=wfts(simple).<term>` if fuzzy> |
The Inputs table must be **internally consistent with the routing
rules below**: do not list a status value as an accepted input here
and then say "for that value, route to JTBD Y". If a value belongs
elsewhere, drop it from this table.
**Recipe:** see [`references/<jtbd-slug>.md`](references/<jtbd-slug>.md).
*(Reference shape, used when the JTBD was classified `reference` in
Step 3 Pass 3. The full recipe body, lookup-then-compose-then-write,
including any branching, label composition, and computed-value
recompute, lives in the linked file. Do not paste it here.)*
*(Script shape, used when the JTBD was classified `script`:)*
**Recipe:** run `scripts/<op-slug>.sh <arg1> <arg2>`. The agent
invokes; do not paste the script body here. Exit `0` on success,
non-zero with the failed step on error.
*(Inline shape, only when classified `inline` in Pass 3, ≤5 lines of
recipe, no branching:)*
**Recipe:**
```bash
semantius call crud postgrestRequest '{"method":"POST","path":"/<table>","body":{...}}'
```
**Validation:** <2–3 short post-conditions, only the ones that have
actually been broken in practice. The reference file may carry deeper
validation; this list is what an agent skimming the SKILL.md needs to
sanity-check the call.>
**Failure modes:** <1–2 most-likely failures, each paired with a
*recovery action*. Long lists belong in the reference file's extended
failure-modes section; here, surface only what an agent needs to bail
out cleanly. Examples:
- `409 on <table>.<column>` (uniqueness) → row already exists; PATCH
the existing row instead.
- FK violation on `<column>` → the parent was deleted; ask the user
whether to recreate or abort.>
---
### <next job…>
…
---
## Common queries
<Optional appendix from Pattern I, pre-shaped cube queries for
reporting tasks. These are *not* JTBDs; they're examples the calling
agent can adapt. Open with one note, then 3–5 query blocks.>
Always run `cube discover '{}'` first to refresh the schema. Match the
dimension and measure names below against what `discover` returns ,
field names drift when the model is regenerated, and `discover` is the
source of truth at query time.
```bash
# Pipeline by stage (count + total amount)
semantius call cube load '{"query":{
"measures":["opportunities.count","opportunities.sum_amount"],
"dimensions":["opportunities.stage"],
"order":{"opportunities.sum_amount":"desc"}
}}'
```
<…2–4 more representative queries, each with a one-line title comment.>
---
## Guardrails
<Domain-specific rules the calling model should never violate. Each rule
should appear here *or* in the relevant JTBD's failure-modes, not both.
Pull from §7.1 of the model (resolved blockers / explicit constraints)
and from the merit signals that triggered each JTBD. Examples:
- Never PATCH `opportunities.stage` directly to `closed_won` without
setting `closed_date` and `won_amount` in the same call.
- `accounts` rows are only created via the close-won flow, never
insert directly.
- `*_status` flips in this domain are not DB-guarded; always read
current status before writing.
- Junction labels are caller-populated, see each junction JTBD for the
composition convention.>
## What this skill does NOT do
- Schema changes, use `use-semantius` directly.
- RBAC / permissions, use `use-semantius` directly.
- One-off seed data, write a script, don't bake it into a JTBD.
- <Inline the bullet list of unbuilt features here. Pull each item from
§7.2 "Future considerations" of the model at generation time and
write it as a plain bullet, do *not* cite "§7.2" in the SKILL.md,
the calling agent has no way to look it up. If §7.2 is empty or
missing, drop this bullet entirely.>
What goes into each recipe, concretely
When you bake a recipe, resolve every reference:
- Enum values, copy verbatim from §5. Write
"stage":"closed_won", not"stage":"<terminal value>". - FK fields, list by name with target table; if the agent passes a
human-friendly value (an email, a code), the first recipe step is the
lookup that resolves it to an id, using the
wfts(simple)pattern above for searchable entities. - Timestamps and dates, never hardcode a date or timestamp literal
in a recipe. The model file is read once at generation time but the
recipes run weeks or years later; a baked-in
"2026-05-01T10:30:00Z"silently corrupts every future call. For fields likevoted_at,posted_at,submitted_at,actual_release_date,closed_date, always render the value as a placeholder the calling agent fills at call time,"<current ISO timestamp>"or"<today's date, YYYY-MM-DD>", and add a one-line note in the recipe: "posted_at: set to the current timestamp at call time; do not copy the example value." This applies to every example body in the SKILL.md, including the Common queries appendix. - Caller-populated label fields, when a
label_columnis required but not auto-derived (the model marks the field as required with no default; common on junction tables and sub-entities likecomments), call this out both in the glossary's "auto-managed fields" note and in each affected JTBD. The default Semantius behavior is thatid,created_at,updated_atare auto-managed, butlabel_columnis not auto-managed when the entity declares it as a required caller-populated field, the recipe must compose and POST a value. Word the auto-managed-fields note carefully so an agent skimming the glossary doesn't conclude it can omit*_labelfrom POST bodies. - Required-on-create field sets, the model's
Requiredcolumn is intent, not platform-enforced. Spell out the business-required fields per JTBD; they often differ from create vs update. - Audit-logged entities, Semantius handles audit rows automatically
on writes; recipes don't manage them. The non-obvious case is reading
the audit trail. If the merit test surfaced an audit-read JTBD, the
recipe is a single GET against the audit endpoint with the entity id ,
see
use-semantiusreferences/crud-tools.mdfor the path shape. - Platform-derived fields (
computed_fields), the platform writes these on every INSERT/UPDATE and overwrites whatever the caller sent. Recipes must never include them in POST/PATCH bodies, and must not re-derive them client-side. After a write that touches the inputs (reach_score,impact_score,confidence_score,effort_scorefor a RICE-derivedrice_score), the recipe's "Validation" block should read back the row and assert the derived field has the expected value, that's the only place the formula is restated, and only to spot-check that the trigger fired. - Platform-enforced invariants (
validation_rules), the platform rejects bad writes with a structured{ "errors": [...] }body and a 4xx status. Recipes must not pre-validate these, duplicating the rule client-side is brittle (the rule can change in the platform without a recipe regen) and surfaces the same error twice on bad input. Instead, the JTBD'sFailure modesblock names each platformcodethe user is likely to hit and the recovery action: "code: release_only_when_committed, the user tried to attach a release before the feature is committed; tell them to triage the feature toplannedfirst, then retry." Thecodeis the i18n binding key, never paraphrase it. - Platform-enforced permissions (
require_permissionrules), the platform throws when the caller lacks the named permission, and the throw lands as a regularvalidation_rulesfailure (samecode/messageshape, same 4xx response). Recipes here differ from the generic "don't pre-validate" rule above: it is cheap and high-value for the recipe to pre-flight the permission check by reading the caller's role-permissions before attempting the write. The point is not to duplicate the platform's enforcement (the platform is the source of truth, and the recipe still surfaces the throw verbatim if the pre-flight is wrong) but to give the calling agent a chance to avoid the failure entirely by handing off to a user who holds the permission, or by escalating to confirm. The recipe shape is: (1) read the conditional-permission row fromPlatform-enforced permissionspreamble, naming the gate, the permission code, and the role-hint; (2) if the caller is the current user and they lack the permission, stop and ask ("this transition requires the<permission_code>permission, typically held by ; do you want to hand off, or proceed as the user who is signed in?"); (3) if the caller is acting on behalf of a specific user, surface who that user is and confirm they hold the permission before proceeding; (4) the JTBD'sFailure modesblock names the platformcodeand the recovery action just like a regular invariant. Family-13 owner / manager edit gates use the same shape: pre-flight by reading the record's owner field against the caller's$user_id, fall back torequire_permissionof the override role only when ownership doesn't match. - 1:1 / unique constraints, flag in Failure modes with the exact 409 condition and the recovery action (PATCH the existing row, pick a different parent, etc.).
- Row-scoped reads (
select_rule), the platform filters rows from every read of a scoped entity via an RLS policy. The rule applies uniformly to every caller withview_permission. Three recipe shapes apply:- Read recipes against a scoped entity lead with a one-sentence
visibility callout pulled from the Row-level read scope preamble
("every caller with
view_permissionsees only rows where<plain- English predicate>; rows outside the predicate are filtered out at the RLS layer regardless of the caller's role"). Recipes that enumerate the entity to make a decision ("are there any open tickets?") need this callout most loudly — a missing row is indistinguishable from no-such-row, and a recipe that concludes "no open tickets exist" from an empty result is wrong when the caller is scoped. - Broader-access path (when the model's §7 resolves a mechanism).
If the Row-level read scope preamble named a documented
broadening mechanism (separate cube view / entity surface, or a
Postgres
BYPASSRLSrole attribute), the recipe's "you might want more" block names that mechanism and how the caller routes to it. The recipe does not auto-elevate; it proposes the route and lets the caller decide. If the preamble did NOT name a mechanism, the recipe says so plainly: "broader read access for this entity is not provisioned in this catalog; escalate to a user who holds the role with broader access" — and never invents a "grant yourself<slug>:view_all_X" hand-off. - No client-side re-encoding of the rule. Recipes must not duplicate
the
select_ruleJsonLogic in their own filter (e.g. wiringsubmitter_user_id=eq.$user_idinto the GET path "to be safe"). The platform applies the rule; client-side duplication is redundant and breaks if the model's rule changes between regenerations. The correct posture is: read with the natural query; the platform filters; surface the visibility limit to the user via the preamble callout.
- Read recipes against a scoped entity lead with a one-sentence
visibility callout pulled from the Row-level read scope preamble
("every caller with
- Conditional UI states (
input_type_rule), the platform overrides a field's effectiveinput_typeat form render per-record. Recipes that POST/PATCH bypass the form and are not gated by the rule, but two patterns matter:- Conditional-required side-effect. For every entry in the
conditional_required_fieldssub-index (a rule that flipsdefault → requiredon a transition), the JTBD that performs the triggering transition must set the dependent field in the same PATCH. Thedynamic_input_typesindex gives the trigger and the dependent; the JTBD's Recipe block makes the PATCH explicit ("setstatus=approvedANDapproved_at=$nowin one call"). When a pairedvalidation_rulesfamily-5 entry exists, name itscodein the Failure modes block as the platform's enforcement-side counterpart. - Post-terminal lock. For every rule that flips a field to
readonlyafter a terminal state, recipes that update the entity after that state should drop the locked field from their PATCH body. The platform doesn't enforce this server-side viainput_type_rule, so the recipe is what keeps it consistent with the UI; if a paired server-side rule exists (a Stage 8 family-10 state-transition rule typically), surface itscodein Failure modes. - No re-derivation of the rule's effective value. Recipes must not
attempt to compute the field's UI mode client-side and branch on
it — the rule is for the form, not for the recipe. A recipe that
reads
statusand decides whether to includeapproved_atin the body should do so based on the JTBD's own transition semantics (which it owns), not by interpreting theinput_type_ruleJsonLogic.
- Conditional-required side-effect. For every entry in the
- Cube queries in the appendix, always lead with
cube discover '{}'and tell the calling agent to map the appendix's measure/dimension names against discover's output. Cube schema names drift on regeneration; the appendix is a starting point, not a contract.
Trigger phrasing
The trigger language lives on two distinct surfaces that serve different audiences and take different shapes. They are NOT copies of each other; they overlap only in what they describe (the domain), not in their wording.
Surface 1: SKILL.md frontmatter description. Matcher fuel,
read by Claude Code's skill router to decide whether to load this
skill for a given user prompt. The matcher works on semantic
similarity between the description and the user's prompt, so the
description should describe the intent classes the skill
handles, not list literal user phrasings. Concrete user-voice
sentences over-fit to specific wordings; abstract intent
enumeration generalizes across the thousands of phrasings real
users actually produce.
This pattern matches how Anthropic's own skills are authored
(pdf, docx, xlsx, pptx): their descriptions are intent
enumerations, not lists of quoted user sentences. Follow that
convention.
Surface 2: README.mdx ## Sample prompts. Catalog display,
read by humans browsing a skill list. Its job is to show the
breadth of what users can ask of this domain in literal
phrasings the reader recognizes ("yes, I'd say something like
that"). Concrete user-voice sentences belong here, in volume.
How to author the SKILL.md description
Two parts, in this order:
-
Domain noun + scope (1 sentence). Lead with the domain ("CRM", "ITAM", "workforce planning") and the entities or capabilities it covers in plain words. Example: "Use this skill for anything involving ITAM (IT Asset Management), the in-house domain that tracks hardware assets, software products and licenses, installations, asset assignments, contracts, and purchase orders, plus the cost-center and chargeback rollup that links them."
-
Action intent enumeration (one sentence, verb-phrase list). List the JTBDs as abstract verb-phrase intents, comma-separated inside a single sentence introduced by "Trigger when the user wants to ...". Each intent corresponds to one JTBD section in the body; the matcher generalizes from these phrasings to varied user wording. Example: "Trigger when the user wants to deploy a hardware asset to a user, return or unassign an asset, retire or decommission equipment, install software on an asset with optional license seat tracking, uninstall software, mark a purchase order received, or renew a contract."
- Aim for 6–10 intent phrases (one per JTBD; bundle close siblings if needed). Each phrase 4–10 words.
- Lead each with an active verb. Cover the primary verb plus one common informal synonym where natural ("retire or decommission"), so the matcher hears both vocabularies.
- Do not quote literal user sentences ("Bob is leaving, return his laptop"). The matcher generalizes better from intent phrases.
- Do not include discovery intents (lookups, reports).
Those are handled by
use-semantiusalone in most cases; loading the full domain skill for "who has asset X" wastes context. Discovery triggers belong in the README only.
Do not add a "Loads alongside use-semantius..." compose
hint to the description. The description is matcher fuel, and a
sentence about which sibling skill owns CLI install / PostgREST
encoding / cube mechanics does not help the router map a user
prompt to this skill (users do not type "I need PostgREST
encoding"). Compose-metadata is the harness's concern, not the
matcher's. The compose-hint already lives in the SKILL.md body's
opening paragraph ("Platform mechanics ... live in
use-semantius. Assume it loads alongside"), where the calling
agent reads it after the skill has loaded; that is the right
place for it.
How to author the README sample prompts
A bulleted list of concrete user-voice sentences, the kind of thing a real person types into chat. Volume matters here; the reader is browsing.
- 12–17 prompts total.
- 8–11 action prompts: concrete sentences for the JTBDs. Multiple synonymous phrasings for high-traffic JTBDs are welcome and useful, "Bob is leaving, return his laptop" and "I need to take back Sarah's MacBook" both map to Return and both belong in the catalog because real users say both.
- 4–6 discovery prompts: concrete lookup or report questions. "Who has asset SN-12345 right now", "which contracts expire next quarter", "show license utilization for Microsoft 365". These are NOT in the description (per surface-1 rules) but ARE in the README, because the catalog reader benefits from seeing them.
The relationship between the two surfaces
The description and the sample-prompts list overlap only in what they describe (the same domain, the same JTBDs). They do not share wording: the description has abstract verb-phrase intents, the README has concrete user-voice sentences. Each README sample prompt should map cleanly to either an intent in the description (action prompts) or a Common-queries example or lookup-convention pattern (discovery prompts). Principle 6 (Step 9) gates that mapping.
Step 6, Write the reference files (one per JTBD classified reference)
For every JTBD that Pass 3 classified as reference, write a sibling
file at <skills-root>/<modelslug>/references/<jtbd-slug>.md. The
slug matches the SKILL.md <jtbd-slug> link target byte-for-byte, the
agent will fail to load the file if the link drifts. Use a short
verb-phrase slug (schedule-feature.md, cast-vote.md,
ship-release.md), not a full sentence.
The reference file is loaded by the calling agent only when it enters that specific JTBD. SKILL.md has already been read by then, so the agent knows the glossary, enums, lookup convention, timestamp rule, and guardrails. Do not restate any of those. The reference file owns the recipe body, the FK / shape assumptions the recipe relies on, and any rules that only matter inside this JTBD.
Reference file template
# <Job title (matches SKILL.md ### heading verbatim)>
<One opening sentence: what this recipe does and the most
load-bearing invariant. No marketing, no value-prop, the agent has
already chosen this recipe. Example: "Cast or update a vote on a
feature. The `(feature_id, user_id)` junction has no DB-level
uniqueness, so the recipe must read first.">
## FK & shape assumptions
<List only the FK / uniqueness / audit-logging facts THIS recipe
relies on. One line per fact, in the form
`<child>.<col> -> <parent>.id` plus a short note on uniqueness or
junction shape. Skip anything the recipe doesn't actually depend on,
this is an assumption ledger, not a schema dump.
If any of these turn out to be wrong at runtime (a 409 / 422 the
recipe didn't predict), the agent follows the SKILL.md "When the
runtime disagrees with the recipe" escape hatch: query
`read_field`, surface the drift, abort cleanly. Do not silently
adapt.
Audit-logging notes belong here only if the recipe's correctness
depends on whether audit rows are written; otherwise omit, audit
rows are Semantius's concern, not the recipe's.>
- `feature_votes.feature_id -> features.id`
- `feature_votes.user_id -> users.id`
- `(feature_id, user_id)` junction has **no DB-level unique
constraint**, recipe MUST read-first before insert to avoid
duplicate vote rows.
## Composition rules
<Required only when this JTBD composes a caller-populated label or
recomputes a stored field. Spell out the algorithm so two callers
given the same inputs produce byte-identical output. "If possible",
"approximately", and "around N characters" are banned, replace with
deterministic rules: where to cut, what separator, whether to
append an ellipsis, how to round.>
- `<feature_vote_label>`: composed as
`"{user.user_full_name} -> {feature.feature_title}"`. ASCII arrow
` -> ` (space-hyphen-greater-space). The values come from the
read-first calls in step 1; do not invent.
- `<rice_score>`: computed as
`(reach * impact * confidence) / effort`. Round to <N> decimals
to match the `numeric(<precision>, <scale>)` column scale, no
truncation, no implicit float drift. If `effort` is null or zero
after the patch, write `rice_score: null` rather than a
placeholder.
## Recipe
```bash
# Step 1: parallel-fetch (no dependency between these reads)
semantius call crud postgrestRequest '{"method":"GET","path":"/<table>?<filter>&select=<cols>"}'
semantius call crud postgrestRequest '{"method":"GET","path":"/<other>?<filter>&select=<cols>"}'
# Step 2: <branch / compute / refuse logic with explicit conditions,
# e.g. "if release_status in (released, cancelled), refuse">
# Step 3: <write> (paired fields go in one call, never split)
semantius call crud postgrestRequest '{
"method":"<POST|PATCH>",
"path":"/<table>[?<filter>]",
"body":{<resolved body>}
}'
# Step 4: <verify the post-condition the validation block claims>
Annotate each recipe step's leading comment with what the step depends on: independent reads carry the leading comment "parallel-fetch (no dependency)" so the agent runs them in one round trip; dependent steps name what they consume from earlier steps. Without the explicit hint, the agent treats numbered lists as sequential.
Validation
<Three or four post-conditions, including any deeper invariants the SKILL.md outline only summarised. Each one must be checkable from a single GET; if you cannot, fix the recipe so it can.>
Failure modes (extended)
<Long-form discussion of every failure that matters: triggering condition, why it happens, the exact recovery procedure (often a follow-up call), how to detect after-the-fact that this failure happened, and whether the recovery is safe to run blindly or needs a user confirmation. Pair every failure with a recovery, not just a description.>
#### Where FK detail lives, and why
The split exists so SKILL.md stays small *and* tolerates live-schema
drift. Apply the test for each piece of content:
- *Every JTBD touches it* (lookup convention, timestamp rule,
glossary, enums, guardrails) → SKILL.md only.
- *Only this JTBD touches it* (recipe body, JTBD-specific
composition rules, FK / uniqueness assumptions, audit-logging
facts the recipe depends on, extended failure modes,
computed-field rounding) → reference file's "FK & shape
assumptions" section.
- *Genuinely cross-cutting fact 2+ JTBDs depend on in load-bearing
ways* (rare, often empty in practice) → SKILL.md "Cross-cutting
data rules" section. Default is "doesn't qualify"; promote
reluctantly.
Per-FK shape used to live in a SKILL.md cheatsheet. It moved to the
reference files because (1) most FKs are touched by only the one
JTBD that mutates them, so the SKILL.md slot was paid by every
trigger but consumed by one, and (2) live schema can drift between
generation and call (admins toggling unique indexes, audit-logging,
FKs); colocating the assumption with the recipe that depends on it
makes regeneration and drift-recovery localised. The runtime escape
hatch in SKILL.md ("When the runtime disagrees with the recipe")
covers the drift case.
A reference file aimed at fewer than ~40 lines means the JTBD
probably belongs inline in SKILL.md after all, the file split has
overhead (an extra read). A reference file growing past ~120 lines
means either the JTBD is doing two jobs (split it) or the reference
is restating SKILL.md material (delete the duplicates).
### Step 7, Write the script files (one per JTBD classified `script`)
For every JTBD that Pass 3 classified as `script`, write a sibling
file at `<skills-root>/<modelslug>/scripts/<op-slug>.sh`. Mark it
executable in the comment header; the calling agent invokes the
script through the shell rather than reading it into context.
The script's contract is its **inputs**, **exit codes**, and **stdout
on success**. The agent never reads the body, so the body must be
self-defending: validate args, refuse on bad preconditions, never
silently leave half-state.
#### Script file template
```bash
#!/usr/bin/env bash
# <op-slug>.sh: <one-line purpose, e.g. "Ship a release: PATCH the
# release row, sweep its planned/in_progress features to shipped,
# verify the sweep is complete.">
#
# Usage: <op-slug>.sh <arg1> <arg2> [optional]
#
# <Optional per-mode / per-subcommand detail block goes HERE, AFTER
# Usage, never before. For a single-mode script omit this block
# entirely. Example for a two-mode script:
# Add: read-first dedupe; if row exists active, no-op; ...
# Remove: soft-deactivate (is_active=false) so history survives.>
#
# Exit: 0 on success
# 1 on usage/validation failure (bad args, precondition not met)
# 2 on platform error (semantius call failed)
#
# Idempotent: re-running with the same inputs is safe. Partial-state
# recovery: on failure, print the failed step and exit non-zero; the
# next run resumes from where it left off.
set -euo pipefail
if [ "$#" -lt <N> ]; then
echo "Usage: $(basename "$0") <arg1> <arg2> [optional]" >&2
exit 1
fi
arg1="$1"
arg2="$2"
# Step 1: read; refuse if precondition fails.
result=$(semantius call crud postgrestRequest "{\"method\":\"GET\",\"path\":\"/<table>?<filter>&select=<cols>\"}") \
|| { echo "step 1 (read <table>) failed" >&2; exit 2; }
# parse result, check precondition; bail with a clear message if not met.
# Step 2..N: write, then verify.
semantius call crud postgrestRequest "{...}" \
|| { echo "step 2 (write <table>) failed" >&2; exit 2; }
semantius call crud postgrestRequest "{...}" \
|| { echo "step 3 (sweep <child>) failed; release row already updated, retry will be a no-op for already-shipped rows" >&2; exit 2; }
echo "<op-slug>: ok"
Conventions every script follows
-
First-line shebang
#!/usr/bin/env bash,set -euo pipefailimmediately after, no exceptions. Silent failures in shell are how cascade scripts leave half-state. -
Header section order is fixed: purpose one-liner, Usage, optional per-mode detail, Exit, Idempotent. Usage comes immediately after the purpose line so a human auditor sees "how do I call this" before any prose. Per-mode behavior detail (e.g.
Add:/Remove:) goes after Usage, never between purpose and Usage; the synopsis introduces the modes, then the detail explains them. -
Validate args before any platform call. Print usage to stderr, exit 1. The agent reads stderr to recover.
-
Exit codes are part of the contract. The recipe script's own exit codes are: 0 = ok, 1 = bad inputs or unresolved/ambiguous lookup, 2 = platform error. The agent only branches on these three; do not invent more for the script layer. The underlying
semantiusCLI carries finer-grained signal —1(bad args /--singlezero rows),2(--single≥2 rows),3(network / transport, transient and retryable),4(tool execution failed: RLS, dup key, schema, validation rule),5(auth failure, permanent) — but the script collapses platform failures into its own2and lets stderr carry the specifics. The diagnostic string must name which step failed (e.g.step 3 (sweep <child>) failed) and surface any platformvalidation_ruleserror verbatim so the agent can recover. -
Failure messages name the failed step.
step 3 (sweep <child>) failedis recoverable;erroris not. -
Surface platform
validation_ruleserrors verbatim, never duplicate them. When asemantius call ... POST/PATCHfails because the platform rejected the write, the response body carries{ "errors": [{ "code", "message" }, ...] }. Pass thecodeandmessagethrough to stderr unchanged so the agent can surface a clean recovery hint to the user. The script may name known codes in its diagnostic prose (e.g. "step 3 failed; if code isrelease_only_when_committed, the user should triage the feature toplannedfirst"), but it must NOT pre-check the JsonLogic client-side; that duplicates a rule the platform owns and rots the moment the rule changes. -
Idempotent. A repeat run on partially-applied state must either complete or be a deterministic no-op. For cascade scripts, filter writes to "rows still in the source state", not "all rows in the parent set"; running twice then becomes safe.
-
No interactive prompts. The agent invokes non-interactively. If the operation needs a user confirmation, the JTBD is a
reference, not ascript. -
URL-encode every caller-provided value interpolated into a GET path, PATCH filter, or DELETE filter. Raw bash interpolation of
$varinto/<table>?search_vector=wfts(simple).$varor?<col>=eq.$varbreaks the moment the value contains a space, an ampersand, a parenthesis, a+, a%, a#, a?, a quote, or any other character outside the URL unreserved set. Real catalog data is full of these (Customer Management,Investment Banking,Sales & Marketing,Account (Salesforce)). The canonical encode-then-interpolate pattern is one line viajq:enc_term=$(printf '%s' "$user_input" | jq -sRr @uri) semantius call crud postgrestRequest \ "{\"method\":\"GET\",\"path\":\"/${table}?search_vector=wfts(simple).${enc_term}&select=id\"}"This applies to every caller value: search terms going into
wfts(simple).<term>, exact-match values going into<col>=eq.<value>or<col>=in.(<values>), label-equality dedupe filters, and any other position where a caller string ends up in the URL. UUIDs already resolved from a--singleread are URL-safe and do not need encoding. Recipe lines that interpolate raw$varinto a path are a defect; Principle 7 in Step 9 catches them on self-review. -
Build JSON bodies with
jq -nc --arg, never string concatenation. A naïvebody="{\"notes\":\"$caller_notes\"}"breaks the moment$caller_notescontains a", a\, a literal newline, or a control character. Research-agent prose contains all four. The failure mode is opaque, the platform sees mangled JSON and returns a parse error rather than a clean validation message. The canonical build-then-pass pattern is:body=$(jq -nc \ --arg status "$new_status" \ --arg notes "$caller_notes" \ '{record_status: $status, notes: $notes}') semantius call crud postgrestRequest \ "{\"method\":\"PATCH\",\"path\":\"/${table}?id=eq.${row_id}\",\"body\":${body}}"Use
--argfor strings,--argjsonfor booleans / numbers / nested objects, and--rawfilefor multiline content read from a file. Conditional fields (only includeindustry_idwhenalias_type=industry_term) compose by building the base object then layering with+ {field: $val}inside the samejqcall, never by concatenating fragments. Recipe lines that build JSON via string concatenation are a defect; Principle 7 catches them. -
Refuse writes to columns the entity does not declare. The model parse in Step 2 already produces a per-entity field list. For any optional column a script writes conditionally (
notes,description,condition_notes,is_preferred), the script must either (a) bake in the list of entities that carry the column and refuse the arg with a clear stderr message when the caller targets one that does not, or (b) be specialized to a single entity that has the column. A polymorphic script that accepts anentityarg and unconditionally writesnoteswill 4xx on every entity that lacks anotescolumn, and the platform's response (column "notes" does not exist) is opaque to the agent, surface a precise error early instead. The canonical pattern:case "$entity" in data_object_aliases|data_object_relationships|solutions|vendors|industry_business_functions|business_function_domains|business_function_capabilities|domain_data_objects|capability_domains|solution_domains|solution_data_objects|solution_capabilities|domain_regulations) supports_notes=1 ;; *) supports_notes=0 ;; esac if [ "$supports_notes" = "0" ] && [ -n "${caller_notes:-}" ]; then echo "step 0: entity '$entity' has no notes column; supported entities are: ..." >&2 exit 1 fiThe list is computed from the model at generation time, not at script-run time. The same rule applies to every optional column the recipe ever writes.
Step 8, Write the README.mdx (human-facing catalog entry)
After the SKILL.md is written, generate a sibling README.mdx in the
same folder. This file is not loaded by any agent harness; it
exists so a person browsing a skill catalog can understand at a glance
what the skill is for and whether they want it. Optimize for human
skim, not agent triggering.
This step is not optional. A run that produces only SKILL.md is
incomplete; the catalog system depends on the README being there. If
you find yourself about to print the Step 10 summary without having
written README.mdx, stop and write it now.
Audience and tone
The reader is a human evaluating skills, not an LLM deciding whether to invoke one. Two consequences shape every rule that follows:
- Drop the pushy trigger language and the "delegates platform mechanics
to
use-semantius" plumbing. Those belong in the SKILL.md, where the matcher reads them. A human browsing the catalog does not need to know about theuse-semantiuscomposition to decide whether the domain matches their problem. - Drop the agent-harness jargon (Agent Skills, frontmatter matching, trigger phrases as keyword strings). The catalog is read by people picking which skills to install, not by the harness picking which skill to load.
Lead with the domain narrative the model file already authored. The
catalog reader needs to understand the SYSTEM here. The unique
"why install this specific skill" angle lives only in the front-matter
description.
Hard banned characters and phrases
These are non-negotiable. The README is read by humans browsing a skill catalog, and prior generations have repeatedly violated them.
- Never emit the em-dash character (Unicode U+2014, the long
dash often used as a parenthetical break) anywhere in the
README. Use commas, colons, parentheses, or split sentences.
Hyphens (
-) and en-dashes (–, used only in number ranges) are fine. - Never use the substring "this skill" anywhere in the README. The catalog renders many skill cards side by side and "This skill helps with…" openings make the list unreadable.
- Never include
generated_from,semantic_model, or any other provenance key in the README front matter. The catalog only readstitleanddescription. Provenance lives in the SKILL.md.
If you find yourself reaching for an em-dash because the sentence "feels right" with one, rewrite the sentence. The user has flagged this multiple times.
Front-matter description rules
The description is the single line a non-technical person reads
on the catalog card before deciding whether to open the skill.
It must follow the same audience and verb logic as the body's
skill-explanation paragraph (Step 8 §3), compressed to one
sentence.
Required shape (one sentence, ≤140 characters):
<Extracted verb><s><the domain object in user words><list of lifecycle stages, operations, or capabilities in plain words>.
The description names what the skill actually does, by listing the real nouns of the domain (workflow stages, operations, artefacts). It does not include a "so [benefit]" clause. The benefit / value-prop lives in the body paragraph's failure-modes sentence, not on the catalog card. A reader of the catalog card wants to know what the skill does, not why it exists.
- Extracted verb comes from the same three-tier verb-extraction
rule used in the body paragraph (Step 8 § "Verb-extraction
rule"). The description must use the same verb as paragraph 2,
resolved in this order:
- Tier 1, suffix match.
Tracking|Tracker → tracks,Planning|Planner → plans,Management|Manager → manages,Budgeting → budgets,Scheduling|Scheduler → schedules,Forecasting → forecasts,Reporting → reports,Monitoring → monitors,Routing → routes,Booking → books,Provisioning → provisions. - Tier 2, implied-domain-verb lookup. If Tier 1 misses, check
the noun table:
Roadmap → plans,Inventory → tracks,Ledger → records,Directory → lists,Registry → registers,Pipeline → moves,Catalog → lists,Calendar → schedules,Knowledge Base|KB → answers,Dashboard → surfaces,CRM → tracks,Helpdesk|Service Desk → resolves. Example:Product Roadmap→ "Plans how every idea moves from intake to a shipped feature...". - Tier 3, fallback palette. Only when neither tier fires:
Captures,Holds,Describes,Maps,Organises, orHelps an agent <verb-from-domain> .... Should be rare.
- Tier 1, suffix match.
- Domain object uses the model's user-facing labels, never
table names or schema-cased identifiers. "candidates", not
candidates; "headcount plans", notheadcount_plans. - Capability list names the actual lifecycle stages, operations, or artefacts the skill works with, in plain domain words from the model. Examples: "from requisition through application, interview, offer, and hire"; "subscriptions, license assignments, renewals, and planned spend by cost center"; "with decision packages, funding levels, ranking, and approval workflow"; "from intake through RICE scoring, objective alignment, and release scheduling". Pull these nouns from the model's entities, enums, and lifecycle fields, they should be recognisable to an operator of the system.
Hard bans (zero tolerance):
- "Bakes in", "baked in", "bakes the rules", "bakes the invariants". These read as internal implementation talk; the catalog reader does not know or care that anything is being baked. Use the extracted verb instead.
- "Multi-step rules", "paired status fields", "paired writes", "cascade", "invariants", "knock-on changes baked in", "the multi-table cascade that fires when". All mechanics jargon.
- Any snake_case or identifier-shaped token (regex
\b[a-z][a-z0-9]*(_[a-z0-9]+)+\b). - "This skill", "A skill for", "Domain skill for", "Skill that", any self-referential opener.
- Em-dashes (U+2014).
- Engineer-coded LLM language ("non-deterministic", "stochastic", "deterministic output").
- Restating the system name itself ("The Applicant Tracking System..."). The title already says it.
Coherence check: the description's verb must match the body paragraph's verb (same verb-extraction tier). The description itself names capabilities, not benefits, the body paragraph's sentence 3 (failure modes) is where the value-prop lives. Do not paraphrase the body's "so X" clause into the description; that shape belongs in the body, not on the catalog card.
Examples (illustrative, regenerate from the actual model):
system_name |
Verb | GOOD description |
|---|---|---|
| Applicant Tracking System | tracks | Tracks candidates through requisitions, applications, interviews, offers, and hires. |
| Workforce Planning | plans | Plans headcount across scenarios, commits approved scenarios into positions, and opens requisitions for new seats. |
| Equipment Lease Management | manages | Manages equipment leases through schedules, payments, renewals, and end-of-term disposition. |
| Zero-Based Budgeting | budgets | Runs ZBB cycles with decision packages, funding levels, ranking, and approval workflow. |
| SaaS Expense Tracker & Budget | tracks | Tracks SaaS subscriptions, license assignments, renewals, and planned spend by cost center. |
| Product Roadmap | plans (Tier 2: Roadmap → plans) |
Plans features through intake, RICE scoring, objective alignment, release scheduling, and ship. |
| Inventory Tracker | tracks (Tier 1) | Tracks items, locations, movements, counts, and reorder points across warehouses. |
| General Ledger | records (Tier 2: Ledger → records) |
Records transactions across accounts, journals, and periods, and runs balanced period closes. |
| Service Desk | resolves (Tier 2: Service Desk → resolves) |
Resolves tickets through intake, triage, assignment, work, and follow-up. |
BAD examples (do not produce):
Bakes in the multi-step rules for moving a candidate from application to hire, including paired status fields and offer-acceptance ripples.(Bakes in, paired status fields, ripples, all jargon.)Tracks candidates from application to hire so the right pieces always get filled in in the right order.(The "so [outcome]" clause is trigger-fodder; the description should name capabilities, not justify them.)Plans headcount across scenarios so an approved scenario walks into real seats with the right paperwork at every step.(Same anti-pattern, "so [outcome]" clause replaces the capability list.)This skill helps with the Applicant Tracking System.(Self-referential opener, restates the title.)
Body structure (in this exact order)
The body has exactly five sections after the heading. Do not add, rename, or rearrange.
-
Heading. See "Title grammar" below.
-
Model description (verbatim from the model's §1 narrative). Immediately after the heading, copy the model file's §1 narrative paragraph(s) verbatim. Do not paraphrase. Do not insert your own summary. This paragraph explains what the SYSTEM is.
- Ignore the
domainfrontmatter field entirely when building the README body. In every model file we have seen,domainis a short category tag (ATS,CDP,ERP,Workforce Planning,Product Management), not a description. Pasting it as a body paragraph leaves an orphan acronym floating under the heading and looks broken. The §1 narrative is always the source of truth for the body description. - If the model file has no §1 narrative, stop and ask the user to add one. Do not fabricate a description.
- Ignore the
-
Skill explanation paragraph (one paragraph, ~3 sentences, built on a fixed three-noun definition). After a blank line, write one paragraph aimed at a non-technical human browsing the catalog. The paragraph names three things in order: the model, the skill, and the failure modes.
The three-noun definition
- The model describes the static structure: what entities exist, what fields they have, what values are allowed. It tells the agent what can be recorded.
- The agent is good at understanding instructions and chaining steps, but two runs of the same prompt can take subtly different paths and produce subtly different records.
- The skill is the missing piece between them: it teaches the agent how to use this specific model to do its specific jobs reliably, the same way every time.
That third bullet is the value-prop, every paragraph must carry it somehow. The skill is not a metaphor about habits or shorthand, it is the model's instruction manual for the agent.
Sentence-by-sentence shape
Sentence 1, the model. Name the model and what it lays out or tracks. Use the verb-extraction rule below. Bridge from the previous paragraph by referring to the same kind of thing it ended on (records, plans, ideas, contracts).
Sentence 2, the skill. Name the skill (by its title, not the pronoun "this skill") and what it teaches the agent: how to use the model to do its specific jobs reliably and the same way every time. Reuse the domain verb from sentence 1 if it reads naturally.
Sentence 3, what going wrong looks like. Two or three concrete failure modes in plain domain words, semicolon- separated. These are evidence for the value-prop, not complaints about the platform.
Optional sentence 4, what life looks like with it loaded. Only if it earns its keep, do not pad.
Verb-extraction rule (critical, prevents all paragraphs
reading the same)
Most
system_namevalues either have an action verb hiding in a suffix or imply one through a domain noun. Resolve in three tiers, in order. Stop at the first tier that fires.Tier 1: Suffix match. If
system_nameends in an-inggerund or an-er/-oragent noun from the table below, use the bare verb form.Suffix Verb Example Tracking,Trackertracks "The Applicant Tracking model tracks ..." Planning,Plannerplans "The Workforce Planning model plans ..." Management,Managermanages "The Equipment Lease Management model manages ..." Budgetingbudgets "The Zero-Based Budgeting model budgets ..." Scheduling,Schedulerschedules Forecastingforecasts Reportingreports Monitoringmonitors Routingroutes Bookingbooks Provisioningprovisions Tier 2: Implied-domain-verb lookup. If Tier 1 does not fire, check whether
system_namecontains one of these domain nouns, each of which implies a verb a user would actually say. Treat this list as authoritative; extend it as new patterns appear.Noun in system_nameVerb Reasoning Roadmapplans A roadmap is a planning artefact Inventorytracks What you do with an inventory is track stock Ledgerrecords A ledger records transactions Directorylists A directory lists entities Registryregisters A registry registers entries Pipelinemoves A pipeline moves work through stages Cataloglists A catalogue lists items Calendarschedules A calendar schedules events Knowledge Base,KBanswers A KB answers questions Dashboardsurfaces A dashboard surfaces metrics CRMtracks Customer relationship tracking Helpdesk,Service Deskresolves A helpdesk resolves tickets Example:
system_name = "Product Roadmap"matchesRoadmapin Tier 2 → "The Product Roadmap model plans how every idea moves from intake through scoring and release commitment to a shipped feature ..."Tier 3: Fallback palette. Only when neither Tier 1 nor Tier 2 fires, pick from
captures,holds,describes,lays out,organises,maps. The fallback should be rare. If you find yourself reaching for it, pause and ask: "what does an operator actually do with this thing?" If a single verb captures it (even informally), prefer that verb and add it to the Tier 2 table for future runs. If neither tier fires and no domain verb feels obvious, surface a one-line confirmation to the user before writing files:"No clear domain verb in '<system_name>'; using '<fallback>'. Override with a different verb, or proceed?"The Tier 3 fallback palette has six options so even genuinely verbless system names do not all read the same.
Worked examples (use as shape templates, not for verbatim
copy)
Applicant Tracking (verb extracted: track)
The Applicant Tracking model tracks every step of a hire, from first application to recorded acceptance. The Applicant Tracking Skill teaches an agent how to use that model to track candidates through the funnel reliably and the same way every time, so the right pieces always get filled in in the right order. Without it, an offer can go out with no recorded approver; a rejection can land with no reason on file and quietly blank the funnel report; a candidate can accept while the requisition stays open and get pulled into another pipeline by mistake.
Workforce Planning (verb extracted: plan)
The Workforce Planning model plans how the team grows across scenarios and the positions a committed scenario will create. The Workforce Planning Skill teaches an agent how to use that model to plan headcount across scenarios and walk an approved scenario into real seats reliably, with the right paired updates and the right handoff to the recruiting team. Without it, a plan can be marked approved with no record of who signed off; two people can end up assigned to the same seat; an approved scenario can sit dormant while the real positions never get created.
Zero-Based Budgeting (verb extracted: budget)
The Zero-Based Budgeting model budgets every line item from scratch each cycle, with the assumptions behind each number. The Zero-Based Budgeting Skill teaches an agent how to use that model to budget a fresh cycle reliably, with assumptions captured alongside the numbers and the prior cycle's reasoning still readable when someone asks. Without it, a budget line can land with no recorded assumption; a justification from last cycle can quietly carry over without being re-examined; a cut can ship without naming who approved it.
Product Roadmap (Tier 2:
Roadmap→ plans)The Product Roadmap model plans how every idea moves from intake through scoring and release commitment to a shipped feature, with the rationale weighing each one and the release it lands in. The Product Roadmap Skill teaches an agent how to use that model to plan a feature from intake through to a shipped release reliably, without the handoffs between PM, design, and engineering quietly going missing. Without it, an idea can get scheduled with no recorded owner; a release can ship with features still marked "in design"; a deferred feature can lose the rationale that explains why it slipped.
Hard bans (literal scans before declaring done)
- Any snake_case or identifier-shaped token (
approved_at,originated_from_action_id,*_id,*_at,*_by_*). Implementation, not value. - Mechanics jargon:
writes,rows,calls,PATCH,POST,API,schema,column,FK,foreign key,constraint,uniqueness,join,table,query,back-pointer,cascade(the noun). - LLM-engineer language:
non-deterministic,stochastic,improvises,drift between runs,reproducible output. Catalog readers do not think of agents that way. - Band-aid framings: "the platform does not enforce", "the database does not catch", "multi-step writes that aren't enforced". These frame the skill as patching a defect.
- The phrase "this skill" anywhere; the literal front-matter
descriptioncopied verbatim;use-semantius; CLI commands; file paths; code fragments. - Schema-cased entity names: "Position" not
positions, "Interview Feedback" notinterview_feedback. Use the model'ssingular_labelvalues. - Verb-list rephrasing of the trigger bullets ("approving plans, opening requisitions, filling positions, onboarding new hires"). The bullets render right below; do not echo them.
- The literal phrase "lays out what the system can record". That was the example, not a template; paraphrase based on the model's actual flavor.
Required ingredients
- The three-noun definition: the model paragraph names the model and its verb, the skill paragraph names the skill by title and the value-prop ("teaches an agent how to use that model to do its jobs reliably and the same way every time"), the failure paragraph names two or three concrete bad days.
- Domain verb extracted from
system_nameper the rule above (or fallback palette if no verb is hiding). - Bridge to the previous paragraph by reusing a noun from its last clause (records, plans, ideas, contracts, the funnel, etc.). Do not start cold.
The acceptance tests
- Pattern test. Replace the model name with "X" everywhere in the paragraph. If the result reads as something that could apply to any of the other skills in the catalog, the paragraph is too generic; the verb-extracted noun and the failure modes should make it unmistakably about this one model.
- No-duplication test. Place the paragraph above the rendered trigger bullet list. If any sentence reads like a paraphrase of the bullets, replace it.
- Coffee test. Read aloud as if to the operator the model describes (recruiter, planning lead, controller, support engineer). If they would squint at any sentence, rewrite.
-
Sample prompts. A bulleted list of every trigger phrase from the SKILL.md frontmatter
description, verbatim and quoted, one per bullet, in the same order they appear in the description. Do not pick a subset. Do not paraphrase. If the description has ten quoted phrases, the bullet list has ten bullets. Sample prompts come before the capability list because catalog readers recognize and resonate with user-voice phrasing faster than verb-phrase capability bullets; the prompts pull the reader in, and the capability list gives them the scope check they want next. -
What it covers. A short bulleted list (5–7 bullets, each a verb phrase under ~12 words) summarizing the capability scope of the skill. Pulled from the JTBD section titles plus the Common-queries appendix, grouped and condensed. Each bullet names a capability in plain domain words, no script names, no file paths, no jargon. The list functions as the catalog reader's scope check after the sample prompts hook them: "the prompts feel right, but does it actually do what I'd want?" See "What it covers, content rules" below for shaping guidance.
-
Semantic model. A Mermaid diagram copied verbatim from the model (see Mermaid section below).
README.mdx template
---
title: <Heading text, see "Title grammar" below>
description: <One sentence, ≤140 chars. Shape: "<Extracted-verb>s <domain object> <list of lifecycle stages, operations, or artefacts in plain words>." Verb-led, names the actual nouns of the domain. No "so [benefit]" clause. No "Bakes in", no mechanics jargon, no snake_case, no "this skill", no em-dashes.>
---
# <Heading text, same as front-matter title, must contain "Skill">
<Verbatim copy of the model's §1 narrative paragraph(s). Ignore the
`domain` frontmatter field entirely (it is a short category tag like
ATS, CDP, ERP, never a description). No paraphrase, no code, no CLI,
no enum names.>
<Skill explanation paragraph: 2 to 4 sentences expanding the
front-matter `description` into prose for a human. Same value-prop,
not the same words. Mentions concrete domain nouns. Does not say
"this skill". Does not mention `use-semantius`, CLI, or harness
plumbing. Explains what loading this skill enables on top of the
system the previous paragraph described.>
## Sample prompts
- "<phrase 1 from SKILL.md description, verbatim>"
- "<phrase 2 from SKILL.md description, verbatim>"
- "<phrase 3 from SKILL.md description, verbatim>"
- ... (one bullet per quoted phrase in the SKILL.md description, no subset)
## What it covers
- <verb-phrase capability 1, e.g. "Move candidates through requisitions, applications, interviews, offers, and hires">
- <verb-phrase capability 2>
- <verb-phrase capability 3>
- ... (5–7 bullets total, see "What it covers, content rules" below)
## Semantic model
```mermaid
<Mermaid diagram, see "Mermaid diagram" below>
```
That is the entire README. Six elements only: front-matter, heading, model description (verbatim from §1), skill explanation paragraph (prose expansion of the front-matter description), sample prompts, what it covers, mermaid diagram. Do not add any other section, including "When to use it", "What's inside", "Generated from", or "About".
What it covers, content rules
The ## What it covers section is the catalog reader's scope check.
It comes after the sample prompts (which hook recognition) and
before the diagram (which is the visual model). Five rules
shape it:
- 5–7 bullets. Fewer reads as too thin; more reads as a feature dump and starts to compete with the prompts for attention. If the source has 10+ JTBDs, group them.
- Each bullet is a verb phrase under ~12 words. Lead with a verb in the same Tier 1 / 2 / 3 family as the front-matter description's verb (no need to repeat the exact verb on every bullet, but stay in the family of action verbs the domain uses). Examples: "Deploy, return, and retire hardware assets"; "Install and uninstall software with license seat tracking"; "Receive purchase orders"; "Renew contracts".
- Group related JTBDs into single bullets when natural. Deploy + return + retire collapse to one bullet ("Deploy, return, and retire hardware assets"); install + uninstall collapse to one ("Install and uninstall software with license seat tracking"). Do not split for the sake of bullet count.
- Common queries get one rolled-up bullet at the end. Format: "Common reports: <list 3–5 query themes in plain words>". Example: "Common reports: asset count by status, contracts expiring, license utilization, spend by cost center". Do not list each query separately; that bloats the list and competes with the JTBDs for attention.
- No script names, no file paths, no jargon. "Renew contracts" is good; "renew-contract.sh" is bad; "materialize the renewal handoff" is bad. The reader is non-technical; they care about what the skill does, not how the generator classified it.
The list is sourced from the JTBD section titles plus the Common-queries appendix. Walk the JTBDs in the order they appear in the SKILL.md, group adjacent related ones, render each group as a verb-phrase bullet, then append the rolled-up "Common reports" bullet last. Keep the order from the SKILL.md so the catalog list and the SKILL.md sections agree on narrative flow.
Title grammar
This rule governs two places in the README.mdx that must carry
the same text: the front-matter title and the H1 heading. Both
must contain the word "Skill" with a capital S, written as a
standalone word (never "domain skill", never "Skill that…", never
lowercased).
The SKILL.md H1 is a different heading, governed by its own rule
(see the SKILL.md template above): it is system_name verbatim, no
"Skill" suffix, because the file is named SKILL.md and repeating
"Skill" in the heading is redundant. Title grammar does not
apply to the SKILL.md H1.
Algorithm:
- Start with
system_name. - If it ends in the word "System" (case-insensitive), drop that trailing word. Reason: appending "Skill" to a name that already ends in "System" produces "...System Skill", which reads as two nouns colliding. Resolve the collision in favor of "Skill", because "Skill" is the catalog's anchor word and every entry carries it; "System" is the one that drops. So "Applicant Tracking System" becomes "Applicant Tracking".
- Append " Skill".
Examples:
system_name |
Title |
|---|---|
| Applicant Tracking System | Applicant Tracking Skill |
| Workforce Planning | Workforce Planning Skill |
| Product Roadmap | Product Roadmap Skill |
| Customer Relations | Customer Relations Skill |
| CRM | CRM Skill |
If the resulting title reads awkwardly to a fluent English ear (rare, flag it to the user), prefer rewording the system_name itself in the model file rather than dropping "Skill" from the title. "Skill" is the catalog's anchor word; humans scanning the list expect every entry to have it.
Mermaid diagram
Copy the mermaid block from the source model file verbatim. The
semantic model already authors a domain-map mermaid diagram (it is
produced by semantic-model-analyst and is the single source of
truth for the entity layout). Locate the existing ```mermaid
fenced block in the model file and paste it under ## Semantic model
unchanged.
Do not generate a new diagram from the model's entity list and FK section. Do not apply your own layout, labels, or grouping. Any deviation from the model's diagram causes the README to drift from the model the next time someone updates the model file.
If the model file has no mermaid block at all (rare; flag this to the
user as a model defect), omit the ## Semantic model section entirely
and note in the Step 10 summary: Model file has no mermaid block; omitted Semantic model section. Ask <semantic-model-analyst> to add one.
Step 9, Self-review pass
Before printing the summary, re-read the SKILL.md you just wrote with fresh eyes, as if a colleague were going to use it tomorrow against a different model. The point is not a checklist; it is that small drafting errors here scale, because every future invocation of the generated skill pays the cost. Fix issues in place; surface anything you can't fix without a design change in the Step 10 summary as a known limitation.
Run the pass against the principles below. Principle 0 is a structural-compliance check that runs first because it catches the single most common defect from prior generations: skipping Pass 3 entirely and emitting a monolithic SKILL.md. The remaining principles are examples of what to look for, drawn from defects observed in prior generations, they are not exhaustive, and the principle is what should drive the review on a new model where the specific defects may be different.
0. Pass 3 was actually applied (structural compliance). Run this check before anything else. It is mechanical and detects the "agent skimmed past the classification step" failure mode that has caused more re-runs than every other defect combined.
Walk every ### <JTBD> heading in the SKILL.md body. For each one,
locate its **Recipe:** block and apply this rule:
- Pointer form is one line. The acceptable shapes are exactly:
**Recipe:** see [references/.md](references/<slug>.md).or**Recipe:** run \scripts/.sh `.` plus optional one-sentence elaboration about exit codes. - Inline form is ≤5 lines of bash inside a single fenced code block,
no
# Step N:comments, no branching prose between steps. If the recipe block has step comments, multiplesemantius callinvocations in sequence, or paragraphs of "if X, refuse" between bash lines, it is not inline. It belongs inreferences/<slug>.mdand the SKILL.md body must shrink to the pointer form.
Count the JTBDs that violate one of those shapes (call this V) and
the JTBDs Pass 3 classified as inline (call this I_classified).
If V > 0 or V != I_classified, Pass 3 was bypassed. Stop the
self-review. Go back, re-classify the violating JTBDs as reference
or script, write the corresponding files in references/ or
scripts/, and replace the inline body in SKILL.md with the pointer
form. Then re-run this check from the top. Do not advance to
Principle 1 until V == I_classified.
Two separate count-based enforcements, applied after the per-JTBD shape check:
- Zero
references/files with 5+ JTBDs: unusual but not automatically wrong. Verify by walking each JTBD's body and confirming none of them carries a "ask the user before X" branch. Most domains have at least one such branch (charging to an inactive cost center, rewriting committed history, deleting with cascade). If you find one, it is a reference; promote it. - Zero
scripts/files with 5+ JTBDs: almost certainly a misclassification. Walk each reference and apply the "user-prompt branch" test from Pass 3: does this recipe actually need the agent to ask the user something, or does it just read, compute, validate, and write? If the latter, demote it to a script. Composition rules and computed-value recompute do not count as user-prompt branches; they are mechanical and belong in scripts. Cascade flows almost always belong in scripts.
1. Self-contained at runtime. The calling agent should never need to consult the source model file or a deployment-specific config to execute a recipe, the whole point of generating this skill is to condense the model's domain facts (enums, FK shapes, label rules, required fields) into the SKILL.md so the model file doesn't have to load alongside.
use-semantius is the deliberate exception. It loads alongside this
skill by design and owns the platform mechanics, CLI install, env
vars, PostgREST URL-encoding, schema-management tools, cube query
DSL. Pointing to a use-semantius reference for platform concerns
(e.g. "see references/cube-queries.md for date filtering syntax")
is correct and not a punt. Pointing to it for domain facts the
generator should have resolved (an enum value, a FK target, a
required-on-create field set) is a punt, fix those.
Three things to scan for:
- Hardcoded literals that will rot. Dates, timestamps, ids, the
generation year, anything that was correct only at write time.
Every such literal in a recipe body must become a placeholder
(
<current ISO timestamp>,<today's date, YYYY-MM-DD>,<feature_id>) with a one-line note telling the agent to fill it at call time. Hardcoded timestamps are the most common offender , search for them explicitly even if you don't think you wrote any. - Verbatim-copied content matches the source. The README's body
§1 paragraph(s) and the
## Semantic modelmermaid block are byte-copied from the model. Re-open the model file and diff these two regions against the README; any difference means the generator paraphrased instead of copying (defect, fix in place). This is the same check Step 4's drift pass runs on already-generated skills; on a fresh write it catches the generator paraphrasing through habit even though Step 2 had the source loaded. - Recipes that punt. If a JTBD ends with "see
use-semantiusfor the path" or "look up X in references/Y.md", the bake-in failed. Either resolve the value now (re-read the model and the use-semantius references) or, if it truly isn't knowable at generation time, keep the punt explicit and reconsider whether the JTBD passes the Step-2 merit test. A JTBD that just redirects fails the test, drop it and say why in the Step 10 summary.
2. Each fact lives in one place. Restating the same rule in two sections doesn't reinforce it; it creates drift. Re-read the SKILL.md asking: if this rule changed, how many places would I need to edit? The answer should be one.
- Cross-cutting rules belong in Guardrails; JTBD-specific recovery belongs in that JTBD's Failure modes. If the same sentence appears in both, delete one.
- Composition rules for caller-populated labels (
{user} → {feature}) belong in one JTBD plus a one-line glossary pointer, not verbatim in three places. - "What this skill does NOT do" is about scope (unbuilt features, out-of-domain tasks), not correct usage. If a bullet there overlaps with a guardrail, trim whichever side restates the other.
3. No surprises in the cheap-to-read parts. The glossary, enums, and Cross-cutting data rules are skimmed first; the per-JTBD reference files (with their FK & shape assumptions and extended failure modes) are read on demand. Anything that will surprise the calling agent at write time should surface in whichever layer the agent reads before the surprise can bite, cross-cutting surprises in SKILL.md, JTBD-local surprises at the top of the reference file's recipe.
- Unique constraints on natural keys (e.g.
tag_nameunique). - Delete behaviors that block writes (e.g.
restrictonauthor_idmeans deleting a user with comments fails). - Required caller-populated labels on junction or sub-entities.
- Built-in Semantius tables that overlap with declared entities (the
model's §8 flags this, commonly
users). The calling agent must not POST to a duplicate table. - Internal contradictions: re-read the "auto-managed fields"
paragraph against every POST recipe. If any recipe POSTs a
*_labelvalue, the auto-managed paragraph must carve those fields out explicitly; otherwise an agent skimming the glossary will conclude it can omit them.
4. Recipes match the platform conventions. The use-semantius references encode how the platform actually works; the generated skill should follow the same patterns so it composes cleanly.
- Fuzzy text search uses
search_vector=wfts(simple).<term>, neverilikeorfts(those bypass the search index). - Field-equality (
<column>=eq.<value>) is for known-exact values (ids, enums, unique keys), not banned, just a different tool than fuzzy search. - Read-before-write on junctions without DB-level uniqueness.
- Audit-logged tables don't need explicit audit writes; recipes don't manage them.
- No recipe re-derives a
computed_fields[].name. Walk every POST/PATCH body in every reference and inline recipe; if any of them writes a column listed under "Platform-derived fields" in the SKILL.md preamble, the recipe is wrong (the platform overwrites it). Drop the field from the body. The post-write read-back is fine and encouraged. - No recipe pre-validates a
validation_rules[].code. Walk every reference's branching prose; if it duplicates a check the platform already enforces (e.g. "refuse to attach a release_id when status isn't planned/in_progress/shipped" whenrelease_only_when_committedis in the model), drop the client-side check and rewrite the JTBD's Failure modes block to name the platformcodeand the recovery action instead. Duplicated checks are brittle and confuse the agent about who owns the rule. - No recipe duplicates a
select_rulein its own filter (analyst v2.2+). Walk every GET path against an entity that appears in therow_visibility_rulesindex. If the path adds a filter that re-encodes the rule (e.g.submitter_user_id=eq.$user_idagainst an entity whoseselect_rulealready filters on the same column), drop the duplicate filter. The platform applies the rule via RLS; the client-side duplication is dead code that breaks the moment the analyst updates the rule. Recipes against a scoped entity should read with the natural query and let the platform filter; the user-facing scope reminder lives in the JTBD preamble (sourced from the SKILL.md "Row-level read scope" block). - Every read recipe against a row-scoped entity carries a visibility
callout. For every JTBD whose Recipe reads an
entity that appears in the
row_visibility_rulesindex, the JTBD's preamble or Inputs block must contain a one-sentence scope reminder ("every caller withview_permissionsees only rows where<plain- English uniform predicate>"). A read recipe on a scoped entity without that reminder is the silent-empty-result defect: a missing row is indistinguishable from no-such-row, and the agent (or the user) draws the wrong conclusion. Pull the scope sentence from the SKILL.md "Row-level read scope" preamble. - No recipe promises a
view_all_<plural>-style permission bypass ) critical rule). Walk every preamble line, every JTBD visibility callout, every Failure-modes block. If any prose names a permission code as a way to "see every row" / "bypass the filter" / "broader read access" / "elevated tier" / similar, the correspondingSelect ruleJsonLogic must literally reference that permission code via{"require_permission": "<code>"}or an equivalent platform operator AND the model's §7 must resolve the architectural decision. A prose claim about permission bypass with no JsonLogic reference and no §7 entry is the canonical v2.2 defect the audit exists to catch — the prose promises something the platform does not honor, and the calling agent ships with broken RBAC. Fix by either deleting the bypass prose (rule applies uniformly; broader access goes through human escalation), pointing at a real documented mechanism from §7, or routing the user back tosemantic-model-analystto resolve Stage 12 / Stage 12.5 properly. - Every transition JTBD that fires a
conditional_required_fieldstrigger writes the dependent field in the same PATCH (analyst v2.2+). Walk theconditional_required_fieldssub-index from Step 2. For every(entity, trigger_field, trigger_value, dependent_field)tuple, find the JTBD whose Recipe PATCHes the trigger field to the trigger value. The Recipe body must include the dependent field in the same PATCH body. A transition that sets the trigger without the dependent is the silent-empty-housekeeping-field defect — the UI would have rendereddependent_fieldasrequiredat form time, but the API recipe has no form. Fix: add the dependent to the PATCH body with the correct value (typically$nowfor*_atfields,$user_idfor*_byfields). - No recipe interprets the JsonLogic of an
input_type_rule(analyst v2.2+). The rule is for the form-rendering layer, not for the recipe. Recipes drive their transition semantics from the JTBD's own intent (the analyst's prose, thevalidation_rules, the trigger condition captured inconditional_required_fields), not by re-evaluating theinput_type_rulebody. A recipe with a branch like "check whetherinput_type_rulewould have rendered this field ashiddenfor the current record" is wrong — drop the branch, let the recipe's own preconditions decide.
If a new platform convention shows up in the use-semantius references that this list doesn't mention, treat the principle ("match the platform") as the authority and add the new convention to the recipes , the bullets above will lag.
5. Recipes are reproducible. Two callers given the same inputs must produce byte-identical output. Most "weird drift" bugs in generated skills trace to phrases that seem deterministic but are not.
- Composition rules are exact. For every caller-populated label
field (
*_labelon a junction or sub-entity), the rule names a precise algorithm: separator characters (with whitespace), cut points (column counts on which side of which boundary), fallback when the input is shorter than the cut, casing. Phrases like "first ~80 characters", "trim at a word boundary if possible", "approximately N", "around" are bans, replace with rules a script could implement: "first 80 chars ofcomment_body; if char 80 falls mid-word, cut at the last space at or before position 80; if the body was longer than the cut, append the literal…(U+2026), no trailing space." - Symbols are consistent across the file tree. If the composition
uses
->(ASCII), every example, every guardrail, every validation note uses->; do not mix ASCII and Unicode arrows (→), do not switch separators between examples (/,|,->). Pick one set per skill and apply. - Computed values specify rounding. A formula like
(reach * impact * confidence) / effortover anumeric(p, s)column withs = 2requires the recipe to round to 2 decimals before the PATCH; otherwise the float-to-stored cast truncates the value and theValidationpost-condition ("rice_scoreequals the formula") fails on a tiny rounding diff. Name the scale and the rounding behavior in the recipe. - Side-effect timestamps are placeholders. Every
*_at,*_date,submitted_at,voted_at,posted_at,actual_release_datefield in a recipe body is a placeholder, the rule lives once at the SKILL.md top level, the per-JTBD reminder has been deleted. Re-search the file tree for hardcoded ISO dates and ISO timestamps; any hit is a defect.
6. Cross-section coherence. Each fact stated in one place must not be silently contradicted in another.
- Inputs tables match routing rules. If a JTBD's prose says "for
status X, route to JTBD Y instead", then X must not appear in
this JTBD's
Inputstable as a valid value. The contradiction forces the agent to guess. Drop X from the table and say outright which targets this JTBD owns. - Cube queries reference columns the glossary names. If
Common queriesuses<Entity>.sum_<column>or<Entity>.<dimension>, the underlying column must appear in the glossary, in some reference file's FK & shape assumptions, or in the "what the entity carries" line. A query mentioningFeatures.sum_estimated_costwith noestimated_costnamed anywhere else in SKILL.md leaves the agent unable to verify the field exists; it will guess and write a query that 500s. - SKILL.md outlines and reference files agree on the recipe shape.
Open the reference file and read its
## Recipe. The steps, the writes, and the validation must match what SKILL.md'sValidationandFailure modessummary claim. If the SKILL.md failure-modes mentions a409 on <column>and the reference recipe never POSTs to that column, one of them drifted; fix. - Reference-file slugs match SKILL.md links. For every
references/<jtbd-slug>.mdlink in SKILL.md, the file exists at that exact path. For every reference file written, the SKILL.md links to it. List both sets and diff. - Script names match SKILL.md invocations. For every
scripts/<op-slug>.shmentioned in a SKILL.mdRecipe:line, the script exists, and itsUsage:comment matches the args the SKILL.md tells the agent to pass. - Every trigger phrase in the description is answerable from
what the skill already provides. For every quoted phrase in
the SKILL.md frontmatter
description, the agent must be able to fulfill it from one of: (1) a JTBD section in## Jobs to be done(the trigger maps to a recipe), (2) a literal example in the## Common queriesappendix (the trigger is essentially that query), (3) the established pattern in the Common-queries appendix the agent can adapt (e.g. swap a measure or filter; appendix examples are intentionally pattern-establishing, not exhaustive), (4) the## Lookup conventionblock (e.g. "who has asset X" →serial_number=eq.X&select=current_user_id; this works as long as the convention block names the column). Each backing type is legitimate; the goal is not 1:1 recipe coverage, it is that an agent with the skill loaded does not have to guess column names, FK shapes, or query shapes from outside the skill. Triggers backed by (2)–(4) are not a fallback; they are an encouraged pattern. See Step 5 "Trigger phrasing" for the action-vs-discovery split: a healthy description carries both, because discovery triggers (lookups and reports) are how the catalog reader sees the breadth of the model, and they are how the skill makes those queries deterministic for users who install it. A trigger fails this check only when none of (1)–(4) applies: the agent would have to invent a recipe, infer a relationship the skill never names, or look up enum values not in the glossary. In that case the fix is one of two things: (a) drop the trigger from the description and the README's sample prompts; (b) add the missing backing (usually one new Common-queries example, occasionally a new JTBD if the merit test passes, or a one-line addition to the lookup convention). Run this check before the README cross-check so the catalog list inherits a clean trigger set.
7. Scripts handle real-world inputs. Pre-existing structural
checks pass on scripts that would still 4xx in production the moment
a real catalog name with a space hits them. Walk every file in
scripts/ AND every inline bash recipe in SKILL.md and run these
literal-text scans. Each is mechanical and detects a class of bug
the earlier principles miss because the offending lines look fine
in isolation.
- Raw
$varinterpolation into a URL path. Regex-scan everysemantius call crud postgrestRequest "{...}"invocation for$<varname>or${<varname>}appearing inside apathvalue in a position that is not preceded by an=eq.against a known-UUID variable (id,<entity>_id). Every other interpolated value (search terms going intowfts(simple).<term>, exact-match filters whose value is a caller string likealias_name=eq.<value>orrelationship_verb=eq.<value>, value lists inin.(...)filters) must come from a pre-encoded variable produced byprintf '%s' "$raw" | jq -sRr @uri. A line that interpolates raw$user_inputinto the URL is a defect. Most catalog names contain spaces (Customer Management,Investment Banking,Service Desk); raw interpolation silently mangles them. Fix in place: introduceenc_<name>=$(printf '%s' "$raw_<name>" | jq -sRr @uri)and reference${enc_<name>}in the path. - String-concatenated JSON bodies. Scan every script for the
shapes
body="{orbody="$body,or any line that builds a JSON body by appending interpolated"$var"fragments. Every body must be built withjq -nc --arg <key> "$<var>" ... '{...}'. The naïve concatenation pattern fails the moment a caller value contains a", a\, a newline, or a control character; research-agent prose is full of these. The platform surfaces a generic JSON parse error rather than the validation error the recipe was trying to test. Fix in place: rewrite the body usingjq, using--argjsonfor booleans / numbers / nested objects and--argfor strings. - Optional columns written without a per-entity guard. For every
script that accepts an optional arg corresponding to a column
(
notes,description,condition_notes,is_preferred, any arg whose name matches a column on some but not all targeted entities), walk back to the model's per-entity field list (from Step 2's parse) and confirm the script enforces the entity- carries-this-column check before composing the body. A polymorphic script that unconditionally writesnotesagainst anentityarg will 4xx on every entity that lacks anotescolumn; the platform'scolumn "notes" does not existresponse is opaque to the agent. Fix in place: bake the supports-column entity list into the script (computed at generation time from the model's §3) and refuse the caller's arg with a precise stderr message when the target entity does not carry the column. The Step 7 template shows the canonical case-statement form. --singlevs array misuse on caller-driven lookups. For every fuzzywfts(simple).<term>read, the recipe's intent is one of two things, "the term must resolve to exactly one row or the recipe cannot proceed" (use--singlewith a one-line guard) or "zero matches is a normal branch, ambiguous needs the candidate list" (use array-default with a two-step body inspection). Mixing these silently mis-reports: a--singleread on an ambiguous match exits 2 (which the script may handle as "platform error" rather than "user must disambiguate"); an array read whose code path assumes--single's bare-object shape mis-parses every row. Fix in place per the use-semantiusPattern A/Pattern Bconvention.
The fix for each defect is in-place rewriting of the offending script lines, not "flag in summary and ship". Step 9 is a gate, not a status report. Re-run this principle until every script passes all four scans.
Final pass: read it cold. After applying principles 1–7, set the files aside for a moment, then read SKILL.md top-to-bottom as if encountering it for the first time, and skim each reference file once. Where you backtrack, re-read, or pause to figure out what a sentence means, the writing is doing more work than it should. The specific defects that show up here are usually patterns the earlier principles missed because they were too local: a glossary that introduces a term still used unexplained in a JTBD three sections later, a guardrail that contradicts a JTBD's failure-mode recovery, a "see X" pointer where X never quite delivers what the pointer promises. Tighten in place.
README cross-check. The README is for humans browsing a catalog, so different failure modes apply. Run these as literal-text scans, not vibes. If any check fails, fix and re-scan before declaring done.
-
File exists. Both
SKILL.mdandREADME.mdxare present in the target folder. -
Em-dash scan. Literal-search the README for Unicode codepoint U+2014 (the em-dash, the long horizontal dash, distinct from the shorter hyphen
-and en-dash–). Must be zero hits. Replace each with commas, colons, parentheses, or split sentences. The user has flagged em-dash use repeatedly. -
"this skill" scan. Literal-search the README (case-insensitive) for the substring "this skill". Must be zero hits. Rewrite any sentence that uses it. The catalog renders many cards side by side and "This skill helps with..." openings make the list unreadable.
-
Headings: one rule per file, do NOT make them match.
- SKILL.md H1: must equal
system_nameverbatim. No "Skill" suffix (the file isSKILL.md, the word would be redundant), no "domain" prefix, no rewording. Forsystem_name = "Applicant Tracking System"the only valid SKILL.md H1 is# Applicant Tracking System. - README.mdx H1 and front-matter
title: must be identical to each other and must follow Title grammar: contain "Skill" with capital S as a standalone word, no "domain" anywhere, no lowercased "skill", trailing "System" dropped fromsystem_namebefore " Skill" is appended. For the samesystem_namethe only valid README H1 is# Applicant Tracking Skill.
Reject for the README:
# Applicant Tracking System Skill,# Applicant Tracking System domain skill,# Applicant Tracking System,# Applicant Tracking domain skill, and any other variant. Reject for the SKILL.md: anything that adds "Skill", "domain", or rewords the system name. - SKILL.md H1: must equal
-
Model description paragraph is verbatim from §1. The first body paragraph(s) under the heading are a verbatim copy of the model file's §1 narrative. Diff against the source model. No paraphrase, no summary.
- No orphan
domainline. Thedomainfrontmatter is ignored when building the README body (it is always a short category tag likeATS,CDP,ERP). If the README starts with such a short word or acronym on its own line under the heading, thedomaintag was wrongly pasted as a body paragraph. Delete it. The §1 narrative is the only source for this section.
- No orphan
-
Skill explanation paragraph passes the non-technical reader test AND does not duplicate the trigger list. Verify there is a second paragraph between the model description and
## Sample prompts. Run these literal scans on that paragraph; any failure means rewrite, not patch:- Not a copy of the front-matter
description. Diff against the front-matterdescription. They must share the value-prop but not the words. - Three-noun definition is present. Sentence 1 names the model and what it tracks/plans/manages/captures (model). The skill is named by its title (not "this skill") and explained as "teaches an agent how to use that model to reliably and the same way every time". The paragraph ends with two or three concrete failure modes.
- Domain verb extracted from
system_name(three-tier). Run the three-tier resolution and verify the verb actually used in sentence 1 matches.- Tier 1 fail: if
system_nameends inTracking|Tracker|Planning|Planner|Management|Manager|Budgeting|Scheduling|Scheduler|Forecasting|Reporting|Monitoring|Routing|Booking|Provisioning, the bare verb (track, plan, manage, budget, schedule, forecast, report, monitor, route, book, provision) must appear in sentence 1. - Tier 2 fail: if
system_namecontainsRoadmap|Inventory|Ledger|Directory|Registry|Pipeline|Catalog|Calendar|Knowledge Base|KB|Dashboard|CRM|Helpdesk|Service Desk, the corresponding implied verb (plans, tracks, records, lists, registers, moves, lists, schedules, answers, surfaces, tracks, resolves) must appear in sentence 1. - Tier 3 misuse: using a fallback verb (
captures,holds,describes,lays out,organizes,maps) when Tier 1 or Tier 2 would have fired is a fail. Rewrite using the matched verb. The verb in the front-matterdescriptionmust be the same.
- Tier 1 fail: if
- Pattern test (catalog uniqueness). Mentally substitute "X" for the model name everywhere in the paragraph. The result must NOT read as something that could apply to any other skill in the catalog. The verb-extracted noun and the failure stories should make the paragraph unmistakably about this one model. Generic openers like "The system has a handful of multi-step moves", "Loading the domain knowledge means", "Multi-step writes are not enforced" are a fail.
- No duplication of the trigger bullet list. Read the
paragraph and the rendered
## Sample promptslist together. Comma-strung verb lists that mirror the bullets are a fail. - No band-aid framing. Phrases like "the platform does not enforce", "the database does not catch", "multi-step writes not enforced" frame the skill as patching a defect. Replace with the positive three-noun definition.
- No LLM-engineer language. Scan for "non-deterministic", "deterministic", "stochastic", "improvises", "drift between runs", "reproducible output". Catalog readers do not think of agents that way. Use "the same way every time".
- No identifier-shaped tokens. Regex
\b[a-z][a-z0-9]*(_[a-z0-9]+)+\bmatches snake_case (approved_at,originated_from_action_id,headcount_actions). Zero hits. - No mechanics jargon. Case-insensitive scan for
writes,rows,calls,PATCH,POST,API,schema,column,FK,foreign key,constraint,uniqueness,join,table,query,back-pointer,cascade(noun). Zero hits. - No
use-semantius, no CLI commands, no file paths, no code fences. - No "this skill" substring, does not start with "This skill".
- Carries two or three concrete failure modes in plain domain words, semicolon-separated. Phrased the way a domain expert would describe a bad day: "an offer can go out with no recorded approver", "two people can end up on the same seat".
- Domain nouns use user labels, not table names. Cross-check
against the model's
singular_labelvalues. - Skill self-reference is by title. Look for
# <Title>H1, then verify the paragraph refers to the skill as "the " (e.g. "the Applicant Tracking Skill"), not as "this skill". - Banned literal phrase: "lays out what the system can record". That was an example, not a template. Paraphrase based on the model's actual flavor and the extracted verb.
- Not a copy of the front-matter
-
descriptionfollows the verb-led capability shape. One sentence, ≤140 characters, written for a non-technical reader. The description names what the skill does by listing the actual capabilities/lifecycle stages of the domain, not by justifying the skill with a "so [outcome]" benefit clause. Run these literal scans:- Shape:
<Verb><s> <domain object> <list of lifecycle stages, operations, or artefacts>. The verb is the same one extracted for paragraph 2 (tracks,plans,manages,budgets,schedules,forecasts,reports,monitors,routes,books,provisions) or the fallback (Captures,Holds,Maps,Organises,Records,Resolves). - Banned shape: "so [outcome]" clause. If the description
contains
sofollowed by a benefit/outcome clause (e.g. "so the right pieces always get filled in in the right order", "so renewals never fall off the calendar", "so two people never end up on the same seat"), it is using the body paragraph's value-prop shape instead of the catalog's capability shape. Rewrite to list the actual capabilities (workflow stages, operations, artefacts) instead. Also banned:while,withfollowed by an outcome clause. - Banned starters and substrings:
Bakes in,bakes in,baked in,Bakes the. Catalog readers do not know or care that anything is baked. Replace with the extracted verb. - No mechanics jargon in the description:
multi-step rules,paired status fields,paired writes,cascade,invariants,knock-on changes,multi-table cascade,seat-cascade. Same ban list as paragraph 2. - No identifier-shaped tokens (snake_case regex from item 6).
- No "This skill", "A skill for", "Domain skill for", "Skill that". Lead with the verb.
- No restatement of the system name. "The Applicant Tracking System ..." is bad; the title already says it.
- Verb coherence with paragraph 2. The description's verb must match the verb extracted for paragraph 2 (same Tier 1/2/3 resolution). The description names the capabilities (catalog card view); paragraph 2 carries the value-prop (failure modes). They share the verb but not the shape.
- Shape:
-
No provenance keys in front matter. Exactly two keys:
titleanddescription. Nogenerated_from, nosemantic_model, no other key. -
Mermaid is the model's mermaid, byte-for-byte. Diff the
```mermaidblock in the README against the one in the source model file. They are identical. Any difference means the diagram was regenerated instead of copied. -
Sample prompts: concrete user-voice sentences, mapped to backings. The README's
## Sample promptslist and the SKILL.md frontmatterdescriptionare independently authored with different shapes: the description carries abstract verb-phrase intents, the README carries concrete user-voice sentences. They do not share wording. Run these checks on the README's sample-prompts list:- Volume. 12–17 bullets. Below 12 the catalog list looks thin; above 17 it dominates the page.
- Concrete user-voice phrasing. Each bullet is a sentence
a real person would type, in casual or natural register.
Reject bullets that read as abstract intent phrases (e.g.
"return a hardware asset"is wrong;"Bob is leaving, return his laptop"is right). The description is where intent phrases belong, not the README. - Action vs discovery split. 8–11 action prompts (concrete phrasings of JTBD intents, multiple synonyms welcome) plus 4–6 discovery prompts (concrete lookup or report questions).
- Backing per Principle 6. Each action prompt maps to a JTBD section; each discovery prompt maps to a Common-queries example, its adaptable pattern, or the lookup convention. Unbacked prompts are leaks: drop or back.
- No literal user-voice quotes appear in the SKILL.md
description. Run a separate scan on the description: if
it contains quoted full-sentence user phrasings (e.g.
"Bob is leaving, return his laptop"), that is matcher signal pollution. The description should enumerate intent classes ("return or unassign a hardware asset"), not list literal phrasings. Strip the quoted sentences from the description; the equivalent concrete phrasings stay in the README sample prompts.
-
## What it coversis present, ordered, and well-shaped. The section sits between## Sample promptsand## Semantic model(this order is load-bearing: prompts hook the reader, capabilities anchor the scope check, the diagram closes with the visual). Run these literal scans:- Section heading is exactly
## What it covers. - Bullet count is in
[5, 7]. Fewer reads as thin; more reads as a feature dump. - Each JTBD bullet leads with a verb and is under ~12 words.
The trailing
Common reports: ...bullet is the one structural exception, it leads with the noun phrase "Common reports:" and may run longer (it lists 3–5 themes separated by commas); apply only the no-jargon and no-identifier scans to it. - No script names (regex
\.sh\b), no file paths (regex/followed by lowercase), no snake_case identifiers (regex\b[a-z][a-z0-9]*(_[a-z0-9]+)+\b), no jargon from the banned mechanics list (Principle 6's body-paragraph bans apply here too: writes, rows, calls, PATCH, POST, FK, cascade, etc.). This applies to every bullet, including the Common reports bullet. - The bullets cover, in order, the same JTBDs the SKILL.md's
## Jobs to be donelists, with adjacent related JTBDs grouped into single bullets where natural (e.g. deploy + return + retire collapse to one bullet about hardware lifecycle). The Common-queries appendix is summarized in a single trailing "Common reports: ..." bullet, not split. - No bullet duplicates a sample prompt. The prompt list is user-voice phrasing; the capability list is action scope. Overlap means the capability list isn't earning its place.
- Section heading is exactly
-
No
use-semantiusmentions and no agent-harness jargon anywhere in the README.
Output of the self-review. Report per-principle, not "no issues found" as a single line. For each of Principles 0–7 plus the README cross-check, write either:
- the fix you made, in one phrase (e.g. "Principle 1: replaced 3 hardcoded timestamps with placeholders"); or
- the evidence that the principle was actually scanned against the
generated files (e.g. "Principle 7 URL-encoding scan: 14
interpolation sites checked, all routed through
jq -sRr @uri"; "Principle 7 JSON-body scan: 9 bodies checked, all built viajq -nc"; "Principle 7 column-existence scan: 2 polymorphic scripts have entity-supports-column guards onnotesarg").
"Self-review pass, no issues found" is not an acceptable form without the per-principle evidence, because the most common defect in prior generations was the generator declaring no issues while having skipped a principle entirely. Behavioral correctness (does the script actually run on real catalog names?) is checked by Principle 7, not by Principle 0; a Principle 0 pass plus a missing Principle 7 line is the recurring failure mode this report shape is designed to catch. If a principle has nothing to check on a given generation (e.g. no scripts written, so Principle 7's script scans are vacuous), say so explicitly: "Principle 7: no scripts written, vacuous."
Step 10, Summarize
Print to the user:
- The folder created:
<skills-root>/<modelslug>/(state which root was used, project or user) and the files inside it. The two top-level files (SKILL.md,README.mdx) are required, missing either means the run is incomplete. Listreferences/andscripts/contents if any were written. - The sections written (one bullet each), with the merit signal
that earned the spot and the Pass-3 classification, e.g.
- "Vote on a feature, junction without uniqueness + caller-populated
label, reference (
references/cast-vote.md)" - "Ship a release, cascade flow, script (
scripts/ship-release.sh)" - "Capture a feature, simple POST with no branching, inline"
- "Vote on a feature, junction without uniqueness + caller-populated
label, reference (
- The Common queries baked into the appendix (titles only).
- The dropped candidates with reasons (e.g. "manage-tag, pure CRUD
on
tags, no merit signal, calling agent uses use-semantius directly"). The user may ask to add some back. - The platform-enforceable gaps from Pass 2's tracking, when any. One bullet per gap (entity, field, kind, §3-prose evidence), and whether the user chose to proceed with the stopgap recipe or paused to fix the model. This survives in the user's audit trail so a later regeneration can confirm the gap was either closed (model now carries the rule, no gap reported) or knowingly accepted again.
- The self-review result from Step 9, one line per principle
(0 through 7 plus the README cross-check), in the per-principle
evidence shape required by Step 9's "Output of the self-review".
Either the fix made or the explicit scan-evidence; a collapsed
"no issues found" line is rejected by Step 9 and must not appear
here. The Principle 7 lines in particular must name a count of
interpolation sites / JSON bodies / polymorphic-column guards
inspected, not just "passed", because that was the principle
prior generations skipped most often. Example shape:
- "Principle 0: 7 JTBDs, 0 inline / 0 reference / 7 script; Pass 3 alignment confirmed."
- "Principle 1: replaced 2 hardcoded timestamps with placeholders; §1 paragraph and mermaid byte-match the model."
- "Principle 4: dropped a client-side discriminator pre-check in
add-alias.sh; failure-modes now name the platformcode." - "Principle 7 URL-encoding: 12 interpolation sites scanned, all
routed through
jq -sRr @uri." - "Principle 7 JSON bodies: 9 bodies scanned, all built via
jq -nc --arg." - "Principle 7 column-existence:
promote-record.shnow refusesnotesarg on entities without anotescolumn (7 entities listed)." - "Principle 7
--singlevs array: 14 reads scanned, classified per use-semantius Pattern A / Pattern B." - "README cross-check: 12 checks, all pass."
- The file-tree size profile, one line:
SKILL.md <N> lines, references/ <M> files (<lo>-<hi> lines each), scripts/ <K> files. Surfacing this gives the user a feel for whether the split landed in a reasonable place; a 700-line SKILL.md with emptyreferences/is a signal the classifier was too conservative. - The audit findings from Step 4. On a fresh generation Step 4
is skipped (there are no pre-existing artifacts to audit), but
the bullet still appears, with the wording "Fresh generation;
Step 4 audit skipped. Behavioral correctness of the freshly-
written files is covered by the Step 9 self-review above
(Principle 0 structural + Principle 7 script-robustness)." Do
not collapse to "nothing to audit", that wording invites the
generator (and the user) to read it as "behavioral correctness
was also skipped", which is exactly the failure mode the
per-principle self-review report is designed to prevent. On a
regeneration, replace the fresh-generation sentence with the
real findings, grouped by file and severity:
- Defects (file is broken; rewrite recommended): e.g.
"
references/score-rice.md: linked from SKILL.md but missing on disk." - Non-canonical (works but uses an outdated pattern; user
decides): e.g. "
cast-vote.sh: 4 reads use legacy two-guard pattern,--singlewould simplify." - Orphans (no JTBD with this slug in current plan): e.g.
"
tag-feature.md: orphan; consider removing or restoring the JTBD." After listing, ask: "Regenerate flagged files? (all-defects / all-flagged / per-file / none)", defaultall-defects. The user picks; Steps 5–7 rerun for the chosen subset, files outside the subset stay untouched.
- Defects (file is broken; rewrite recommended): e.g.
"
What this skill does not do
- It does not run
semantiusitself, recipes are written, not executed. - It does not deploy the model, that's
semantic-model-deployer. - It does not generate evals, add via
skill-creatorlater.
Re-running on an updated model
Regenerate into the same target folder. The flow is:
- Step 4 audits the existing artifacts (read-only) and produces a findings list grouped by defect / non-canonical / orphan. See Step 4 for the checks it runs.
- Step 10 surfaces the findings to the user and asks which flagged files to regenerate (default: defects only).
- Steps 5–7 write the chosen subset.
SKILL.mdandREADME.mdxare always rewritten because they always need to reflect the current generation's plan; references and scripts are rewritten only for files the user opted to regenerate. Files outside that subset stay untouched, including hand-edited ones.
Orphans (files for JTBDs that no longer exist in the current plan) are surfaced in the audit but not auto-deleted. The user may have referenced them externally or be mid-edit. The Step 10 summary names each orphan and asks; default is to keep.
Hand-edits stay safe by default. The README.mdx and reference
files attract domain-expert hand-edits (narrative copy, failure-mode
wording, user-prompt phrasing); the audit treats those edits as
authoritative unless the file has a real defect (broken link,
missing required field, contradicts the model). For non-canonical
patterns alone, the user decides whether to take the upgrade.
Rebuild from scratch (Mode D)
A holistic regeneration of the entire generated skill folder, distinct from the conservative "Re-running on an updated model" flow above. The conservative re-run preserves hand-edits and rewrites only defective files; Mode D throws out the prior generation entirely and re-derives every artifact from the current model, treating the prior folder as archived knowledge for lessons learned but never as a structural constraint.
When to choose Mode D
Trigger phrases the user is likely to use:
- "Rebuild / reanalyze / re-author the
<slug>skill from scratch." - "The model has drifted across many iterations, regenerate everything."
- "Throw out the existing skill folder and start over."
- "We accumulated cruft in the generated skill, I want a fresh pass."
When not to use Mode D
- The model has minor changes and you only want to update affected files, use the conservative Re-running on an updated model flow (the default re-run behavior).
- The user wants to preserve hand-edits to README.mdx narrative or reference-file copy, use the conservative flow. Mode D rewrites everything.
- The user wants audit-only (no writes), run Step 4 alone and stop without proceeding to Step 10's regeneration prompt.
- The model file's major differs from
EXPECTED_MAJOR, refuse and route the user to the analyst's Mode D Rebuild on the model file first; this skill cannot rebuild a generated skill from a stale-major model.
How a rebuild runs
-
Run Steps 1–3 as if generating from scratch. Re-parse the current model file and re-derive jobs to be done from first principles. Do not consult the prior
SKILL.mdor its reference files for the JTBD list, the model is the only input. Reading the prior generation here would import its biases and defeat the point of a rebuild. -
At Step 4, the audit's role flips. Instead of producing a "regenerate this defective file" list, it produces a "lessons from the prior generation" list, surfaced for the user at Step 10. Look for:
- Hand-edited narrative copy in README.mdx or reference files that captures domain nuance not in the model (rare; surface so the user can paste relevant bits manually after the rebuild lands).
- JTBDs present in the prior plan that no longer have a model basis (likely intentional drops; surface as "removed").
- Guardrails or domain-glossary entries that look user-customized (preserve verbatim only if the user explicitly opts in at Step 10).
- Orphan files (JTBDs that no longer exist in the rebuilt plan); surfaced but never auto-deleted.
-
Steps 5–9 write a complete fresh folder. Default target: a sibling folder
<skills-root>/<slug>.rebuild/so the prior survives for diffing. Overwriting<skills-root>/<slug>/directly is allowed only after explicit user confirmation at the Step 10 summary gate. -
Step 10 shows the diff before overwriting. The summary names: JTBDs added / removed / renamed, references regenerated, scripts regenerated, and any hand-edits the audit flagged for manual carry-over. Ask: "Does this rebuild look right, or anything to keep from the prior folder?" Loop until confirmed. No overwrite of
<slug>/until the user says yes.
What carries over
- Nothing structural by default. The semantic-model file is the single source of truth; prior artifacts are not. The rebuilt SKILL.md, README.mdx, references, and scripts are derived fresh from the model.
- Hand-edited content (README narrative, reference-file failure-mode wording, guardrail prose) is surfaced in the Step 10 diff for opt-in manual carry-over. The skill does not auto-merge; the user pastes the bits they want to keep into the rebuilt folder.
- The target folder name (
<slug>/) stays the same unless the user explicitly renames the system, in which case follow the slug change loudly per the front-matter rules in Step 5 (a slug change breaks any external references to the prior folder).
What this is not
- Not the conservative re-run. That preserves hand-edits and rewrites only defective files. Mode D rewrites everything.
- Not a fix-up of broken artifacts. Use Step 4 audit + Step 10 subset selection for that, the conservative flow handles it correctly.
- Not an upgrade path for major-version mismatches. Major mismatch is rejected at Step 2; the user must re-author the model via
semantic-model-analystMode D Rebuild first, then re-run skill-maker (any flow) against the fresh file.
Failure modes
- Model file's
versionmajor differs fromEXPECTED_MAJOR(orversionis missing), refuse. The model is from a different schema era and recipes would bake in stale patterns. Tell the user to runsemantic-model-analystto re-author the model at current major (archived-knowledge mode handles older files), then re-run skill-maker against the new file. - Model file missing required frontmatter, stop and ask. Don't
guess
system_slug. - Model file has open §7.1 blockers, refuse. Tell the user to
resolve blockers in
semantic-model-analystfirst. - Conflicting target folder, if
<skills-root>/<modelslug>/already exists and the SKILL.md was not generated by this skill (no link back to the model file in its header), stop and ask before overwriting. - JTBD count > ~12 after filtering, Step 3.5 (Cluster check) runs and may surface a clean two-skill split. If it does, the Confirmation checkpoint already presents the split as a fourth list; no separate warning needed. If Step 3.5 finds no clean cut (the model really is one tightly-coupled big domain), warn the user that the resulting one-skill description will under-trigger and ask whether to drop lower-merit candidates or split the model file itself. Proceed with one oversized skill only if the user confirms.