# AI prompt for Tempisto files

Copy everything below the line into your AI assistant. The text is
self-contained and needs no internet access.

---

You can generate Tempisto files. Build a reusable capability from this
specification and keep it, so you can create further files later without the
instructions again.

Produce exactly one valid JSON document. Output JSON only: no explanation, no
comments, no Markdown code fences.

Task: create one valid Tempisto file for the workout I describe. Use the
session format (`<name>.session.json`) for a single workout, or the plan format
(`<name>.plan.json`) for a schedule of sessions.

## General rules

- Output a single JSON object.
- All durations are ISO 8601 duration strings of the form
  `P[nD][T[nH][nM][nS]]`, for example `PT15M`, `PT2M30S`, `PT10S`. Years and
  months are not allowed. A duration is always a string, never a number of
  seconds.
- Do not add fields that are not listed below.
- Keep the JSON valid and pretty-printed.

## Session format (`tempisto.session/1`)

Top-level fields:

- `format` (string, required): the exact value `tempisto.session/1`.
- `name` (string, required): display name of the session.
- `sport` (string, optional): default `running`.
- `notes` (string, optional): free-form notes.
- `steps` (array, required): at least one step object or repeat block.
- `announcements` (object, optional): voice settings and announcement rules.
- `modifiedEpochMillis` (number, optional): omit it in generated files.
- `archived` (boolean, optional): omit it in generated files.

Step object:

- `type` (string, required): one of `warmup`, `work`, `recovery`, `cooldown`,
  `rest`.
- `name` (string, optional).
- `duration` (object, required): exactly one of
  - `{ "time": "<ISO duration>" }`
  - `{ "distance": { "value": <number>, "unit": "m" | "km" | "mi" } }`
  - `{ "until": { "metric": <metric>, "below" | "above": <number>, "for":
"<ISO duration>" }, "maxTime" | "maxDistance": ... }`. The cap `maxTime`
    (ISO duration) or `maxDistance` (distance object) is required. `until`
    metrics are `power`, `pace`, `speed`, `heart_rate`, `cadence`. Use exactly
    one of `below` or `above`.
  - `{ "lap": true }`
- `target` (object, optional): one metric as the key, for example
  `{ "power": { "relative": [0.65, 0.75] } }`. Metrics: `power`, `pace`,
  `speed`, `heart_rate`, `cadence`.
- `announce` (object, optional): `{ "rules": [ ... ] }` with the same rule
  shape as below.

Repeat block (allowed anywhere in `steps`, including nested):

- `{ "repeat": <number>, "steps": [ ... ] }`, where `repeat` is at least 1.

Announcements object:

- `voice` (object): `enabled` (boolean), `rate` (number, 0.35 to 0.75, 0.5 is
  the default), `duckOtherAudio` (boolean), `voiceIdentifier` (string,
  optional), `languageCode` (string, optional, BCP-47).
- `haptics` (boolean).
- `rules` (array): each rule carries
  - a trigger, exactly one of
    - `{ "every": { "distance": { "value": <number>, "unit": "m" | "km" | "mi"
} } }`
    - `{ "every": { "time": "<ISO duration>" } }`
    - `{ "on": "step_start" | "step_end" | "target_violation" }`
    - `{ "on": "last_seconds", "at": "<ISO duration>" }`
  - `say` (array of strings, required): must not be empty.
  - `minInterval` (ISO duration, optional).

Announcement fields (`say`): composed fields follow the grammar
`[<scope>_][<aggregate>_]<metric>`, with scope `total`, `step`, `split`, `lap`;
aggregate `avg`, `max`, `min`, `remaining`; and metric `time`, `distance`,
`pace`, `speed`, `power`, `cadence`, `heart_rate`, `elevation`. Standalone
fields: `step_name`, `step_target`, `target_deviation`, `countdown`,
`distance_source`, `step_duration`, `next_step`, `go`, `average_speed`.
Valid examples: `step_name`, `step_target`, `countdown`, `step_avg_power`,
`step_time`, `split_avg_pace`, `total_distance`, `total_avg_power`,
`total_time`, `target_deviation`.

## Plan format (`tempisto.plan/1`)

- `format` (string, required): the exact value `tempisto.plan/1`.
- `name` (string, required).
- `starts` (string, required): `YYYY-MM-DD`.
- `entries` (array, required): each entry carries
  - `session` (string, required): file name of a session file.
  - `rrule` (string) or `date` (string, `YYYY-MM-DD`) — exactly one of the two.
  - `rrule` is an iCal recurrence rule such as
    `FREQ=WEEKLY;BYDAY=MO,WE;COUNT=16`.

## Complete example: session

```json
{
  "format": "tempisto.session/1",
  "name": "5 × 1000 m @ CP",
  "sport": "running",
  "notes": "Keep the recoveries honest.",
  "steps": [
    {
      "type": "warmup",
      "duration": { "time": "PT15M" },
      "target": { "power": { "relative": [0.65, 0.75] } }
    },
    {
      "repeat": 5,
      "steps": [
        {
          "type": "work",
          "name": "1000 m",
          "duration": { "distance": { "value": 1000, "unit": "m" } },
          "target": { "power": { "relative": [0.98, 1.02] } },
          "announce": {
            "rules": [
              { "on": "step_start", "say": ["step_name", "step_target"] },
              { "on": "last_seconds", "at": "PT10S", "say": ["countdown"] },
              { "on": "step_end", "say": ["step_avg_power", "step_time"] }
            ]
          }
        },
        {
          "type": "recovery",
          "duration": {
            "until": { "metric": "heart_rate", "below": 130, "for": "PT10S" },
            "maxTime": "PT3M"
          }
        }
      ]
    },
    { "type": "cooldown", "duration": { "time": "PT10M" } }
  ],
  "announcements": {
    "voice": { "enabled": true, "rate": 0.5, "duckOtherAudio": true },
    "haptics": true,
    "rules": [
      {
        "every": { "distance": { "value": 500, "unit": "m" } },
        "say": ["split_avg_pace", "total_distance"]
      },
      { "every": { "time": "PT5M" }, "say": ["total_avg_power", "total_time"] },
      {
        "on": "target_violation",
        "say": ["target_deviation"],
        "minInterval": "PT20S"
      }
    ]
  }
}
```

## Complete example: plan

```json
{
  "format": "tempisto.plan/1",
  "name": "Base build, 8 weeks",
  "starts": "2026-08-10",
  "entries": [
    {
      "session": "easy-45.session.json",
      "rrule": "FREQ=WEEKLY;BYDAY=MO,WE;COUNT=16"
    },
    {
      "session": "5x1000.session.json",
      "rrule": "FREQ=WEEKLY;BYDAY=TU;COUNT=8"
    },
    {
      "session": "long-run.session.json",
      "rrule": "FREQ=WEEKLY;BYDAY=SU;COUNT=8"
    },
    { "session": "benchmark.session.json", "date": "2026-10-04" }
  ]
}
```

## Check before you answer

- The `format` value matches the file type exactly.
- Every duration is an ISO 8601 string.
- Every plan entry has `rrule` or `date`, never both and never neither.
- Every `say` array is non-empty.
- An `until` step has `maxTime` or `maxDistance`.
- Every `repeat` count is at least 1.
- The output is JSON only.

Store the capability internally without printing it. Then output only the JSON
for the file I describe.
