Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 26 additions & 194 deletions .claude/skills/allium/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
---
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"]
description: "Writes, reads, and validates .allium behavioural specification files. Use when the user asks to create an allium spec, edit a .allium file, write a behavioural specification, or understand allium syntax."
metadata:
version: 3
auto_trigger_file_patterns: "**/*.allium"
auto_trigger_keywords: "allium, allium spec, allium specification, .allium file"
---

# Allium

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:

- Describes observable behaviour, not implementation
- Captures domain logic that matters at the behavioural level
- Generates integration and end-to-end tests (not unit tests)
- Forces ambiguities into the open before implementation
- Implementation-agnostic: the same spec could be implemented in any language

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).
Allium is a formal language for capturing software behaviour at the domain level. It describes observable behaviour, not implementation, and generates integration and end-to-end tests.

## Routing table

Expand Down Expand Up @@ -74,38 +62,7 @@ value TimeRange { start: Timestamp, end: Timestamp, duration: end - start }

### Sum type

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.

### Module given

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.
Capitalised pipe values are variant references (`kind: Branch | Leaf`), lowercase are enum literals (`status: pending | active`). See [language reference](./references/language-reference.md) for full variant syntax.

### Rule

Expand All @@ -125,43 +82,20 @@ rule InvitationExpires {

### Trigger types

- **External stimulus**: `when: CandidateSelectsSlot(invitation, slot)` — action from outside the system
- **State transition**: `when: interview: Interview.status transitions_to scheduled` — entity changed state (transition only, not creation)
- **State becomes**: `when: interview: Interview.status becomes scheduled` — entity has this value, whether by creation or transition
- **Temporal**: `when: invitation: Invitation.expires_at <= now` — time-based condition (always add a `requires` guard against re-firing)
- **Derived condition**: `when: interview: Interview.all_feedback_in` — derived value becomes true
- **Entity creation**: `when: batch: DigestBatch.created` — fires when a new entity is created
- **Chained**: `when: AllConfirmationsResolved(candidacy)` — subscribes to a trigger emission from another rule's ensures clause

All 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)`.

### Rule-level iteration

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, ...)
}
```
- **External stimulus**: `when: CandidateSelectsSlot(invitation, slot)`
- **State transition**: `when: interview: Interview.status transitions_to scheduled` (transition only, not creation)
- **State becomes**: `when: interview: Interview.status becomes scheduled` (creation or transition)
- **Temporal**: `when: invitation: Invitation.expires_at <= now` (always add `requires` guard)
- **Derived condition**: `when: interview: Interview.all_feedback_in`
- **Entity creation**: `when: batch: DigestBatch.created`
- **Chained**: `when: AllConfirmationsResolved(candidacy)` (subscribes to trigger emission)

### Ensures patterns

Ensures clauses have four outcome forms:

- **State changes**: `entity.field = value`
- **Entity creation**: `Entity.created(...)` — the single canonical creation verb
- **Trigger emission**: `TriggerName(params)` — emits an event for other rules to chain from
- **Entity removal**: `not exists entity` — asserts the entity no longer exists

These 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.
- **Entity creation**: `Entity.created(...)` (the only creation verb)
- **Trigger emission**: `TriggerName(params)`
- **Entity removal**: `not exists entity`

### Surface

Expand All @@ -185,120 +119,18 @@ surface InterviewerDashboard {
}
```

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`.

### Surface-to-implementation contract

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

```allium
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.
}
```
Surfaces define boundary contracts: `facing` names the external party, `context` scopes the entity, `exposes` lists visible data, `provides` lists available operations, `contracts:` references module-level contract declarations.

Contracts are module-level declarations referenced by name in surface `contracts:` clauses (`demands Codec`, `fulfils EventSubmitter`). See [Contracts](./references/language-reference.md#contracts) for declaration syntax and referencing rules.
For full syntax of contracts, expressions, config, defaults, invariants, transition graphs, state-dependent fields, deferred specs, and open questions, see the [language reference](./references/language-reference.md).

### Expressions
## Authoring workflow

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`.

### Modular specs

```
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

```
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`.

### Defaults

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

### Invariant

```allium
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](./references/language-reference.md#invariants).

### Transition graph (v3)

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

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

### State-dependent field presence (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 specs

```
deferred InterviewerMatching.suggest -- see: detailed/interviewer-matching.allium
```

### Open questions

```
open question "Admin ownership - should admins be assigned to specific roles?"
```
1. Define entities with fields, relationships, and derived values
2. Write rules with triggers, guards, and ensures clauses
3. Add surfaces for boundary contracts (UI, API, integration points)
4. Add config, invariants, and open questions as needed
5. Validate with the `allium` CLI (if installed) or against the language reference
6. Fix any reported issues and re-validate

## Verification

Expand Down
39 changes: 4 additions & 35 deletions .claude/skills/create-core-check/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
---
name: create-core-check
description: Create a new Go core check that collects metrics and sends them to Datadog
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion
argument-hint: "[check-name]"
description: "Scaffolds a new Go core check for the Datadog Agent that collects metrics, service checks, or events. Use when the user asks to create a new agent check, add a custom core check, scaffold a Go metric collector, or build a new Datadog Agent integration."
metadata:
allowed-tools: "Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion"
argument-hint: "[check-name]"
Comment on lines +4 to +6
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep tool and argument fields at frontmatter root

allowed-tools and argument-hint were moved into a nested metadata map, but Claude Code only reads them as top-level frontmatter keys. As a result this skill loses tool pre-approval and slash-command argument hints, which degrades the guided workflow and can block execution with extra permission prompts; the same nesting pattern appears in other edited skills in this commit.

Useful? React with 👍 / 👎.

---

Create a new Go-based core check for the Datadog Agent. Core checks collect metrics, service checks, or events and send them to Datadog at regular intervals.

## Instructions

### Step 1: Gather information from the user
Expand Down Expand Up @@ -134,36 +133,6 @@ Follow the test patterns from the reference file read in Step 2. The standard te

4. Report the results to the user.

## Sender Methods Reference

The sender (`c.GetSender()`) provides these methods for submitting data:

| Method | Description |
|---|---|
| `Gauge(metric, value, hostname, tags)` | Submit a gauge metric |
| `Rate(metric, value, hostname, tags)` | Submit a rate metric |
| `Count(metric, value, hostname, tags)` | Submit a count metric |
| `MonotonicCount(metric, value, hostname, tags)` | Submit a monotonic count |
| `Histogram(metric, value, hostname, tags)` | Submit a histogram metric |
| `Distribution(metric, value, hostname, tags)` | Submit a distribution metric |
| `ServiceCheck(name, status, hostname, tags, message)` | Submit a service check |
| `Event(event)` | Submit an event |
| `Commit()` | Flush all submitted data — **must be called at end of Run()** |

- Pass `""` for hostname to use the agent's default hostname.
- Pass `nil` for tags if no tags are needed.
- Service check statuses: `servicecheck.ServiceCheckOK`, `ServiceCheckWarning`, `ServiceCheckCritical`, `ServiceCheckUnknown` (from `pkg/metrics/servicecheck`).

## Important Notes

- `CheckBase` provides default implementations for most `Check` interface methods. You only need to override `Run()` and optionally `Configure()`, `Stop()`, and `Interval()`.
- `CommonConfigure` handles standard configuration: collection interval (`min_collection_interval`), custom tags, service tag, etc.
- `FinalizeCheckServiceTag()` must be called after `CommonConfigure` to apply the service tag to the sender.
- Always call `sender.Commit()` at the end of `Run()` to flush data.
- For multi-instance checks, `BuildID()` must be called **before** `CommonConfigure()`.
- The `option.None[func() check.Check]()` pattern is used for platform stubs — the loader skips checks with no factory.
- `integration.FakeConfigHash` is the constant to use in tests for the config digest parameter.

## Usage

- `/create-core-check` — Interactive: prompts for all details
Expand Down
20 changes: 5 additions & 15 deletions .claude/skills/create-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
---
name: create-pr
description: Create a pull request for the current branch with proper labels and description
disable-model-invocation: true
allowed-tools: Bash, Read, Glob
argument-hint: "[--real] [additional labels...]"
description: "Creates a pull request for the current branch with proper labels, conventional commit title, and Datadog PR template. Use when the user asks to open a PR, submit code for review, create a pull request, or push changes for merging."
metadata:
disable-model-invocation: true
Comment on lines +4 to +5
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move disable-model-invocation back to top-level frontmatter

Placing disable-model-invocation under metadata prevents Claude Code from applying it, because this flag is only recognized as a top-level frontmatter field. In this skill, that means /create-pr is no longer protected as a manual-only workflow and can be auto-invoked by the model, which is risky for a command that can create and submit real pull requests.

Useful? React with 👍 / 👎.

allowed-tools: "Bash, Read, Glob"
argument-hint: "[--real] [additional labels...]"
---

Create a pull request for the current branch following the Datadog Agent contributing guidelines.
Expand Down Expand Up @@ -39,17 +40,6 @@ Create a pull request for the current branch following the Datadog Agent contrib
- **Describe how you validated your changes**: How you validated the change (tests added/run, benchmarks, manual testing). Only needed when testing included work not covered by test suites.
- **Additional Notes**: Any extra context, links to predecessor PRs if part of a chain, notes that make code understanding easier. **Only include this section if there is genuinely useful context to add** — omit it entirely rather than filling it with filler.

## PR Description Guidelines (from CONTRIBUTING.md)

The PR description should incorporate everything reviewers and future maintainers need:
- A description of what is changed
- A reason why the change is made (pointing to an issue is a good reason)
- When testing had to include work not covered by test suites, a description of how you validated your change
- Any relevant benchmarks
- Additional notes that make code understanding easier
- If part of a chain of PRs, point to the predecessors
- If there are drawbacks or tradeoffs, raise them

## Example

```bash
Expand Down
31 changes: 27 additions & 4 deletions .claude/skills/create-status-provider/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
---
name: create-status-provider
description: Add a new section to the agent status output (agent status command)
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion
argument-hint: "[provider-name]"
description: "Adds a new status provider section to the Datadog Agent status command output in JSON, text, and HTML formats. Use when the user asks to extend the agent status output, add a status section, create a status provider, or customize what the agent status command displays."
metadata:
allowed-tools: "Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion"
argument-hint: "[provider-name]"
---

Add a new status provider to the Datadog Agent. Status providers contribute sections to the `agent status` output in JSON, plain text, and HTML formats.
Expand Down Expand Up @@ -32,7 +33,29 @@ Before writing any code, read the appropriate reference files to follow existing

### Step 3: Implement the provider

Create the provider implementation following the reference. Also create the `status_templates/<name>.tmpl` and `status_templates/<name>HTML.tmpl` files following the templates in the reference's `status_templates/` directory.
Create the provider implementation following the reference. Minimal provider skeleton:

```go
//go:embed status_templates
var templatesFS embed.FS

type myProvider struct{}

func (p myProvider) Name() string { return "My Feature" }
func (p myProvider) Section() string { return "my_section" }
func (p myProvider) JSON(_ bool, stats map[string]interface{}) error {
stats["myFeatureStats"] = collectStats()
return nil
}
func (p myProvider) Text(_ bool, buffer io.Writer) error {
return status.RenderText(templatesFS, "myfeature.tmpl", buffer, collectStats())
}
func (p myProvider) HTML(_ bool, buffer io.Writer) error {
return status.RenderHTML(templatesFS, "myfeatureHTML.tmpl", buffer, collectStats())
}
```

Also create `status_templates/<name>.tmpl` and `status_templates/<name>HTML.tmpl` following the templates in the reference's `status_templates/` directory.

To make a provider conditional, return `nil` from the constructor when the feature is disabled.

Expand Down
Loading
Loading