Skip to content

Stack Plugin Guide

TL;DR. Each stack is a stack.json declaration that lives in stacks/<name>/. The framework loads them via a StackRegistry. To add a new stack you write one JSON file; to customize an existing one you drop an override in .tausik/stacks/<name>/.

Why a plugin layout

Before v1.6 every stack was hardcoded in five places: project_types.DEFAULT_STACKS, bootstrap_config.STACK_SIGNATURES, gate_stack_dispatch._EXT_TO_STACKS, default_gates.DEFAULT_GATES, and project_config.STACK_GATE_MAP. Adding a stack meant editing all five, with a high drift risk. The plugin model collapses them into a single source of truth: stacks/<name>/stack.json. Consumers read the registry; the registry reads the JSON.

Built-in stacks

stacks/
├── _schema.json       ← JSON Schema for stack.json (Draft-07)
├── python/
│   ├── stack.json
│   └── guide.md
├── typescript/
│   ├── stack.json
│   └── guide.md
└── ...                ← 25 built-in stacks total

Each stack.json follows stacks/_schema.json. guide.md is the per-stack human-facing guide that surfaces in skill output.

stack.json fields

FieldTypePurpose
namestring (required)Lowercase slug. Must match ^[a-z][a-z0-9_-]*$.
versionstringFree-form decl version. Default "1".
extendsstring"builtin:NAME" to inherit from a built-in stack. Used in user overrides.
detectlist of {file, type, keyword?}Project-root signatures used by detect_stacks(). typeexact / glob / dir-marker. Optional keyword filters file content.
extensionslist of stringsFile extensions (with leading dot) that signal this stack. Powers _EXT_TO_STACKS.
filenameslist of stringsLowercase filenames (e.g. dockerfile) that signal the stack regardless of extension.
path_hintslist of stringsPath fragments (/playbooks/) used to disambiguate YAML/JSON in IaC trees.
gatesobjectGate-name → gate-config dict. null value disables an inherited gate.
guide_pathstringRelative path to the stack guide. Default "guide.md".
extensions_extralist of stringsAdditive on top of inherited extensions. Useful in user overrides.

Adding a new built-in stack

  1. Create stacks/<name>/stack.json. Minimum:

    json
    {
      "name": "ruby",
      "detect": [{ "file": "Gemfile", "type": "exact" }],
      "extensions": [".rb"]
    }
  2. (Optional) Add stacks/<name>/guide.md with sections: Testing, Review Checklist, Conventions, Common Pitfalls. The skills-maturity test asserts these are present.

  3. (Optional) Declare gates in the gates field — for example, an RSpec gate scoped to ruby. The gate config follows default_gates.UNIVERSAL_GATES shape: enabled, severity, trigger, command, description, timeout, stacks.

  4. Run python bootstrap/bootstrap.py --no-detect to refresh .claude/stacks/, _STACKS_ENUM in MCP tools.py, and the rest of the consumers.

  5. Run pytest tests/test_stack_registry.py and pytest tests/test_gates.py to confirm nothing regressed.

Where the registry is read

The single import point is scripts/stack_registry.default_registry(). After v1.6 every consumer routes through it:

ConsumerField consumed
project_types.DEFAULT_STACKSall_stacks()
bootstrap_config.STACK_SIGNATURESsignatures_for(name)
gate_stack_dispatch._EXT_TO_STACKSextensions_for(name)
gate_stack_dispatch._FILENAME_TO_STACKSfilenames_for(name)
gate_stack_dispatch._PATH_HINTSpath_hints_for(name)
default_gates.DEFAULT_GATES (stack-scoped)gates_for(name)
project_config.STACK_GATE_MAPderived from DEFAULT_GATES
MCP tools.py _STACKS_ENUMregenerated by bootstrap from all_stacks()

Every consumer carries a hardcoded fallback so module import never crashes if the registry can't load.

Validation

scripts/stack_schema.validate_decl(decl, source) returns a list of actionable errors. Each error names the source file, the offending field, and the rule violated. Empty list = valid. Use it programmatically when generating decls, or run the full lint via tausik stack lint.

Testing

tests/test_stack_registry.py covers:

  • Loading: missing dir, malformed JSON, schema-invalid, hidden directories, duplicates.
  • Overrides: extends, extensions_extra additive merge, null gate disable, per-key gate override, unknown extends target, standalone user stacks.
  • Reload + cache invalidation.
  • Source tracking (source_for, is_user_overridden).

Run: pytest tests/test_stack_registry.py -v.

DEFAULT_STACKS (25)

python, fastapi, django, flask, react, next, vue, nuxt, svelte, typescript, javascript, go, rust, java, kotlin, swift, flutter, laravel, php, blade, ansible, terraform, helm, kubernetes, docker. The list is open for extension via .tausik/config.jsoncustom_stacks.

See also