Installs into .claude/skills of the current project.
Are you the author of Nika Migration?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/supernovae-st-nika-migration)
---
name: nika-migration
description: Convert existing scripts, CI jobs or prompt chains into .nika workflows while preserving behavior. Use when an existing automation is being ported.
---
# Migrating existing automation to Nika
A workflow makes its inputs, effects and execution evidence inspectable.
Migration re-declares the intent so the checker can see it; a clean check
does not authorize effects or prove that the migrated behavior is correct.
## When to migrate (and when not to)
Migrate when the automation: calls an LLM anywhere · chains
HTTP/file/JSON steps around AI output · is repeated (cron, CI, "run
this every release") · needs a cost bound or an audit trail · is
handed to someone else to run.
Do NOT migrate: one-shot commands · interactive debugging sessions ·
sub-second pure-shell pipelines with zero AI and zero HTTP (a
`Makefile` that only compiles code is already in its best form).
## The mapping table
| In the script | In the workflow |
|---|---|
| a step / function | one task, exactly one verb |
| `curl` / `wget` / `fetch()` helper | `invoke:` `tool: "nika:fetch"` — **for an API, set `mode: raw` or `mode: jq`** (the default `markdown` mode is for pages and escapes JSON bodies) |
| `curl … \| jq` in one breath | ONE fetch task: `mode: jq` + `jq: '<expression>'` — the shape rides the fetch |
| `jq` / `sed` on JSON | `nika:jq` (arg name is `expression`), or an `extract:` binding |
| `cat` / `cp` / `mkdir` / `tee` | `nika:read` / `nika:write` — a directory is made by writing its FIRST file inside it with `create_dirs: true`, never by an empty write at the directory's path (that creates a FILE there, and nothing deletes it) |
| in-place file edits | `nika:edit` |
| the LLM call (SDK, `curl` to an API) | `infer:` with `prompt`, `schema?`, `max_tokens` |
| an agent loop (retry-until-good) | `agent:` with `tools` allowlist + `max_turns` |
| a retry/backoff loop around a flaky call | `retry:` on the task — `max_attempts` + `backoff_strategy: exponential` + `jitter: true` (transient provider/network errors only; a wrong prompt never heals by retry) |
| `for item in …` | `for_each:` fan-out |
| `if <condition>` | a `when:` gate |
| `$1` positional parameters | an `inputs:` declaration · supplied with `--var key=value` |
| a value baked into the script | a `const:` entry · read as `${{ const.x }}` |
| `$SOME_SETTING` (non-sensitive) | an `inputs:` declaration with `required: false` and a `default:` · read as `${{ inputs.KEY }}` |
| an env var a CHILD process must see | `permits: { env: [NAME] }` — a child inherits nothing |
| `API_KEY=…` literals | `${{ secrets.X }}` + `secrets:` block with its `egress:` sink |
| step B reads step A's output | `with: { a: "${{ tasks.A.output }}" }` on B — the binding IS the edge — then `${{ with.a }}` in the body |
| step B only waits for step A (no data) | `after: { A: success }` (predicates: `success` · `failure` · `skipped` · `terminal`) |
| the irreversible step (deploy, send, publish) | preserve its authorization and any required gate; use `nika:prompt` for a decision still needed at run time, not to re-request authorization already given |
| what no builtin/MCP covers (git, build tools) | `exec:` with `command:` as ARGV (`["git", "log", "-1"]`) + a row in the exec ledger |
| a pipe, redirect or glob inside the command | `shell:` explicitly — `command:` has no implicit shell |
## The port protocol
1. **Read the source completely.** Inventory: inputs · outputs · side
effects · credentials · the failure the author feared (that guard
clause is the intent — keep it).
2. **Inspect an exact skeleton**: `nika compile --list` lists the embedded
set; pick the OUTER shape (chain · fanout · gate-and-act ·
etl-state · agent-loop · human-gated-ship) and preview with
`nika compile <template> --json`. Answer its stable questions explicitly,
then name a destination to write a Ready candidate. Unsupported migration
intent remains incomplete; do not substitute a nearby shape for the request.
3. **Map with the table.** Native-first is the law: `invoke: nika:*`
→ `invoke: mcp:<server>/<tool>` → `exec:` last. Every surviving
`exec:` gets its ledger row (task · command · why no native path ·
unlock that removes it).
4. **Validate the intended model and structure**: `nika check <file>`
performs static checks without inference. Preserve the selected model;
use `mock/echo` only for a deliberate simulated rehearsal. Repair the
final candidate from diagnostics and require `--native-strict` readiness.
5. **Declare the boundary**: `permits:` is mandatory — an effect under
no block refuses `NIKA-AUTH-006` at check.
`nika check <file> --infer-permits` proposes a block;
review it against intended effects before applying it. The script trusted its author; the workflow trusts
nobody by default (a pure-compute port still declares
`permits: {}`).
6. **Prove the intended parity** on controlled inputs and authorized
destinations. Inspect the old script's effects before running it too.
`nika run <file> --model mock/echo` changes only the envelope model.
Per-task model pins are unchanged; tools, subprocesses, writes and
secret sources remain real. Compare expected artifacts in isolated fixtures;
do not rerun a production publish or send merely to compare behavior.
For a workflow requiring no network, subprocess or write effect,
`nika test <file> --update` can pin its outputs in a golden. That
simulated plane refuses those effects: an effecting port needs artifact
assertions and trace inspection, not a promised golden it cannot produce.
7. **Deliver within the authorized scope**: report the workflow, the parity
checks that actually passed, and a golden only where applicable. Provide
the run line (`nika run <file> --var … --max-cost-usd <n>`) or execute it
when already authorized, through the normal engine and host gates.
Retire the old script once parity covers its intended behavior, its
callers are migrated and removal is authorized; do not ask again for
that same authorization. An unknown business decision or human-gate
answer still needs the user's answer.
## Porting a pre-0.106 workflow file
A `.nika` written before 0.106 can refuse to check today — the
flag day changed what an existing file MEANS. Run `nika check <file>
--fix` first: it migrates three classes mechanically, comment-
preserving and idempotent.
| Dead form | Becomes | Repair |
|---|---|---|
| `vars:` entry, caller-supplied | `inputs:` (typed · `required:` · `default:`) | `--fix` |
| `vars:` entry, fixed value | `const:` | `--fix` |
| `workflow:` envelope key (scalar or object) | `nika: <kebab-case-name>` + `tasks:` map | `--fix` |
| `after: { t: succeeded / failed }` | `success` / `failure` | `--fix` |
| `env:` entry, non-sensitive | `inputs:` (typed · `required: false` · `default:`) | **yours** |
| `env:` entry, a credential | `secrets:` (a store reference) | **yours** |
| `env:` name a child must see | `permits: { env: [NAME] }` | **yours** |
| no `permits:` block, any effect | the inferred block (`--infer-permits`) | **yours** |
`--fix` is atomic-or-nothing per class: on a credential-shaped name, a
typed-only declaration, a flow-style `vars: {…}` header or an empty
block it leaves the file UNTOUCHED and names the reason — it never
guesses. `env:` has NO mechanical repair by design: re-shaping a flat
string map into typed declarations is a classification, and only you
know whether a name is configuration, a credential, or something a
child process needs to see.
## Traps
- Porting a helper script by wrapping it (`exec: node helper.mjs`) is
not a migration — that is `native-first/005`. Unbundle the helper
into fetch/jq/read/write tasks.
- A prompt chain in a doc usually hides implicit state ("then take
the output and…") — make every handoff an explicit
`${{ tasks.X.output }}` reference so the checker can trace it.
- Scripts swallow errors (`|| true`); workflows should not. If the
source ignored a failure, ask whether that was intent or debt —
default to letting the task fail loudly.
- Credentials in the script's environment become DECLARED secrets
with sinks — the engine masks them; the script never did.