allium

allium

热门

给你的AI代理比提示更有用的东西。通过清晰性实现速度。

437Star
0Fork
更新于 2026/7/26
SKILL.md
readonly只读
name
allium
description

给你的AI代理比提示更有用的东西。通过清晰性实现速度。

version
3

Allium

Allium是一种用于在领域层面捕获软件行为的正式语言。它介于非正式的功能描述和实现之间,提供了一种精确的方式来指定软件做什么,而不规定如何构建它。

这个名字来源于包含洋葱和青葱的植物科,延续了由Cucumber和Gherkin建立的行为规范工具的传统。

关键原则:

  • 描述可观察的行为,而非实现
  • 捕获在行为层面重要的领域逻辑
  • 生成集成测试和端到端测试(而非单元测试)
  • 在实现之前将歧义暴露出来
  • 与实现无关:同一个规范可以用任何语言实现

Allium不指定编程语言或框架选择、数据库模式或存储机制、API设计或UI布局,以及内部算法(除非它们是领域层面的关注点)。

路由表

任务 工具 何时使用
编写或阅读.allium文件 本技能 你需要语言语法和结构
通过对话构建规范 elicit技能 用户描述他们想要构建的功能或行为
从现有代码提取规范 distill技能 用户有实现代码并希望从中获取规范
修改现有规范 tend技能 用户希望对.allium文件进行有针对性的更改
检查规范与代码的一致性 weed技能 用户希望发现或修复规范与实现之间的差异
从规范生成测试 propagate技能 用户希望从规范生成测试、PBT属性或状态机测试
驱动整个循环直至收敛 本技能(参见驱动循环 用户希望端到端构建或协调一个功能——/allium <goal>自动运行收集→行动→验证→重复循环,直到规范、测试和代码一致

响应/allium(循环优先)

/allium是入口点。偏向自主路径——整个循环的价值正是偶尔使用单一技能所缺失的:

  • 明确单一任务 → 直接路由到该技能(根据路由表);不要让用户浏览菜单。

  • 目标或功能(例如“添加礼品卡”、“让密码重置工作”)→ 自行驱动整个循环端到端,而不是运行一个阶段。遵循驱动循环

  • 空白或模糊 → 以循环优先引导用户:提供驱动循环作为默认选项,然后列出各个技能作为控制路径,每个技能附带一行提示,并根据项目状态建议具体的起点(现有的.allium规范?有代码但没有规范?需要协调的漂移?)。例如:

    告诉我一个目标,我会驱动整个循环——规范→测试→代码,直到它们一致。或者你自己运行一步:elicit(从意图生成规范),distill(从现有代码生成规范),propagate(从规范生成测试),tend(编辑规范),weed(修复规范↔代码漂移)。你已经有代码但还没有.allium,所以我建议从distill开始——或者直接给我目标,我会端到端处理。

以循环为主导;将各个技能保留一步之遥,供希望手动控制的用户使用。一旦单个技能完成,主动建议下一步,而不是等待被询问。

Allium循环(推荐顺序)

这些技能不是一次性命令;它们组合成一个自主风格的循环——收集上下文→采取行动→验证→重复——驱动三个工件达成一致:规范(意图)、测试(契约)和代码(实现)。使用/elicit/distill收集上下文(规范是持久的上下文);使用/propagate然后实现采取行动(在规范优先的工作中,先确认新测试失败——一个在实现之前就已经通过的测试要么已经被覆盖,要么是空洞的);通过运行测试、然后/weed、然后CLI结构检查来验证;重复直到收敛。验证是最重要的阶段,规范加测试加weed信号使循环值得信赖。在调用一个技能后,主动建议下一步,而不是等待被询问。要一次性运行整个循环直至收敛,只需给/allium一个目标——它会为你驱动循环,遵循驱动循环

两个入口点,一个收敛循环:

  • 规范优先(正向,从意图出发): /elicit/propagate → 实现 → /weed;当需求变化时使用/tend然后重新/propagate
  • 代码优先(反向,从现有代码出发): /distill → 审查预期行为与意外行为 → /propagate → 对代码运行测试 → /weed进行协调 → 按区域重复。

当测试通过、/weed报告无差异且没有未解决的问题时,工作“完成”(对于代码优先,还需要一次新的/distill没有发现新内容)。循环中的两条固定规则:永远不要削弱生成的测试使其通过(而是修复规范并重新传播),并将真正的歧义升级给人类而不是猜测。

实现本身是普通的编码——Allium生成规范和测试,而不是应用程序代码。有关完整演练、图表、退出条件和实现提示,请参见推荐循环参考。

快速语法总结

实体

entity Candidacy {
    -- 字段
    candidate: Candidate
    role: Role
    status: pending | active | completed | cancelled   -- 内联枚举
    retry_count: Integer

    -- 关系
    invitation: Invitation with candidacy = this         -- 一对一
    slots: InterviewSlot with candidacy = this           -- 一对多

    -- 投影
    confirmed_slots: slots where status = confirmed
    pending_slots: slots where status = pending

    -- 派生
    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 }

和类型

基础实体声明一个判别字段,其大写值命名变体。变体使用variant关键字。

entity Node {
    path: Path
    kind: Branch | Leaf              -- 判别字段
}

variant Branch : Node {
    children: List<Node?>
}

variant Leaf : Node {
    data: List<Integer>
    log: List<Integer>
}

小写管道值是枚举字面量(status: pending | active)。大写值是变体引用(kind: Branch | Leaf)。类型守卫(requires:if分支)缩小到某个变体并解锁其字段。

模块given

声明模块规则操作的实体实例。所有规则继承这些绑定。并非每个模块都需要:由领域实体上的触发器作用域的规则从触发器获取其实体。given用于规则操作在模块作用域内存在一次的共享实例的规范。

given {
    pipeline: HiringPipeline
    calendar: InterviewCalendar
}

导入的模块实例通过限定名称访问(scheduling/calendar),不会出现在本地的given块中。与表面context不同,后者为边界契约绑定参数化作用域。

规则

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
        -- 非规范性实现建议。
}

触发器类型

  • 外部刺激when: CandidateSelectsSlot(invitation, slot) — 来自系统外部的动作
  • 状态转换when: interview: Interview.status transitions_to scheduled — 实体改变状态(仅转换,非创建)
  • 状态变为when: interview: Interview.status becomes scheduled — 实体具有此值,无论是通过创建还是转换
  • 时间性when: invitation: Invitation.expires_at <= now — 基于时间的条件(始终添加requires守卫以防止重复触发)
  • 派生条件when: interview: Interview.all_feedback_in — 派生值变为真
  • 实体创建when: batch: DigestBatch.created — 当新实体创建时触发
  • 链式when: AllConfirmationsResolved(candidacy) — 订阅来自另一个规则的ensures子句的触发器发射

所有实体作用域的触发器使用显式的var: Type绑定。在不需要名称的地方使用_作为丢弃绑定:when: _: Invitation.expires_at <= nowwhen: SomeEvent(_, slot)

规则级迭代

for子句对集合中的每个元素应用一次规则体:

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模式

Ensures子句有四种结果形式:

  • 状态变化entity.field = value
  • 实体创建Entity.created(...) — 唯一的规范创建动词
  • 触发器发射TriggerName(params) — 发射事件供其他规则链式使用
  • 实体移除not exists entity — 断言实体不再存在

这些形式与for迭代(for x in collection: ...)、if/else条件语句和let绑定组合使用。

实体创建仅使用.created()。领域含义存在于实体名称和规则名称中,而非创建动词。

在状态变化赋值中,右侧表达式引用规则前的字段值。Ensures块内的条件(if守卫、创建参数、触发器发射参数)引用结果状态。

表面

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
}

表面定义边界处的契约。facing子句命名外部方,context作用域实体。其余子句使用单一词汇,无论边界是面向用户还是代码对代码:exposes(可见数据,支持对集合的for迭代),provides(可用操作,带有可选的when守卫),contracts:(引用模块级contract声明,带有demands/fulfils方向标记),@guarantee(关于边界的命名散文断言),@guidance(非规范性建议),related(从此表面可到达的关联表面),timeout(引用在表面上下文内适用的时间规则)。

facing子句接受参与者类型(带有相应的actor声明和identified_by映射)或直接接受实体类型。当边界具有特定身份要求时使用参与者声明;当任何实例可以交互时使用实体类型(例如,facing visitor: User)。对于外部方是代码的集成表面,声明一个参与者类型,带有最小的identified_by表达式。在identified_by表达式中引用within的参与者必须声明预期的上下文类型:within: Workspace

表面到实现的契约

exposes块是字段级契约:实现恰好返回这些字段,消费者恰好使用这些字段。不要添加未列出的字段。不要省略列出的字段。

契约

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:子句中引用(demands Codecfulfils EventSubmitter)。有关声明语法和引用规则,请参见契约

表达式

导航:interview.candidacy.candidate.emailreply_to?.author(可选),timezone ?? "UTC"(空值合并)。集合:slots.countslot in invitation.slotsinterviewers.any(i => i.can_solo)for item in collection: item.status = cancelledpermissions + inherited(集合并集),old - new(集合差集)。比较:status = pendingcount >= 2status in {confirmed, declined}provider not in providers。布尔逻辑:a and ba or bnot aa implies b

模块化规范

use "github.com/allium-specs/google-oauth/abc123def" as oauth

限定名称跨规范引用实体:oauth/Session。坐标是不可变的(git SHA或内容哈希)。本地规范使用相对路径:use "./candidacy.allium" as candidacy

配置

config {
    invitation_expiry: Duration = 7.days
    max_login_attempts: Integer = 5
    extended_expiry: Duration = invitation_expiry * 2              -- 表达式形式默认值
    sync_timeout: Duration = core/config.default_timeout           -- 配置参数引用
}

规则将配置值引用为config.invitation_expiry。对于默认实体实例,使用default

默认值

default Role viewer = { name: "viewer", permissions: { "documents.read" } }

不变量

invariant NonNegativeBalance {
    for account in Accounts:
        account.balance >= 0
}

带表达式的不变量(invariant Name { expression })断言实体状态的属性。它们是逻辑断言,而非运行时检查。与契约中的散文注释(@invariant Name)不同,后者使用@符号标记检查器不评估的内容。请参见不变量

转换图(v3)

entity Order {
    status: pending | confirmed | shipped | delivered | cancelled

    transitions status {
        pending -> confirmed
        confirmed -> shipped
        shipped -> delivered
        pending -> cancelled
        confirmed -> cancelled
        terminal: delivered, cancelled
    }
}

状态依赖字段存在性(v3)

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    -- 参见:detailed/interviewer-matching.allium

未解决问题

open question "管理员所有权——是否应将管理员分配到特定角色?"

验证

当安装了allium CLI时,钩子会在每次写入或编辑后自动验证.allium文件。在呈现结果之前修复任何报告的问题。如果CLI不可用,请对照语言参考进行验证。

参考

  • 语言参考 — 实体、规则、表达式、表面、契约、不变量和验证的完整语法
  • 测试生成 — 从规范生成测试
  • 推荐循环 — 收集上下文→采取行动→验证→重复循环,包含规范优先和代码优先的演练
  • 驱动循环/allium遵循的将目标驱动到收敛的过程(入口检测、滴答、停止条件、账本)
  • 模式 — 9个工作模式:认证、RBAC、邀请、软删除、通知、使用限制、评论、库规范集成、框架集成契约