--- 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: ''` — 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 ` | 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