원클릭으로
allium
Give your AI agents something more useful than a prompt. Velocity through clarity.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Give your AI agents something more useful than a prompt. Velocity through clarity.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Extract an Allium specification from an existing codebase. Use when the user has existing code and wants to distil behaviour into a spec, reverse engineer a specification from implementation, generate a spec from code, turn implementation into a behavioural specification, or document what a codebase does in Allium terms.
Run a structured discovery session to build an Allium specification through conversation. Use when the user wants to create a new spec from scratch, elicit or gather requirements, capture domain behaviour, specify a feature or system, define what a system should do, or is describing functionality and needs help shaping it into a specification.
Generate tests from Allium specifications. Use when the user wants to propagate tests, generate test files from a spec, write tests for a specification, create property-based tests, produce state machine tests, check test coverage against spec obligations, or understand what tests a specification requires.
Tend the Allium garden. Use when the user wants to write, edit, update, add to, improve, clarify, refine, restructure, fix or migrate Allium specs. Covers adding entities, rules, triggers, surfaces and contracts, fixing syntax or validation errors, renaming or refactoring within specs, migrating specs to a new language version, and translating requirements into well-formed specifications. Pushes back on vague requirements.
Weed the Allium garden. Find where Allium specifications and implementation code have diverged, and help resolve the divergences. Use when the user wants to check spec-code alignment, compare specs against implementation, audit for spec drift or violations, sync specs with code or code with specs, or verify whether the implementation matches what the spec says.
| name | allium |
| description | Give your AI agents something more useful than a prompt. Velocity through clarity. |
| version | 3 |
| auto_trigger | [{"file_patterns":["**/*.allium"]},{"keywords":["allium","allium spec","allium specification",".allium file"]}] |
Allium is a formal language for capturing software behaviour at the domain level. It sits between informal feature descriptions and implementation, providing a precise way to specify what software does without prescribing how it's built.
The name comes from the botanical family containing onions and shallots, continuing a tradition in behaviour specification tooling established by Cucumber and Gherkin.
Key principles:
Allium does NOT specify programming language or framework choices, database schemas or storage mechanisms, API designs or UI layouts, or internal algorithms (unless they are domain-level concerns).
| Task | Tool | When |
|---|---|---|
Writing or reading .allium files | this skill | You need language syntax and structure |
| Building a spec through conversation | elicit skill | User describes a feature or behaviour they want to build |
| Extracting a spec from existing code | distill skill | User has implementation code and wants a spec from it |
| Modifying an existing spec | tend skill | User wants targeted changes to .allium files |
| Checking spec-to-code alignment | weed skill | User wants to find or fix divergences between spec and implementation |
| Generating tests from a spec | propagate skill | User wants to generate tests, PBT properties or state machine tests from a specification |
| Driving the whole loop to convergence | this skill (see driving the loop) | User wants to build or reconcile a feature end to end — /allium <goal> runs the gather→act→verify→repeat loop autonomously until spec, tests and code agree |
/allium (loop-first)/allium is the entry point. Bias toward the autonomous path — the whole-loop value is exactly what occasional single-skill use misses:
Clear single task → route straight to that skill (per the routing table); don't make the user wade through a menu.
A goal or feature (e.g. "add gift cards", "get password reset working") → drive the whole loop end to end yourself, rather than running one phase. Follow driving the loop.
Bare or ambiguous → orient the user loop-first: offer to drive the loop as the default, then list the individual skills as the control path with a one-line hint each, and suggest a concrete starting point from the project state (existing .allium specs? code but no spec? drift to reconcile?). For example:
Tell me a goal and I'll drive the whole loop — spec → tests → code, until they agree. Or run one step yourself:
elicit(spec from intent),distill(spec from existing code),propagate(tests from a spec),tend(edit a spec),weed(fix spec↔code drift). You have code but no.alliumyet, so I'd start by distilling — or just give me the goal and I'll take it end to end.
Lead with the loop; keep the individual skills one step away for users who want manual control. And once a single skill finishes, proactively suggest the next phase rather than waiting to be asked.
The skills are not one-shot commands; they compose into an autonomous-style loop — gather context → take action → verify → repeat — that drives three artefacts to agreement: the spec (intent), the tests (contract), and the code (implementation). Gather context with /elicit or /distill (the spec is durable context); take action with /propagate then implementation (in spec-first work, confirm the new tests fail first — a test already green before you implement is already-covered or vacuous); verify by running the tests, then /weed, then CLI structural checks; repeat until converged. Verification is the phase that matters most, and the spec-plus-tests-plus-weed signal is what makes the loop trustworthy. After invoking one skill, proactively suggest the next step rather than waiting to be asked. To run the whole loop to convergence in one go, just give /allium a goal — it drives the loop for you, following driving the loop.
Two entry points, one convergence loop:
/elicit → /propagate → implement → /weed; use /tend then re-/propagate when requirements change./distill → review intended vs accidental behaviour → /propagate → run tests against the code → /weed to reconcile → repeat per area.The work is "done" when tests pass, /weed reports no divergence, and no open questions remain (plus, for code-first, a fresh /distill finds nothing new). Two standing rules while looping: never weaken a generated test to make it pass (fix the spec and re-propagate instead), and escalate genuine ambiguity to the human rather than guessing.
Implementation itself is ordinary coding — Allium produces the spec and tests, not the application code. See the recommended loops reference for the full walkthrough, diagrams, exit conditions and the implementation prompt.
entity Candidacy {
-- Fields
candidate: Candidate
role: Role
status: pending | active | completed | cancelled -- inline enum
retry_count: Integer
-- Relationships
invitation: Invitation with candidacy = this -- one-to-one
slots: InterviewSlot with candidacy = this -- one-to-many
-- Projections
confirmed_slots: slots where status = confirmed
pending_slots: slots where status = pending
-- Derived
is_ready: confirmed_slots.count >= 3
has_expired: invitation.expires_at <= now
}
external entity Role { title: String, required_skills: Set<Skill>, location: Location }
value TimeRange { start: Timestamp, end: Timestamp, duration: end - start }
A base entity declares a discriminator field whose capitalised values name the variants. Variants use the variant keyword.
entity Node {
path: Path
kind: Branch | Leaf -- discriminator field
}
variant Branch : Node {
children: List<Node?>
}
variant Leaf : Node {
data: List<Integer>
log: List<Integer>
}
Lowercase pipe values are enum literals (status: pending | active). Capitalised values are variant references (kind: Branch | Leaf). Type guards (requires: or if branches) narrow to a variant and unlock its fields.
Declares the entity instances a module's rules operate on. All rules inherit these bindings. Not every module needs one: rules scoped by triggers on domain entities get their entities from the trigger. given is for specs where rules operate on shared instances that exist once per module scope.
given {
pipeline: HiringPipeline
calendar: InterviewCalendar
}
Imported module instances are accessed via qualified names (scheduling/calendar) and do not appear in the local given block. Distinct from surface context, which binds a parametric scope for a boundary contract.
rule InvitationExpires {
when: invitation: Invitation.expires_at <= now
requires: invitation.status = pending
let remaining = invitation.proposed_slots where status != cancelled
ensures: invitation.status = expired
ensures:
for s in remaining:
s.status = cancelled
@guidance
-- Non-normative implementation advice.
}
when: CandidateSelectsSlot(invitation, slot) — action from outside the systemwhen: interview: Interview.status transitions_to scheduled — entity changed state (transition only, not creation)when: interview: Interview.status becomes scheduled — entity has this value, whether by creation or transitionwhen: invitation: Invitation.expires_at <= now — time-based condition (always add a requires guard against re-firing)when: interview: Interview.all_feedback_in — derived value becomes truewhen: batch: DigestBatch.created — fires when a new entity is createdwhen: AllConfirmationsResolved(candidacy) — subscribes to a trigger emission from another rule's ensures clauseAll entity-scoped triggers use explicit var: Type binding. Use _ as a discard binding where the name is not needed: when: _: Invitation.expires_at <= now, when: SomeEvent(_, slot).
A for clause applies the rule body once per element in a collection:
rule ProcessDigests {
when: schedule: DigestSchedule.next_run_at <= now
for user in Users where notification_setting.digest_enabled:
let settings = user.notification_setting
ensures: DigestBatch.created(user: user, ...)
}
Ensures clauses have four outcome forms:
entity.field = valueEntity.created(...) — the single canonical creation verbTriggerName(params) — emits an event for other rules to chain fromnot exists entity — asserts the entity no longer existsThese forms compose with for iteration (for x in collection: ...), if/else conditionals and let bindings.
Entity creation uses .created() exclusively. Domain meaning lives in entity names and rule names, not in creation verbs.
In state change assignments, the right-hand expression references pre-rule field values. Conditions within ensures blocks (if guards, creation parameters, trigger emission parameters) reference the resulting state.
surface InterviewerDashboard {
facing viewer: Interviewer
context assignment: SlotConfirmation where interviewer = viewer
exposes:
assignment.slot.time
assignment.status
provides:
InterviewerConfirmsSlot(viewer, assignment.slot)
when assignment.status = pending
related:
InterviewDetail(assignment.slot.interview)
when assignment.slot.interview != null
}
Surfaces define contracts at boundaries. The facing clause names the external party, context scopes the entity. The remaining clauses use a single vocabulary regardless of whether the boundary is user-facing or code-to-code: exposes (visible data, supports for iteration over collections), provides (available operations with optional when-guards), contracts: (references module-level contract declarations with demands/fulfils direction markers), @guarantee (named prose assertions about the boundary), @guidance (non-normative advice), related (associated surfaces reachable from this one), timeout (references to temporal rules that apply within the surface's context).
The facing clause accepts either an actor type (with a corresponding actor declaration and identified_by mapping) or an entity type directly. Use actor declarations when the boundary has specific identity requirements; use entity types when any instance can interact (e.g., facing visitor: User). For integration surfaces where the external party is code, declare an actor type with a minimal identified_by expression. Actors that reference within in their identified_by expression must declare the expected context type: within: Workspace.
The exposes block is the field-level contract: the implementation returns exactly these fields, the consumer uses exactly these fields. Do not add fields not listed. Do not omit fields that are listed.
contract Codec {
serialize: (value: Any) -> ByteArray
deserialize: (bytes: ByteArray) -> Any
@invariant Roundtrip
-- deserialize(serialize(value)) produces a value
-- equivalent to the original for all supported types.
}
Contracts are module-level declarations referenced by name in surface contracts: clauses (demands Codec, fulfils EventSubmitter). See Contracts for declaration syntax and referencing rules.
Navigation: interview.candidacy.candidate.email, reply_to?.author (optional), timezone ?? "UTC" (null coalescing). Collections: slots.count, slot in invitation.slots, interviewers.any(i => i.can_solo), for item in collection: item.status = cancelled, permissions + inherited (set union), old - new (set difference). Comparisons: status = pending, count >= 2, status in {confirmed, declined}, provider not in providers. Boolean logic: a and b, a or b, not a, a implies b.
use "github.com/allium-specs/google-oauth/abc123def" as oauth
Qualified names reference entities across specs: oauth/Session. Coordinates are immutable (git SHAs or content hashes). Local specs use relative paths: use "./candidacy.allium" as candidacy.
config {
invitation_expiry: Duration = 7.days
max_login_attempts: Integer = 5
extended_expiry: Duration = invitation_expiry * 2 -- expression-form default
sync_timeout: Duration = core/config.default_timeout -- config parameter reference
}
Rules reference config values as config.invitation_expiry. For default entity instances, use default.
default Role viewer = { name: "viewer", permissions: { "documents.read" } }
invariant NonNegativeBalance {
for account in Accounts:
account.balance >= 0
}
Expression-bearing invariants (invariant Name { expression }) assert properties over entity state. They are logical assertions, not runtime checks. Distinct from prose annotations (@invariant Name) in contracts, which use the @ sigil to mark content the checker does not evaluate. See Invariants.
entity Order {
status: pending | confirmed | shipped | delivered | cancelled
transitions status {
pending -> confirmed
confirmed -> shipped
shipped -> delivered
pending -> cancelled
confirmed -> cancelled
terminal: delivered, cancelled
}
}
entity Order {
status: pending | confirmed | shipped | delivered | cancelled
customer: Customer
total: Money
tracking_number: String when status = shipped | delivered
shipped_at: Timestamp when status = shipped | delivered
transitions status {
pending -> confirmed
confirmed -> shipped
shipped -> delivered
pending -> cancelled
confirmed -> cancelled
terminal: delivered, cancelled
}
}
deferred InterviewerMatching.suggest -- see: detailed/interviewer-matching.allium
open question "Admin ownership - should admins be assigned to specific roles?"
When the allium CLI is installed, a hook validates .allium files automatically after every write or edit. Fix any reported issues before presenting the result. If the CLI is not available, verify against the language reference.
/allium follows to drive a goal to convergence (entry detection, the tick, stop conditions, the ledger)