Jastr is a deterministic Markdown template renderer for AI-agent workflows. It
lets authors keep reusable templates in a conventional .jastr/ catalog or in
direct .md files, validate typed inputs, evaluate template directives, resolve
safe includes, and print final Markdown to stdout.
Agent Skill generation is a CLI target. A generated SKILL.md remains a small
wrapper that runs jastr run and tells the agent to follow the rendered output.
Required inputs are inlined into the command as --flag=<value> placeholders the
agent fills in. A template with exactly one input folds it into a tailored
instruction sentence with no ## Inputs section; a template with two or more
inputs lists them under a ## Inputs section and instructs the agent to
construct the matching --flag=value arguments.
packages/engineis@jastr/engine: pure template parsing, schema validation, domain input validation, directive evaluation, interpolation, and rendering from explicit source through injected include resolvers.packages/cliis@jastr/cli: thejastrbinary, Commander wiring, filesystem template lookup, CLI flag parsing, include containment, Agent Skill generation, output writing, error formatting, help, and version output.
jastr run <template-ref> [input flags...]
jastr generate agent-skill <template-ref> --out <path> [--check] [--force]
jastr validate <template-ref>
jastr --help
jastr help [command]
jastr --versionInput values for bare named template runs are resolved in this order: CLI input
flags, then selected .jastr/config.yml values for inputs.<template-ref>,
then template-author default: values in frontmatter. Variant runs add a
stronger selected lock layer: variants.<template-ref>.<variant-id>.locked-inputs
overrides CLI flags, baseline config inputs, and template defaults. A CLI flag
for a locked input is rejected instead of silently overwritten. Direct .md
template runs use only CLI input flags and frontmatter defaults; they do not
read .jastr/config.yml and cannot select variants.
<template-ref> is syntactic:
- A value ending in
.mdis a direct Markdown template file path. - A value matching
^[a-z0-9][a-z0-9-]*$is a named template id and resolves to.jastr/<template-id>/TEMPLATE.mdunder the nearest ancestor containing.jastr/. - A value shaped as
<group>/<template-id>, where both segments match^[a-z0-9][a-z0-9-]*$, is a grouped named template and resolves to.jastr/<group>/templates/<template-id>/TEMPLATE.mdunder the nearest ancestor containing.jastr/, when.jastr/<group>/.jastrgroupexists as a file. - A named or grouped named template may add
#<variant-id>, where<variant-id>uses the same lowercase kebab-case segment grammar. For example,analyze#strictselectsvariants.analyze.strict;team/review#deepselectsvariants["team/review"].deep. Direct.md#variantrefs are invalid.
--version reports the @jastr/cli package version together with the git commit
it was built from, for example 0.1.0 (abc1234), or 0.1.0 (dev) when run from
source.
---
inputs:
language:
type: enum
values: [typescript, python]
required: true
target-file:
type: string
required: false
default: src/index.ts
targets:
agent-skill:
frontmatter:
name: analyze-code
description: Analyze code with the rendered Jastr template output.
allowed-tools: Read, Grep
---
# Analyze {{language}}
::::if{condition="${language} == 'typescript'"}
Use the TypeScript checklist.
::include{path="fragments/typescript.md"}
::::
::::else-if{condition="${language} == 'python'"}
Use the Python checklist.
::::
::::else
Ask the user for a supported language.
::::Named templates live at .jastr/<template-id>/TEMPLATE.md. Grouped templates
live alongside them under .jastr/, at
.jastr/<group>/templates/<template-id>/TEMPLATE.md with a .jastrgroup marker
at .jastr/<group>. Direct file templates can live anywhere the caller can
reference with a .md path.
Named template runs may use project-local config at .jastr/config.yml:
inputs:
analyze:
language: typescript
target-file: src/index.ts
team/review:
depth: deepKeys under inputs and variants are exact named template refs. A one-segment
template uses a key such as analyze; a grouped template uses a key such as
team/review. Config values are strict YAML values, so boolean inputs use YAML
booleans such as true rather than quoted strings.
Variants specialize a base template without copying it:
variants:
analyze:
strict:
locked-inputs:
language: typescript
agent-skill:
frontmatter:
name: analyze-typescript
description: Analyze TypeScript using the strict policy.jastr run analyze#strict renders the base .jastr/analyze/TEMPLATE.md with
language locked to typescript. jastr generate agent-skill analyze#strict --out path/to/SKILL.md writes a wrapper whose command points back to
jastr run analyze#strict, inlining any unlocked required input as
--flag=<value>. The wrapper renders only the inputs the variant did not lock:
two or more unlocked inputs get a ## Inputs section, exactly one unlocked input
gets a tailored single-input sentence, and a variant that locks every declared
input omits the inputs view entirely.
generate agent-skill --check answers "is the committed wrapper still up to date
with its template?" It rebuilds the wrapper in memory, byte-compares it against
the file at --out, and writes nothing. It exits 0 with
agent-skill at <out> is up to date. on an exact match, and exits 1 when the
committed file is stale (its bytes differ) or missing. Comparison is exact bytes
with no normalization, so even a line-ending or trailing-newline drift is
reported as stale. Because --check runs the full template and variant
validation first, an invalid template fails with its own error rather than a
stale or missing report. --check cannot be combined with --force.
jastr validate <template-ref> answers "is this template well-formed enough to
use at all?" It runs the same static-validation pipeline as run and generate
— frontmatter and schema validation, variant resolution for a #<variant-id>
ref, a static render that exercises directives, conditions, interpolation, and
include resolution, and (when the ref declares it) agent-skill target metadata
validation — without taking input flags, requiring an --out, or writing
anything. On success it prints Template <template-ref> is valid. and exits 0;
on any defect it fails with the same Error: <message> and exit code that defect
produces under run/generate. A template with no targets.agent-skill still
passes, because it remains runnable via jastr run.
Includes are contained by final resolved realpath. Standalone templates can
include only inside the top-level template directory. Grouped templates can
include only inside the group root. ::include{path="fragment.md"} resolves
from the top-level template directory,
::include{root="group", path="shared.md"} resolves from the group root, and
::include{root="file", path="sibling.md"} resolves from the file containing
the directive.
bun install
bun run format
bun run check
bun run typecheck
bun run test
bun run test:cli:e2e
bun run docs:cli:living --check
bun run buildCLI functional requirements live under
packages/cli/requirements/functional/. CLI e2e cases live under
packages/cli/test/e2e/cases/<case-id>/case.yml and are executed by
bun run test:cli:e2e.
bun run docs:cli:living regenerates packages/cli/docs/BEHAVIOR.md from the
CLI requirements and e2e cases. Use bun run docs:cli:living --check to fail
when the committed behavior reference is stale.