Guide for customizing the Jinja2 templates that intake uses to generate spec files and exports.


How it works

intake uses 17 Jinja2 templates to generate all its output files. The template system has two priority levels:

  1. User templates (in .intake/templates/ of the project) — high priority
  2. Built-in templates (included in the intake package) — fallback

If you place a file with the same name as a built-in template in .intake/templates/, your version is automatically used instead of the built-in. This allows you to customize the format of any generated file without modifying intake.

my-project/
├── .intake/
│   └── templates/               # Your overrides
│       ├── requirements.md.j2   # Overrides the requirements template
│       └── verify_sh.j2         # Overrides the verify.sh template
└── .intake.yaml

Configuration

# .intake.yaml
templates:
  user_dir: ".intake/templates"   # User templates directory (default)
  warn_on_override: true          # Log when a built-in template is overridden (default)
FieldDefaultDescription
user_dir.intake/templatesRelative path to the project where to look for user templates
warn_on_overridetrueEmit template_override log when an override is detected

Available templates

Spec templates (generation)

Used by intake init and intake add to generate the 6 spec files:

TemplateGenerated fileDescription
requirements.md.j2requirements.mdFunctional and non-functional requirements
design.md.j2design.mdTechnical design: components, files, decisions
tasks.md.j2tasks.mdImplementation tasks with summary table
acceptance.yaml.j2acceptance.yamlAcceptance checks in YAML
context.md.j2context.mdProject context for AI agents
sources.md.j2sources.mdRequirement-to-source traceability

Export templates

Used by intake export -f <format>:

TemplateExporterGenerated file
claude_md.j2claude-code## intake Spec section in CLAUDE.md
claude_task.md.j2claude-codeIndividual TASK-NNN.md files
verify_sh.j2claude-code, genericExecutable verify.sh
cursor_rules.mdc.j2cursor.cursor/rules/intake-spec.mdc
kiro_requirements.md.j2kirorequirements.md Kiro format
kiro_design.md.j2kirodesign.md Kiro format
kiro_tasks.md.j2kirotasks.md Kiro format
copilot_instructions.md.j2copilot.github/copilot-instructions.md
feedback.md.j2feedbackFeedback report

CI templates

Used by intake export-ci:

TemplatePlatformGenerated file
gitlab_ci.yml.j2GitLab.gitlab-ci.yml
github_actions.yml.j2GitHub.github/workflows/intake-verify.yml

Available variables per template

requirements.md.j2

VariableTypeDescription
functional_requirementslist[dict]Functional requirements. Each has: id, title, description, priority, source, acceptance_criteria (list)
non_functional_requirementslist[dict]Non-functional requirements (same structure)
conflictslist[dict]Detected conflicts. Each: id, description, source_a, source_b, severity, recommendation
open_questionslist[dict]Open questions. Each: id, question, context, source, recommendation

design.md.j2

VariableTypeDescription
componentslist[str]Architecture component names
files_to_createlist[dict]Files to create. Each: path, description
files_to_modifylist[dict]Files to modify. Each: path, description
tech_decisionslist[dict]Technical decisions. Each: decision, justification, requirement
dependencieslist[str]Project dependencies

tasks.md.j2

VariableTypeDescription
taskslist[dict]Implementation tasks. Each: id, title, description, status, estimated_minutes, dependencies (list), files (list), checks (list)

acceptance.yaml.j2

VariableTypeDescription
checkslist[dict]Acceptance checks. Each: id, name, type, required, tags (list), command (if type=command), paths (if type=files_exist), glob and patterns (if type=pattern_*)

context.md.j2

VariableTypeDescription
project_namestrProject name
languagestrSpec language
stacklist[str]Project tech stack
conventionsdict[str, str]Project conventions
functional_countintNumber of functional requirements
non_functional_countintNumber of non-functional requirements
question_countintOpen questions
conflict_countintDetected conflicts
risk_countintIdentified risks
riskslist[dict]Risks. Each: id, category, probability, impact, description
component_countintArchitecture components
files_to_create_countintFiles to create
files_to_modify_countintFiles to modify
task_countintTotal tasks
check_countintTotal checks

sources.md.j2

VariableTypeDescription
sourceslist[dict]Sources used. Each: source, format, word_count
all_requirementslist[dict]All requirements with id, title, source
conflictslist[dict]Conflicts with sources

claude_md.j2

VariableTypeDescription
spec_namestrSpec name
context_summarystrProject context summary
requirements_countintNumber of requirements
design_summarystrDesign summary
taskslist[dict]Tasks with id, title, status
acceptance_countintNumber of checks
spec_fileslist[str]Spec file names

claude_task.md.j2

VariableTypeDescription
taskdictIndividual task with id, title, status, description, checks (list)
context_summarystrContext summary

verify_sh.j2

VariableTypeDescription
spec_namestrSpec name
checkslist[dict]Command-type checks. Each: name, command

cursor_rules.mdc.j2

VariableTypeDescription
spec_namestrSpec name
context_summarystrContext summary
requirements_countintNumber of requirements
requirements_summarystrRequirements summary
design_summarystrDesign summary
taskslist[dict]Tasks with id, title, status, description
acceptance_checkslist[dict]Checks with name, type, command, pattern

kiro_requirements.md.j2

VariableTypeDescription
spec_namestrSpec name
requirementslist[dict]Requirements with id, title, description, acceptance_criteria (list)

kiro_design.md.j2

VariableTypeDescription
spec_namestrSpec name
design_contentstrDesign content

kiro_tasks.md.j2

VariableTypeDescription
spec_namestrSpec name
taskslist[dict]Tasks with id, title, status, description, checks (list)

copilot_instructions.md.j2

VariableTypeDescription
spec_namestrSpec name
context_summarystrContext summary
requirements_countintNumber of requirements
requirements_summarystrRequirements summary
design_summarystrDesign summary
taskslist[dict]Tasks with id, title, status, description
acceptance_checkslist[dict]Checks with name, command, pattern

feedback.md.j2

VariableTypeDescription
resultdictAnalysis result. Has: failures (list), estimated_effort, summary
result.failures[].check_namestrFailed check name
result.failures[].severitystrSeverity
result.failures[].categorystrCategory
result.failures[].root_causestrRoot cause
result.failures[].suggestionstrFix suggestion
result.failures[].affected_taskslist[str]Affected tasks
result.failures[].spec_amendmentdictSuggested amendment: target_file, section, action, content
agent_formatstrAgent format (claude-code, cursor, etc.)

gitlab_ci.yml.j2 / github_actions.yml.j2

VariableTypeDescription
spec_dirstrPath to the spec directory

Jinja2 syntax

Templates use Jinja2 with these options enabled:

  • trim_blocks: the first newline after a {% %} block is removed
  • lstrip_blocks: whitespace at the beginning of a line before {% %} is removed
  • keep_trailing_newline: the final newline of the file is preserved

Quick reference

{# Comment #}

{# Variable #}
{{ variable }}

{# Loop #}
{% for item in items %}
{{ item.name }}
{% endfor %}

{# Conditional #}
{% if condition %}
content
{% endif %}

{# Filter: default value #}
{{ variable | default("fallback") }}

{# Filter: list join #}
{{ items | join(", ") }}

{# Filter: length #}
{{ items | length }}

{# Filter: lowercase #}
{{ text | lower }}

Examples

Customize the requirements format

To add an “Owner” field to each requirement:

mkdir -p .intake/templates

Create .intake/templates/requirements.md.j2:

# Project Requirements

{% for req in functional_requirements %}
## {{ req.id }}: {{ req.title }}

{{ req.description }}

| Field | Value |
|-------|-------|
| Priority | {{ req.priority }} |
| Source | {{ req.source }} |
| Owner | _(to be assigned)_ |

**Acceptance criteria:**
{% for ac in req.acceptance_criteria %}
- [ ] {{ ac }}
{% endfor %}

{% endfor %}

Customize verify.sh for a specific environment

To add environment setup before checks:

Create .intake/templates/verify_sh.j2:

#!/usr/bin/env bash
# Verification script — {{ spec_name }}
set -euo pipefail

PROJECT_DIR="${1:-.}"
PASSED=0
FAILED=0
TOTAL=0

# Environment setup (customized)
source "${PROJECT_DIR}/.env.test" 2>/dev/null || true
export DATABASE_URL="${DATABASE_URL:-sqlite:///test.db}"

check() {
    local name="$1"
    local cmd="$2"
    TOTAL=$((TOTAL + 1))
    echo -n "  [$TOTAL] $name ... "
    if (cd "$PROJECT_DIR" && eval "$cmd") > /dev/null 2>&1; then
        echo "PASS"
        PASSED=$((PASSED + 1))
    else
        echo "FAIL"
        FAILED=$((FAILED + 1))
    fi
}

echo "=== Verification: {{ spec_name }} ==="
echo ""

{% for check in checks %}
check '{{ check.name }}' '{{ check.command }}'
{% endfor %}

echo ""
echo "=== Results: $PASSED passed, $FAILED failed (of $TOTAL) ==="

if [ "$FAILED" -gt 0 ]; then
    exit 1
fi
exit 0

Customize the generated CI configuration

To add caching and notifications to GitHub Actions:

Create .intake/templates/github_actions.yml.j2:

name: intake-verify

on:
  pull_request:
  push:
    branches: [main, develop]

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: {% raw %}${{ runner.os }}{% endraw %}-pip-intake
      - run: pip install intake-ai-cli --quiet
      - run: intake validate {{ spec_dir }} --strict
      - run: intake verify {{ spec_dir }} -p . --format junit -o report.xml
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: intake-report
          path: report.xml

Add corporate branding to specs

To make all specs have a corporate header, override context.md.j2:

# Project Context

> Generated by intake for ACME Corp. Confidential.

| Field | Value |
|-------|-------|
| Project | {{ project_name }} |
| Language | {{ language }} |
{% if stack %}
| Stack | {{ stack | join(", ") }} |
{% endif %}
| Classification | Internal Use Only |

{% if conventions %}
## Conventions

{% for key, value in conventions.items() %}
- **{{ key }}:** {{ value }}
{% endfor %}
{% endif %}

## Metrics

| Category | Count |
|----------|-------|
| Functional requirements | {{ functional_count }} |
| Non-functional requirements | {{ non_functional_count }} |
| Open questions | {{ question_count }} |
| Risks | {{ risk_count }} |
| Tasks | {{ task_count }} |
| Checks | {{ check_count }} |

{% if risks %}
## Key Risks

{% for risk in risks %}
- **{{ risk.id }}** [{{ risk.category }}] P={{ risk.probability }} I={{ risk.impact }}: {{ risk.description }}
{% endfor %}
{% endif %}

List active templates

The TemplateLoader can list all available templates and their origin:

from intake.templates.loader import TemplateLoader

loader = TemplateLoader(project_dir=".")
for name, source in loader.list_templates().items():
    print(f"  {name}: {source}")

Possible values for source:

ValueMeaning
builtinTemplate included in intake
userUser template (does not override a built-in)
user (override)User template that overrides a built-in

Best practices

  1. Start from the built-in. Copy the original template from src/intake/templates/ before modifying it. This way you don’t lose variables or structure.

  2. Test your changes. After creating an override, run intake init or intake export and review the output.

  3. Version your templates. Commit .intake/templates/ to git so the entire team uses the same templates.

  4. Maintain compatibility. If you update intake, check whether the built-in templates changed and update your overrides if necessary.

  5. Use warn_on_override: true. The log notifies you when a built-in template is being overridden, useful for detecting forgotten overrides.

  6. Don’t override everything. Only override the templates you need to change. The rest are automatically loaded from the built-in.