@kici-dev/compiler 0.1.22 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/cli.js +34 -10
  2. package/dist/commands/compile.d.ts +6 -0
  3. package/dist/commands/compile.js +6 -3
  4. package/dist/commands/docs.d.ts +8 -8
  5. package/dist/commands/docs.js +35 -16
  6. package/dist/commands/index.d.ts +4 -2
  7. package/dist/commands/index.js +3 -2
  8. package/dist/commands/init.js +2 -2
  9. package/dist/commands/org.js +2 -2
  10. package/dist/commands/pat.d.ts +27 -0
  11. package/dist/commands/pat.js +76 -0
  12. package/dist/commands/preview.d.ts +88 -0
  13. package/dist/commands/{test.js → preview.js} +15 -14
  14. package/dist/commands/run.d.ts +27 -2
  15. package/dist/commands/run.js +117 -18
  16. package/dist/commands/test.d.ts +4 -0
  17. package/dist/commands/types.d.ts +2 -0
  18. package/dist/commands/types.js +1 -1
  19. package/dist/commands/verify-attestation.d.ts +4 -1
  20. package/dist/commands/verify-attestation.js +26 -10
  21. package/dist/fixtures/describe-event.d.ts +6 -0
  22. package/dist/fixtures/describe-event.js +18 -0
  23. package/dist/fixtures/picker.d.ts +19 -0
  24. package/dist/fixtures/picker.js +64 -0
  25. package/dist/generators/secrets-dts.js +2 -0
  26. package/dist/index.d.ts +2 -2
  27. package/dist/index.js +2 -2
  28. package/dist/llm-context/llms-architecture.txt +1440 -0
  29. package/dist/llm-context/llms-cli.txt +2509 -0
  30. package/dist/llm-context/llms-features.txt +2491 -0
  31. package/dist/llm-context/llms-full.txt +1364 -361
  32. package/dist/llm-context/llms-getting-started.txt +519 -0
  33. package/dist/llm-context/llms-patterns.txt +1324 -0
  34. package/dist/llm-context/llms-providers.txt +805 -0
  35. package/dist/llm-context/llms-sdk.txt +3844 -0
  36. package/dist/llm-context/llms.txt +16 -1
  37. package/dist/local-executor/index.js +42 -4
  38. package/dist/local-executor/job-runner.d.ts +2 -0
  39. package/dist/local-executor/job-runner.js +38 -6
  40. package/dist/local-executor/types.d.ts +2 -0
  41. package/dist/lockfile/generator.d.ts +10 -2
  42. package/dist/lockfile/generator.js +112 -49
  43. package/dist/remote/history.d.ts +1 -1
  44. package/dist/remote/history.js +1 -1
  45. package/dist/remote/local-repo-identity.d.ts +32 -0
  46. package/dist/remote/local-repo-identity.js +74 -0
  47. package/dist/remote/platform-client.d.ts +6 -0
  48. package/dist/remote/prod-defaults.d.ts +8 -0
  49. package/dist/remote/prod-defaults.js +9 -1
  50. package/dist/remote/uploader.js +1 -0
  51. package/dist/templates/agents-md.d.ts +1 -1
  52. package/dist/templates/agents-md.js +2 -2
  53. package/dist/templates/package-json.js +1 -1
  54. package/dist/test-runner/rule-evaluator.d.ts +1 -1
  55. package/dist/test-runner/rule-evaluator.js +2 -1
  56. package/dist/test-runner/step-context.d.ts +1 -1
  57. package/dist/test-runner/step-context.js +8 -2
  58. package/dist/types.d.ts +38 -7
  59. package/dist/types.js +5 -1
  60. package/package.json +4 -7
  61. package/sbom.spdx.json +35 -35
@@ -0,0 +1,2509 @@
1
+ # KiCI CLI and authoring
2
+
3
+ This bundle covers: Running the CLI: compile, test, run local/remote, auth, hooks, lock-file drift.
4
+
5
+ ## Drive KiCI from your coding agent
6
+
7
+ Source: https://docs.kici.dev/user/ai-agents/
8
+
9
+ KiCI ships a hosted **MCP server** so a coding agent (Claude Code, or any MCP
10
+ client) can drive your CI directly: trigger runs, read a structured result,
11
+ fetch the failing step's logs, cancel, and re-run — all under your own identity,
12
+ org-scoped, and audited. There are no per-tool tokens to configure: point the
13
+ agent at one URL with one credential and it's done.
14
+
15
+ The MCP exposes only what you can already do yourself through the `kici` CLI and
16
+ the dashboard. It is not a new privileged surface — every tool maps to an
17
+ existing user-facing operation and is gated by the same permissions your role
18
+ grants.
19
+
20
+ ## 1. Mint an agent token
21
+
22
+ The MCP accepts **only** an agent-kind personal access token (PAT). Mint one with
23
+ the `kici` CLI (log in first with `kici login`):
24
+
25
+ ```bash
26
+ kici pat create --agent --name "claude-code"
27
+ ```
28
+
29
+ The `--name` value is the **agent label**. It is recorded on every action the
30
+ agent takes, so your audit log shows exactly which agent did what (and on whose
31
+ behalf). The token is printed once — save it now; it cannot be retrieved later.
32
+
33
+ An agent PAT inherits your permissions unchanged — it carries provenance, not
34
+ extra authority. Powerful operator capabilities (secret rotation, agent and peer
35
+ management, draining) are intentionally **not** exposed here.
36
+
37
+ ## 2. Point your coding agent at the MCP server
38
+
39
+ Configure your MCP client with the KiCI MCP endpoint and the agent PAT as a
40
+ Bearer credential. The endpoint is the hosted Platform URL plus `/api/v1/mcp`.
41
+
42
+ For Claude Code, add a remote MCP server whose URL is your KiCI Platform's
43
+ `/api/v1/mcp` and whose `Authorization` header is `Bearer <your-agent-pat>`.
44
+
45
+ That's the entire setup. The agent can now call the tools below.
46
+
47
+ ## 3. What the agent can do
48
+
49
+ **Read**
50
+
51
+ - `list_runs` — recent runs in your organization.
52
+ - `get_run` — the structured, provenance-tagged result of a run: the typed job
53
+ graph, per-step statuses and exit codes, durations, and a derived failure
54
+ category.
55
+ - `get_step_logs` — the log lines for a specific step.
56
+ - `list_workflows` — your registered workflows.
57
+
58
+ **Drive**
59
+
60
+ - `trigger_run` — run a registered workflow ("run now").
61
+ - `rerun_run` — re-run a completed run.
62
+ - `cancel_run` — cancel an in-progress run.
63
+
64
+ If you belong to a single organization, the org is resolved automatically. If
65
+ you belong to several, pass an `orgId` argument to any tool.
66
+
67
+ ## 4. Why the structured result is agent-safe
68
+
69
+ `get_run` and `get_step_logs` return a machine-first shape designed for an agent
70
+ to reason over without being misled by repository content. Every field that
71
+ comes from your repo, a contributor, or a process's output — workflow and job
72
+ names, refs, error messages, log lines, job outputs — is wrapped in an
73
+ `{ untrusted: true, value: … }` envelope. KiCI-generated values (ids, statuses,
74
+ exit codes, durations, the derived failure category) are left plain. An agent can
75
+ keep user-controlled content out of its instruction channel by refusing to act
76
+ on anything tagged `untrusted`.
77
+
78
+ Secret values are never returned — only the names of the secret keys a step
79
+ accessed.
80
+
81
+ ## 5. The audit guarantee
82
+
83
+ Because the MCP accepts only an agent-kind PAT, **every action that flows through
84
+ it is agent-attributed by construction** — there is no path that produces an
85
+ untagged, human-looking action. Each read and each drive operation is recorded in
86
+ your orchestrator's access log under your identity plus the agent label, so you
87
+ always have a complete trail of what your agent did.
88
+
89
+ Inspect that trail with `kici-admin access-log list --json` (or
90
+ `kici-admin access-log show <id>` for one entry). An agent-attributed row keeps
91
+ `actor_type` as `user` and `actor_id` as your own identity — the agent provenance
92
+ rides in the row's actor metadata as `agentLabel` (the `--name` you minted the
93
+ PAT with) and `agentPatId` (the token that acted). The label is also stored in a
94
+ dedicated `agent_label` column on every such row.
95
+
96
+ ---
97
+
98
+ ## CLI authentication
99
+
100
+ Source: https://docs.kici.dev/user/cli-auth/
101
+
102
+ The KiCI CLI supports three authentication methods: browser-based OAuth (default), device authorization flow (for headless environments), and API key paste (for CI/CD pipelines).
103
+
104
+ ## Authentication methods
105
+
106
+ ### Browser OAuth (default)
107
+
108
+ The default `kici login` flow:
109
+
110
+ 1. Opens your default browser to the KiCI identity provider
111
+ 2. You authenticate in the browser
112
+ 3. The CLI receives a token via localhost callback
113
+ 4. A personal access token (PAT) is created and stored locally
114
+
115
+ ```bash
116
+ kici login
117
+ ```
118
+
119
+ The CLI auto-detects headless environments (SSH sessions, CI runners) and switches to device flow automatically.
120
+
121
+ ### Device flow (headless)
122
+
123
+ For environments without a browser (SSH, remote servers):
124
+
125
+ ```bash
126
+ kici login --device
127
+ ```
128
+
129
+ This displays a URL and a code. Open the URL on any device, enter the code, and authenticate. The CLI polls for completion.
130
+
131
+ ### API key paste
132
+
133
+ For CI/CD pipelines and automated environments, paste an API key directly:
134
+
135
+ ```bash
136
+ kici login --token kici_sk_abc123...
137
+ ```
138
+
139
+ The API key (starts with `kici_sk_`) is passed directly as the flag value and stored in your local config file.
140
+
141
+ ## kici logout
142
+
143
+ Revoke your PAT and clear local authentication:
144
+
145
+ ```bash
146
+ kici logout
147
+ ```
148
+
149
+ This:
150
+
151
+ 1. Revokes the PAT on the server (preventing further use)
152
+ 2. Clears auth fields from the local config file
153
+ 3. Preserves non-auth settings (active org, default clusters, Platform endpoint)
154
+
155
+ ## Organization management
156
+
157
+ ### List organizations
158
+
159
+ ```bash
160
+ kici org list
161
+ ```
162
+
163
+ Shows all organizations you belong to, with your role in each. The active organization is marked with an asterisk.
164
+
165
+ ### Switch active organization
166
+
167
+ ```bash
168
+ kici org use <name-or-id>
169
+ ```
170
+
171
+ Name matching is case-insensitive. You can also use the organization ID directly.
172
+
173
+ The active organization is both the scope for org-scoped commands (`kici runs list`, `kici diagnostics`, `kici secrets list`, …) **and** the default target for `kici run remote`. After `kici login` and `kici org use <org>`, `kici run remote` dispatches to that org through the Platform — that is the complete path to a remote run. Override the target for a single run with `kici run remote --org <id>`.
174
+
175
+ If an organization has more than one connected orchestrator cluster, set its default cluster once with `kici orchestrators use <name>` (list them with `kici orchestrators list`). `kici run remote` then targets that cluster unless you pass `--orchestrator <name>`. With a single connected orchestrator the cluster is selected automatically.
176
+
177
+ ### Show current organization
178
+
179
+ ```bash
180
+ kici org current
181
+ ```
182
+
183
+ Displays the currently active organization name and ID.
184
+
185
+ ## Auth status
186
+
187
+ `kici org current` shows your current login state and active organization:
188
+
189
+ ```bash
190
+ kici org current
191
+ ```
192
+
193
+ It reports whether you are logged in and which organization is active. PAT
194
+ expiry and the full list of your tokens are managed from the dashboard (see
195
+ "Dashboard management" below).
196
+
197
+ ## Personal access tokens
198
+
199
+ Personal access tokens (PATs) are created automatically when you log in via OAuth. You can also create and manage PATs through the dashboard.
200
+
201
+ ### How PATs work
202
+
203
+ - **User-scoped**: PATs work across all organizations you belong to
204
+ - **120-day default expiry**: Configurable when creating from the dashboard
205
+ - **Named per machine**: Each login creates a PAT named after the machine hostname
206
+ - **Permission inheritance**: PATs inherit your effective role permissions in each org
207
+
208
+ ### PATs vs API keys
209
+
210
+ | | Personal access tokens | API keys |
211
+ | ---------- | ---------------------- | --------------- |
212
+ | Scope | User (cross-org) | Organization |
213
+ | Prefix | `kici_pat_` | `kici_sk_` |
214
+ | Created by | CLI login or dashboard | Dashboard |
215
+ | Expiry | 120 days (default) | No expiry |
216
+ | Use case | Developer CLI access | CI/CD pipelines |
217
+
218
+ ### Dashboard management
219
+
220
+ Create, view, and revoke PATs from the dashboard:
221
+
222
+ 1. Click your avatar in the sidebar
223
+ 2. Select **Account settings**
224
+ 3. Navigate to the **Personal access tokens** tab
225
+
226
+ From here you can:
227
+
228
+ - Create PATs with custom names and expiry periods
229
+ - View active PATs with their prefixes and expiry dates
230
+ - Revoke PATs that are no longer needed
231
+
232
+ ## Reaching the Platform API directly
233
+
234
+ The Platform exposes a versioned REST API under `/api/v1/*`. The same endpoints back the dashboard SPA, the `kici` CLI, and any third-party automation. There is no separate "public" surface — the dashboard's API is the API.
235
+
236
+ ### Base URL
237
+
238
+ | Deployment | Base URL pattern |
239
+ | ----------- | ------------------------------------------------------------------------- |
240
+ | KiCI Cloud | `https://<your-platform-host>/api/v1/` |
241
+ | Self-hosted | `https://<orchestrator-host>/<deployment-slug>/api/v1/` (slug is optional |
242
+ | | — `KICI_BASE_PATH` may add a prefix when the Platform is reverse-proxied) |
243
+
244
+ `/api/v1/*` requires authentication (see below). `/health`, `/metrics`, and `/ws` (WebSocket) sit outside that prefix and have their own access posture (`/metrics` is meant for Prometheus scrape, not public exposure).
245
+
246
+ ### Authentication
247
+
248
+ Every request to `/api/v1/*` carries an `Authorization: Bearer <token>` header. The Platform routes on the prefix:
249
+
250
+ | Prefix | Token type | Created via | Scope |
251
+ | ----------- | ----------------------------- | --------------------------------------- | ---------------- |
252
+ | `kici_pat_` | Personal access token | `kici login` or dashboard | User (cross-org) |
253
+ | `kici_sk_` | User API key | Dashboard → Settings → API keys | Org |
254
+ | `kici_sa_` | Service account key | Dashboard → Settings → Service accounts | Org |
255
+ | (other) | OIDC JWT or opaque OIDC token | OIDC login (browser SPA) | User (cross-org) |
256
+
257
+ JWT and opaque OIDC tokens are validated against the configured OIDC issuer (JWKS for JWTs, the issuer's UserInfo endpoint for opaque ones). All `kici_*` tokens are validated by SHA-256 hash lookup against the Platform DB. See [RBAC: authentication methods](https://docs.kici.dev/architecture/security/rbac/#authentication-methods) for the full model.
258
+
259
+ > **Note:** `kici_ok_` keys are **not** for the HTTP API — they authenticate orchestrator-to-Platform WebSocket connections only. Use `kici_sk_` (or `kici_pat_`) for HTTP calls.
260
+
261
+ ### Permissions
262
+
263
+ Tokens authenticate; RBAC authorizes. Every org-scoped route runs `orgContextMiddleware` (verifies you are a member of the target org) followed by `requirePermission(resource, level)`. The 17 resources and 5 levels are documented in [RBAC](https://docs.kici.dev/architecture/security/rbac/#permission-model). User API keys carry their own permission matrix bounded above by the creator's effective permissions; PATs inherit the user's role permissions (or are capped further by their `scopes` field).
264
+
265
+ ### Configurable surfaces
266
+
267
+ The dashboard is a browser SPA on top of the same `/api/v1/*` surface, so anything you can configure in the dashboard you can configure over HTTP. The mounted route groups include:
268
+
269
+ - **Auth & identity:** `/cli/exchange-token`, `/pats`, `/user`, `/identity-links`, `/github-oauth`, `/invites`, `/invites/pending`, `/invites/:inviteId/{accept,decline}`
270
+ - **Org & membership:** `/orgs`, `/orgs/:customerId`, `/orgs/:customerId/{members,roles,api-keys,orchestrator-keys,service-accounts,billing,trust-policies}`
271
+ - **Workflows & runs:** `/orgs/:customerId/{runs,registrations,workflows,held-runs,environments,secrets,global-workflows}`
272
+ - **Webhooks & event log:** `/orgs/:customerId/{sources,webhook-endpoints,event-log}`
273
+ - **Diagnostics & activity:** `/orgs/:customerId/{diagnostics,activity,access-log}`
274
+
275
+ The full route tree is the source of truth — every method, request schema, and response schema is enumerated server-side. There is currently no auto-generated OpenAPI spec; the typed `DashboardApiType` export is the canonical contract for TypeScript clients.
276
+
277
+ ### Calling the API
278
+
279
+ Two short examples — adapt the base URL and token to your deployment.
280
+
281
+ **curl (PAT or API key):**
282
+
283
+ ```bash
284
+ TOKEN="$(grep -E '^pat=' ~/.kici/config | cut -d= -f2)" # or paste a kici_sk_…
285
+ ORG="<your-org-id>"
286
+ curl -sS \
287
+ -H "Authorization: Bearer $TOKEN" \
288
+ "https://<orchestrator-host>/<deployment-slug>/api/v1/orgs/$ORG/runs?limit=5" | jq
289
+ ```
290
+
291
+ **Browser console (after dashboard login):**
292
+
293
+ ```js
294
+ const ns = Object.keys(localStorage).find((k) => k.startsWith('oidc.user:'));
295
+ const { access_token } = JSON.parse(localStorage.getItem(ns));
296
+ const res = await fetch('/<deployment-slug>/api/v1/orgs/<your-org-id>/runs?limit=5', {
297
+ headers: { Authorization: `Bearer ${access_token}` },
298
+ });
299
+ console.log(await res.json());
300
+ ```
301
+
302
+ ### Rate limits and body size
303
+
304
+ There is currently no per-token rate limit on `/api/v1/*`. A single global body-size cap applies to webhook ingress and dashboard API requests alike.
305
+
306
+ ### Audit trail
307
+
308
+ Every `/api/v1/*` mutation that touches tenant-plane data is recorded in the upstream tenant-plane audit log, stamped with the actor (user, API key, service account, or upstream operator on a break-glass support read). Reads on customer data go through the orchestrator over the WebSocket proxy and land in the orchestrator's `access_log` table. See [Audit log](https://docs.kici.dev/operator/security/audit-log/) for the orchestrator schema and the dashboard's "Activity" page for the federated view.
309
+
310
+ ## Token storage
311
+
312
+ The CLI stores authentication data in `~/.kici/config` with `0600` permissions (owner read/write only). The config file contains:
313
+
314
+ - PAT token
315
+ - PAT expiry date
316
+ - Active organization ID
317
+ - Per-org default orchestrator clusters
318
+ - Platform endpoint URL
319
+
320
+ ## Troubleshooting
321
+
322
+ ### Browser doesn't open
323
+
324
+ If `kici login` can't open a browser:
325
+
326
+ - Use `kici login --device` for the device flow
327
+ - Or set the `KICI_BROWSER_CMD` environment variable to your browser command (e.g., `KICI_BROWSER_CMD='firefox {url}'`)
328
+
329
+ ### Device flow timeout
330
+
331
+ The device flow has a 5-minute timeout. If it expires:
332
+
333
+ - Run `kici login --device` again to get a new code
334
+ - Ensure you're using the correct URL displayed by the CLI
335
+
336
+ ### Expired PAT
337
+
338
+ If you see "Personal access token has expired":
339
+
340
+ - Run `kici login` to create a new PAT
341
+ - The old expired PAT is automatically superseded
342
+
343
+ ### "Not a member" errors
344
+
345
+ If authenticated commands return 403:
346
+
347
+ - Check your active org: `kici org current`
348
+ - List available orgs: `kici org list`
349
+ - Switch to the correct org: `kici org use <name>`
350
+
351
+ ### Connection refused
352
+
353
+ If the CLI can't reach the server:
354
+
355
+ - Verify the endpoint: check `~/.kici/config` for the correct URL
356
+ - Test connectivity: `curl <your-platform-url>/health`
357
+
358
+ ---
359
+
360
+ ## CLI reference
361
+
362
+ Source: https://docs.kici.dev/user/cli-reference/
363
+
364
+ The `@kici-dev/compiler` package provides the `kici` CLI for compiling, testing, and managing workflows.
365
+
366
+ ## Installation
367
+
368
+ ```bash
369
+ pnpm add -D @kici-dev/compiler
370
+ ```
371
+
372
+ The examples use pnpm, but npm and yarn work too — `npm install -D @kici-dev/compiler` or `yarn add -D @kici-dev/compiler`.
373
+
374
+ Run commands with `npx kici` or add scripts to your `package.json`:
375
+
376
+ ```json
377
+ {
378
+ "scripts": {
379
+ "kici:compile": "kici compile",
380
+ "kici:preview": "kici preview"
381
+ }
382
+ }
383
+ ```
384
+
385
+ ## Commands
386
+
387
+ ### kici compile
388
+
389
+ Compile workflows from `.kici/workflows/` to `kici.lock.json`.
390
+
391
+ ```bash
392
+ kici compile [options]
393
+ ```
394
+
395
+ **Options:**
396
+
397
+ | Option | Default | Description |
398
+ | ------------------- | ------- | -------------------------------------------- |
399
+ | `--check` | `false` | Validate workflows without writing lock file |
400
+ | `--watch` | `false` | Watch for changes and recompile |
401
+ | `--kici-dir <path>` | `.kici` | Path to .kici directory |
402
+ | `--verbose` | `false` | Detailed output |
403
+
404
+ **Examples:**
405
+
406
+ ```bash
407
+ # Compile all workflows
408
+ kici compile
409
+
410
+ # Validate only (CI-friendly, no file writes)
411
+ kici compile --check
412
+
413
+ # Watch mode for development
414
+ kici compile --watch
415
+
416
+ # Custom .kici directory location
417
+ kici compile --kici-dir packages/app/.kici
418
+
419
+ # Verbose output for debugging
420
+ kici compile --verbose
421
+ ```
422
+
423
+ **Exit codes:**
424
+
425
+ | Code | Meaning |
426
+ | ---- | --------------------------- |
427
+ | 0 | Compilation successful |
428
+ | 1 | Compilation failed (errors) |
429
+
430
+ The `--check` flag is useful in CI pipelines and pre-commit hooks. It validates that workflows are syntactically and semantically correct without writing the lock file or any other files.
431
+
432
+ **Auto-type regeneration:** When authenticated (via `kici login`), `kici compile` automatically refreshes `.kici/types/secrets.d.ts` after each successful compilation. This keeps type declarations in sync with your orchestrator's secret contexts. The type regeneration is non-blocking -- if the orchestrator is unreachable, compilation still succeeds with a warning. The `--check` flag skips type regeneration since no files are written.
433
+
434
+ ### kici run
435
+
436
+ Execute workflows locally or remotely. The `run` command has two subcommands: `local` for direct execution without infrastructure, and `remote` for fixture-based execution through an orchestrator.
437
+
438
+ #### kici run local
439
+
440
+ Execute workflows locally without orchestrator infrastructure. Compiles workflows, matches triggers against the specified event, expands matrices, and runs jobs with DAG-based parallel scheduling.
441
+
442
+ ```bash
443
+ kici run local [event] [options]
444
+ ```
445
+
446
+ **Arguments:**
447
+
448
+ | Argument | Required | Description |
449
+ | -------- | ---------------------- | ------------------------------------------------ |
450
+ | `event` | when `--pick` is unset | Event type (e.g., `push`, `pr:open`, `schedule`) |
451
+
452
+ **Options:**
453
+
454
+ | Option | Default | Description |
455
+ | --------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
456
+ | `-p, --pick` | `false` | Interactively pick a workflow + trigger (see below) |
457
+ | `--workflow <name>` | none | Run only the specified workflow (mutex with `--pick`) |
458
+ | `--job <name>` | none | Run only the specified job (and its dependencies) |
459
+ | `--branch <name>` | detected | Override detected git branch |
460
+ | `--sha <hash>` | detected | Override detected git SHA |
461
+ | `--payload <path>` | none | Path to explicit event payload JSON file |
462
+ | `--concurrency <n>` | CPU cores | Max parallel jobs **within one run** (job-level only). Cross-run [concurrency groups](https://docs.kici.dev/user/concurrency/) declared in `workflow({ concurrency: ... })` are enforced separately — see "Concurrency enforcement" below. |
463
+ | `--keep-going` | `false` | Continue after job failure |
464
+ | `--container` | `false` | Use Podman container isolation |
465
+ | `--env <KEY=VALUE>` | none | Environment variable override (repeatable) |
466
+ | `--input <KEY=VALUE>` | none | Typed workflow-dispatch input (repeatable) — coerced + validated against the workflow's `dispatch({ inputs })` schema, exposed as `ctx.dispatchInputs` (see [triggers → typed dispatch inputs](https://docs.kici.dev/user/sdk/triggers/#typed-dispatch-inputs)) |
467
+ | `--files <path>` | git diff | Override changed file paths (repeatable, default: git diff) |
468
+ | `--quiet` | `false` | Suppress streaming output (summary only) |
469
+ | `--json` | `false` | Output structured JSON result |
470
+ | `--junit <path>` | none | Output JUnit XML result to file |
471
+ | `--debug` | `false` | Verbose internals |
472
+ | `--kici-dir <path>` | `.kici` | Path to .kici directory |
473
+ | `--in-place` | `false` | Run against the real working directory instead of an isolated tmp checkout (see "Execution isolation" below) |
474
+ | `--keep` | `false` | Always retain the isolated tmp checkout (default: keep only on failure) |
475
+ | `--check` | `false` | Run in [check mode](https://docs.kici.dev/user/idempotent-steps/): each step reports drift instead of applying; the run still exits 0 |
476
+ | `--fail-on-drift` | `false` | In check mode, exit non-zero if any step reports drift (no effect without `--check`) |
477
+
478
+ **Interactive workflow selection (`--pick` / `-p`):**
479
+
480
+ When you do not remember the event arg for a workflow, pass `--pick` (or `-p`) to open an interactive picker. It lists every workflow with a compact summary of its triggers, lets you choose one, and (for multi-trigger workflows) prompts again for which trigger to simulate. The selected trigger is converted back into an event arg and fed through the normal pipeline.
481
+
482
+ ```bash
483
+ # Open the picker across all triggerable workflows
484
+ kici run local --pick
485
+
486
+ # Scope the picker to a trigger family (e.g. only workflows that react to pr:*)
487
+ kici run local pr:open --pick
488
+ ```
489
+
490
+ Rules:
491
+
492
+ - `--pick` is mutually exclusive with `--workflow`. Passing both exits with code 2.
493
+ - When `stdin` is not a TTY, `--pick` prints the available workflows and exits without running anything — fall back to `kici run local <event> --workflow <name>` in scripts.
494
+ - Passing an event arg together with `--pick` narrows the picker to workflows that declare at least one trigger in that event family (e.g. `schedule --pick` shows only scheduled workflows).
495
+
496
+ **Concurrency enforcement:**
497
+
498
+ When the workflow declares a `concurrency` block, `kici run local` enforces it across concurrent local invocations on the same machine and user account. The behavior mirrors the orchestrator:
499
+
500
+ - The `group` callback is evaluated against the simulated event (same `{ branch, event }` context that the agent sees), and the resulting key is used as the lock identity. Throwing from `group` aborts the workflow run with a clear error — there is no fallback to the workflow name.
501
+ - `cancelInProgress: true` interrupts the holder via `SIGTERM`, then escalates to `SIGKILL` after a grace window if the holder does not exit, and proceeds with the new run.
502
+ - Otherwise the new invocation waits in FIFO order. A status line is printed when the wait starts and roughly every five seconds thereafter.
503
+ - Locks live under `$XDG_RUNTIME_DIR/kici-local-locks/` on Linux, falling back to `os.tmpdir()/kici-local-locks-<uid>/` on platforms without a per-user runtime dir. Each lock file records the holder PID, hostname, workflow name, group key, and start timestamp so concurrent invocations can describe what they are waiting for.
504
+ - Stale locks (the recorded holder PID is gone, per `process.kill(pid, 0)`) are reclaimed automatically.
505
+
506
+ Coordination is local only — running the same workflow on two different machines does not serialize across them. That requires the orchestrator.
507
+
508
+ The `SIGTERM`-to-`SIGKILL` grace window defaults to 30 000 ms. Override it with the `KICI_LOCAL_LOCK_KILL_GRACE_MS` environment variable (positive integer, milliseconds) when iterating on workflows that need longer to clean up on cancellation.
509
+
510
+ **Execution isolation:**
511
+
512
+ By default, `kici run local` executes steps inside an **isolated tmp checkout** rather than against your real working directory. Any file a step writes, builds, or deletes — and any `git` mutation a step performs — lands in that throwaway copy, so casual local runs never touch your tree.
513
+
514
+ What gets materialized into the isolated checkout has full parity with what `kici run remote` reconstructs: your current working tree minus gitignored files, with `.kiciignore` applied to local changes, over a real `.git` directory. Concretely, the checkout is built from a clone pinned to your current `HEAD`, with your local overlay (modified, staged, and untracked-but-not-ignored files) copied on top and locally-deleted files removed. Workflows that read git metadata work because the `.git` directory is present and pinned to your `HEAD`.
515
+
516
+ The path is logged at run start (for example, `running in /tmp/kici-run-ab12cd`) so you can inspect it.
517
+
518
+ Cleanup policy:
519
+
520
+ - On a fully successful run, the isolated checkout is removed.
521
+ - On failure, it is retained and its path is logged so you can inspect the failed state.
522
+ - `--keep` always retains it, even on success.
523
+ - Retained checkouts are garbage-collected after 72 hours by the next `kici run local` invocation — copy a checkout elsewhere if you need it longer.
524
+
525
+ Set the `KICI_RUN_DIR` environment variable to place the isolated checkout under a base directory other than the system temp directory.
526
+
527
+ Secrets are always sourced from your real `.kici/` directory, not from the isolated checkout. Gitignored secret files (such as `.kici/.env.local` and `.kici/secrets.yaml`) are never copied into the checkout, so a step that reads a secret still gets it from the original location.
528
+
529
+ Pass `--in-place` to run against the real working directory instead — useful when you explicitly want in-tree execution. `--in-place` requires no git repository; the default isolated mode does, and fails with an actionable error pointing at `--in-place` when the directory is not a git repository.
530
+
531
+ **Examples:**
532
+
533
+ ```bash
534
+ # Run workflows matching a push event
535
+ kici run local push
536
+
537
+ # Run only a specific workflow
538
+ kici run local push --workflow ci
539
+
540
+ # Run only a specific job (and its dependencies)
541
+ kici run local push --job test
542
+
543
+ # JSON output for CI scripting
544
+ kici run local push --json
545
+
546
+ # JUnit XML for CI integration
547
+ kici run local push --junit results.xml
548
+
549
+ # Quiet mode (summary only, no streaming)
550
+ kici run local push --quiet
551
+
552
+ # Override branch and SHA
553
+ kici run local push --branch main --sha abc1234
554
+
555
+ # Environment variable overrides
556
+ kici run local push --env NODE_ENV=test --env CI=true
557
+
558
+ # Continue running other jobs after one fails
559
+ kici run local push --keep-going
560
+ ```
561
+
562
+ **Exit codes:**
563
+
564
+ | Code | Meaning |
565
+ | ---- | ----------------------- |
566
+ | 0 | All workflows succeeded |
567
+ | 1 | One or more jobs failed |
568
+
569
+ **Output formats:**
570
+
571
+ - **Default:** Streaming job output during execution, followed by a tree-format summary with per-step timing
572
+ - **`--json`:** Structured JSON with workflows, jobs, steps, timing, and matrix values
573
+ - **`--junit <path>`:** Standard JUnit XML for CI integration (Jenkins, GitLab, etc.)
574
+ - **`--quiet`:** Summary only, no streaming output during execution
575
+
576
+ #### kici run remote
577
+
578
+ Execute fixtures remotely through the full CI pipeline. Fixtures are defined in `.kici/tests/*.ts` using the `fixture()` factory function. Without arguments, lists available fixtures.
579
+
580
+ Remote runs route through the Platform. Authenticate with a personal access token (`kici login`), then target an organization with `kici org use <org>` or the `--org` flag. The Platform relays the run to the org's orchestrator, while your working-tree overlay uploads directly to object storage — see [How the run is routed](https://docs.kici.dev/user/cli-reference/#how-the-run-is-routed) and [The two planes](https://docs.kici.dev/user/cli-reference/#the-two-planes) below.
581
+
582
+ Like `kici run local`, `kici run remote` recompiles your workflows (`.kici/workflows` → `kici.lock.json`) before dispatching, so the orchestrator matches and dispatches against your current workflow definitions — a brand-new or edited workflow takes effect without a separate `kici compile`. A compile or validation error aborts the run before anything is uploaded.
583
+
584
+ The orchestrator must have **cache storage configured** (`KICI_STORAGE_TYPE` = `s3` or `filesystem`) with a dev-reachable upload endpoint so the CLI's direct upload succeeds; see the [testing guide](https://docs.kici.dev/user/testing-guide/) and [Storage layout](https://docs.kici.dev/operator/orchestrator/storage-layout/) for setup.
585
+
586
+ ```bash
587
+ kici run remote [fixture] [options]
588
+ ```
589
+
590
+ **Arguments:**
591
+
592
+ | Argument | Required | Description |
593
+ | --------- | -------- | ----------------------------------------------- |
594
+ | `fixture` | no | Fixture name or glob pattern (omit to list all) |
595
+
596
+ **Options:**
597
+
598
+ | Option | Default | Description |
599
+ | --------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
600
+ | `--org <id>` | active | Target organization for this run (overrides `kici org use`) |
601
+ | `--orchestrator <name>` | default | Target orchestrator cluster within the org (overrides the per-org default) |
602
+ | `--all` | `false` | Run all fixtures |
603
+ | `-p, --pick` | `false` | Interactively pick fixtures to run (multi-select; mutex with a fixture arg, `--all`, and `--workflow`) |
604
+ | `--workflow <name>` | none | Run a specific workflow directly (bypass triggers) |
605
+ | `--parallel` | `false` | Run multiple fixtures concurrently |
606
+ | `--no-wait` | - | Fire and forget (print runIds, don't stream) |
607
+ | `--quiet` | `false` | Minimal output (only final result) |
608
+ | `--json` | `false` | Machine-readable JSON output |
609
+ | `--junit <path>` | none | JUnit XML output to file for CI integration |
610
+ | `--history` | `false` | Show table of recent test runs |
611
+ | `--context <ctx.key=value>` | none | Inject a namespaced context secret, uploaded encrypted (repeatable) |
612
+ | `--env <KEY=VALUE>` | none | Provide a per-run secret, uploaded encrypted (repeatable) — see [testing guide](https://docs.kici.dev/user/testing-guide/) |
613
+ | `--input <KEY=VALUE>` | none | Typed workflow-dispatch input (repeatable) — validated + coerced + defaulted on the orchestrator from the lock descriptor, exposed as `ctx.dispatchInputs` (see [triggers → typed dispatch inputs](https://docs.kici.dev/user/sdk/triggers/#typed-dispatch-inputs)) |
614
+ | `--target <selector>` | none | Narrow `runsOnAll` jobs to hosts matching this label selector (repeatable, AND-combined) |
615
+ | `--target-allow-empty` | `false` | A `--target` that narrows a `runsOnAll` job to zero hosts skips it instead of failing |
616
+ | `--debug` | `false` | Verbose internals |
617
+ | `--kici-dir <path>` | `.kici` | Path to .kici directory |
618
+ | `--routing-key <key>` | none | Override the routing key for this run (advanced; selecting the org normally suffices — see [How the run is routed](https://docs.kici.dev/user/cli-reference/#how-the-run-is-routed)) |
619
+ | `--check` | `false` | Run in [check mode](https://docs.kici.dev/user/idempotent-steps/): each step reports drift instead of applying |
620
+ | `--fail-on-drift` | `false` | In check mode, fail the run if any step reports drift (no effect without `--check`) |
621
+ | `--approve-all, --yes` | `false` | Auto-approve every [approval gate](https://docs.kici.dev/user/approvals/) this run holds on (run-scoped; eligibility still enforced) |
622
+
623
+ **Examples:**
624
+
625
+ ```bash
626
+ # List available fixtures
627
+ kici run remote
628
+
629
+ # Run a single fixture against the active org
630
+ kici run remote push-main
631
+
632
+ # Target a specific org for this run
633
+ kici run remote push-main --org xyz789ghi012
634
+
635
+ # Target a specific orchestrator cluster within the org
636
+ kici run remote push-main --orchestrator us-east
637
+
638
+ # Run all push-related fixtures
639
+ kici run remote 'push-*'
640
+
641
+ # Run everything
642
+ kici run remote --all
643
+
644
+ # Run a specific workflow directly (bypass trigger matching)
645
+ kici run remote --workflow ci
646
+
647
+ # Quiet mode -- just pass/fail
648
+ kici run remote push-main --quiet
649
+
650
+ # JSON output for scripting
651
+ kici run remote push-main --json
652
+
653
+ # Fire and forget
654
+ kici run remote push-main --no-wait
655
+
656
+ # View recent test run history
657
+ kici run remote --history
658
+
659
+ # Interactively pick which fixtures to run (multi-select)
660
+ kici run remote --pick
661
+
662
+ # Narrow runsOnAll jobs to a subset of the host roster
663
+ kici run remote deploy --target role:web
664
+
665
+ # AND-combine repeated --target values (hosts must match every selector)
666
+ kici run remote deploy --target role:web --target dc:eu
667
+
668
+ # Skip a runsOnAll job instead of failing it when the target matches no host
669
+ kici run remote deploy --target role:gpu --target-allow-empty
670
+ ```
671
+
672
+ **Interactive fixture selection (`--pick` / `-p`):**
673
+
674
+ Pass `--pick` (or `-p`) to open an interactive checkbox menu of the available
675
+ fixtures. Toggle one or more with space, confirm with enter, and the selected
676
+ fixtures run through the normal remote pipeline (honoring `--parallel`,
677
+ `--no-wait`, and the other run flags). Notes:
678
+
679
+ - `--pick` is mutually exclusive with a fixture argument, `--all`, and
680
+ `--workflow`. Passing any together exits with code 2.
681
+ - When `stdin` is not a TTY, `--pick` prints the available fixtures and exits
682
+ without running anything — pass a fixture name (or `--all`) in scripts.
683
+
684
+ #### Host narrowing with `--target`
685
+
686
+ `--target <selector>` is a runtime narrowing for `runsOnAll` jobs, analogous to
687
+ Ansible's `--limit`. A `runsOnAll` job normally fans out to **every** roster host
688
+ matching its predicate, one pinned execution per host. `--target` intersects that
689
+ matched roster with a label selector, so the effective host set is
690
+ `runsOnAll ∩ target`:
691
+
692
+ - **Narrow-only.** `--target` can only _remove_ hosts from the matched set, never
693
+ add them. The widening dimension (OR across host groups) lives in the workflow's
694
+ `runsOnAll`; `--target` only subtracts.
695
+ - **Run-global, `runsOnAll`-only.** A single `--target` applies to every
696
+ `runsOnAll` job in the run. Jobs pinned to a single host with `runsOn` are
697
+ untouched.
698
+ - **Repeatable and AND-combined.** Each `--target` value is its own selector; a
699
+ host must satisfy **all** of them to survive the narrowing. Use a single value
700
+ for an OR-style match within one selector and repeated values for AND.
701
+ - **Selector syntax** matches `runsOn`: an exact label (`role:web`), a glob
702
+ (`role:*`), or a regex (`/^box-0[1-3]$/`).
703
+
704
+ When `--target` narrows a `runsOnAll` job to zero hosts, the default is to **fail**
705
+ the run (fail-loud — a typo in the selector shouldn't silently skip work). Pass
706
+ `--target-allow-empty` to **skip** the zeroed job instead; the job records a
707
+ `skipped` status, and any downstream job that needs it with `when: 'on-skip'` (or
708
+ `when: 'always'`) still runs. See [Job dependencies](https://docs.kici.dev/user/sdk/core/#job-dependencies-needs)
709
+ for the `when` gating model.
710
+
711
+ **Exit codes:**
712
+
713
+ | Code | Meaning |
714
+ | ---- | ---------------------------- |
715
+ | 0 | All matched workflows passed |
716
+ | 1 | One or more workflows failed |
717
+
718
+ #### How the run is routed
719
+
720
+ A remote run is dispatched to your **active organization** — the one set with `kici org use <org>`, or overridden per-run with `--org <id>`. The org is resolved in this order:
721
+
722
+ 1. The `--org <id>` flag, if provided.
723
+ 2. Otherwise the active org saved in your global config by `kici org use <org>`.
724
+ 3. If neither is set, the command errors and asks you to select an org with `kici org use` or pass `--org`.
725
+
726
+ The orchestrator anchors the org without any manual webhook source: it auto-provisions a system-managed **remote source** (routing key `remote:<orgId>`) that maps to its bound organization, so even a zero-source org is immediately routable for remote runs. You never set a routing key for a remote run — selecting the org is enough.
727
+
728
+ When an org has more than one connected orchestrator cluster, the CLI picks the target cluster in this order:
729
+
730
+ 1. The `--orchestrator <name>` flag, if provided.
731
+ 2. Otherwise the per-org default cluster, set with `kici orchestrators use <name>`.
732
+ 3. If the org has exactly **one** connected orchestrator, it is auto-selected.
733
+ 4. Otherwise the run errors with the list of connected clusters, and you pass `--orchestrator <name>` to choose one. Run `kici orchestrators list` to see the available cluster names.
734
+
735
+ #### The two planes
736
+
737
+ `kici run remote` uses two independent paths:
738
+
739
+ - **Control plane** — run initiation, trigger, status, log retrieval, and cancellation flow from your machine through the Platform, which relays them over a WebSocket connection to the org's orchestrator. Logs are delivered by the CLI polling the Platform for log chunks (tracked by a monotonic line cursor) and run status until the run reaches a terminal state; there is no direct streaming socket to the orchestrator.
740
+ - **Data plane** — your working-tree overlay tarball uploads **directly** from your machine to the orchestrator's object store via a presigned PUT URL. The overlay never passes through the Platform. This is why the orchestrator's object-store upload endpoint must be reachable from your machine; see [Storage layout](https://docs.kici.dev/operator/orchestrator/storage-layout/).
741
+
742
+ An orchestrator with no Platform connection cannot serve remote runs — the Platform is the service that offers them. For executing workflow steps on your own machine without an orchestrator (no scaler, agents, or environments), use [`kici run local`](https://docs.kici.dev/user/cli-reference/#kici-run-local).
743
+
744
+ #### Fresh repos (no GitHub remote)
745
+
746
+ `kici run remote` works even if the repo has never been pushed to GitHub. When no remote is detected:
747
+
748
+ - The entire repo content is uploaded (not just a diff overlay)
749
+ - The lock file is sent inline (no GitHub API fetch)
750
+ - Steps that use git commands will fail (no `.git` directory in the remote workspace)
751
+ - Build cache (`__build__` jobs) is skipped for local repos
752
+ - Environments must have `allowLocalExecution: true` to be accessible from local runs (default is `false`)
753
+
754
+ Destination routing is unchanged for fresh repos: the run still goes to your active org through the Platform.
755
+
756
+ For a detailed guide on writing fixtures, configuring secrets, and understanding the upload flow, see [Testing guide](https://docs.kici.dev/user/testing-guide/).
757
+
758
+ #### kici orchestrators
759
+
760
+ List the orchestrator clusters connected to an organization, and set the per-org default cluster used by `kici run remote`. Requires `kici login` and an active org (or pass `--org`).
761
+
762
+ ```bash
763
+ kici orchestrators list [--org <id>]
764
+ kici orchestrators use <clusterName> [--org <id>]
765
+ ```
766
+
767
+ **`kici orchestrators list`** prints the org's connected orchestrator clusters, so you know what to pass to `--orchestrator` (or to `kici orchestrators use`).
768
+
769
+ **`kici orchestrators use <clusterName>`** sets the default orchestrator cluster for the org, stored per-org in your global config. Subsequent `kici run remote` invocations target that cluster unless overridden with `--orchestrator`.
770
+
771
+ **Examples:**
772
+
773
+ ```bash
774
+ # List the active org's connected clusters
775
+ kici orchestrators list
776
+
777
+ # List a specific org's clusters
778
+ kici orchestrators list --org xyz789ghi012
779
+
780
+ # Set the default cluster for the active org
781
+ kici orchestrators use us-east
782
+
783
+ # Set the default cluster for a specific org
784
+ kici orchestrators use us-east --org xyz789ghi012
785
+ ```
786
+
787
+ ### kici preview
788
+
789
+ Preview which workflows match a trigger event (dry-run, no execution). Useful for verifying trigger configurations during development.
790
+
791
+ ```bash
792
+ kici preview [event] [options]
793
+ ```
794
+
795
+ **Arguments:**
796
+
797
+ | Argument | Required | Description |
798
+ | -------- | -------- | ----------------------------------------------------------- |
799
+ | `event` | no | Event type to preview (e.g., `push`, `pr:open`, `schedule`) |
800
+
801
+ **Options:**
802
+
803
+ | Option | Default | Description |
804
+ | --------------------------- | ------- | ------------------------------------------------------------ |
805
+ | `--workflow <name>` | none | Filter to specific workflow |
806
+ | `--job <name>` | none | Filter to specific job |
807
+ | `--branch <name>` | `main` | Override target branch for trigger matching |
808
+ | `--sha <hash>` | none | Override commit SHA |
809
+ | `--files <path>` | none | Simulate changed file path for trigger matching (repeatable) |
810
+ | `--secret <key=value>` | none | Inject flat secret (repeatable) |
811
+ | `--context <ctx.key=value>` | none | Inject context secret (repeatable) |
812
+ | `--debug` | `false` | Verbose internals |
813
+ | `--kici-dir <path>` | `.kici` | Path to .kici directory |
814
+
815
+ **Examples:**
816
+
817
+ ```bash
818
+ # Preview which workflows match a push event
819
+ kici preview push
820
+
821
+ # Preview PR trigger matching
822
+ kici preview pr:open
823
+
824
+ # Preview with branch override
825
+ kici preview push --branch develop
826
+
827
+ # Filter to specific workflow
828
+ kici preview push --workflow ci
829
+
830
+ # Simulate changed files for path-filtered triggers
831
+ kici preview push --files src/index.ts --files README.md
832
+ ```
833
+
834
+ **Exit codes:**
835
+
836
+ | Code | Meaning |
837
+ | ---- | ------------------------------------------ |
838
+ | 0 | Preview completed (including zero matches) |
839
+ | 1 | Error |
840
+
841
+ **Migration from the old `test` command:** The dry-run preview command was renamed from `test` to `preview`. If you were using the old `test` command with a fixture name for remote fixture execution, use `kici run remote <fixture-name>` instead. For local workflow execution, use `kici run local <event>`.
842
+
843
+ ### kici login
844
+
845
+ Authenticate with KiCI via browser-based OAuth (default) or API key (`--token`).
846
+
847
+ By default, `kici login` opens your browser for OIDC authentication using PKCE. In headless environments (SSH, CI, containers), it automatically switches to the RFC 8628 device authorization flow where you visit a URL and enter a code.
848
+
849
+ After OAuth, the CLI exchanges the OIDC token for a personal access token (PAT) stored in the config directory (`~/.kici/config` by default, overridable with `KICI_CONFIG_DIR`).
850
+
851
+ `kici login` targets the hosted KiCI Platform by default. To authenticate against another environment (a self-hosted Platform, for example), pass `--platform-endpoint` / `--oidc-issuer` or set `KICI_PLATFORM_URL` / `KICI_OIDC_ISSUER`. Login persists the platform endpoint and OIDC issuer it authenticated against alongside the PAT, so a saved PAT always matches its endpoint. Because the config describes one environment at a time, **switching the endpoint resets the active organization and default clusters** — re-run `kici org use <name>` after switching environments.
852
+
853
+ ```bash
854
+ kici login [options]
855
+ ```
856
+
857
+ **Options:**
858
+
859
+ | Option | Default | Description |
860
+ | --------------------------- | ------- | --------------------------------------------------- |
861
+ | `--token <key>` | none | API key for direct authentication (legacy) |
862
+ | `--device` | false | Force device authorization flow (headless/SSH) |
863
+ | `--platform-endpoint <url>` | none | Platform relay URL |
864
+ | `--oidc-issuer <url>` | none | OIDC issuer URL (selects a non-default environment) |
865
+ | `--routing-key <key>` | none | Routing key for webhook source identification |
866
+
867
+ **Environment variables:**
868
+
869
+ | Variable | Default | Description |
870
+ | --------------------- | -------------------------------------------- | ---------------------------------------------------------------------- |
871
+ | `KICI_PLATFORM_URL` | `https://api.kici.dev` | Platform API base URL (override for a self-hosted Platform) |
872
+ | `KICI_OIDC_ISSUER` | `https://auth.kici.dev/realms/kici-internal` | OIDC issuer URL (override for a self-hosted Platform) |
873
+ | `KICI_OIDC_CLIENT_ID` | `kici-cli` | OIDC client ID (override for a self-hosted Platform) |
874
+ | `KICI_BROWSER_CMD` | uses `open` package | Custom browser command with `{url}` placeholder, or `none` to suppress |
875
+ | `KICI_CALLBACK_PORT` | random | Fixed port for OAuth PKCE callback server |
876
+ | `KICI_CONFIG_DIR` | `~/.kici` | Override config directory |
877
+
878
+ **Examples:**
879
+
880
+ ```bash
881
+ # Browser-based OAuth login (default)
882
+ kici login
883
+
884
+ # Force device flow (for SSH/headless)
885
+ kici login --device
886
+
887
+ # Legacy API key login
888
+ kici login --token kici_sk_abc123...
889
+
890
+ # Log in against a self-hosted Platform
891
+ kici login --platform-endpoint https://platform.example.com \
892
+ --oidc-issuer https://auth.example.com/realms/kici-internal
893
+
894
+ # Suppress browser opening (print authorize URL to stdout)
895
+ KICI_BROWSER_CMD=none kici login
896
+
897
+ # Use custom browser command
898
+ KICI_BROWSER_CMD='firefox {url}' kici login
899
+
900
+ # Fixed callback port and custom config directory
901
+ KICI_CALLBACK_PORT=19876 KICI_CONFIG_DIR=/tmp/kici-test kici login
902
+ ```
903
+
904
+ **Headless detection:** The CLI automatically detects headless environments by checking for `SSH_CLIENT`, `SSH_TTY`, `CI`, `GITHUB_ACTIONS`, `GITLAB_CI`, `container`, or `DOCKER_CONTAINER` env vars, and on Linux, the absence of `DISPLAY` and `WAYLAND_DISPLAY`.
905
+
906
+ ### kici logout
907
+
908
+ Revoke your personal access token on the server and clear local credentials.
909
+
910
+ If the server is unreachable, local credentials are still cleared (the PAT will expire automatically). Non-auth config fields (active org, default clusters, etc.) are preserved.
911
+
912
+ ```bash
913
+ kici logout
914
+ ```
915
+
916
+ **Examples:**
917
+
918
+ ```bash
919
+ # Log out and revoke PAT
920
+ kici logout
921
+ ```
922
+
923
+ ### kici org
924
+
925
+ Manage organization context. Requires a PAT (run `kici login` first).
926
+
927
+ #### kici org list
928
+
929
+ List organizations you belong to. The active org is marked with a star (`*`).
930
+
931
+ ```bash
932
+ kici org list
933
+ ```
934
+
935
+ **Example output:**
936
+
937
+ ```
938
+ Organizations:
939
+
940
+ * Personal (owner) abc123def456
941
+ My team (admin) xyz789ghi012
942
+ ```
943
+
944
+ #### kici org use
945
+
946
+ Switch the active organization by name (case-insensitive) or ID.
947
+
948
+ ```bash
949
+ kici org use <name>
950
+ ```
951
+
952
+ **Arguments:**
953
+
954
+ | Argument | Required | Description |
955
+ | -------- | -------- | ----------------------- |
956
+ | `name` | yes | Organization name or ID |
957
+
958
+ **Examples:**
959
+
960
+ ```bash
961
+ # Switch by name
962
+ kici org use "My team"
963
+
964
+ # Switch by ID
965
+ kici org use xyz789ghi012
966
+ ```
967
+
968
+ #### kici org current
969
+
970
+ Show the current active organization.
971
+
972
+ ```bash
973
+ kici org current
974
+ ```
975
+
976
+ ### kici diagnostics
977
+
978
+ Show the orchestrators, scalers, and agents serving your organization — the
979
+ terminal equivalent of the dashboard Diagnostics page. Reads the same
980
+ org-scoped data the dashboard does, so it needs `kici login` and an active org
981
+ (`kici org use <name>`).
982
+
983
+ The output has three parts: a one-line header (runs in the last 24h, success
984
+ rate, average duration, queued/running job counts), any infrastructure alerts
985
+ (only shown when present), and a tree of each orchestrator with its scalers and
986
+ agents. Each agent line shows its labels, platform/architecture, active/maximum
987
+ concurrency, and heartbeat age.
988
+
989
+ ```bash
990
+ kici diagnostics [options]
991
+ ```
992
+
993
+ **Options:**
994
+
995
+ | Option | Default | Description |
996
+ | --------------------- | ------- | --------------------------------------------------- |
997
+ | `--json` | `false` | Machine-readable JSON output |
998
+ | `--verbose` | `false` | Show extended per-agent fields (host, node, memory) |
999
+ | `--orchestrator <id>` | all | Scope the tree to one orchestrator connection id |
1000
+
1001
+ **Examples:**
1002
+
1003
+ ```bash
1004
+ # Show the full infrastructure tree
1005
+ kici diagnostics
1006
+
1007
+ # Extended per-agent detail
1008
+ kici diagnostics --verbose
1009
+
1010
+ # Only one orchestrator's scalers and agents
1011
+ kici diagnostics --orchestrator conn-abc123
1012
+
1013
+ # Machine-readable output
1014
+ kici diagnostics --json
1015
+ ```
1016
+
1017
+ ### kici runs
1018
+
1019
+ Inspect and manage execution runs from the terminal — the equivalent of the
1020
+ dashboard Runs page. All `kici runs` subcommands read/write the same org-scoped
1021
+ data as the dashboard, so they require `kici login` and an active org
1022
+ (`kici org use <name>`).
1023
+
1024
+ #### kici runs list
1025
+
1026
+ List runs with optional filters. Output is a table (run id, workflow, status,
1027
+ branch, trigger, started, duration); pagination is reported at the bottom.
1028
+
1029
+ ```bash
1030
+ kici runs list [options]
1031
+ ```
1032
+
1033
+ **Options:**
1034
+
1035
+ | Option | Default | Description |
1036
+ | ----------------------- | ------- | --------------------------------------------- |
1037
+ | `--status <s>` | all | Filter by run status |
1038
+ | `--workflow <w>` | all | Filter by workflow name |
1039
+ | `--branch <b>` | all | Filter by branch/ref |
1040
+ | `--repo <r>` | all | Filter by repository |
1041
+ | `--trigger <t>` | all | Filter by trigger type |
1042
+ | `--source <routingKey>` | all | Filter by source routing key |
1043
+ | `--since <ts>` | none | Only runs since this ISO-8601 or epoch ms |
1044
+ | `--page <n>` | `1` | Page number (server page size is fixed at 20) |
1045
+ | `--json` | `false` | Machine-readable JSON output |
1046
+
1047
+ ```bash
1048
+ kici runs list
1049
+ kici runs list --status running
1050
+ kici runs list --workflow ci --branch main
1051
+ kici runs list --json | jq '.runs[].runId'
1052
+ ```
1053
+
1054
+ #### kici runs show
1055
+
1056
+ Show a run's summary header plus its jobs-and-steps tree (name, status,
1057
+ duration, exit code). If the run id is not on the Platform but exists in your
1058
+ local run history (from `kici run local`), the local record is shown instead.
1059
+
1060
+ ```bash
1061
+ kici runs show <run-id> [options]
1062
+ ```
1063
+
1064
+ | Option | Default | Description |
1065
+ | -------- | ------- | ---------------------------- |
1066
+ | `--json` | `false` | Machine-readable JSON output |
1067
+
1068
+ ```bash
1069
+ kici runs show abc123
1070
+ kici runs show abc123 --json
1071
+ ```
1072
+
1073
+ #### kici runs logs
1074
+
1075
+ Print each job/step's log lines in order, with headers.
1076
+
1077
+ ```bash
1078
+ kici runs logs <run-id> [options]
1079
+ ```
1080
+
1081
+ | Option | Default | Description |
1082
+ | -------------- | ------- | -------------------------------------- |
1083
+ | `--job <name>` | all | Only print logs for this job |
1084
+ | `-f, --follow` | `false` | Tail logs for a live run until it ends |
1085
+ | `--json` | `false` | Machine-readable JSON output |
1086
+
1087
+ ```bash
1088
+ kici runs logs abc123
1089
+ kici runs logs abc123 --job build
1090
+ kici runs logs abc123 --follow
1091
+ ```
1092
+
1093
+ #### kici runs rerun
1094
+
1095
+ Re-trigger a completed run. Prints the new run id. The server enforces a short
1096
+ cooldown between reruns of the same run.
1097
+
1098
+ ```bash
1099
+ kici runs rerun <run-id> [options]
1100
+ ```
1101
+
1102
+ | Option | Default | Description |
1103
+ | -------- | ------- | ---------------------------- |
1104
+ | `--json` | `false` | Machine-readable JSON output |
1105
+
1106
+ ```bash
1107
+ kici runs rerun abc123
1108
+ ```
1109
+
1110
+ #### kici runs cancel
1111
+
1112
+ Cancel a single run, or all in-progress runs on a branch.
1113
+
1114
+ ```bash
1115
+ kici runs cancel [run-id] [options]
1116
+ ```
1117
+
1118
+ | Argument | Required | Description |
1119
+ | -------- | -------- | ---------------- |
1120
+ | `run-id` | no | Run ID to cancel |
1121
+
1122
+ | Option | Default | Description |
1123
+ | ----------------- | ------- | ------------------------------------------- |
1124
+ | `--force` | `false` | Force cancel (kill immediately, skip hooks) |
1125
+ | `--branch <name>` | none | Cancel all in-progress runs on this branch |
1126
+
1127
+ ```bash
1128
+ kici runs cancel abc123
1129
+ kici runs cancel abc123 --force
1130
+ kici runs cancel --branch feature/wip
1131
+ ```
1132
+
1133
+ When `--json` is set on any of these commands, `kici` emits only the JSON
1134
+ document on stdout — the `kici v<version>` banner is suppressed — so the output
1135
+ is safe to pipe into `jq` or `JSON.parse`. The same holds for the other
1136
+ `--json` commands (`kici run remote --json`, `kici workflows list --json`) and
1137
+ for `--quiet`.
1138
+
1139
+ ### kici approve
1140
+
1141
+ Approve a held [approval gate](https://docs.kici.dev/user/approvals/) so the run resumes. Identify the held element by run ID, optionally narrowed to a job and step.
1142
+
1143
+ ```bash
1144
+ kici approve <run-id> [options]
1145
+ ```
1146
+
1147
+ **Arguments:**
1148
+
1149
+ | Argument | Required | Description |
1150
+ | -------- | -------- | ---------------------------------- |
1151
+ | `run-id` | yes | Run ID holding the gate to approve |
1152
+
1153
+ **Options:**
1154
+
1155
+ | Option | Default | Description |
1156
+ | ---------------- | ------- | ---------------------------------------------------- |
1157
+ | `--job <name>` | none | Approve a held job (omit for a workflow-level hold) |
1158
+ | `--step <index>` | none | Approve a held step by its index (used with `--job`) |
1159
+
1160
+ **Examples:**
1161
+
1162
+ ```bash
1163
+ # Approve a workflow-level hold
1164
+ kici approve abc123
1165
+
1166
+ # Approve a held job
1167
+ kici approve abc123 --job deploy-production
1168
+
1169
+ # Approve a held step (steps are addressed by index)
1170
+ kici approve abc123 --job migrate-and-deploy --step 1
1171
+ ```
1172
+
1173
+ You must be eligible for at least one unsatisfied clause (a member of a named team, or a named user) and hold the `environments:write` or `ci_trust:write` permission. The command reports whether the element was released, how many clauses remain, or that it was rejected.
1174
+
1175
+ ### kici reject
1176
+
1177
+ Reject a held [approval gate](https://docs.kici.dev/user/approvals/). A rejection fails the held element and the run. A reason is required.
1178
+
1179
+ ```bash
1180
+ kici reject <run-id> --reason <text> [options]
1181
+ ```
1182
+
1183
+ **Arguments:**
1184
+
1185
+ | Argument | Required | Description |
1186
+ | -------- | -------- | --------------------------------- |
1187
+ | `run-id` | yes | Run ID holding the gate to reject |
1188
+
1189
+ **Options:**
1190
+
1191
+ | Option | Default | Description |
1192
+ | ----------------- | ------- | --------------------------------------------------- |
1193
+ | `--reason <text>` | none | Required. Reason recorded with the rejection |
1194
+ | `--job <name>` | none | Reject a held job (omit for a workflow-level hold) |
1195
+ | `--step <index>` | none | Reject a held step by its index (used with `--job`) |
1196
+
1197
+ **Examples:**
1198
+
1199
+ ```bash
1200
+ # Reject a held job with a reason
1201
+ kici reject abc123 --job deploy-production --reason "Wrong release branch"
1202
+ ```
1203
+
1204
+ ### kici secrets list
1205
+
1206
+ List secret contexts available for test runs. Shows context names and key names (not values).
1207
+
1208
+ ```bash
1209
+ kici secrets list
1210
+ ```
1211
+
1212
+ Each "context" corresponds to an environment configured on the orchestrator. The output lists every environment whose `allowLocalExecution` flag is `true` (the gate that lets CLI-initiated test runs resolve secrets through that environment), along with the secret key names reachable from the environment's bound scopes.
1213
+
1214
+ Only key names are shown — secret values are never returned over this endpoint.
1215
+
1216
+ **Prerequisites:** authenticate via `kici login` and select an active organization with `kici org use <name>`.
1217
+
1218
+ ### kici pat create
1219
+
1220
+ Mint a personal access token under your own identity. Pass `--agent` to mint an
1221
+ **agent-kind** PAT — the credential a coding agent points the KiCI MCP server at.
1222
+
1223
+ ```bash
1224
+ kici pat create --agent --name "claude-code"
1225
+ ```
1226
+
1227
+ - `--agent` marks the token as agent-kind. An agent PAT inherits your
1228
+ permissions unchanged (it carries provenance, not extra authority) and is the
1229
+ **only** credential the MCP server accepts.
1230
+ - `--name <label>` sets the token name. For an agent PAT this is the **agent
1231
+ label** recorded on every action the agent takes — required with `--agent`.
1232
+ - `--expires-in-days <n>` overrides the default expiry.
1233
+
1234
+ The token is printed once — save it immediately; it cannot be retrieved later.
1235
+ See [Drive KiCI from your coding agent](https://docs.kici.dev/user/ai-agents/) for the full setup.
1236
+
1237
+ **Prerequisites:** authenticate via `kici login` first.
1238
+
1239
+ ### kici types
1240
+
1241
+ Generate TypeScript declaration files from orchestrator environment metadata. The generated `.d.ts` file augments the SDK's `KnownSecretKeys` and `EnvironmentSecrets` interfaces, providing compile-time autocomplete and type checking for secret key names.
1242
+
1243
+ ```bash
1244
+ kici types [options]
1245
+ ```
1246
+
1247
+ **Options:**
1248
+
1249
+ | Option | Default | Description |
1250
+ | ------------------- | ------- | ----------------------- |
1251
+ | `--kici-dir <path>` | `.kici` | Path to .kici directory |
1252
+
1253
+ **Prerequisites:** Must be authenticated via `kici login`.
1254
+
1255
+ **Output:** `.kici/types/secrets.d.ts`
1256
+
1257
+ **Examples:**
1258
+
1259
+ ```bash
1260
+ # Generate types from orchestrator
1261
+ kici types
1262
+
1263
+ # Use custom .kici directory
1264
+ kici types --kici-dir packages/app/.kici
1265
+ ```
1266
+
1267
+ **How it works:**
1268
+
1269
+ 1. Fetches all environment metadata (environment names and secret key names) from the orchestrator
1270
+ 2. Generates a `.d.ts` file that augments `@kici-dev/sdk`'s `KnownSecretKeys` and `EnvironmentSecrets` interfaces
1271
+ 3. Writes the file to `.kici/types/secrets.d.ts`
1272
+
1273
+ After generating types, `ctx.secrets.get('MY_KEY')` and `ctx.secrets.expose('DB_HOST')` gain autocomplete and type checking in your IDE.
1274
+
1275
+ **Git workflow:** Commit the generated `.kici/types/secrets.d.ts` so team members get type checking without needing orchestrator access. Run `kici types` to refresh when environments change.
1276
+
1277
+ **Auto-regeneration:** `kici compile` automatically runs `kici types` after successful compilation when authenticated. See the [kici compile](https://docs.kici.dev/user/cli-reference/#kici-compile) section for details.
1278
+
1279
+ **Escape hatch:** For dynamic keys not in the generated types, use a cast: `(ctx.secrets as any).DYNAMIC_KEY`.
1280
+
1281
+ ### kici fixture
1282
+
1283
+ Generate a fixture template for an event type. Useful for creating custom test payloads.
1284
+
1285
+ ```bash
1286
+ kici fixture <event> [options]
1287
+ ```
1288
+
1289
+ **Arguments:**
1290
+
1291
+ | Argument | Required | Description |
1292
+ | -------- | -------- | ----------------------------- |
1293
+ | `event` | yes | Event to generate fixture for |
1294
+
1295
+ **Valid events:** `pr:open`, `pr:sync`, `pr:close`, `pr:reopen`, `push`, `tag`, `comment`, `review`, `review_comment`, `release`, `dispatch`, `create`, `delete`, `status`, `workflow_run`, `fork`, `star`, `watch`, `kici_event`, `workflow_complete`, `job_complete`, `generic_webhook`, `schedule`, `lifecycle` (many support `:action` suffixes, e.g. `comment:edited`, `release:published`, `lifecycle:workflow_complete`). `webhook:<source>` is a shorthand alias for `generic_webhook:<source>`.
1296
+
1297
+ **Options:**
1298
+
1299
+ | Option | Default | Description |
1300
+ | ----------------- | ------- | ------------------------------- |
1301
+ | `--output <path>` | stdout | Write to file instead of stdout |
1302
+
1303
+ **Examples:**
1304
+
1305
+ ```bash
1306
+ # Print fixture to stdout
1307
+ kici fixture pr:open
1308
+
1309
+ # Write fixture to file
1310
+ kici fixture pr:open --output fixtures/pr-open.json
1311
+
1312
+ # Generate push fixture
1313
+ kici fixture push --output fixtures/push.json
1314
+ ```
1315
+
1316
+ Use generated fixtures as reference when writing test fixture files in `.kici/tests/`:
1317
+
1318
+ ```bash
1319
+ kici fixture pr:open --output fixtures/pr-open-reference.json
1320
+ # Use the generated JSON as reference when writing .kici/tests/pr-open.ts
1321
+ ```
1322
+
1323
+ ### kici init
1324
+
1325
+ Initialize a `.kici/` directory with default workflow templates.
1326
+
1327
+ ```bash
1328
+ kici init [options]
1329
+ ```
1330
+
1331
+ **Options:**
1332
+
1333
+ | Option | Default | Description |
1334
+ | ------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------- |
1335
+ | `--force` | `false` | Overwrite existing `.kici/` directory |
1336
+ | `--skip-install` | `false` | Create files without installing dependencies |
1337
+ | `--package-manager <npm\|pnpm\|yarn>` | auto-detect | Force a package manager for the install step (default: detect from your repo) |
1338
+ | `--mjs` | `false` | JavaScript-only mode (no TypeScript, no deps) |
1339
+ | `--no-agents-md` | writes `AGENTS.md` | Skip writing `.kici/AGENTS.md` (the LLM authoring context file) |
1340
+ | `--private-registry <url>` | none | Scaffold a workflow `registries:` entry pointing at `<url>` (e.g. CodeArtifact, GH Packages, Verdaccio) |
1341
+ | `--private-registry-scope <scope>` | none | Optional npm package scope (e.g. `@my-org`) for the private registry |
1342
+ | `--private-registry-secret <ref>` | `production:NPM_TOKEN` | Qualified secret reference (`env:NAME`) the private registry token comes from |
1343
+
1344
+ **Examples:**
1345
+
1346
+ ```bash
1347
+ # Interactive initialization
1348
+ kici init
1349
+
1350
+ # Overwrite existing setup
1351
+ kici init --force
1352
+
1353
+ # Skip dependency install (faster, install manually later)
1354
+ kici init --skip-install
1355
+
1356
+ # Force a specific package manager (default: detect from your repo)
1357
+ kici init --package-manager pnpm
1358
+
1359
+ # JavaScript mode (no TypeScript)
1360
+ kici init --mjs
1361
+
1362
+ # Skip writing the AGENTS.md LLM authoring context file
1363
+ kici init --no-agents-md
1364
+
1365
+ # Scaffold a workflow registries entry for a private npm registry
1366
+ kici init --private-registry https://npm.pkg.github.com/ \
1367
+ --private-registry-scope @my-org \
1368
+ --private-registry-secret production:GITHUB_PACKAGES_TOKEN
1369
+ ```
1370
+
1371
+ **What it creates:**
1372
+
1373
+ ```
1374
+ .kici/
1375
+ workflows/
1376
+ hello-world.ts # Minimal push workflow
1377
+ pr-checks.ts # Comprehensive PR workflow
1378
+ tests/
1379
+ push-test.ts # Sample test fixture
1380
+ types/ # Directory for generated type declarations (kici types)
1381
+ package.json # Dependencies (@kici-dev/sdk)
1382
+ tsconfig.json # TypeScript configuration (includes types/**/*.d.ts)
1383
+ .kiciignore # Default exclusion patterns for test uploads
1384
+ ```
1385
+
1386
+ In interactive mode (TTY), `kici init` prompts you to:
1387
+
1388
+ 1. Select which workflow templates to include
1389
+ 2. Optionally install a pre-commit hook
1390
+
1391
+ **Package manager:** the dependency install step uses the package manager detected for your repo — the `packageManager` field in the nearest `package.json` (Corepack convention), then a lockfile in the project root (`pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `package-lock.json` → npm), then the package manager that invoked `kici` (`pnpm dlx` / `yarn dlx` / `npx`), defaulting to npm. Pass `--package-manager <npm|pnpm|yarn>` to override detection, or `--skip-install` to set up the files and install later yourself.
1392
+
1393
+ **Development mode:** When `KICI_DEV=true` or `package.json` has `"kici": { "development": true }`, the generated `package.json` uses prerelease-compatible version ranges (`>=0.0.1-0`) so npm resolves Verdaccio's prerelease builds.
1394
+
1395
+ ### kici hook install
1396
+
1397
+ Install a pre-commit hook that runs `kici compile` before each commit.
1398
+
1399
+ ```bash
1400
+ kici hook install [options]
1401
+ ```
1402
+
1403
+ **Options:**
1404
+
1405
+ | Option | Default | Description |
1406
+ | ------- | ------- | ------------------------------------------ |
1407
+ | `--git` | `false` | Use raw git hook (`.git/hooks/pre-commit`) |
1408
+
1409
+ **Examples:**
1410
+
1411
+ ```bash
1412
+ # Auto-detect hook tool (husky, lint-staged, etc.)
1413
+ kici hook install
1414
+
1415
+ # Force raw git hook
1416
+ kici hook install --git
1417
+ ```
1418
+
1419
+ The command auto-detects existing hook tools in your project:
1420
+
1421
+ - **Husky**: Adds to `.husky/pre-commit`
1422
+ - **lint-staged**: Adds to lint-staged configuration
1423
+ - **Raw git**: Writes `.git/hooks/pre-commit`
1424
+
1425
+ If multiple tools are detected, you are prompted to choose.
1426
+
1427
+ ### kici endpoints
1428
+
1429
+ List all webhook entrypoints for the current project. Reads the compiled lock file and displays webhook URLs grouped by type (git provider, generic webhooks, scheduled, event-driven).
1430
+
1431
+ ```bash
1432
+ kici endpoints [options]
1433
+ ```
1434
+
1435
+ **Options:**
1436
+
1437
+ | Option | Default | Description |
1438
+ | ------------------- | ------- | ----------------------- |
1439
+ | `--kici-dir <path>` | `.kici` | Path to .kici directory |
1440
+
1441
+ **Prerequisites:** Run `kici compile` first to generate the lock file.
1442
+
1443
+ **Examples:**
1444
+
1445
+ ```bash
1446
+ # List all webhook entrypoints
1447
+ kici endpoints
1448
+
1449
+ # Custom .kici directory
1450
+ kici endpoints --kici-dir packages/app/.kici
1451
+ ```
1452
+
1453
+ ### kici workflows list
1454
+
1455
+ List permanently registered workflows on the orchestrator.
1456
+
1457
+ ```bash
1458
+ kici workflows list [options]
1459
+ ```
1460
+
1461
+ **Options:**
1462
+
1463
+ | Option | Default | Description |
1464
+ | ----------------------- | ------- | ---------------------------------------------- |
1465
+ | `--json` | `false` | Output as JSON |
1466
+ | `--stale <duration>` | none | Filter stale registrations (e.g., `30d`, `7d`) |
1467
+ | `--trigger-type <type>` | none | Filter by trigger type |
1468
+ | `--repo <repo>` | none | Filter by repository |
1469
+
1470
+ **Examples:**
1471
+
1472
+ ```bash
1473
+ # List all registered workflows
1474
+ kici workflows list
1475
+
1476
+ # JSON output for scripting
1477
+ kici workflows list --json
1478
+
1479
+ # Show workflows not updated in 30 days
1480
+ kici workflows list --stale 30d
1481
+
1482
+ # Filter by trigger type
1483
+ kici workflows list --trigger-type push
1484
+
1485
+ # Filter by repository
1486
+ kici workflows list --repo my-org/my-repo
1487
+ ```
1488
+
1489
+ ### kici docs
1490
+
1491
+ Open the KiCI documentation site in the default browser. With the `llm` subcommand, print the LLM-friendly documentation bundle that ships with `@kici-dev/compiler` — pipe it into a coding agent's context buffer to brief the agent on authoring conventions without an internet round-trip.
1492
+
1493
+ ```bash
1494
+ kici docs # open https://kici.dev/docs/
1495
+ kici docs --no-open # print the URL instead of opening a browser
1496
+ kici docs llm # print the llms.txt index (a router over the task bundles)
1497
+ kici docs llm sdk # print the SDK task bundle
1498
+ kici docs llm full # print llms-full.txt (every page in one file)
1499
+ kici docs llm sdk --out sdk-context.md # write a bundle to a file
1500
+ ```
1501
+
1502
+ **Examples:**
1503
+
1504
+ ```bash
1505
+ # Open the docs site in your browser
1506
+ kici docs
1507
+
1508
+ # Pipe just the SDK bundle into a coding agent (small, task-scoped context)
1509
+ kici docs llm sdk | claude -- "Read this and help me author a deploy workflow"
1510
+
1511
+ # Save the router index for offline reference
1512
+ kici docs llm --out kici-llms-index.txt
1513
+ ```
1514
+
1515
+ Bundles are regenerated from `docs/` every time `@kici-dev/compiler` is built, so they always match your installed CLI version. The index lists each task bundle — `getting-started`, `sdk`, `cli`, `patterns`, `features`, `providers`, `architecture` — with its size and a one-line purpose; pass the bundle id as the topic. Every cross-reference link inside a bundle is an absolute `docs.kici.dev` URL. The same files are published online following the [llms.txt convention](https://llmstxt.org/).
1516
+
1517
+ ### kici admin
1518
+
1519
+ Operator-facing commands for running instances.
1520
+
1521
+ #### kici admin drain-worker
1522
+
1523
+ Trigger graceful drain on a worker instance. Sends a POST request to the worker's `/drain` endpoint.
1524
+
1525
+ ```bash
1526
+ kici admin drain-worker [options]
1527
+ ```
1528
+
1529
+ **Options:**
1530
+
1531
+ | Option | Required | Description |
1532
+ | ------------- | -------- | --------------------------------------------- |
1533
+ | `--url <url>` | yes | Worker URL (e.g., `http://worker-host:10143`) |
1534
+
1535
+ **Examples:**
1536
+
1537
+ ```bash
1538
+ # Drain a local worker
1539
+ kici admin drain-worker --url http://localhost:10143
1540
+
1541
+ # Drain a remote worker
1542
+ kici admin drain-worker --url http://worker-2.internal:10143
1543
+ ```
1544
+
1545
+ **Exit codes:**
1546
+
1547
+ | Code | Meaning |
1548
+ | ---- | ----------------------------------- |
1549
+ | 0 | Drain request accepted |
1550
+ | 1 | Error (unreachable or request fail) |
1551
+
1552
+ ### kici verify-attestation
1553
+
1554
+ Verify a KiCI build-provenance attestation bundle offline. A bundle is the signed package a workflow step produces via `ctx.attestProvenance(...)`: a DSSE-wrapped SLSA in-toto statement, the ephemeral public key that signed it, and the KiCI identity token that anchors the build context. For the end-to-end attest → verify → view journey, see the [build provenance guide](https://docs.kici.dev/user/provenance/). Verification establishes the full chain — the identity token verifies against the trusted issuer's JWKS, the DSSE signature verifies against the bundled key, and the statement's build context must match the token's claims (a mismatch is a hard failure). When an `[artifact]` is given, its SHA-256 digest is also matched against the attestation subject.
1555
+
1556
+ ```bash
1557
+ kici verify-attestation [artifact] --bundle <path-or-url> [--trust-root <url-or-file>] [options]
1558
+ ```
1559
+
1560
+ **Arguments:**
1561
+
1562
+ | Argument | Required | Description |
1563
+ | ------------ | -------- | --------------------------------------------------------------------- |
1564
+ | `[artifact]` | no | Artifact path to digest-check against the attestation subject digest. |
1565
+
1566
+ **Options:**
1567
+
1568
+ | Option | Required | Description |
1569
+ | ---------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
1570
+ | `--bundle <path-or-url>` | yes | Path or `http(s)` URL to the attestation bundle JSON. |
1571
+ | `--trust-root <url-or-file>` | no | Trusted issuer (see below). Defaults to the hosted KiCI platform. The token issuer is pinned to it, never taken from the token. |
1572
+ | `--audience <aud>` | no | Expected token audience (defaults to the KiCI provenance audience). |
1573
+ | `--json` | no | Print the structured verification result as JSON instead of human-readable output. |
1574
+
1575
+ **Trust root:** `--trust-root` defaults to the hosted KiCI platform's provenance issuer — the same platform you `kici login` against (see [Which trust root do I use?](https://docs.kici.dev/user/provenance/#which-trust-root-do-i-use)), so the common case needs no flag. The verifier never trusts the issuer named inside the token; supplying it out-of-band is what prevents a forged bundle from self-attesting. To override the default, pass `--trust-root` in one of two forms:
1576
+
1577
+ - **Online — an HTTPS issuer URL.** The verifier fetches `<url>/.well-known/openid-configuration`, reads its `issuer` and `jwks_uri`, and fetches the JWKS. The token's `iss` is pinned to the discovery document's `issuer`.
1578
+ - **Offline — a self-contained trust-root file.** A local JSON file with the issuer and JWKS inlined, so no network access is needed (air-gapped verification):
1579
+
1580
+ ```json
1581
+ {
1582
+ "issuer": "https://platform.example/issuer",
1583
+ "jwks": {
1584
+ "keys": [
1585
+ { "kty": "EC", "crv": "P-256", "x": "...", "y": "...", "alg": "ES256", "kid": "..." }
1586
+ ]
1587
+ }
1588
+ }
1589
+ ```
1590
+
1591
+ **Examples:**
1592
+
1593
+ ```bash
1594
+ # Default: verify against the hosted KiCI platform (no --trust-root needed)
1595
+ kici verify-attestation ./dist/app.tgz --bundle ./app.tgz.kici.json
1596
+
1597
+ # Override: verify a bundle against a specific issuer, digest-checking the artifact
1598
+ kici verify-attestation ./dist/app.tgz \
1599
+ --bundle ./app.tgz.kici.json \
1600
+ --trust-root https://platform.example/issuer
1601
+
1602
+ # Offline / air-gapped: verify against a self-contained trust-root file
1603
+ kici verify-attestation ./dist/app.tgz \
1604
+ --bundle ./app.tgz.kici.json \
1605
+ --trust-root ./kici-trust-root.json
1606
+
1607
+ # Machine-readable result for scripting
1608
+ kici verify-attestation --bundle ./app.tgz.kici.json \
1609
+ --trust-root https://platform.example/issuer --json
1610
+ ```
1611
+
1612
+ **Exit codes:**
1613
+
1614
+ | Code | Meaning |
1615
+ | ---- | ----------------------------------------------------------------------------------------- |
1616
+ | 0 | Verified — signature, identity, build context (and digest, if checked) all pass |
1617
+ | 1 | Not verified, or an error (missing `--bundle`, unreadable bundle, unreachable trust root) |
1618
+
1619
+ ## Workflow discovery
1620
+
1621
+ The CLI discovers workflows by scanning `.kici/workflows/*.ts` (or `.mjs` in MJS mode). Each file should `export default` a single workflow:
1622
+
1623
+ ```typescript
1624
+ // .kici/workflows/ci.ts
1625
+ import { workflow, job, step, pr } from '@kici-dev/sdk';
1626
+
1627
+ export default workflow('ci', {
1628
+ on: pr(),
1629
+ jobs: [
1630
+ /* ... */
1631
+ ],
1632
+ });
1633
+ ```
1634
+
1635
+ Multiple workflow files are supported -- each becomes a separate workflow in `kici.lock.json`.
1636
+
1637
+ ## Lock file
1638
+
1639
+ The `kici compile` command produces `.kici/kici.lock.json` inside the `.kici` directory. This file:
1640
+
1641
+ - Contains all workflow definitions in a portable JSON format
1642
+ - Is used by the orchestrator to evaluate triggers without code checkout
1643
+ - Should be committed to version control
1644
+ - Is regenerated on every `kici compile` run
1645
+
1646
+ Use `kici compile --check` in CI to validate that workflows are correct without writing files. For the full story on drift, pre-commit/CI, and agent-side verification, see [Lock file and workflow drift](https://docs.kici.dev/user/lock-file-and-drift/).
1647
+
1648
+ ## Exit codes
1649
+
1650
+ All commands follow a consistent exit code convention:
1651
+
1652
+ | Code | Meaning |
1653
+ | ---- | -------------------- |
1654
+ | 0 | Success |
1655
+ | 1 | Failure (see output) |
1656
+
1657
+ ## Debug output
1658
+
1659
+ Use `--debug` (on `kici run local`, `kici run remote`, `kici preview`) or `--verbose` (on `kici compile`) for detailed output:
1660
+
1661
+ ```bash
1662
+ # Shows trigger matching, rule evaluation, decision traces
1663
+ kici run local push --debug
1664
+
1665
+ # Shows detailed compilation steps
1666
+ kici compile --verbose
1667
+
1668
+ # Shows trigger matching preview
1669
+ kici preview pr:open --debug
1670
+ ```
1671
+
1672
+ Set `KICI_DEBUG=true` for additional internal debug output across all commands.
1673
+
1674
+ ## Environment variables
1675
+
1676
+ | Variable | Description |
1677
+ | ------------ | ----------------------------------------- |
1678
+ | `KICI_DEV` | Set to `true` for development mode |
1679
+ | `KICI_DEBUG` | Set to `true` for verbose internal output |
1680
+ | `CI` | When `true`, disables interactive prompts |
1681
+
1682
+ ## See also
1683
+
1684
+ - [Getting started](https://docs.kici.dev/user/getting-started/) -- install the SDK and write your first workflow
1685
+ - [Testing guide](https://docs.kici.dev/user/testing-guide/) -- writing fixtures, remote test runs, secret contexts, and repo state transfer
1686
+ - [SDK reference](https://docs.kici.dev/user/sdk-reference/) -- complete API for the workflow definitions that the CLI compiles
1687
+ - [Workflow patterns](https://docs.kici.dev/user/workflow-patterns/) -- example workflows to compile and test with these commands
1688
+
1689
+ ---
1690
+
1691
+ ## Lifecycle hooks
1692
+
1693
+ Source: https://docs.kici.dev/user/hooks/
1694
+
1695
+ Hooks are callbacks that run at specific points in the execution lifecycle. They let you react to outcomes (cancellation, success, failure) and perform cleanup without affecting the execution flow.
1696
+
1697
+ ## Hook types
1698
+
1699
+ KiCI supports six hook types at three levels (step, job, workflow):
1700
+
1701
+ | Hook | When it runs | Available on |
1702
+ | ------------ | ------------------------------------ | ------------------- |
1703
+ | `onCancel` | After step/job/workflow is cancelled | Step, Job, Workflow |
1704
+ | `cleanup` | Always (success, failure, or cancel) | Step, Job, Workflow |
1705
+ | `onSuccess` | After job/workflow succeeds | Job, Workflow |
1706
+ | `onFailure` | After job/workflow fails | Job, Workflow |
1707
+ | `beforeStep` | Before each step in a job | Job |
1708
+ | `afterStep` | After each step in a job | Job |
1709
+
1710
+ ## Basic usage
1711
+
1712
+ ### Job-level hooks
1713
+
1714
+ ```typescript
1715
+ import { workflow, job, step, push } from '@kici-dev/sdk';
1716
+
1717
+ export default workflow('deploy', {
1718
+ on: push({ branches: ['main'] }),
1719
+ jobs: [
1720
+ job('deploy-prod', {
1721
+ runsOn: 'linux',
1722
+ steps: [
1723
+ step('deploy', async ({ $ }) => {
1724
+ await $`kubectl apply -f manifests/`;
1725
+ }),
1726
+ ],
1727
+ onCancel: async (ctx) => {
1728
+ console.log(`Deploy cancelled: ${ctx.outcome.reason}`);
1729
+ await ctx.$`kubectl rollout undo deployment/app`;
1730
+ },
1731
+ cleanup: async (ctx) => {
1732
+ // Always runs -- release lock, notify team, etc.
1733
+ await ctx.$`curl -X POST https://slack.com/webhook -d '{"text": "Deploy ${ctx.outcome.status}"}'`;
1734
+ },
1735
+ onSuccess: async (ctx) => {
1736
+ console.log(`Deploy succeeded in ${ctx.outcome.duration}ms`);
1737
+ },
1738
+ onFailure: async (ctx) => {
1739
+ console.log(`Deploy failed at step: ${ctx.outcome.failedStep}`);
1740
+ },
1741
+ gracePeriod: 60, // 60 seconds before SIGKILL on cancel
1742
+ }),
1743
+ ],
1744
+ });
1745
+ ```
1746
+
1747
+ ### Step-level hooks
1748
+
1749
+ ```typescript
1750
+ step('download-artifacts', {
1751
+ run: async ({ $ }) => {
1752
+ await $`wget https://artifacts.example.com/build.tar.gz`;
1753
+ },
1754
+ onCancel: async (ctx) => {
1755
+ // Clean up partial downloads
1756
+ await ctx.$`rm -f build.tar.gz`;
1757
+ },
1758
+ cleanup: async (ctx) => {
1759
+ await ctx.$`rm -rf /tmp/staging`;
1760
+ },
1761
+ });
1762
+ ```
1763
+
1764
+ ### Workflow-level hooks
1765
+
1766
+ ```typescript
1767
+ workflow('ci', {
1768
+ on: push({ branches: ['main'] }),
1769
+ jobs: [
1770
+ /* ... */
1771
+ ],
1772
+ onCancel: async (ctx) => {
1773
+ // Notify when any job in the workflow is cancelled
1774
+ console.log('CI workflow cancelled');
1775
+ },
1776
+ cleanup: async (ctx) => {
1777
+ // Always runs after all jobs complete
1778
+ console.log(`CI workflow finished with status: ${ctx.outcome.status}`);
1779
+ },
1780
+ });
1781
+ ```
1782
+
1783
+ ## Hook context
1784
+
1785
+ Hook functions receive the same `StepContext` as regular steps (`$`, `ctx`, `log`, `env`), plus an `outcome` object with metadata about the execution result.
1786
+
1787
+ ### ctx.outcome
1788
+
1789
+ ```typescript
1790
+ interface OutcomeMetadata {
1791
+ /** Final status of the job/workflow. */
1792
+ status: 'cancelled' | 'success' | 'failed';
1793
+ /** Reason for cancellation (e.g., "User requested", "Superseded by run #42"). */
1794
+ reason?: string;
1795
+ /** Name of the step that caused failure (for onFailure hooks). */
1796
+ failedStep?: string;
1797
+ /** Outputs from all completed steps. */
1798
+ stepOutputs: Record<string, unknown>;
1799
+ /** Total execution duration in milliseconds. */
1800
+ duration: number;
1801
+ }
1802
+ ```
1803
+
1804
+ ### Capabilities
1805
+
1806
+ Hooks can do everything regular steps can:
1807
+
1808
+ - Run shell commands via `$`
1809
+ - Set environment variables via `ctx.setEnv()` and prepend to `PATH` via `ctx.addPath()`
1810
+ - Access previous step outputs via `ctx.outputsOf()` and `ctx.jobOutputs()`
1811
+ - Publish encrypted secret outputs via `ctx.setSecretOutput()`
1812
+ - Log via `log.info()`, `log.error()`, etc.
1813
+
1814
+ ## Hook timeout
1815
+
1816
+ Each hook has a timeout (default: 5 minutes). You can customize it per-hook:
1817
+
1818
+ ```typescript
1819
+ job('deploy', {
1820
+ runsOn: 'linux',
1821
+ steps: [
1822
+ /* ... */
1823
+ ],
1824
+ cleanup: {
1825
+ run: async (ctx) => {
1826
+ await ctx.$`./lengthy-cleanup.sh`;
1827
+ },
1828
+ timeout: 10 * 60 * 1000, // 10 minutes in ms
1829
+ },
1830
+ });
1831
+ ```
1832
+
1833
+ ## Hook execution order
1834
+
1835
+ Hooks execute inside-out on cancellation (like stack unwinding):
1836
+
1837
+ 1. **Step-level** cleanup (on the cancelled step)
1838
+ 2. **Job-level** onCancel, then cleanup
1839
+ 3. **Workflow-level** onCancel, then cleanup
1840
+
1841
+ On success: step afterStep (after each step), then job onSuccess + cleanup, then workflow onSuccess + cleanup.
1842
+
1843
+ On failure: job onFailure + cleanup, then workflow onFailure + cleanup.
1844
+
1845
+ **cleanup always runs** -- regardless of whether the outcome was success, failure, or cancel.
1846
+
1847
+ ## Hooks are observers
1848
+
1849
+ Hooks follow the "one mechanism per concern" principle:
1850
+
1851
+ - **Rules** control whether a step/job executes (conditional logic)
1852
+ - **Hooks** react to execution outcomes (lifecycle callbacks)
1853
+
1854
+ Hooks cannot short-circuit step execution or change the execution flow. They observe and respond.
1855
+
1856
+ ## beforeStep and afterStep
1857
+
1858
+ These job-level hooks run around every step in the job:
1859
+
1860
+ ```typescript
1861
+ job('test', {
1862
+ runsOn: 'linux',
1863
+ beforeStep: async (ctx) => {
1864
+ console.log(`Starting step at ${new Date().toISOString()}`);
1865
+ },
1866
+ afterStep: async (ctx) => {
1867
+ console.log(`Step completed with status: ${ctx.outcome.status}`);
1868
+ },
1869
+ steps: [
1870
+ step('lint', async ({ $ }) => {
1871
+ await $`pnpm lint`;
1872
+ }),
1873
+ step('test', async ({ $ }) => {
1874
+ await $`pnpm test`;
1875
+ }),
1876
+ ],
1877
+ });
1878
+ ```
1879
+
1880
+ `afterStep` runs immediately after its step, before the next step starts (not deferred to the end of the job).
1881
+
1882
+ ## Step-level rules
1883
+
1884
+ Step-level rules control whether a step executes, evaluated at runtime by the agent:
1885
+
1886
+ ```typescript
1887
+ import { step, rule, skip, isEventType } from '@kici-dev/sdk';
1888
+
1889
+ step('deploy', {
1890
+ run: async ({ $ }) => {
1891
+ await $`kubectl apply -f manifests/`;
1892
+ },
1893
+ rules: [
1894
+ rule('only on main pushes', (ctx) => {
1895
+ if (!isEventType(ctx.event, 'push')) return false;
1896
+ return ctx.event.payload.ref === 'refs/heads/main';
1897
+ }),
1898
+ ],
1899
+ });
1900
+
1901
+ // Or use skip() for explicit skip with a reason
1902
+ step('optional-check', {
1903
+ run: async ({ $ }) => {
1904
+ await $`./optional-check.sh`;
1905
+ },
1906
+ rules: [skip('not needed in CI', () => true)],
1907
+ });
1908
+ ```
1909
+
1910
+ When a rule returns `false`, the step is reported as `skipped` and subsequent steps continue normally. Skipped steps don't cause the job to fail.
1911
+
1912
+ Step rules have access to runtime context via `RuleContext`: `event` (typed discriminated union), `changedFiles`, `env`, and `$`. They evaluate agent-side (unlike job-level rules which evaluate at the orchestrator during trigger matching).
1913
+
1914
+ ## Hook failure behavior
1915
+
1916
+ If a hook throws an error or times out:
1917
+
1918
+ - The job status changes to `failed` with a compound reason (e.g., "cancelled (onCancel hook failed: Connection timeout)")
1919
+ - Remaining hooks for that level are skipped
1920
+ - The failure is visible in the dashboard as a failed hook step
1921
+ - Force cancel kills running hooks immediately via SIGKILL
1922
+
1923
+ This behavior is consistent across all hook types.
1924
+
1925
+ ---
1926
+
1927
+ _Source: `packages/sdk/src/hooks/`, `packages/sdk/src/types.ts`_
1928
+
1929
+ ---
1930
+
1931
+ ## Lock file and workflow drift
1932
+
1933
+ Source: https://docs.kici.dev/user/lock-file-and-drift/
1934
+
1935
+ KiCI uses a **two-artifact model**: TypeScript workflows are the source of truth; the lock file (`kici.lock.json`) is the execution contract. The orchestrator reads only the lock file to match triggers and decide cache vs build. Keeping these in sync is important.
1936
+
1937
+ ## Why the lock file matters
1938
+
1939
+ - **Orchestrator** fetches the lock file at the commit SHA and uses it to evaluate triggers and to look up the cached `.kici/` source tarball + `node_modules` tarball. It never runs your TypeScript.
1940
+ - **Agents** download the cached source tarball (or, on cold cache, the build agent clones + packs it), register the shared TypeScript loader hook, and dynamic-`import()` the workflow `.ts` directly. The lock file's per-workflow `contentHash` identifies the expected raw-source bytes and is verified against the extracted source before any step runs.
1941
+
1942
+ If you change a workflow file (`.ts`) but do **not** regenerate and commit the lock file, the repo at that commit has **drift**: the lock file no longer matches the source. Triggers and cache keys can be wrong, and runs can fail with a clear “stale lock file” error once the agent verifies the hash.
1943
+
1944
+ ## Lock file structure
1945
+
1946
+ The lock file (`kici.lock.json`) is a JSON file with the following top-level fields:
1947
+
1948
+ | Field | Description |
1949
+ | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1950
+ | `schemaVersion` | Lock file schema version (currently 29). Incremented on breaking format changes. |
1951
+ | `source` | Reference to the source file and export (e.g., `{ file: “.kici/workflows/ci.ts”, export: “#default” }`). |
1952
+ | `contentHash` | SHA-256 of the serialized lock file content (excluding itself). Changes when any workflow, trigger, or job changes. |
1953
+ | `lockfileHash` | SHA-256 of the detected package manager's lockfile, used as the dependency cache key. The lockfile is `.kici/package-lock.json` for npm, or the repo-root `pnpm-lock.yaml` / `yarn.lock` for a pnpm/yarn workspace; the hash input is prefixed with the manager name so a manager change is a guaranteed cache miss. Omitted when no lockfile exists. |
1954
+ | `workflows` | Array of workflow entries, each with its own `contentHash`, `compileSchemaVersion`, triggers, and jobs. |
1955
+
1956
+ Each workflow entry includes:
1957
+
1958
+ | Field | Description |
1959
+ | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1960
+ | `name` | Workflow name. |
1961
+ | `source` | Per-workflow source file and export reference. |
1962
+ | `contentHash` | SHA-256 of the raw workflow source mixed with `compileSchemaVersion` (and an `assetDigest` of declared `hashFiles` when present): `SHA-256(compileSchemaVersion + ":" + rawSource [+ "\0" + assetDigest])`. The orchestrator uses this as the source-tarball cache key and the agent re-computes it against the extracted source to detect drift. |
1963
+ | `compileSchemaVersion` | Compiler schema version used when computing `contentHash` (currently `5`). The hash input is line-ending-normalized (CRLF → LF) so a lock file produced on Linux matches the agent's hash on Windows where Git's `core.autocrlf=true` rewrites checked-out text to CRLF. Bumping the schema version invalidates every existing source cache entry even if source is unchanged, which is the correct behavior when the compile-time or runtime contract changes. |
1964
+ | `triggers` | Trigger definitions extracted from the workflow (used by the orchestrator for event matching). |
1965
+ | `jobs` | Job definitions with scheduling metadata (runsOn, needs, matrix, environment, concurrency, container, checkout, gracePeriod, label routing, dynamic fields, etc.). |
1966
+ | `rules` | Workflow-level conditional rules (optional). Stored as dynamic references since rule functions cannot be serialized. |
1967
+ | `description` | Optional workflow description. |
1968
+ | `hashFiles` | Declared glob patterns for extra files included in the content hash (optional). See [extra files in the content hash](https://docs.kici.dev/user/lock-file-and-drift/#extra-files-in-the-content-hash-hashfiles). |
1969
+ | `resolvedHashFiles` | Resolved file paths from `hashFiles` at compile time (optional). Recorded so the agent can verify without re-discovering. |
1970
+ | `contexts` | Secret contexts declared by the workflow (optional). The orchestrator validates access to each context before dispatch. |
1971
+ | `registries` | Private npm registry declarations the agent authenticates against before install (optional): `url`, `scope`, `tokenSecret` reference, `alwaysAuth`. Resolved token bytes never appear in the lock file. See [private registries](https://docs.kici.dev/user/private-registries/). |
1972
+ | `installEnv` | Extra qualified secret refs (`<environment>:<secret-name>`) projected as env vars on the install subprocess for use with a committed `.kici/.npmrc` (optional). See [private registries](https://docs.kici.dev/user/private-registries/). |
1973
+ | `concurrency` | Workflow-level concurrency config: `hasGroup`, `cancelInProgress`, `max` (optional). See [concurrency groups](https://docs.kici.dev/user/concurrency/). |
1974
+ | `timeout` | Whole-run wall-clock timeout in milliseconds (optional). The orchestrator reads this at run creation to set the run deadline. |
1975
+ | Hook flags | Boolean flags (`hasOnCancel`, `hasCleanup`, `hasOnSuccess`, `hasOnFailure`) indicating which lifecycle hooks are defined. Job entries additionally have `hasBeforeStep` and `hasAfterStep`. |
1976
+
1977
+ ## Rule: commit both together
1978
+
1979
+ **Always commit `.kici/kici.lock.json` in the same commit as the workflow source files it was generated from.**
1980
+
1981
+ 1. After editing `.kici/workflows/*.ts`, run:
1982
+ ```bash
1983
+ npx kici compile
1984
+ ```
1985
+ 2. Stage both the workflow file(s) and `.kici/kici.lock.json`.
1986
+ 3. Commit them together.
1987
+
1988
+ That way the lock file at every commit SHA matches the workflow source at that SHA.
1989
+
1990
+ ## Catch drift early: pre-commit and CI
1991
+
1992
+ Use automation so drift is caught before it reaches the repo.
1993
+
1994
+ ### Pre-commit hook
1995
+
1996
+ Install a hook that compiles and stages the lock file before each commit:
1997
+
1998
+ ```bash
1999
+ npx kici hook install
2000
+ ```
2001
+
2002
+ This runs `kici compile && git add .kici/kici.lock.json` before each commit: if compilation fails the commit is blocked; if it succeeds the updated lock file is automatically staged. See [CLI Reference — kici hook](https://docs.kici.dev/user/cli-reference/#kici-hook) for options (husky, lefthook, pre-commit, prek, raw git).
2003
+
2004
+ ### CI check
2005
+
2006
+ In your CI pipeline, verify that the workflow source compiles without errors:
2007
+
2008
+ ```bash
2009
+ kici compile --check
2010
+ ```
2011
+
2012
+ This validates all workflows and generates the lock file in memory without writing it. If any workflow has syntax errors or invalid configuration, the command exits non-zero. Pair this with the agent-side hash verification (below) for full drift detection -- `--check` catches broken source, while the agent catches source-lock-file mismatches at run time.
2013
+
2014
+ ## Extra files in the content hash (`hashFiles`)
2015
+
2016
+ By default, the per-workflow content hash is `SHA-256(compileSchemaVersion + ":" + rawSource)` where `rawSource` is the TypeScript text of the workflow entry file. If your workflow depends on files outside `.kici/workflows/` -- configuration files, scripts, Dockerfiles, etc. -- changes to those files will **not** invalidate the cache unless you declare them.
2017
+
2018
+ Use the `hashFiles` option on a workflow to include additional paths or glob patterns (relative to the repo root) in the content hash:
2019
+
2020
+ ```typescript
2021
+ export default workflow('deploy', {
2022
+ hashFiles: ['config.json', 'scripts/*.sh'],
2023
+ jobs: [
2024
+ /* ... */
2025
+ ],
2026
+ });
2027
+ ```
2028
+
2029
+ When any of the matched files change, the content hash formula becomes `SHA-256(compileSchemaVersion + ":" + rawSource + "\0" + assetDigest)` where `assetDigest` is a deterministic encoding of the resolved file paths and their contents. This busts the source-tarball cache and forces the build agent to pack and upload a fresh `source/{contentHash}.tar.gz`. The resolved file paths are recorded in the lock file under `resolvedHashFiles` so the agent can verify without re-discovering the workflow.
2030
+
2031
+ ## Agent-side safety net
2032
+
2033
+ If drift still occurs (e.g. someone committed only the `.ts` change), the agent detects it at run time before any step runs:
2034
+
2035
+ - After extracting the `.kici/` source tarball (or loading source from a `git clone` on the build path), the agent reads the workflow entry file and re-computes `contentHash = SHA-256(compileSchemaVersion + ":" + rawSource [+ "\0" + assetDigest])` using the same formula as the compiler.
2036
+ - If the orchestrator sent a `contentHash` (from the lock file) and the computed hash does **not** match, the agent fails the run with an error like: **lock file is out of date** (workflow source changed without regenerating the lock file). The error includes the baked agent `@kici-dev/sdk` version + bundle hash so operators can debug cross-host compile mismatches.
2037
+
2038
+ So even without a pre-commit or CI check, a stale lock file will cause the run to fail with a clear message instead of running with the wrong workflow.
2039
+
2040
+ ## Summary
2041
+
2042
+ | Goal | What to do |
2043
+ | ---------------------------- | ------------------------------------------------------------------------------------------ |
2044
+ | Keep lock file in sync | Commit `kici.lock.json` with the workflow `.ts` changes; run `kici compile` before commit. |
2045
+ | Catch drift before commit | Install a pre-commit hook with `kici hook install`. |
2046
+ | Catch broken source in CI | Run `kici compile --check` in CI. |
2047
+ | Bust cache on external files | Add `hashFiles: [‘config.json’]` to include non-workflow files in the content hash. |
2048
+ | Fail fast when drift remains | Rely on the agent’s hash verification when it compiles from source. |
2049
+
2050
+ ## See also
2051
+
2052
+ - [Getting Started](https://docs.kici.dev/user/getting-started/) — compile and commit the lock file
2053
+ - [CLI Reference](https://docs.kici.dev/user/cli-reference/) — `kici compile`, `kici compile --check`, `kici hook`
2054
+ - [Architecture — Data flows](https://docs.kici.dev/architecture/data-flows/) — how the lock file is used in the pipeline
2055
+
2056
+ ---
2057
+
2058
+ ## Testing guide
2059
+
2060
+ Source: https://docs.kici.dev/user/testing-guide/
2061
+
2062
+ Test your workflows remotely against the full CI pipeline from your local machine. `kici run remote` uploads your current repo state (including uncommitted changes), triggers the pipeline, and streams execution logs back in real time.
2063
+
2064
+ ## Overview
2065
+
2066
+ `kici run remote` connects your local development environment to the remote orchestrator/agent pipeline. Instead of pushing a commit and waiting for CI, you can:
2067
+
2068
+ - Run any workflow against your current working tree (including unstaged changes)
2069
+ - Get real-time log output streamed back to your terminal
2070
+ - Give test runs test-scoped secrets — your local secret files and `--env` values (uploaded encrypted) plus any environment flagged `allowLocalExecution: true` — while production environments stay unreachable
2071
+ - Detect test mode in workflow code via `ctx.isTestRun`
2072
+
2073
+ The command is remote-only -- all execution happens on the orchestrator and agent. For local-only trigger matching previews, use `kici preview <event>`.
2074
+
2075
+ :::note[Orchestrator prerequisite: cache storage]
2076
+ `kici run remote` uploads your working-tree overlay to the orchestrator's **cache storage** via a pre-signed URL, and the agent fetches it from there (see [Repo state transfer](https://docs.kici.dev/user/testing-guide/#repo-state-transfer)). The target orchestrator must therefore have cache storage enabled (`KICI_STORAGE_TYPE` = `s3` or `filesystem`).
2077
+
2078
+ - **Both quickstarts wire this up for you** — the [Docker / Podman quickstart](https://docs.kici.dev/user/quickstart/compose/) and the [bare-metal quickstart](https://docs.kici.dev/user/quickstart/bare-metal/) each ship a SeaweedFS object store and pre-fill the orchestrator's `KICI_STORAGE_*` block, so `kici run remote` works out of the box (see each guide's "run a workflow without pushing" step).
2079
+ - **A hand-rolled orchestrator deploy does not configure storage by default** — enable a backend before using `kici run remote`:
2080
+ - **`filesystem`** — simplest for a single-host orchestrator: set `KICI_STORAGE_TYPE=filesystem` and `KICI_STORAGE_FS_PATH=/var/lib/kici/cache`. No external service needed; blobs are served through the orchestrator's own HMAC-signed HTTP route.
2081
+ - **`s3`** — any S3-compatible bucket. **A non-public / self-hosted endpoint works**: set `KICI_STORAGE_TYPE=s3`, `KICI_STORAGE_BUCKET`, `KICI_STORAGE_ENDPOINT=https://your-endpoint` and (for most self-hosted services) `KICI_STORAGE_FORCE_PATH_STYLE=true`. If the developer machine running `kici run remote` reaches the bucket at a different address than the orchestrator, set `KICI_STORAGE_UPLOAD_ENDPOINT` to the developer-reachable address; if agents reach it at yet another address (e.g. agents in containers), set `KICI_STORAGE_EXTERNAL_ENDPOINT` to the agent-routable URL.
2082
+
2083
+ See [Storage layout](https://docs.kici.dev/operator/orchestrator/storage-layout/) for the full env-var reference.
2084
+ :::
2085
+
2086
+ ## Getting started
2087
+
2088
+ ### 1. Authenticate
2089
+
2090
+ ```bash
2091
+ kici login
2092
+ ```
2093
+
2094
+ This opens your browser for OAuth authentication and stores a personal access token in `~/.kici/config`. For CI/CD pipelines or headless environments, use `kici login --token <your-api-key>` or `kici login --device` instead. See [CLI authentication](https://docs.kici.dev/user/cli-auth/) for details.
2095
+
2096
+ ### 2. Write a test fixture
2097
+
2098
+ Fixtures define the events you want to simulate. They live in `.kici/tests/*.ts` and use the same SDK trigger functions as workflows.
2099
+
2100
+ ```typescript
2101
+ // .kici/tests/push-tests.ts
2102
+ import { fixture, push } from '@kici-dev/sdk';
2103
+
2104
+ export const pushMain = fixture('push-main', {
2105
+ event: push({ branches: ['main'] }),
2106
+ });
2107
+
2108
+ export const pushDevelop = fixture('push-develop', {
2109
+ event: push({ branches: ['develop'] }),
2110
+ });
2111
+ ```
2112
+
2113
+ Each file can export multiple fixtures. The `fixture()` factory takes an ID (used on the command line) and options including the event to simulate.
2114
+
2115
+ ### 3. Run a fixture
2116
+
2117
+ ```bash
2118
+ # List available fixtures
2119
+ kici run remote
2120
+
2121
+ # Run a specific fixture
2122
+ kici run remote push-main
2123
+
2124
+ # Run all fixtures matching a glob
2125
+ kici run remote 'push-*'
2126
+
2127
+ # Run everything
2128
+ kici run remote --all
2129
+ ```
2130
+
2131
+ The single quotes keep your shell from expanding `push-*` against local files, so the pattern reaches KiCI intact for its own fixture-glob matching.
2132
+
2133
+ ## Fixture reference
2134
+
2135
+ ### Event types
2136
+
2137
+ Fixtures accept any SDK trigger function as their event:
2138
+
2139
+ ```typescript
2140
+ import { fixture, push, pr, comment, tag, release } from '@kici-dev/sdk';
2141
+
2142
+ // Push event
2143
+ export const pushMain = fixture('push-main', {
2144
+ event: push({ branches: ['main'] }),
2145
+ });
2146
+
2147
+ // PR event
2148
+ export const prOpen = fixture('pr-open', {
2149
+ event: pr({ branches: ['main'], actions: ['opened'] }),
2150
+ });
2151
+
2152
+ // Comment event
2153
+ export const prComment = fixture('pr-comment', {
2154
+ event: comment({ actions: ['created'] }),
2155
+ });
2156
+
2157
+ // Tag event
2158
+ export const tagRelease = fixture('tag-release', {
2159
+ event: tag({ tags: ['v*'] }),
2160
+ });
2161
+
2162
+ // Release event
2163
+ export const published = fixture('release-published', {
2164
+ event: release({ actions: ['published'] }),
2165
+ });
2166
+ ```
2167
+
2168
+ ### Overrides
2169
+
2170
+ Override default payload values per fixture:
2171
+
2172
+ ```typescript
2173
+ export const pushFeature = fixture('push-feature', {
2174
+ event: push({ branches: ['feature/*'] }),
2175
+ branch: 'feature/auth', // Override branch name
2176
+ sha: 'abc123def456', // Override commit SHA
2177
+ repo: 'myorg/myrepo', // Override repository
2178
+ pr: 42, // Override PR number (for PR events)
2179
+ });
2180
+ ```
2181
+
2182
+ When not specified, these default to values detected from your local git repo (current branch, HEAD SHA, remote URL).
2183
+
2184
+ ### Secret context mappings
2185
+
2186
+ Map secret contexts to your fixture:
2187
+
2188
+ ```typescript
2189
+ export const pushWithSecrets = fixture('push-with-secrets', {
2190
+ event: push({ branches: ['main'] }),
2191
+ secrets: {
2192
+ db: 'test-database',
2193
+ api: 'test-api-keys',
2194
+ },
2195
+ });
2196
+ ```
2197
+
2198
+ This maps the `db` secret context to the `test-database` context, and `api` to `test-api-keys`.
2199
+
2200
+ This mapping is honored by **both** `kici run local` and `kici run remote`:
2201
+
2202
+ - For **`kici run local`** (see [`kici run local`](https://docs.kici.dev/user/cli-reference/#kici-run-local)), each named context is resolved from your local secret files (`.kici/.secrets`, `.env.local`, `secrets.yaml`, and `--env` flags).
2203
+ - For **`kici run remote`**, each named context maps to an orchestrator **environment**, and the orchestrator resolves that environment's secrets for the run. The target environment must be flagged `allowLocalExecution: true` — mapping a context to a missing or non-test environment rejects the run (see [Secret contexts for testing](https://docs.kici.dev/user/testing-guide/#secret-contexts-for-testing) below).
2204
+
2205
+ ### Async fixtures
2206
+
2207
+ For dynamic fixture configuration, export an async function:
2208
+
2209
+ ```typescript
2210
+ export const dynamicFixture = fixture('dynamic', async () => ({
2211
+ event: push({ branches: ['main'] }),
2212
+ sha: await getCurrentSha(),
2213
+ }));
2214
+ ```
2215
+
2216
+ ## Running tests
2217
+
2218
+ ### Basic commands
2219
+
2220
+ ```bash
2221
+ # List all available fixtures (discovers .kici/tests/*.ts)
2222
+ kici run remote
2223
+
2224
+ # Run a single fixture by ID
2225
+ kici run remote push-main
2226
+
2227
+ # Glob matching -- run all push-related fixtures
2228
+ kici run remote 'push-*'
2229
+
2230
+ # Run all fixtures sequentially
2231
+ kici run remote --all
2232
+
2233
+ # Run all fixtures in parallel
2234
+ kici run remote --all --parallel
2235
+ ```
2236
+
2237
+ ### Direct workflow run
2238
+
2239
+ Bypass trigger matching and run a specific workflow directly:
2240
+
2241
+ ```bash
2242
+ kici run remote --workflow ci
2243
+ ```
2244
+
2245
+ This skips the trigger evaluation step and runs all jobs in the named workflow.
2246
+
2247
+ ### Output modes
2248
+
2249
+ ```bash
2250
+ # Default: full log streaming with colored job prefixes
2251
+ kici run remote push-main
2252
+
2253
+ # Quiet: minimal output (just pass/fail result)
2254
+ kici run remote push-main --quiet
2255
+
2256
+ # JSON: machine-readable structured output
2257
+ kici run remote push-main --json
2258
+
2259
+ # JUnit XML: for CI integration
2260
+ kici run remote push-main --junit results.xml
2261
+ ```
2262
+
2263
+ ### Non-blocking execution
2264
+
2265
+ ```bash
2266
+ # Fire and forget -- returns immediately with run ID
2267
+ kici run remote push-main --no-wait
2268
+
2269
+ # Check status later
2270
+ kici runs show <run-id>
2271
+ ```
2272
+
2273
+ ### Cancellation
2274
+
2275
+ Press Ctrl+C during a running test to send a cancel signal to the orchestrator. The agent job will be terminated gracefully.
2276
+
2277
+ ## Repo state transfer
2278
+
2279
+ When you run `kici run remote`, the CLI:
2280
+
2281
+ 1. Detects all files differing from HEAD (staged, unstaged, and untracked)
2282
+ 2. Creates a compressed tarball of changed files
2283
+ 3. Encrypts the tarball using X25519 ECDH key exchange
2284
+ 4. Uploads the encrypted tarball to storage via a signed URL
2285
+ 5. Triggers the pipeline with a reference to the upload
2286
+
2287
+ The agent clones your repo at HEAD, then applies the overlay tarball on top -- giving you the exact same file state as your local working tree.
2288
+
2289
+ ### What gets included
2290
+
2291
+ - Modified tracked files (staged and unstaged)
2292
+ - New untracked files (not in `.gitignore`)
2293
+ - File deletions (tracked files you deleted locally)
2294
+
2295
+ ### What gets excluded
2296
+
2297
+ - Files matching `.gitignore` patterns
2298
+ - Files matching `.kiciignore` patterns (additional exclusions)
2299
+ - The `.git` directory itself
2300
+
2301
+ ### `.kiciignore`
2302
+
2303
+ Create a `.kiciignore` file in your repo root to exclude additional files from the upload:
2304
+
2305
+ ```
2306
+ # Large binaries
2307
+ *.bin
2308
+ *.iso
2309
+ data/fixtures/large-dataset.csv
2310
+
2311
+ # Local-only configs
2312
+ .env.local
2313
+ docker-compose.override.yml
2314
+ ```
2315
+
2316
+ The format is the same as `.gitignore` -- one glob pattern per line, `#` for comments.
2317
+
2318
+ ### Size limits
2319
+
2320
+ | Threshold | Behavior |
2321
+ | --------- | --------------------------------------------------------------------------------- |
2322
+ | < 50 MB | Normal upload |
2323
+ | 50-500 MB | Warning displayed, upload proceeds |
2324
+ | > 500 MB | Error -- reduce bundle size via `.kiciignore` or check for unintended large files |
2325
+
2326
+ The CLI always shows a pre-upload summary before transferring:
2327
+
2328
+ ```
2329
+ 12 files changed, 3 new, 1 deleted (2.3 MB compressed)
2330
+ ```
2331
+
2332
+ ## Secret contexts for testing
2333
+
2334
+ The goal of the test-secret model is to let test runs reach **test-only credentials** while keeping production credentials out of reach. `kici run remote` combines two sources of secrets for a test run, then merges them with a clear precedence and a fail-closed gate.
2335
+
2336
+ ### CLI-uploaded local secrets
2337
+
2338
+ `kici run remote` collects the same local secret values that `kici run local` reads — `.kici/.secrets`, `.kici/.env.local`, `.kici/secrets.yaml`, and any `--env KEY=VALUE` flags — and uploads them **encrypted** to the orchestrator alongside the run. The orchestrator decrypts them only to inject them into the agent for that run; the control plane never sees the values.
2339
+
2340
+ ```bash
2341
+ # Provide an ad-hoc test value for a single remote run
2342
+ kici run remote push-main --env KICI_DATABASE_URL=postgresql://localhost/test
2343
+ ```
2344
+
2345
+ `--env` provides a **flat** per-run override; `--context <ctx>.<KEY>=<value>` is its sibling for a **namespaced** per-run override, placing the value under the named context `ctx`. Both are uploaded **encrypted** and follow the same precedence rule below — a CLI-supplied value wins over the orchestrator test-environment secret on a key collision.
2346
+
2347
+ ```bash
2348
+ # Provide a namespaced per-run value under the 'db' context
2349
+ kici run remote push-db --context db.KICI_DATABASE_URL=postgresql://localhost/test
2350
+ ```
2351
+
2352
+ Because these values originate on your machine, they are the natural place to put throwaway test credentials without touching any orchestrator-stored secret.
2353
+
2354
+ ### Orchestrator test-environment secrets
2355
+
2356
+ In addition to your uploaded values, the orchestrator resolves test-scoped secrets from its own store for a remote test run:
2357
+
2358
+ - The job's own declared `environment` contributes its resolved secrets (flat). Static strings and **pure dynamic functions** both participate: a pure `environment:` function (see [Dynamic values](https://docs.kici.dev/user/dynamic-values/)) is evaluated against the fixture's simulated event, and the resolved name is gated and resolved like a static one. Impure dynamic functions (those requiring an init job) are not evaluated for test runs — use a fixture `secrets:` mapping (or `--context`) to supply such a job's secrets.
2359
+ - Each fixture `secrets: { ctx: envName }` mapping resolves the named environment's secrets under the namespaced context `ctx`.
2360
+
2361
+ Both paths are restricted to environments flagged `allowLocalExecution: true`. A production environment left at the default `false` is never resolvable for a test run.
2362
+
2363
+ ```typescript
2364
+ export const pushWithDb = fixture('push-db', {
2365
+ event: push({ branches: ['main'] }),
2366
+ secrets: { db: 'test-database' }, // 'test-database' must be allowLocalExecution: true
2367
+ });
2368
+ ```
2369
+
2370
+ ```typescript
2371
+ step('migrate', async (ctx) => {
2372
+ const dbUrl = await ctx.secrets.get('KICI_DATABASE_URL');
2373
+ await ctx.$`npx prisma migrate deploy`;
2374
+ });
2375
+ ```
2376
+
2377
+ ### Precedence: CLI values win
2378
+
2379
+ When a key exists in both sources, the **CLI-uploaded local value wins** over the orchestrator test-environment value. This makes a local override a per-run knob: set `--env KICI_DATABASE_URL=...` (or put it in `.kici/.secrets`) to shadow the test environment's value for just that run, without changing anything on the orchestrator.
2380
+
2381
+ ### Fail-closed on non-test environments
2382
+
2383
+ Test-run secret resolution is fail-closed:
2384
+
2385
+ - If a fixture maps a context to an environment that does not exist, the run is **rejected**.
2386
+ - If a fixture maps a context to an environment whose `allowLocalExecution` is `false`, the run is **rejected**.
2387
+ - The `allowLocalExecution` gate applies to **all** remote test runs: a run whose matched workflow targets an environment with the flag off is rejected, so a test run can never resolve production secrets.
2388
+
2389
+ ### The `allowLocalExecution` environment flag
2390
+
2391
+ Each environment carries an `allowLocalExecution` flag (default `false`) that controls test-run access to that environment and to its secrets. Production environments should leave it at `false`; create a dedicated test environment with `allowLocalExecution: true` that binds only test-only secret scopes for the contexts you want test runs to use.
2392
+
2393
+ The flag is set by the orchestrator operator, either via the CLI:
2394
+
2395
+ ```bash
2396
+ kici-admin environment set-policy --env test-database --allow-local-execution true
2397
+ ```
2398
+
2399
+ or via the dashboard's "Test runs" toggle on the environment detail page. `kici secrets list` only surfaces contexts whose owning environment has `allowLocalExecution: true`, so production environments are never advertised as test-accessible.
2400
+
2401
+ ### Local execution as an alternative
2402
+
2403
+ `kici run local` resolves the same local secret files entirely on your machine and honors the fixture `secrets: { ... }` mapping to pick which local context backs each name (see [`kici run local`](https://docs.kici.dev/user/cli-reference/#kici-run-local)). Because the values never leave your machine, it's a good fit when you want to exercise secret-dependent steps without involving the orchestrator at all.
2404
+
2405
+ ### Discovering available contexts
2406
+
2407
+ ```bash
2408
+ # List test-accessible secret contexts and their key names (not values)
2409
+ kici secrets list
2410
+ ```
2411
+
2412
+ ## Detecting test mode in workflows
2413
+
2414
+ Use `ctx.isTestRun` to conditionally skip destructive operations:
2415
+
2416
+ ```typescript
2417
+ step('deploy', async (ctx) => {
2418
+ if (ctx.isTestRun) {
2419
+ ctx.log.info('Skipping deployment in test mode');
2420
+ return;
2421
+ }
2422
+ await ctx.$`kubectl apply -f k8s/`;
2423
+ });
2424
+ ```
2425
+
2426
+ ## Run history
2427
+
2428
+ ### Viewing history
2429
+
2430
+ ```bash
2431
+ # Show recent test runs (from local history)
2432
+ kici run remote --history
2433
+ ```
2434
+
2435
+ ### Run details
2436
+
2437
+ ```bash
2438
+ # Show run summary (reads the Platform, falls back to local history)
2439
+ kici runs show <run-id>
2440
+
2441
+ # Show full logs
2442
+ kici runs logs <run-id>
2443
+
2444
+ # Show logs for a specific job
2445
+ kici runs logs <run-id> --job build
2446
+
2447
+ # Machine-readable output
2448
+ kici runs show <run-id> --json
2449
+ ```
2450
+
2451
+ ## Scaffolding with kici init
2452
+
2453
+ Running `kici init` in a new project scaffolds a sample test fixture alongside the workflow templates:
2454
+
2455
+ ```
2456
+ .kici/
2457
+ workflows/
2458
+ hello-world.ts # Sample workflow
2459
+ pr-checks.ts # Sample PR workflow
2460
+ tests/
2461
+ push-test.ts # Sample push fixture
2462
+ package.json
2463
+ tsconfig.json
2464
+ .kiciignore # Default exclusion patterns
2465
+ ```
2466
+
2467
+ The generated fixture uses the detected default branch:
2468
+
2469
+ ```typescript
2470
+ // .kici/tests/push-test.ts
2471
+ import { fixture, push } from '@kici-dev/sdk';
2472
+
2473
+ export const pushMain = fixture('push-main', {
2474
+ event: push({ branches: ['main'] }),
2475
+ });
2476
+ ```
2477
+
2478
+ ## See also
2479
+
2480
+ - [CLI reference](https://docs.kici.dev/user/cli-reference/) -- complete command reference for all `kici` commands
2481
+ - [SDK reference](https://docs.kici.dev/user/sdk-reference/) -- trigger functions, step context, and workflow API
2482
+ - [Workflow patterns](https://docs.kici.dev/user/workflow-patterns/) -- example workflows to test against
2483
+
2484
+ ---
2485
+
2486
+ ## Workflow patterns
2487
+
2488
+ Source: https://docs.kici.dev/user/workflow-patterns/
2489
+
2490
+ Practical patterns for building real-world KiCI workflows in TypeScript. The patterns are organised across five pages -- start with [Basic CI](https://docs.kici.dev/user/patterns/basic/) if you're new, or jump to [Integrations](https://docs.kici.dev/user/patterns/integrations/) if you're wiring up a non-GitHub forge or a generic webhook.
2491
+
2492
+ | Page | Covers |
2493
+ | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
2494
+ | [Basic CI](https://docs.kici.dev/user/patterns/basic/) | Single-job CI, PR-only / push-only filters, multiple triggers on one workflow, manual-only workflows. |
2495
+ | [Conditionals & matrix](https://docs.kici.dev/user/patterns/conditionals-matrix/) | Conditional execution with rules, matrix builds (static + dynamic), and dynamic job generation. |
2496
+ | [Integrations](https://docs.kici.dev/user/patterns/integrations/) | Workflow chaining, generic webhooks, Stripe handlers, self-hosted git forges (Forgejo / Gitea / Gogs), plain GitHub repo webhooks (no GitHub App). |
2497
+ | [Scheduling & events](https://docs.kici.dev/user/patterns/scheduling-and-events/) | Nightly cron, workflow-complete-triggered deploys, custom event chaining. |
2498
+ | [Pattern reference](https://docs.kici.dev/user/patterns/reference/) | Step context, the examples repository, and GitHub check run output -- cross-cutting reference shared by every pattern above. |
2499
+
2500
+ ## See also
2501
+
2502
+ - [Event system](https://docs.kici.dev/user/events/) -- event model concepts, registration model, circuit breaker
2503
+ - [SDK reference](https://docs.kici.dev/user/sdk-reference/) -- complete API reference for all functions used in these patterns
2504
+ - [CLI reference](https://docs.kici.dev/user/cli-reference/) -- how to compile and test these workflows locally
2505
+ - [Getting started](https://docs.kici.dev/user/getting-started/) -- installation and first workflow setup
2506
+ - [Job execution lifecycle](https://docs.kici.dev/architecture/execution/job-execution/) -- how agents execute the jobs defined in these patterns
2507
+ - [GitHub checks architecture](https://docs.kici.dev/architecture/webhooks/github-checks/) -- deep dive into the check run system
2508
+
2509
+ ---