From ontologian
Use when the user runs /ontologian-analyze or wants to derive an ontology structure from free-form business requirements text.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ontologian:analyzeThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Accept free-form business requirements text and automatically derive an ontology structure (Object Types, Link Types, Action Types). Query the user only for low-confidence items to complete the ontology with minimal intervention.
Accept free-form business requirements text and automatically derive an ontology structure (Object Types, Link Types, Action Types). Query the user only for low-confidence items to complete the ontology with minimal intervention.
Core principles:
Glob .ontology/config.yaml:
"The ontology repository is not initialized. Initialize it now? (y/n)" → n=exit, y=Write the following two files and continue:
.ontology/config.yaml: version: 1 / global_sync: ask / global_path: ~/.ontologian.ontology/domains/_index.yaml: domains: []global_sync, global_path (defaults: ask, ~/.ontologian)If arguments are provided, use them. Otherwise prompt:
Describe your business requirements in free form.
(e.g. "A user adds products to a cart, places an order, which reduces inventory and creates a shipment.")
Requirements:
Store the input as requirements_text.
Read .ontology/domains/_index.yaml. Iterate over the domains array and read each domain's files.
All domains use the directory format:
.ontology/domains/<directory>/objects/*.yaml → read each → collect Object Type names.ontology/domains/<directory>/links/*.yaml → read each → collect Link Type names.ontology/domains/<directory>/actions/*.yaml → read each → collect Action Type namesIf a directory or file cannot be read, skip that domain and continue.
Store in existing_ontology:
{ domains: [{ name, object_types: [names], link_types: [names], action_types: [names] }] }
If no domains exist or all files are empty, use { domains: [] } and continue.
Analyze requirements_text. This step is Claude's autonomous analysis. Do not ask the user any questions.
Initialize internal state at the start of Step 4:
candidate_objects: [] # {name, description, properties[], confidence}
candidate_links: [] # {name, from, to, cardinality, confidence}
candidate_actions: [] # {name, description, trigger, target, parameters[], confidence}
uncertain_items: []
Input: requirements_text
Output: candidate_objects[]
Immediate validation: Ensure all properties have a type (default to string if missing).
Extraction criteria:
type/account_type enum values map one-to-one to other Object Types in the same domain, that is duplicate modeling.
Account.account_type = fixed_deposit exists alongside a FixedDeposit Object, only keep FixedDeposit separate if it has a distinct lifecycle and unique attributes.concept_split in uncertain_items.Infer for each candidate:
name: PascalCase (e.g. "user" → User, "shopping cart" → Cart)description: role descriptionproperties: inferred attributes
type inference: email→string, count→int, amount→float, date→datetimeprimary: truedescription. Omit if not inferrable.
"Allowed values: X, Y, Z" in description. If unclear, add missing_enum_values to uncertain_items.description (e.g. "Sum of gross_amount across settlement_items").computed: true. If the expression is clear, infer expression. If not, add missing_computed_expression to uncertain_items.confidence: high / lowConfidence examples:
confidence: high → "A user places an order" → User, Order (clear nouns, clear roles)
"inventory count decreases" → Inventory(object), quantity(property) — clear
confidence: low → "add to cart" → unclear if Cart is a separate Object or Order.status
Korean-only term where PascalCase mapping is ambiguous
Input: requirements_text, candidate_objects (from 4-A)
Output: candidate_links[]
Immediate validation: Check that from and to values exist in candidate_objects. If not, add as action_target_unclear in uncertain_items.
Extraction criteria:
concept_split in uncertain_items for user confirmation.Infer for each candidate:
name: snake_case pure verb form — do not include the target noun in the name.
places, contains, writes, earns, attends, paid_with, belongs_to, works_atplaces_order, reviews_course, earns_certificate (verb+noun form — not allowed)granted, notified_by, assigned_to, based_on, filled_from — past participle adjectives, passive voice, and prepositional phrases are not allowed. Convert to active present-tense verbs:
granted → grants (Application grants JobOffer)notified_by → generates (Application generates DecisionNotification)assigned_to → handles or treats (active verb from the actor's perspective)based_on → follows (a prescription follows an appointment) or reconsider directionfilled_from → reconsider direction as fulfills (Prescription → Medication one_to_many)from toward to.belongs_to, works_at (verb+preposition) are allowed. Passive voice (assigned_to) and pure prepositional phrases (based_on, filled_from) are not allowed. Avoid overly generic names like references._by pattern allowed when: sent_by, written_by, reported_by — use <past_participle>_by to express ownership or origin ("from was created/sent by to"). Prefer reversing the direction (to→from active verb) if that reads more naturally.as_<role> prefix is a noun — not allowed. Find a verb pair that differentiates the roles:
initiated_by / "User it is directed at" → directed_atsent_by / "User who received" → received_byinitiated_by, directed_at (distinguishing two Follow→User roles)sent_by, received_by (distinguishing two Message→User roles)as_follower, as_followee (noun prefix — not a verb)from/to: Direction principle — prefer "owner/parent → owned/child" direction.
many_to_one is derived and parent-child is clear, consider reversing to one_to_many.
(e.g. "transactions belong to a settlement" → Settlement → Transaction (one_to_many))Settlement → Transaction (one_to_many))Application → JobPosting, Enrollment → Course)one_to_one: prefer semantic reference direction; do not force reversal.cardinality inference:
one_to_manymany_to_manyone_to_oneone_to_many (reversed direction)confidence: lowmany_to_many direct link vs. intermediate Object:
Like, Follow, Enrollment)many_to_many direct link is acceptable (e.g. Post → Hashtag)many_to_one links from that Object to each side. Do not create the original many_to_many link.confidence: high / lowInput: requirements_text, candidate_objects (from 4-A)
Output: candidate_actions[]
Immediate validation: Check that target values exist in candidate_objects. If not, add as action_target_unclear in uncertain_items.
Extraction criteria:
Infer for each candidate:
name: snake_case (e.g. send_order_confirmation, reduce_stock)description: purpose of the actiontarget: interpretation depends on trigger type
object_created → the newly created Object Typeobject_updated → the Object Type whose field change is being detected (the owner of trigger_condition.field). Not the Object created as a side effect.
target: Application (status field owner); Interview creation is a side effecttarget: JobOffer (status field owner); OnboardingProcess creation is a side effecttarget: Interview + trigger_condition.field: status — invalid if Interview has no status propertymanual → the Object Type the action directly creates or modifiestrigger:
object_createdobject_updatedobject_updated + always add ambiguous_trigger_condition to uncertain_itemsmanualtrigger_condition: when trigger is object_updated and the condition is clear:
field: <field being watched>
from: <value before change>
to: <value after change>
If field/from/to is unclear, add ambiguous_trigger_condition to uncertain_items.
to field in a single trigger_condition accepts only one value. For two target states, either omit to and specify only from (fires on any change away from that state), or split into two separate actions. Always mark as ambiguous_trigger_condition for user confirmation.trigger_condition. Simplify to trigger: manual, or add a boolean flag property (e.g. is_flagged: boolean) to the Object and trigger on false→true. Never write comparison expressions as to values (e.g. "threshold_exceeded")."any", "*", or "all values". If a specific value is unknown, omit the field or mark as ambiguous_trigger_condition.trigger_condition.field must be a property of the target Object.field with no from/to is valid — it means "fire on any change to this field". Use this for actions that respond to all state transitions (e.g. audit logging).target is X and trigger: object_created, but the action itself creates X, that is a circular trigger (e.g. send_fraud_alert target=FraudAlert, trigger=object_created on FraudAlert → alert fires when alert is created — meaningless). Change target to an upstream Object (e.g. BankTransaction) or use trigger: manual. If this pattern is found during 4-C extraction, add as ambiguous_trigger_condition in uncertain_items.target to the Object that causes the trigger (Review), or use trigger: manual. Always mark as ambiguous_trigger_condition.target to the actual trigger Object (X)trigger: manualambiguous_trigger_condition in uncertain_items for user confirmation.parameters: input values mentioned in the requirements.
trigger is object_created/object_updated and a separate actor (e.g. pharmacy, warehouse) is needed to perform the action, add that actor's identifier as a parameter (e.g. dispense_medication trigger=Prescription object_created → needs pharmacy_id parameter).trigger to manual and add ambiguous_trigger_condition to uncertain_items.confidence: high / lowInput: candidate_objects, existing_ontology
Output: Mark conflict candidates + add existing_conflict items to uncertain_items
Similarity criteria (flag as conflict candidate if any apply):
Settlement ⊂ SettlementItem)User ↔ Member, Cart ↔ Basket)Add conflict: { domain, existing_name, reason } to conflict candidates.
Input: candidate_* (results from 4-A through 4-D)
Output: uncertain_items[] — all candidates with confidence: low + items matching the criteria below
| Item type | Criteria |
|---|---|
concept_split | Ambiguous whether it's a standalone Object Type or an attribute/status of another type |
existing_conflict | Similar name detected in existing ontology (4-D) |
missing_primary_key | Object Type candidate has no or ambiguous identifier property |
ambiguous_cardinality | Link Type cardinality cannot be determined |
action_target_unclear | Action Type target Object Type is unclear |
ambiguous_trigger_condition | Status change field/value unclear, multiple outcome condition, or circular trigger pattern |
missing_enum_values | Allowed values for a status property are unclear |
missing_computed_expression | Computation expression for a computed property is unclear |
If all candidate arrays are empty after Step 4:
Could not derive any ontology candidates from the requirements. Please provide more detail (e.g. who does what to which entity).
→ Exit.
Output all analysis results in the following format. Do not ask any questions yet.
## Analysis Results
### Object Types (N)
✓ User — Service user [properties: user_id(string, PK), email(string), ...]
✓ Order — Order record [properties: order_id(string, PK), status(string), created_at(datetime)]
? Cart — Shopping cart (unclear if this should be a separate Object Type)
### Link Types (N)
✓ places — User → Order (one_to_many)
? belongs_to — Cart → User (cardinality unclear)
### Action Types (N)
✓ send_order_confirmation — Sends confirmation email on Order creation (object_created)
? reduce_stock — Reduce inventory (target Object Type unclear)
### Uncertain items (N)
[1] Cart: separate Object Type vs. Order.status attribute?
[2] Existing 'Member' (auth domain) and new 'User' — are these the same concept?
[3] Product has no primary key property
[4] Cart-User relationship cardinality unclear
Display rules:
✓ = confidence: high / ? = confidence: low or item is in uncertain_itemsAfter output:
"All items are clear. Proceeding to domain placement." → skip to Step 7"There are N uncertain items. Let's go through them one by one." → proceed to Step 6Query uncertain_items one at a time in order. Every item includes a (S) Skip option. Choosing (S) keeps the item at confidence: low and marks it as pending in the Step 8 preview.
After processing each item → run Step 6-cleanup.
[N/totalN] How should '<name>' be handled?
(A) Create as a separate Object Type
(B) Treat as <related_type>.status = "<value>" (remove this Object Type)
(C) Decide manually — enter another approach
(S) Skip — leave as pending
Choose (A/B/C/S):
A → promote to confidence: highB → remove from candidates → run Step 6-cleanupC → accept free-text input and apply the changeS → keep confidence: low[N/totalN] New '<name>' and existing '<existing_name>' (<domain> domain) appear similar.
(A) Same concept — reuse existing type (do not add the new candidate)
→ Update references in links/actions to point to '<existing_name>'
(B) Different concept — keep both
(C) Request a name change (manual edit recommended)
(S) Skip — leave as pending
Choose (A/B/C/S):
A → remove new candidate + update all references to existing name → run Step 6-cleanupB → promote to confidence: highC → same as B, and suggest manual editS → keep confidence: low[N/totalN] '<name>' has no primary key property.
(A) Auto-add <name_lower>_id (string) as primary key
(B) Specify another property as primary key — enter name and type
(C) Add without a primary key (not recommended)
(S) Skip — leave as pending
Choose (A/B/C/S):
A → prepend { name: "<name_lower>_id", type: "string", primary: true } to that Object Type's propertiesB → prompt for property name, then select type (1.string 2.int 3.float 4.boolean 5.date 6.datetime) → add with primary: true[N/totalN] Confirm the cardinality for the <from>-<to> relationship (<link_name>).
(A) one_to_one (B) one_to_many (C) many_to_many (D) many_to_one (S) Skip
Choose (A/B/C/D/S):
Apply the selection to that Link Type's cardinality.
[N/totalN] Select the target Object Type for action '<action_name>'.
(A) <candidate1> (B) <candidate2> (C) Enter manually (S) Skip
Apply the selection to that Action Type's target.
[N/totalN] Under what condition should action '<action_name>' execute?
(A) When a new [Object] is created (object_created)
(B) When a specific field on [Object] changes (object_updated) — enter field name, before/after values
(C) Manual execution (manual)
(S) Skip
If B is selected, prompt for field, from, and to in order (press Enter to skip each).
[N/totalN] The allowed values for '<ObjectType>.<property_name>' are unclear.
(A) Enter allowed values (comma-separated, e.g. pending, active, closed)
(B) Keep as string without specifying allowed values
(S) Skip
A → write as "Allowed values: ..." in that property's description.
[N/totalN] '<ObjectType>.<property_name>' is a computed property. Do you know the expression?
(A) Enter the expression (e.g. gross_amount - fee)
(B) Keep as computed without an expression
(S) Skip
A → store the input in that property's expression field.
When an Object Type is removed (6-A option B or 6-B option A):
candidate_links where from or to matches the deleted name.candidate_actions where target matches the deleted name → add as action_target_unclear in uncertain_items (skip if already present).uncertain_items related to the deleted Object.After processing all uncertain_items: "All uncertain items have been reviewed."
If no confidence: high items remain after Step 6 → skip to Step 11.
Output the confirmed candidates (confidence: high) and prompt for placement:
## Confirmed Candidates
[Object Types] <name1>, <name2>, ... (N)
[Link Types] <name1>, ... (N)
[Action Types] <name1>, ... (N)
(A) Add all to a single domain
(B) Distribute across multiple domains
Choose (A/B):
Option A — single domain: Display existing domain list + "N. Create new domain". If no domains exist, prompt for a name directly. If creating a new domain, collect the following one at a time:
domain_owner1. experimental / 2. stable / 3. deprecated (default: experimental) → stored as stabilitydependency_direction (split by comma, trim whitespace, store as array). If empty, omit the field.Store governance fields (domain_owner, stability, semantic_version: "1.0.0", dependency_direction) and write them to the _index.yaml entry in Step 9-A. Omit dependency_direction if not provided.
Option B — distribute: Specify a domain for each Object Type:
Assign each type to a domain.
User → (1) auth (2) ecommerce (N) New domain: __
Order → ...
Store the per-type domain assignments. For each new domain created during distribution, collect the following governance fields one at a time before proceeding to Step 9:
domain_owner1. experimental / 2. stable / 3. deprecated (default: experimental) → stored as stabilitydependency_direction. If empty, omit the field.Store governance fields (domain_owner, stability, semantic_version: "1.0.0", dependency_direction) and write them to the _index.yaml entry in Step 9-A. In Step 9, process each domain separately.
Output all confirmed candidates in YAML format. If pending items exist, display them in a separate section.
## Final Preview
Adding the following to the '<domain_name>' domain.
# Object Types (N)
object_types:
- name: User
description: "Service user"
properties:
- name: user_id
type: string
primary: true
- name: status
type: string
description: "Account status. Allowed values: active, inactive, suspended"
...
# Link Types (N)
link_types:
...
# Action Types (N)
action_types:
...
## Pending Items (skipped)
[1] Product.primary_key — pending (confidence:low)
[2] reduce_stock.trigger_condition — pending
Approval prompt:
Proceed with adding the above? (y / n / edit)
n → output "Cancelled." and exitedit → display numbered menu by type (Object/Link/Action) → re-collect the selected item → return to Step 8y → proceed to Step 9Add confirmed Object Types, Link Types, and Action Types to each domain file. If option B (distribute) was selected in Step 7, repeat the logic below for each domain.
Write individual entity files using the per-entity directory format. Do not create a flat ontology.yaml.
Object Types: For each confirmed Object Type, use the Write tool to create:
.ontology/domains/<domain_name>/objects/<Name>.yaml
File content (no wrapper keys):
name: <Name>
description: "<description>"
properties:
- name: <property_name>
type: <type>
description: "<description>" # only if provided
primary: true # only when true
computed: true # only when true
expression: "<expr>" # only when computed=true and expression provided
Link Types: For each confirmed Link Type, use the Write tool to create:
.ontology/domains/<domain_name>/links/<name>.yaml
File content:
name: <name>
from: <ObjectType>
to: <ObjectType>
cardinality: <cardinality>
description: "<description>" # only if provided
Action Types: For each confirmed Action Type, use the Write tool to create:
.ontology/domains/<domain_name>/actions/<name>.yaml
File content:
name: <name>
description: "<description>" # only if provided
target: <ObjectType>
trigger: <trigger>
trigger_condition: # only when trigger=object_updated and field provided
field: <field>
from: <value> # only if provided
to: <value> # only if provided
parameters: # only when at least one parameter
- name: <param>
type: <type>
required: false # only when false; omit when true
Update _index.yaml: Use Edit to add the new domain entry with directory: field (not path:):
- name: <domain_name>
description: "<domain_description>"
domain_owner: "<domain_owner>"
stability: <stability>
semantic_version: "1.0.0"
directory: <domain_name>
dependency_direction: # only if declared; omit entirely if not provided
- <upstream_domain>
last_modified: <today_date> # YYYY-MM-DD
If domains: [], replace the empty array with a populated list before appending.
directory field presentFor each confirmed Object Type, Link Type, and Action Type being added to this domain:
Check whether the file already exists. If it does, warn:
⚠️ <TypeKind> "<name>" already exists at <path>. Overwrite? (y/n)
If n → skip this entry. If y → overwrite.
Use the Write tool to create or overwrite the appropriate file:
.ontology/domains/<directory>/objects/<Name>.yaml.ontology/domains/<directory>/links/<name>.yaml.ontology/domains/<directory>/actions/<name>.yamlFile formats are identical to Step 9-A.
After all writes, always update the domain's last_modified in _index.yaml to today's date (YYYY-MM-DD) using the Edit tool.
ask → "Sync to global store (<global_path>) as well? (y/n)" → y=proceed, n=skipauto → proceed immediatelyoff → skipProceed: Write-copy the modified domain file(s) + _index.yaml to <global_path>/domains/.
When items were added:
✓ [<domain_name>] Analysis complete
Added:
Object Types : N (<name1>, <name2>, ...)
Link Types : N (...)
Action Types : N (...)
→ .ontology/domains/<domain_name>/objects/ (<n> Object Type files)
→ .ontology/domains/<domain_name>/links/ (<n> Link Type files)
→ .ontology/domains/<domain_name>/actions/ (<n> Action Type files)
[i] Tip: Run /ontologian-validate to check for schema issues, or /ontologian-visualize to see your new types in context.
Edge case B (nothing added):
✓ Analysis complete — no new types added (all merged into existing ontology)
uncertain_items and confirm in Step 6.confidence: low are not added to YAML. Show them only in the Step 8 pending section.path: instead of directory: in _index.yaml → New domain entries must use the directory: field (e.g. directory: ecommerce). Never write path: or paths: fields.ontology.yaml → Always write to per-entity files: objects/<Name>.yaml, links/<name>.yaml, actions/<name>.yaml. Never create a flat ontology.yaml.reviews_course, earns_certificate are not allowed. Use pure verb form: reviews, earns. Phrasal verbs with prepositions (results_in, belongs_to, paid_with) are allowed since the suffix is a preposition, not a noun.ambiguous_trigger_condition and get user confirmation. In object_updated actions, target is the Object whose field changes (the field owner), not an Object created as a side effect.granted, notified_by style past-participle adjectives are not allowed. Convert to active present-tense verbs (e.g. grants, generates).follows when the business meaning is ambiguous. Prefer domain-specific verbs: consider stems_from (origin), covers (coverage), pertains_to (attribution). Always choose the most specific active verb that fits the domain language.concerns, triggered_by).Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub swszz/ontologian --plugin ontologian