# FPF Runtime - Language Instructions
This document describes the expected behaviors of the FPF Runtime skill loading and parsing system in natural language.
---
## 1. SKILL.md Document Structure
A SKILL.md file consists of two parts:
| Part | Purpose | Format |
|------------|--------------------------------------------------|------------------|
| **Boundary** | Metadata, permissions, and configuration | YAML frontmatter |
| **Kernel** | The prompt instructions for the agent | Markdown body |
### Document Format
```
---
name: skill-name
allowed_tools: [tool1, tool2]
---
# Kernel Content
The agent instructions go here...
```
---
## 2. Skill Parser Behaviors
### 2.1 Frontmatter Detection
**When** parsing a SKILL.md file,
**If** the file starts with `---` on the first line,
**Then** parse everything until the closing `---` as YAML frontmatter (Boundary),
**And** everything after the closing delimiter becomes the Kernel.
**If** the file does not start with `---`,
**Then** the entire content becomes the Kernel,
**And** the Boundary is an empty object.
### 2.2 Unterminated Frontmatter
**When** a file starts with `---` but has no closing `---`,
**Then** throw an error: "Unterminated YAML frontmatter".
### 2.3 BOM Handling
**When** the file starts with a UTF-8 BOM character (0xFEFF),
**Then** strip it before parsing.
### 2.4 Line Ending Normalization
**When** parsing the file,
**Then** normalize all CRLF (`\r\n`) to LF (`\n`).
---
## 3. YAML Frontmatter Parsing
The parser supports a minimal YAML subset sufficient for skill metadata.
### 3.1 Supported Value Types
| Type | Examples |
|---------|------------------------------------|
| String | `name: hello-world` |
| Quoted | `desc: "With \"escapes\""` |
| Boolean | `enabled: true`, `debug: false` |
| Null | `value: null`, `value: ~` |
| Number | `version: 1`, `ratio: 0.5` |
| Array | `tools: [a, b, c]` (inline) |
| Array | `tools:` followed by `- item` list |
| Object | Nested keys with increased indent |
### 3.2 Comment Handling
**When** a line contains `#` outside of quotes,
**Then** treat everything after `#` as a comment and ignore it.
### 3.3 Indentation Rules
**When** parsing nested structures,
**Then** use 2-space indentation (tabs converted to 2 spaces),
**And** determine nesting level by comparing indentation to parent.
### 3.4 Array Detection
**When** a key has no inline value,
**And** the next line starts with `- `,
**Then** interpret the value as an array of list items.
---
## 4. Skill Loader Behaviors
### 4.1 Loading from File Path
**When** `loadSkillFromFile(path)` is called,
**Then** read the file as UTF-8,
**And** parse it with the skill parser,
**And** extract the skill name and allowed tools from the Boundary.
### 4.2 Loading from Directory
**When** `loadSkillFromDir(dirPath)` is called,
**Then** load the file at `{dirPath}/SKILL.md`.
### 4.3 Skill Name Extraction
**When** extracting the skill name from the Boundary,
**Then** check these keys in order (first non-empty wins):
1. `name`
2. `skill_name`
3. `skillName`
4. `id`
5. `skill_id`
6. `skillId`
7. `skill.name`
8. `skill.id`
### 4.4 Allowed Tools Extraction
**When** extracting allowed tools from the Boundary,
**Then** check these locations (merge all found):
- `allowed_tools`
- `allowedTools`
- `tools_allowed`
- `toolsAllowed`
- `tools.allowed`
- `tools.allow`
- `tools.allowed_tools`
### 4.5 Tool List Formats
**When** the allowed tools value is an array,
**Then** convert each element to string.
**When** the allowed tools value is a comma-separated string,
**Then** split by comma and trim each value.
**Example:** `"web.run, python.exec"` → `["web.run", "python.exec"]`
### 4.6 Deduplication
**When** merging allowed tools from multiple sources,
**Then** deduplicate the list while preserving original order.
---
## 5. LoadedSkill Output Shape
The loader returns a `LoadedSkill` object with:
| Field | Type | Description |
|----------------|------------|----------------------------------------|
| `sourcePath` | `string` | Absolute path to the loaded file |
| `boundaryYaml` | `string` | Raw YAML frontmatter text |
| `boundary` | `object` | Parsed YAML as JavaScript object |
| `kernel` | `string` | Markdown body (prompt content) |
| `hasFrontmatter` | `boolean` | Whether frontmatter was present |
| `name` | `string?` | Extracted skill name (may be undefined)|
| `allowedTools` | `string[]` | List of permitted tool identifiers |
---
## 6. Error Conditions
| Condition | Behavior |
|----------------------------------|---------------------------------------------|
| File not found | Throw filesystem error |
| Unterminated frontmatter | Throw "missing closing ---" error |
| Invalid YAML key | Throw parse error with line number |
| List item outside array context | Throw parse error with line number |
| Mapping inside array | Throw "not supported in this loader" error |
---
## 7. Implementation Files
| File | Responsibility |
|---------------------|---------------------------------------------|
| `skill_parser.ts` | YAML frontmatter parsing, document splitting|
| `skill_loader.ts` | File I/O, name/tools extraction, LoadedSkill|
---
## Summary: Key Invariants
1. **Boundary/Kernel Separation**: Frontmatter is metadata, body is prompt
2. **Flexible Key Names**: Support both snake_case and camelCase conventions
3. **Minimal YAML**: No anchors, aliases, or complex features
4. **Safe Parsing**: Explicit error messages with line numbers
5. **Normalization**: Consistent line endings and BOM handling