Wiswa: Declarative Project Generation

# Wiswa

## Declarative Project Generation

## The Problem

- Starting a new project means recreating the same boilerplate every time

- CI/CD workflows, linter configs, packaging, documentation scaffolding…​

- Keeping all of this in sync across projects is even harder

- Manual updates are tedious, error-prone, and easy to forget

## Existing Solutions

## Cookiecutter

- Popular project templating tool

- One-shot generation— no mechanism for pulling template updates

- Once generated, the project drifts from the template permanently

- Mostly used for Python projects

## Cruft

- Built on top of Cookiecutter to add updates via patching

- Patch-based updates lead to merge conflicts with local changes

- The Cruft PyPI project is no longer maintained

- Still inherits Cookiecutter’s Python-centric limitations

## Common Weaknesses

- Templates are static — no programmable logic

- No automatic dependency version resolution

- No integration with GitHub/GitLab APIs

- Limited to a single language ecosystem

## Introducing Wiswa

A declarative, regeneration-based project generator

- Multi-language: Python, TypeScript, C, C++, Lua, and more

- Programmable configuration with Jsonnet

- Automatic latest version resolution from PyPI, npm, GitHub

- Deep GitHub and GitLab integration

## How It Works: Configuration

Create a `.wiswa.jsonnet` in your project with only the differences from defaults:

    {
      project_name: 'my-app',
      project_type: 'python',
      want_docs: true,
      // ...only what you want to change
    }

## How It Works: Jsonnet Basics

Jsonnet is a superset of JSON with programmable features:

Local variables | Conditionals
--- | ---
    local is_lib = self.project_type
                   == 'python';
    want_docs: if is_lib then true
               else false,
Deep merge with `+:` | Imports and library functions
    pyproject+: {
      tool+: {
        ruff+: {
          'line-length': 120,
        },
      },
    },
    local utils =
      import 'utils.libjsonnet';

    python_deps+: {
      main+: {
        click: utils.latestPypiPackageVersionCaret('click'),
      },
    },

## How It Works: Merge Defaults

Merge with 400+ built-in defaults from `defaults.libjsonnet` (and your own [1]).

`defaults.libjsonnet` + `user defaults.jsonnet` + `.wiswa.jsonnet`

1. Controlled by the `-u` flag — shared personal preferences across all your projects.

## How It Works: Jsonnet Evaluation

Evaluate the merged configuration with native functions — version resolution, date helpers, URI generation, and more.

## How It Works: File Generation

Settings map directly to output files. For example, `package.json`:

`.wiswa.jsonnet` | `package.json` (generated)
--- | ---
    {
      package_json+: {
        description: 'My app',
        prettier+: {
          printWidth: 120,
        },
        scripts+: {
          deploy: 'rsync -a dist/ srv:/',
        },
      },
    }
    {
      "description": "My app",
      "devDependencies": {
        "prettier": "^3.5.3",
        ...
      },
      "prettier": {
        "printWidth": 120,
        "singleQuote": true,
        ...
      },
      "scripts": {
        "deploy": "rsync -a dist/ srv:/",
        "format": "prettier -w . && ...",
        ...
      }
    }

## How It Works: Post-Processing

Lock dependencies, run formatters (Ruff, Prettier), and lint.

## How It Works: Repository Setup

Configure your GitHub or GitLab repository via API — merge settings, branch protection, security scanning, and more.

## Configuration: `.wiswa.jsonnet`

Only specify what differs from defaults:

    local utils = import 'utils.libjsonnet';
    {
      project_name: 'my-app',
      project_type: 'python',
      github_username: 'myuser',
      package_manager: 'uv',

      want_docs: true,
      want_tests: true,
      want_codeql: true,
      pyproject+: {
        tool+: {
          ruff+: {
            'line-length': 100,
          },
        },
      },

      python_deps+: {
        main+: {
          click: utils
            .latestPypiPackageVersionCaret(
              'click'),
        },
      },
    }

## The `utils.libjsonnet` Library

Import it in any `.wiswa.jsonnet` to access built-in helper functions:

    local utils = import 'utils.libjsonnet';

- `utils.year()`, `utils.isodate()`— current year and ISO date

- `utils.manifestYaml(obj)`, `utils.manifestToml(obj)`— serialisation helpers

- `utils.gitHubRepositoryUri(user, project)`— generate common URIs

- `utils.pyprojectAuthors(authors)`— transform author metadata

- `utils.pyprojectClassifiers(settings)`— generate sorted PyPI classifiers

- `utils.formatPep508Deps(name, val)`— convert dependency specs to PEP 508

- `utils.poetryVerToPep508(ver)`— translate `^`/`~` version syntax

## Smart Version Resolution

The utils library also provides functions that fetch the latest versions at generation time:

    local utils = import 'utils.libjsonnet';

    // PyPI (caret, tilde, >= prefixed, or bare)
    utils.latestPypiPackageVersionCaret('click')   // "^8.1.8"
    utils.latestPypiPackageVersionTilde('django')  // "~5.2.1"
    utils.latestPypiPackageVersionGe('requests')   // ">=2.32.3"

    // npm
    utils.latestNpmPackageVersion('typescript')    // "5.7.3"
    utils.latestNpmPackageVersionCaret('eslint')   // "^9.20.0"

    // GitHub releases and tags
    utils.githubLatestReleaseTag('owner', 'repo')  // "v2.4.1"
    utils.githubLatestActionTag('actions', 'checkout') // "v4"
    utils.githubLatestTag('owner', 'repo')         // "v3.1.0"

    // Yarn
    utils.latestYarnVersion()                      // "4.6.0"

## The Update Workflow

1. Make changes to `.wiswa.jsonnet` (or re-run to pick up new defaults)

2. Run `wiswa` to regenerate all managed files (with `-u` to merge user defaults)

3. Post-processing locks dependencies and formats code

4. Review the `git diff`

5. Commit

Key insight: regeneration, not patching.

No merge conflicts. Managed files are always overwritten.
Your source code, tests, and docs content are never touched.

## Bidirectional Sync

What if you change a managed file directly?

- Example: `uv add requests` modifies `pyproject.toml`

- The wiswa-sync agent reflects the change back into `.wiswa.jsonnet`

- Next regeneration preserves your addition

- Configuration and files stay in sync in both directions

## What Gets Generated?

Category | Examples
--- | ---
Build & packaging | `pyproject.toml`, `package.json`, `CMakeLists.txt`, `vcpkg.json`
CI/CD | GitHub Actions workflows, GitLab CI, Dependabot config
Code quality | Ruff, ESLint, clang-format, pre-commit hooks, mypy
Documentation | Sphinx + ReadTheDocs, GitHub Pages, CONTRIBUTING, SECURITY
IDE | VS Code settings, launch configs, extensions, Cursor rules
AI tooling | Claude Code agents, Copilot instructions
Distribution | AppImage, PyInstaller, Snap, Flatpak, winget
Badges | Auto-generated based on tools, platforms, and social media

## Multi-Language Support

Language | Highlights
--- | ---
Python | pyproject.toml, uv/Poetry, Ruff, mypy, pytest, Sphinx, PyPI publishing
TypeScript | package.json, Jest, ESLint, Prettier, npm publishing
C / C++ | CMake, CMakePresets, vcpkg, clang-format
Lua | luacheck, Busted tests, LuaRocks publishing
Swift | clang-format, Xcode project configuration
Generic | package.json, Prettier, basic CI

## GitHub / GitLab Integration

Wiswa configures your repository via API:

- Merge settings (squash, rebase, delete branch on merge)

- Branch protection and commit signature requirements

- Secret scanning and push protection

- Pages deployment (GitHub Pages / GitLab Pages)

- Dependabot security updates (GitHub only)

- CodeQL security analysis (GitHub only)

## The MCP Server

Wiswa exposes its configuration system as an MCP server for AI integration.

    claude mcp add wiswa-mcp -- wiswa-mcp

## MCP Server: Tools

Four tools available:

- `get_defaults(key_path)`— retrieve resolved default settings

- `lookup_setting(key_path)`— get a value with its Jsonnet override snippet

- `list_settings(key_path, depth)`— browse the settings hierarchy

- `search_settings(query)`— full-text search across all settings

## MCP Server: Example

    > lookup_setting("pyproject.tool.ruff.line-length")

    {
      "value": 100,
      "type": "number",
      "override_snippet": "pyproject+: { tool+: { ruff+: { 'line-length': <value> } } }"
    }

AI assistants can explore and modify Wiswa configuration programmatically.

## Customisability

- Jsonnet merge operators (`+:`) for deep, surgical overrides

- User-level defaults (`~/.config/wiswa/defaults.jsonnet`) shared across all projects

- Feature flags (`want_docs`, `want_tests`, `want_main`, `want_codeql`, …​)

- `-J` flag to add directories to the Jsonnet search path for custom libraries

## Architecture Overview

    wiswa/
      main.py              -- async orchestrator
      mcp.py               -- FastMCP server
      session.py           -- cached aiohttp client
      utils/
        jsonnet.py         -- Jsonnet eval + native callbacks
        versions.py        -- package version fetching
        postprocess.py     -- formatting, locking, cleanup

    wiswa-jsonnet/
      defaults.libjsonnet  -- base defaults (400+ fields)
      project.jsonnet      -- generates all config files
      utils.libjsonnet     -- helper functions

## Summary

- Declarative: one `.wiswa.jsonnet` file describes your entire project setup

- Regeneration-based: no merge conflicts, always up to date

- Multi-language: Python, TypeScript, C/C++, Lua, Xcode

- Smart: automatic version resolution, GitHub/GitLab API integration

- Extensible: Jsonnet configuration, user defaults, MCP server

- AI-native: MCP server, Claude Code agents, Copilot/Cursor instructions

github.com/Tatsh/wiswa
pypi.org/project/wiswa