Skip to content

Latest commit

History

1,259 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sc-compose

Compose once. Render deterministically. Ship everywhere.

Install: brew | scoop | winget | cargo | pipcrates.ioPyPICILicense: MIT

Project website


sc-compose is a standalone CLI and library for teams whose templates have outgrown copy-paste. Compose templates from shared, version-controlled fragments, declare inputs up front, and render deterministic output across any runtime — AI agent profiles, pytest fixtures, .NET harnesses, HTML reports, and service configs.

One engine, everywhere. A single Rust library (sc-composer) drives the CLI (sc-compose), Python bindings, and any embedded host. Templates are Jinja2 with YAML frontmatter. Shared fragments use @-include. Required inputs fail loudly at render time — no guessing, no silent defaulting.

For AI agent workflows, one profile resolves across Claude Code, Codex, Gemini, and OpenCode through each runtime's native search chain, with a shared .agents/ fallback so you override only the runtimes that genuinely need it.


Quickstart

# Install
brew install randlee/tap/sc-compose # macOS
winget install randlee.sc-compose # Windows
scoop bucket add randlee https://github.com/randlee/scoop-bucket
scoop install sc-compose # Windows (Scoop)
cargo install sc-compose # from source
pip install sc-compose # Python# Render your first templateecho'Hello {{ name }}!'> hello.txt.j2
sc-compose render --file hello.txt.j2 --var name=World
# → Hello World!

Feature Highlights

Compose Templates from Shared Fragments

Place shared conventions in one file. Reference them from everywhere. Edit once, every downstream template picks up the change.

@<_includes/house-style.md>

Includes nest, cycles are detected, paths are confined to the workspace root.

Declare Inputs Up Front

YAML frontmatter makes required inputs explicit:

---
required_variables:
- task_id
- branchdefaults:
pr_target: develop
---

Missing a required variable? sc-compose fails with an actionable diagnostic that names the missing variable, the file that declared it, and the include chain.

One Profile Across Four Runtimes

Author an agent profile once under .agents/agents/. Override only the runtimes that need specialization:

your-repo/
├── .agents/agents/reviewer.md ← works for Claude, Codex, Gemini, OpenCode
├── .claude/agents/ ← Claude-only overrides (optional)
├── .codex/agents/ ← Codex-only overrides (optional)

sc-compose render --mode profile --kind agent --agent reviewer --runtime claude

Multi-Pass Nested Templates (v1.3.0)

Progressive resolution: deploy-time → install-time → invocation-time variables in one file. Outer passes use more braces; inner passes use fewer. Shared fragments (@-include) work at every pass.

# Render all three passes
sc-compose render config.yaml.2.j2 --all \
--pass 3 --var-file deploy.json \
--pass 2 --var-file install.json \
--pass 1 --var-file invoke.json
# Verify deployed config hasn't drifted
sc-compose verify deployed.yaml --against config.yaml.2.j2 --all --pass ...

Bundled Examples & Personal Templates

sc-compose examples list # discover starter templates
sc-compose examples pytest-fixture # generate test stubs
sc-compose templates add my-template.md.j2 # save for reuse
sc-compose templates my-template # render by name

Reporting Subsystem

Produce compliance evidence from declarative specs:

sc-compose reports init # scaffold report catalog
sc-compose reports smoke # render smoke report fixture
sc-compose reports render-spec spec # render from semantic spec
sc-compose reports finalize # materialize metadata
sc-compose reports publish-manifest # CI handoff manifest

Python Bindings

fromsc_composeimportcompose, render_template, Rendererresult=render_template("Hello {{ name }}", {"name": "world"})
# Multi-pass rendering:# compose(request) — full pipeline with ComposePolicy.passes

Pre-built wheels for macOS, Linux, Windows (Python 3.11+).

Go Bindings

The generated sc-sha-go module exposes typed Go bindings for the canonical sc-sha file and composition hash operations:

go get github.com/randlee/sc-compose/bindings/sc-sha-go@bindings/sc-sha-go/v1.5.0

Import the generated package from github.com/randlee/sc-compose/bindings/sc-sha-go/go/sc_sha_go. See docs/integrations/sc-sha-go.md for the native target matrix, release layout, conformance vectors, and consumer handoff requirements.


Install Matrix

PlatformMethodCommand
macOSHomebrewbrew install randlee/tap/sc-compose
WindowsWingetwinget install randlee.sc-compose
WindowsScoopscoop bucket add randlee https://github.com/randlee/scoop-bucket
scoop install sc-compose
Any (Rust)crates.iocargo install sc-compose
Any (Python)PyPIpip install sc-compose
Any (source)cargocargo build --release -p sc-compose
Rust libCargo.tomlsc-composer = "1.5.0"

Bundled examples are guaranteed in Homebrew, Scoop, Winget, and GitHub Release installs. cargo install ships the binary only — set SC_COMPOSE_DATA_DIR for examples.


Status

Version1.5.0
MSRVRust 1.94.1
Rust edition2024
PlatformsmacOS, Linux, Windows
Stabilitystable 1.5 release line

Documentation


Why sc-compose?

Prompt files drift across repos, tasks, and runtimes. Teams end up with several copies of the same prompt: .claude/agents/foo.md, .codex/agents/foo.md, a Slack paste, a gist, and a shell-history version. Those copies diverge. Agent behavior diverges with them. Debugging turns into prompt diffing.

sc-compose treats prompts as source code you compose, not text you copy. Compose once. Render deterministically. Keep shared fragments in one place and include them by reference. Pass task context as variables. Validate required inputs at render time so missing data fails fast instead of being guessed.

The workspace provides three packages:

  • sc-composer — a Rust library with the render, include-expansion, validation, and diagnostics pipeline
  • sc-compose — a CLI wrapper over the library for scripts, shells, and agent-invocable workflows
  • sc-compose (PyPI) — Python native extension for pip install

All three are standalone. None is coupled to any particular orchestration system.


CLI Reference

CommandWhat it does
renderRender a template or resolved profile to stdout or a file
resolvePrint the resolved profile path and search trace
validateExpand includes and analyze variables without writing output
frontmatter-initDiscover referenced variables and prepend minimal frontmatter
initCreate .prompts/, add it to .gitignore, and scan templates
observability-healthReport process-local structured logging health
examples listList bundled starter templates
examples <name>Render a bundled example with --var / --var-file
templates listList your saved personal templates
templates add <src> [name]Save a file or directory to your local template store
templates <name>Render a saved template with --var / --var-file
template-initConvert a concrete file into a multi-pass stacked template
verifyVerify a deployed file matches its multi-pass template source
reports initCreate the shared report scaffold and starter catalog
reports smokeRender the built-in smoke report fixture
reports finalizeMaterialize metadata and archives for producer-owned outputs
reports render-specRender a semantic report spec into shared artifacts
reports indexSummarize current latest report artifacts
reports verifyVerify required report evidence is present
reports publish-manifestWrite machine-readable publish handoff manifest

Key flags:

FlagPurpose
--mode <file|profile>Template lookup mode (default: file)
--kind <agent|command|skill>Profile kind in profile mode
--agent <name>Profile name in profile mode
--runtime <claude|codex|gemini|opencode>Runtime selector
--file <path>Template path in file mode
--var key=valueInput variable (repeatable)
--var-file <path>JSON/YAML variable file (- for stdin)
--env-prefix <PREFIX_>Absorb env vars matching prefix
--guidance <text> / --guidance-file <path>Append guidance block
--prompt <text> / --prompt-file <path>Append user prompt block
--output <path>Write rendered output to file
--dry-runReport without modifying files
--jsonMachine-readable output with diagnostics envelope
--strictFail on undeclared referenced variables
--allRender all passes (multi-pass templates)
--pass N --var ...Per-pass variable inputs

Run sc-compose <command> --help for the full flag surface.


Resolver Search Chains

RuntimeAgentsCommandsSkills
Claude.claude/agents, .agents/agents.claude/commands, .agents/commands.claude/skills, .agents/skills
Hermes.hermes/agents, .agents/agents, .claude/agents.hermes/commands, .agents/commands, .claude/commands.hermes/skills, .agents/skills, .claude/skills
Codex.codex/agents, .agents/agents, .claude/agents.codex/commands, .agents/commands, .claude/commands.codex/skills, .agents/skills, .claude/skills
Gemini.gemini/agents, .agents/agents, .claude/agents.gemini/commands, .agents/commands, .claude/commands.gemini/skills, .agents/skills, .claude/skills
OpenCode.opencode/agents, .agents/agents, .claude/agentssame patternsame pattern

Claude is the universal fallback because it is the most common author target in practice.


Contributing

main is protected. Create feature branches from develop and follow docs/git-workflows.md for branching and review rules. Adhere to the Pragmatic Rust Guidelines for code style.

License

MIT. See LICENSE.

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages