@kici-dev/compiler 0.1.25 → 0.1.27

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 (96) hide show
  1. package/dist/cli-banner.d.ts +6 -3
  2. package/dist/cli-banner.js +13 -5
  3. package/dist/cli.js +66 -43
  4. package/dist/commands/check-mode.d.ts +1 -1
  5. package/dist/commands/compile.js +3 -3
  6. package/dist/commands/index.d.ts +5 -1
  7. package/dist/commands/index.js +6 -3
  8. package/dist/commands/init.js +1 -1
  9. package/dist/commands/local-trust-root.d.ts +6 -0
  10. package/dist/commands/local-trust-root.js +52 -0
  11. package/dist/commands/local.d.ts +39 -0
  12. package/dist/commands/local.js +137 -0
  13. package/dist/commands/login.d.ts +2 -0
  14. package/dist/commands/login.js +40 -0
  15. package/dist/commands/logout.js +7 -0
  16. package/dist/commands/preview.js +1 -1
  17. package/dist/commands/run-banner.d.ts +31 -0
  18. package/dist/commands/run-banner.js +25 -0
  19. package/dist/commands/run-routed.d.ts +53 -0
  20. package/dist/commands/run-routed.js +176 -0
  21. package/dist/commands/run.d.ts +0 -17
  22. package/dist/commands/run.js +7 -50
  23. package/dist/commands/secrets-list.d.ts +2 -2
  24. package/dist/commands/secrets-list.js +4 -4
  25. package/dist/commands/types.d.ts +3 -3
  26. package/dist/commands/types.js +4 -4
  27. package/dist/execution/executor.js +7 -7
  28. package/dist/generators/secrets-dts.d.ts +9 -9
  29. package/dist/generators/secrets-dts.js +12 -12
  30. package/dist/llm-context/llms-architecture.txt +2 -2
  31. package/dist/llm-context/llms-cli.txt +674 -340
  32. package/dist/llm-context/llms-features.txt +334 -336
  33. package/dist/llm-context/llms-full.txt +1207 -892
  34. package/dist/llm-context/llms-getting-started.txt +7 -15
  35. package/dist/llm-context/llms-patterns.txt +5 -23
  36. package/dist/llm-context/llms-providers.txt +10 -1
  37. package/dist/llm-context/llms-sdk.txt +12 -12
  38. package/dist/llm-context/llms.txt +5 -5
  39. package/dist/local-executor/secret-loader.d.ts +1 -1
  40. package/dist/local-plane/orchestrator-process.d.ts +65 -0
  41. package/dist/local-plane/orchestrator-process.js +115 -0
  42. package/dist/local-plane/paths.d.ts +39 -0
  43. package/dist/local-plane/paths.js +54 -0
  44. package/dist/local-plane/plane-manager.d.ts +151 -0
  45. package/dist/local-plane/plane-manager.js +340 -0
  46. package/dist/local-plane/plane-seed.d.ts +76 -0
  47. package/dist/local-plane/plane-seed.js +59 -0
  48. package/dist/local-plane/plane-trigger.d.ts +62 -0
  49. package/dist/local-plane/plane-trigger.js +97 -0
  50. package/dist/local-plane/platform-attach.d.ts +53 -0
  51. package/dist/local-plane/platform-attach.js +111 -0
  52. package/dist/local-plane/postgres.d.ts +44 -0
  53. package/dist/local-plane/postgres.js +165 -0
  54. package/dist/local-plane/resolve-plane.d.ts +37 -0
  55. package/dist/local-plane/resolve-plane.js +76 -0
  56. package/dist/local-plane/run-follow.d.ts +41 -0
  57. package/dist/local-plane/run-follow.js +104 -0
  58. package/dist/local-plane/scaler-config.d.ts +54 -0
  59. package/dist/local-plane/scaler-config.js +114 -0
  60. package/dist/local-plane/secret-seed.d.ts +59 -0
  61. package/dist/local-plane/secret-seed.js +112 -0
  62. package/dist/local-plane/source-provider.d.ts +43 -0
  63. package/dist/local-plane/source-provider.js +165 -0
  64. package/dist/local-plane/trusted-routing.d.ts +32 -0
  65. package/dist/local-plane/trusted-routing.js +47 -0
  66. package/dist/lockfile/generator.js +7 -7
  67. package/dist/remote/dashboard-client.d.ts +7 -7
  68. package/dist/remote/dashboard-client.js +7 -7
  69. package/dist/remote/platform-client.d.ts +1 -1
  70. package/dist/remote/secret-loader.d.ts +18 -0
  71. package/dist/{local-executor → remote}/secret-loader.js +4 -4
  72. package/dist/remote/secret-upload.d.ts +1 -1
  73. package/dist/remote/secret-upload.js +2 -2
  74. package/dist/remote/uploader.d.ts +1 -1
  75. package/dist/remote/uploader.js +2 -2
  76. package/dist/templates/agents-md.d.ts +1 -1
  77. package/dist/templates/agents-md.js +2 -2
  78. package/dist/templates/package-json.js +1 -1
  79. package/dist/test-runner/job-executor.js +1 -1
  80. package/dist/test-runner/runs-on-display.d.ts +9 -0
  81. package/dist/{local-executor → test-runner}/runs-on-display.js +1 -1
  82. package/dist/test-runner/step-context.d.ts +1 -1
  83. package/dist/test-runner/step-context.js +2 -2
  84. package/dist/types.d.ts +6 -6
  85. package/package.json +7 -4
  86. package/sbom.spdx.json +9884 -1459
  87. package/dist/local-executor/dag-scheduler.js +0 -183
  88. package/dist/local-executor/index.js +0 -378
  89. package/dist/local-executor/job-runner.js +0 -411
  90. package/dist/local-executor/materializer.js +0 -132
  91. package/dist/local-executor/output-streamer.js +0 -168
  92. package/dist/local-executor/payload-generator.js +0 -138
  93. package/dist/local-executor/picker.js +0 -109
  94. package/dist/local-executor/to-event-payload.js +0 -21
  95. package/dist/local-executor/types.js +0 -2
  96. package/dist/local-executor/workflow-lock.js +0 -0
@@ -40,7 +40,7 @@ Guide to the KiCI web dashboard. Covers navigation (sidebar, org switcher, mobil
40
40
 
41
41
  How to run and write tests for KiCI workflows, including remote test execution with `kici run remote`, fixture-based testing, and overlay mode for uncommitted changes.
42
42
 
43
- ### [Environments](https://docs.kici.dev/user/environments/)
43
+ ### [Contexts](https://docs.kici.dev/user/contexts/)
44
44
 
45
45
  Configure deployment environments (staging, production, review/\*) with variables, scoped secrets, and protection rules. Covers the SDK API (`environment`, `env`, `concurrencyGroup` on jobs), the 8-layer variable merge precedence, protection rules (branch restrictions, required reviewers, wait timers, concurrency), dashboard management, type generation, and migration from the legacy contexts system.
46
46
 
@@ -279,21 +279,13 @@ Decision Summary:
279
279
 
280
280
  ## Run locally
281
281
 
282
- Execute matched workflows locally with `kici run local`:
282
+ Execute matched workflows locally with `kici run <event> --local`:
283
283
 
284
284
  ```bash
285
- npx kici run local pr:open
285
+ npx kici run pr:open --local
286
286
  ```
287
287
 
288
- This compiles, matches triggers, and runs all matched jobs with DAG-based parallel scheduling.
289
-
290
- If you do not want to remember the event arg, pass `--pick` (or `-p`) and pick from a list of workflows instead:
291
-
292
- ```bash
293
- npx kici run local --pick
294
- ```
295
-
296
- The picker lists each workflow with a summary of its declared triggers, derives the event arg for the one you choose, and runs it through the same pipeline.
288
+ This compiles, matches triggers, and runs all matched jobs on this machine — which joins as an ephemeral agent through the warm local dev plane — with DAG-based parallel scheduling.
297
289
 
298
290
  ## Workflow dependencies
299
291
 
@@ -418,14 +410,14 @@ Or add the flag to your root `package.json`:
418
410
 
419
411
  ## Authoring KiCI workflows with LLM coding agents
420
412
 
421
- KiCI is LLM-ready by design. Because workflows are real, typed TypeScript, coding agents reason over the SDK's `.d.ts` signatures instead of guessing a bespoke YAML DSL — and they verify their own pipelines with the same `kici preview` and `kici run local` loop you use, so there's no push-to-find-out round-trip. First-class agent context ships in the box, so an agent is briefed the moment it opens the project.
413
+ KiCI is LLM-ready by design. Because workflows are real, typed TypeScript, coding agents reason over the SDK's `.d.ts` signatures instead of guessing a bespoke YAML DSL — and they verify their own pipelines with the same `kici preview` and `kici run <event> --local` loop you use, so there's no push-to-find-out round-trip. First-class agent context ships in the box, so an agent is briefed the moment it opens the project.
422
414
 
423
415
  KiCI ships first-class context for LLM coding agents (Claude Code, Cursor, Aider, etc.). When you scaffold a project with `kici init`, the CLI writes `.kici/AGENTS.md`, a one-page briefing that tells the agent:
424
416
 
425
417
  - where the SDK type declarations live (`node_modules/@kici-dev/sdk/dist/index.d.ts`)
426
418
  - the five canonical authoring patterns with runnable examples
427
419
  - the anti-patterns that catch agents off-guard (no YAML, no `/dist/...` imports, no top-level `await`)
428
- - the local commands the agent should drive (`kici compile --check`, `kici preview`, `kici run local`, `kici docs llm`)
420
+ - the local commands the agent should drive (`kici compile --check`, `kici preview`, `kici run <event> --local`, `kici docs llm`)
429
421
 
430
422
  If you don't want the file, pass `--no-agents-md` to `kici init`, or delete the file afterwards — KiCI never reads it at runtime.
431
423
 
@@ -516,7 +508,7 @@ If you're not sure, pick Docker / Podman.
516
508
 
517
509
  ## Looking for the laptop-only path?
518
510
 
519
- Both quickstarts deploy a real orchestrator + agent. If you only want to write a workflow and dry-run it on your laptop with no infrastructure, [Getting started](https://docs.kici.dev/user/getting-started/) covers `kici preview` and `kici run local` instead.
511
+ Both quickstarts deploy a real orchestrator + agent. If you only want to write a workflow and dry-run it on your laptop with no infrastructure, [Getting started](https://docs.kici.dev/user/getting-started/) covers `kici preview` and `kici run <event> --local` instead.
520
512
 
521
513
  ---
522
514
 
@@ -714,7 +706,7 @@ Sometimes you want a workflow that does **not** fire on pushes, pull requests, t
714
706
 
715
707
  There are two ways to "explicitly invoke" a `dispatch()` workflow:
716
708
 
717
- 1. **Locally from your laptop**, with `kici run local dispatch` — no orchestrator, no agent, no webhook, nothing deployed. This is the only path while you haven't wired the repo to a deployed KiCI orchestrator.
709
+ 1. **Locally from your laptop**, with `kici run dispatch --local` — this machine joins as an ephemeral agent through the warm local dev plane, so no orchestrator deployment is required. This is the path to use while you haven't wired the repo to a deployed KiCI orchestrator.
718
710
  2. **Remotely**, if the repo is connected to a KiCI orchestrator via a GitHub App, by calling GitHub's repository-dispatch API: `curl -X POST -H "Authorization: token <PAT>" -H "Accept: application/vnd.github+json" https://api.github.com/repos/<owner>/<repo>/dispatches -d '{"event_type":"hello"}'`. GitHub fans the webhook out to the App, the orchestrator normalizes it into a KiCI `dispatch` event (see `packages/orchestrator/src/providers/github/normalizer.ts`), and the matched workflow runs.
719
711
 
720
712
  Note that GitHub's `workflow_dispatch` event (the "Run workflow" button / `/actions/workflows/.../dispatches` API) is GitHub-Actions-internal and is **not** delivered to KiCI. The SDK has no `workflowDispatch()` trigger. Only `repository_dispatch` reaches KiCI.
@@ -737,36 +729,18 @@ export default workflow('hello-world', {
737
729
  });
738
730
  ```
739
731
 
740
- Run it locally, without any orchestrator or agent infrastructure:
732
+ Run it locally, without any orchestrator deployment:
741
733
 
742
734
  ```bash
743
735
  npx kici compile # regenerate .kici/kici.lock.json
744
- npx kici run local dispatch
745
- ```
746
-
747
- `kici run local` compiles the workflow, matches triggers against a simulated `dispatch` event, and executes the matched jobs directly on your machine with DAG-based scheduling. No webhook, no GitHub, no deployed orchestrator involved. See [`kici run local`](https://docs.kici.dev/user/cli-reference/#kici-run-local) for options like `--job`, `--env`, `--json`, and `--junit`.
748
-
749
- ### Scoping to a single workflow
750
-
751
- Because `kici run local dispatch` matches **every** workflow that listens for a `dispatch` event, running it in a repo with several dispatch-triggered workflows will fire all of them. Narrow execution to one with `--workflow <name>`:
752
-
753
- ```bash
754
- npx kici run local dispatch --workflow hello-world
755
- ```
756
-
757
- `--workflow` is a post-match filter: the workflow still has to have a trigger that matches the event argument. If `hello-world` does not list a `dispatch()` trigger, the command reports `No workflow named "hello-world" matched the event` and exits successfully without running anything.
758
-
759
- If you do not want to memorise event args, use the interactive picker instead:
760
-
761
- ```bash
762
- npx kici run local --pick
736
+ npx kici run dispatch --local
763
737
  ```
764
738
 
765
- `--pick` (aliased as `-p`) lists every workflow alongside a compact summary of its triggers, lets you select one, and derives a matching event arg from the chosen trigger so the execution still flows through the normal trigger-matching pipeline and "cannot produce an inconsistent run". Multi-trigger workflows show a second prompt for which trigger to simulate. `--pick` is mutually exclusive with `--workflow`; in a non-TTY shell it prints the workflow list and exits without running anything.
739
+ `kici run dispatch --local` compiles the workflow, matches triggers against a simulated `dispatch` event, and executes the matched jobs on this machine which joins as an ephemeral agent through the warm local dev plane with DAG-based scheduling. No webhook, no GitHub, no deployed orchestrator involved. See [`kici run <event> --local`](https://docs.kici.dev/user/cli-reference/#kici-run-event---local) for options like `--env`, `--in-place`, and `--offline`.
766
740
 
767
741
  ### Unfiltered vs typed `dispatch()`
768
742
 
769
- Leave `dispatch()` unfiltered while you drive it from `kici run local`. The CLI currently simulates a dispatch event with no event type (i.e. `action` is undefined), so a trigger defined as `dispatch({ types: ['deploy', 'rollback'] })` will not match `kici run local dispatch` — the typed form is intended for real `repository_dispatch` deliveries from the orchestrator.
743
+ Leave `dispatch()` unfiltered while you drive it from `kici run <event> --local`. The CLI simulates a dispatch event with no event type (i.e. `action` is undefined), so a trigger defined as `dispatch({ types: ['deploy', 'rollback'] })` will not match `kici run dispatch --local` — the typed form is intended for real `repository_dispatch` deliveries from the orchestrator.
770
744
 
771
745
  ## Conditional execution with rules
772
746
 
@@ -2012,8 +1986,8 @@ function workflow(name: string, options: WorkflowOptions): Workflow;
2012
1986
  | `options.rules` | `Rule[]` | no | Conditions that must pass for execution |
2013
1987
  | `options.description` | `string` | no | Human-readable description |
2014
1988
  | `options.hashFiles` | `string[]` | no | Extra repo-relative paths or globs mixed into the workflow content hash. Changes invalidate the source cache. |
2015
- | `options.registries` | `Registry[]` | no | Private npm registries the agent authenticates against before `npm install`. Each `tokenSecret` uses qualified `<environment>:<secret>` syntax. |
2016
- | `options.installEnv` | `string[]` | no | Qualified `<environment>:<secret>` refs projected as env vars onto the install subprocess (used with a customer-committed `.kici/.npmrc`). |
1989
+ | `options.registries` | `Registry[]` | no | Private npm registries the agent authenticates against before `npm install`. Each `tokenSecret` uses qualified `<context>:<secret>` syntax. |
1990
+ | `options.installEnv` | `string[]` | no | Qualified `<context>:<secret>` refs projected as env vars onto the install subprocess (used with a customer-committed `.kici/.npmrc`). |
2017
1991
  | `options.onCancel` | `HookInput` | no | Runs when the workflow is cancelled |
2018
1992
  | `options.cleanup` | `HookInput` | no | Always runs after the workflow (success, failure, or cancel) |
2019
1993
  | `options.onSuccess` | `HookInput` | no | Runs on workflow success |
@@ -2564,7 +2538,7 @@ step('fetch-token', {
2564
2538
  - **Timeout is per-attempt.** Each attempt gets the step's full `timeout` budget — a timed-out attempt counts as one failed attempt and is retried while attempts remain. The total wall-clock can therefore approach `maxAttempts * (timeout + delay)`, so keep `maxAttempts` and `maxDelayMs` sane (the job-level `timeout` still bounds the whole job).
2565
2539
  - **Retries exhaust before `continueOnError`.** A step with both retries first; only the _final_ failure is then softened to a warning by `continueOnError`.
2566
2540
 
2567
- `retry` works identically under `kici run local` and on a remote agent, and applies to dynamically-generated job steps too. The `retryIf` predicate is an in-memory function: it is honored at execution time but never serialized into the lock file.
2541
+ `retry` works identically under `kici run <event> --local` and on a remote agent, and applies to dynamically-generated job steps too. The `retryIf` predicate is an in-memory function: it is honored at execution time but never serialized into the lock file.
2568
2542
 
2569
2543
  > **Retry vs. wait-until-condition.** `retry` re-runs a step that _throws_. To poll until a condition becomes true (a port listening, a `/health` endpoint returning 200, a unit becoming active), use [`waitForStep`](https://docs.kici.dev/user/sdk/wait-for/) instead — it is purpose-built for declarative wait-for-condition with intervals, a timeout, and on-timeout handling.
2570
2544
 
@@ -2650,7 +2624,7 @@ const deploy = job('deploy', {
2650
2624
 
2651
2625
  **Important:** `needs` must be declared explicitly. Output chaining does not auto-infer dependencies -- you must list job dependencies in `needs` even if you access their outputs via `.result`.
2652
2626
 
2653
- Cross-job output chaining works in both local execution (`kici run local`) and remote pipeline execution. The orchestrator's needs-aware dispatch scheduler guarantees upstream jobs reach a terminal state before downstream jobs dispatch, and upstream outputs are transported to the downstream agent sandbox via the `upstreamJobOutputs` field on `job.dispatch`. See [needs-scheduler](https://docs.kici.dev/architecture/execution/needs-scheduler/) for the full dispatch semantics.
2627
+ Cross-job output chaining works in both local execution (`kici run <event> --local`) and remote pipeline execution. The orchestrator's needs-aware dispatch scheduler guarantees upstream jobs reach a terminal state before downstream jobs dispatch, and upstream outputs are transported to the downstream agent sandbox via the `upstreamJobOutputs` field on `job.dispatch`. See [needs-scheduler](https://docs.kici.dev/architecture/execution/needs-scheduler/) for the full dispatch semantics.
2654
2628
 
2655
2629
  ### Job dependencies (`needs`)
2656
2630
 
@@ -3391,7 +3365,7 @@ inside a fan-out job nests its concurrency inside each fan-out child.
3391
3365
  ## Local vs remote execution
3392
3366
 
3393
3367
  Run remotely (the orchestrator + agent), parallel children execute concurrently
3394
- and each surfaces as its own dashboard step. `kici run local` executes the same
3368
+ and each surfaces as its own dashboard step. `kici run <event> --local` executes the same
3395
3369
  children in array order in its single-process model — the results are identical,
3396
3370
  only the wall-clock and the live fail-fast cancellation differ. Use a remote run
3397
3371
  to observe the concurrent timeline.
@@ -3531,7 +3505,7 @@ Every variant carries the shared `EventBase` fields — `type`, `action`, `targe
3531
3505
 
3532
3506
  Matrix configurations expand a single job into multiple instances, one per parameter combination. Maximum 256 combinations.
3533
3507
 
3534
- Expansion happens at **dispatch time**: the orchestrator materializes the matrix into N execution jobs — one per combination, each dispatched to its own agent — before any job runs. Each instance receives its combination as `ctx.matrix`. This is identical whether the workflow runs via `kici run local` or remotely through a webhook trigger, and the dashboard groups the N instances under one parent node.
3508
+ Expansion happens at **dispatch time**: the orchestrator materializes the matrix into N execution jobs — one per combination, each dispatched to its own agent — before any job runs. Each instance receives its combination as `ctx.matrix`. This is identical whether the workflow runs via `kici run <event> --local` or remotely through a webhook trigger, and the dashboard groups the N instances under one parent node.
3535
3509
 
3536
3510
  ### Static array (single dimension)
3537
3511
 
@@ -3677,7 +3651,7 @@ step('collect', async ({ jobOutputs }) => {
3677
3651
  });
3678
3652
  ```
3679
3653
 
3680
- The downstream job waits for **all** matrix combinations to terminate before it dispatches. A non-matrix upstream keeps the flat outputs shape. The envelope is identical under `kici run local` and the remote path.
3654
+ The downstream job waits for **all** matrix combinations to terminate before it dispatches. A non-matrix upstream keeps the flat outputs shape. The envelope is identical under `kici run <event> --local` and the remote path.
3681
3655
 
3682
3656
  ### Matrix type guards
3683
3657
 
@@ -4601,14 +4575,14 @@ verify → view journey, including how to verify a bundle with `kici verify-atte
4601
4575
 
4602
4576
  Workflows access secrets through `ctx.secrets` on `StepContext`. Use `await ctx.secrets.get('KEY')` to retrieve a value (rejects with `SecretNotFoundError` if the key is missing, fail-fast on typos), `ctx.secrets.has('KEY')` for a synchronous existence check, and `await ctx.secrets.expose('KEY')` when you need the value as a `process.env` entry for a child process.
4603
4577
 
4604
- ### Declaring the secret environment
4578
+ ### Declaring the secret context
4605
4579
 
4606
- Each job picks its secret environment via the `environment` option on `job()`. The orchestrator resolves the environment's scoped-secret store at dispatch time, evaluates access rules, and sends the decrypted secrets to the agent:
4580
+ Each job picks its secret context via the `context` option on `job()`. The orchestrator resolves the context's scoped-secret store at dispatch time, evaluates access rules, and sends the decrypted secrets to the agent:
4607
4581
 
4608
4582
  ```typescript
4609
4583
  const deploy = job('deploy', {
4610
4584
  runsOn: 'linux',
4611
- environment: 'production',
4585
+ context: 'production',
4612
4586
  steps: [
4613
4587
  /* ... */
4614
4588
  ],
@@ -4620,11 +4594,11 @@ export default workflow('deploy', {
4620
4594
  });
4621
4595
  ```
4622
4596
 
4623
- `environment` accepts either a static string or an async function `(event) => string | Promise<string>` for dynamic resolution at trigger-evaluation time. The resolved environment's secrets are flattened into `ctx.secrets`.
4597
+ `context` accepts either a static string or an async function `(event) => string | Promise<string>` for dynamic resolution at trigger-evaluation time. The resolved context's secrets are flattened into `ctx.secrets`.
4624
4598
 
4625
4599
  ### Accessing secrets (ctx.secrets)
4626
4600
 
4627
- `ctx.secrets` provides flat access to the secrets resolved for the job's environment.
4601
+ `ctx.secrets` provides flat access to the secrets resolved for the job's context.
4628
4602
 
4629
4603
  ```typescript
4630
4604
  step('deploy', async ({ secrets }) => {
@@ -6127,7 +6101,7 @@ The dashboard is a browser SPA on top of the same `/api/v1/*` surface, so anythi
6127
6101
 
6128
6102
  - **Auth & identity:** `/cli/exchange-token`, `/pats`, `/user`, `/identity-links`, `/github-oauth`, `/invites`, `/invites/pending`, `/invites/:inviteId/{accept,decline}`
6129
6103
  - **Org & membership:** `/orgs`, `/orgs/:customerId`, `/orgs/:customerId/{members,roles,api-keys,orchestrator-keys,service-accounts,billing,trust-policies}`
6130
- - **Workflows & runs:** `/orgs/:customerId/{runs,registrations,workflows,held-runs,environments,secrets,global-workflows}`
6104
+ - **Workflows & runs:** `/orgs/:customerId/{runs,registrations,workflows,held-runs,contexts,secrets,global-workflows}`
6131
6105
  - **Webhooks & event log:** `/orgs/:customerId/{sources,webhook-endpoints,event-log}`
6132
6106
  - **Diagnostics & activity:** `/orgs/:customerId/{diagnostics,activity,access-log}`
6133
6107
 
@@ -6251,15 +6225,6 @@ Compile workflows from `.kici/workflows/` to `kici.lock.json`.
6251
6225
  kici compile [options]
6252
6226
  ```
6253
6227
 
6254
- **Options:**
6255
-
6256
- | Option | Default | Description |
6257
- | ------------------- | ------- | -------------------------------------------- |
6258
- | `--check` | `false` | Validate workflows without writing lock file |
6259
- | `--watch` | `false` | Watch for changes and recompile |
6260
- | `--kici-dir <path>` | `.kici` | Path to .kici directory |
6261
- | `--verbose` | `false` | Detailed output |
6262
-
6263
6228
  **Examples:**
6264
6229
 
6265
6230
  ```bash
@@ -6292,69 +6257,21 @@ The `--check` flag is useful in CI pipelines and pre-commit hooks. It validates
6292
6257
 
6293
6258
  ### kici run
6294
6259
 
6295
- 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.
6296
-
6297
- #### kici run local
6298
-
6299
- Execute workflows locally without orchestrator infrastructure. Compiles workflows, matches triggers against the specified event, expands matrices, and runs jobs with DAG-based parallel scheduling.
6300
-
6301
- ```bash
6302
- kici run local [event] [options]
6303
- ```
6304
-
6305
- **Arguments:**
6306
-
6307
- | Argument | Required | Description |
6308
- | -------- | ---------------------- | ------------------------------------------------ |
6309
- | `event` | when `--pick` is unset | Event type (e.g., `push`, `pr:open`, `schedule`) |
6260
+ Execute workflows locally or remotely. A bare `kici run [event]` performs a real routed run with this machine as the ephemeral agent; the `remote` subcommand runs fixtures through an orchestrator.
6310
6261
 
6311
- **Options:**
6262
+ #### kici run <event> --local
6312
6263
 
6313
- | Option | Default | Description |
6314
- | --------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
6315
- | `-p, --pick` | `false` | Interactively pick a workflow + trigger (see below) |
6316
- | `--workflow <name>` | none | Run only the specified workflow (mutex with `--pick`) |
6317
- | `--job <name>` | none | Run only the specified job (and its dependencies) |
6318
- | `--branch <name>` | detected | Override detected git branch |
6319
- | `--sha <hash>` | detected | Override detected git SHA |
6320
- | `--payload <path>` | none | Path to explicit event payload JSON file |
6321
- | `--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. |
6322
- | `--keep-going` | `false` | Continue after job failure |
6323
- | `--container` | `false` | Use Podman container isolation |
6324
- | `--env <KEY=VALUE>` | none | Environment variable override (repeatable) |
6325
- | `--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)) |
6326
- | `--files <path>` | git diff | Override changed file paths (repeatable, default: git diff) |
6327
- | `--quiet` | `false` | Suppress streaming output (summary only) |
6328
- | `--json` | `false` | Output structured JSON result |
6329
- | `--junit <path>` | none | Output JUnit XML result to file |
6330
- | `--debug` | `false` | Verbose internals |
6331
- | `--kici-dir <path>` | `.kici` | Path to .kici directory |
6332
- | `--in-place` | `false` | Run against the real working directory instead of an isolated tmp checkout (see "Execution isolation" below) |
6333
- | `--keep` | `false` | Always retain the isolated tmp checkout (default: keep only on failure) |
6334
- | `--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 |
6335
- | `--fail-on-drift` | `false` | In check mode, exit non-zero if any step reports drift (no effect without `--check`) |
6264
+ Run a workflow on this machine as a real routed dispatch. `kici run <event> --local` compiles your workflows, matches triggers against the specified event, expands matrices, and executes the matched jobs — this machine joins as an ephemeral agent through the warm local dev plane. No orchestrator deployment is required.
6336
6265
 
6337
- **Interactive workflow selection (`--pick` / `-p`):**
6338
-
6339
- 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.
6266
+ `kici run local` (the old direct-execution subcommand) is retired: every run is now a real routed dispatch. Invoking `kici run local <event>` prints a hint pointing at `kici run <event> --local` and exits without running.
6340
6267
 
6341
6268
  ```bash
6342
- # Open the picker across all triggerable workflows
6343
- kici run local --pick
6344
-
6345
- # Scope the picker to a trigger family (e.g. only workflows that react to pr:*)
6346
- kici run local pr:open --pick
6269
+ kici run [event] --local [options]
6347
6270
  ```
6348
6271
 
6349
- Rules:
6350
-
6351
- - `--pick` is mutually exclusive with `--workflow`. Passing both exits with code 2.
6352
- - 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.
6353
- - 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).
6354
-
6355
6272
  **Concurrency enforcement:**
6356
6273
 
6357
- 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:
6274
+ When the workflow declares a `concurrency` block, `kici run <event> --local` enforces it across concurrent local invocations on the same machine and user account. The behavior mirrors the orchestrator:
6358
6275
 
6359
6276
  - 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.
6360
6277
  - `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.
@@ -6368,7 +6285,7 @@ The `SIGTERM`-to-`SIGKILL` grace window defaults to 30 000 ms. Override it with
6368
6285
 
6369
6286
  **Execution isolation:**
6370
6287
 
6371
- 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.
6288
+ By default, `kici run <event> --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.
6372
6289
 
6373
6290
  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`.
6374
6291
 
@@ -6378,8 +6295,7 @@ Cleanup policy:
6378
6295
 
6379
6296
  - On a fully successful run, the isolated checkout is removed.
6380
6297
  - On failure, it is retained and its path is logged so you can inspect the failed state.
6381
- - `--keep` always retains it, even on success.
6382
- - Retained checkouts are garbage-collected after 72 hours by the next `kici run local` invocation — copy a checkout elsewhere if you need it longer.
6298
+ - Retained checkouts are garbage-collected after 72 hours by the next `kici run <event> --local` invocation copy a checkout elsewhere if you need it longer.
6383
6299
 
6384
6300
  Set the `KICI_RUN_DIR` environment variable to place the isolated checkout under a base directory other than the system temp directory.
6385
6301
 
@@ -6390,32 +6306,23 @@ Pass `--in-place` to run against the real working directory instead — useful w
6390
6306
  **Examples:**
6391
6307
 
6392
6308
  ```bash
6393
- # Run workflows matching a push event
6394
- kici run local push
6309
+ # Run workflows matching a push event on this machine
6310
+ kici run push --local
6395
6311
 
6396
- # Run only a specific workflow
6397
- kici run local push --workflow ci
6312
+ # Run a pull-request-open workflow locally
6313
+ kici run pr:open --local
6398
6314
 
6399
- # Run only a specific job (and its dependencies)
6400
- kici run local push --job test
6401
-
6402
- # JSON output for CI scripting
6403
- kici run local push --json
6404
-
6405
- # JUnit XML for CI integration
6406
- kici run local push --junit results.xml
6407
-
6408
- # Quiet mode (summary only, no streaming)
6409
- kici run local push --quiet
6315
+ # Reuse the working tree instead of an isolated clone
6316
+ kici run push --local --in-place
6410
6317
 
6411
- # Override branch and SHA
6412
- kici run local push --branch main --sha abc1234
6318
+ # Force the throwaway/offline plane
6319
+ kici run push --local --offline
6413
6320
 
6414
6321
  # Environment variable overrides
6415
- kici run local push --env NODE_ENV=test --env CI=true
6322
+ kici run push --local --env NODE_ENV=test --env CI=true
6416
6323
 
6417
- # Continue running other jobs after one fails
6418
- kici run local push --keep-going
6324
+ # Quiet mode (summary only, no streaming)
6325
+ kici run push --local --quiet
6419
6326
  ```
6420
6327
 
6421
6328
  **Exit codes:**
@@ -6425,20 +6332,13 @@ kici run local push --keep-going
6425
6332
  | 0 | All workflows succeeded |
6426
6333
  | 1 | One or more jobs failed |
6427
6334
 
6428
- **Output formats:**
6429
-
6430
- - **Default:** Streaming job output during execution, followed by a tree-format summary with per-step timing
6431
- - **`--json`:** Structured JSON with workflows, jobs, steps, timing, and matrix values
6432
- - **`--junit <path>`:** Standard JUnit XML for CI integration (Jenkins, GitLab, etc.)
6433
- - **`--quiet`:** Summary only, no streaming output during execution
6434
-
6435
6335
  #### kici run remote
6436
6336
 
6437
6337
  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.
6438
6338
 
6439
6339
  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.
6440
6340
 
6441
- 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.
6341
+ Like `kici run <event> --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.
6442
6342
 
6443
6343
  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.
6444
6344
 
@@ -6446,39 +6346,6 @@ The orchestrator must have **cache storage configured** (`KICI_STORAGE_TYPE` = `
6446
6346
  kici run remote [fixture] [options]
6447
6347
  ```
6448
6348
 
6449
- **Arguments:**
6450
-
6451
- | Argument | Required | Description |
6452
- | --------- | -------- | ----------------------------------------------- |
6453
- | `fixture` | no | Fixture name or glob pattern (omit to list all) |
6454
-
6455
- **Options:**
6456
-
6457
- | Option | Default | Description |
6458
- | --------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
6459
- | `--org <id>` | active | Target organization for this run (overrides `kici org use`) |
6460
- | `--orchestrator <name>` | default | Target orchestrator cluster within the org (overrides the per-org default) |
6461
- | `--all` | `false` | Run all fixtures |
6462
- | `-p, --pick` | `false` | Interactively pick fixtures to run (multi-select; mutex with a fixture arg, `--all`, and `--workflow`) |
6463
- | `--workflow <name>` | none | Run a specific workflow directly (bypass triggers) |
6464
- | `--parallel` | `false` | Run multiple fixtures concurrently |
6465
- | `--no-wait` | - | Fire and forget (print runIds, don't stream) |
6466
- | `--quiet` | `false` | Minimal output (only final result) |
6467
- | `--json` | `false` | Machine-readable JSON output |
6468
- | `--junit <path>` | none | JUnit XML output to file for CI integration |
6469
- | `--history` | `false` | Show table of recent test runs |
6470
- | `--context <ctx.key=value>` | none | Inject a namespaced context secret, uploaded encrypted (repeatable) |
6471
- | `--env <KEY=VALUE>` | none | Provide a per-run secret, uploaded encrypted (repeatable) — see [testing guide](https://docs.kici.dev/user/testing-guide/) |
6472
- | `--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)) |
6473
- | `--target <selector>` | none | Narrow `runsOnAll` jobs to hosts matching this label selector (repeatable, AND-combined) |
6474
- | `--target-allow-empty` | `false` | A `--target` that narrows a `runsOnAll` job to zero hosts skips it instead of failing |
6475
- | `--debug` | `false` | Verbose internals |
6476
- | `--kici-dir <path>` | `.kici` | Path to .kici directory |
6477
- | `--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)) |
6478
- | `--check` | `false` | Run in [check mode](https://docs.kici.dev/user/idempotent-steps/): each step reports drift instead of applying |
6479
- | `--fail-on-drift` | `false` | In check mode, fail the run if any step reports drift (no effect without `--check`) |
6480
- | `--approve-all, --yes` | `false` | Auto-approve every [approval gate](https://docs.kici.dev/user/approvals/) this run holds on (run-scoped; eligibility still enforced) |
6481
-
6482
6349
  `--approve-all` works in `--json` / `--quiet` mode: the run still auto-approves each gate it holds on, and the auto-approve diagnostics are written to stderr so stdout stays a pure JSON (or summary-only) payload. Without `--approve-all`, a `--json` / `--quiet` run that hits a gate stays held and prints a one-line "run held; approve via the dashboard or `kici approve <run-id>`" notice to stderr per hold.
6483
6350
 
6484
6351
  **Examples:**
@@ -6600,7 +6467,7 @@ When an org has more than one connected orchestrator cluster, the CLI picks the
6600
6467
  - **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.
6601
6468
  - **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/).
6602
6469
 
6603
- 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).
6470
+ 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 <event> --local`](https://docs.kici.dev/user/cli-reference/#kici-run-event---local).
6604
6471
 
6605
6472
  #### Fresh repos (no GitHub remote)
6606
6473
 
@@ -6653,26 +6520,6 @@ Preview which workflows match a trigger event (dry-run, no execution). Useful fo
6653
6520
  kici preview [event] [options]
6654
6521
  ```
6655
6522
 
6656
- **Arguments:**
6657
-
6658
- | Argument | Required | Description |
6659
- | -------- | -------- | ----------------------------------------------------------- |
6660
- | `event` | no | Event type to preview (e.g., `push`, `pr:open`, `schedule`) |
6661
-
6662
- **Options:**
6663
-
6664
- | Option | Default | Description |
6665
- | --------------------------- | ------- | ------------------------------------------------------------ |
6666
- | `--workflow <name>` | none | Filter to specific workflow |
6667
- | `--job <name>` | none | Filter to specific job |
6668
- | `--branch <name>` | `main` | Override target branch for trigger matching |
6669
- | `--sha <hash>` | none | Override commit SHA |
6670
- | `--files <path>` | none | Simulate changed file path for trigger matching (repeatable) |
6671
- | `--secret <key=value>` | none | Inject flat secret (repeatable) |
6672
- | `--context <ctx.key=value>` | none | Inject context secret (repeatable) |
6673
- | `--debug` | `false` | Verbose internals |
6674
- | `--kici-dir <path>` | `.kici` | Path to .kici directory |
6675
-
6676
6523
  **Examples:**
6677
6524
 
6678
6525
  ```bash
@@ -6699,7 +6546,7 @@ kici preview push --files src/index.ts --files README.md
6699
6546
  | 0 | Preview completed (including zero matches) |
6700
6547
  | 1 | Error |
6701
6548
 
6702
- **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>`.
6549
+ **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 <event> --local`.
6703
6550
 
6704
6551
  ### kici login
6705
6552
 
@@ -6715,16 +6562,6 @@ After OAuth, the CLI exchanges the OIDC token for a personal access token (PAT)
6715
6562
  kici login [options]
6716
6563
  ```
6717
6564
 
6718
- **Options:**
6719
-
6720
- | Option | Default | Description |
6721
- | --------------------------- | ------- | --------------------------------------------------- |
6722
- | `--token <key>` | none | API key for direct authentication (legacy) |
6723
- | `--device` | false | Force device authorization flow (headless/SSH) |
6724
- | `--platform-endpoint <url>` | none | Platform relay URL |
6725
- | `--oidc-issuer <url>` | none | OIDC issuer URL (selects a non-default environment) |
6726
- | `--routing-key <key>` | none | Routing key for webhook source identification |
6727
-
6728
6565
  **Environment variables:**
6729
6566
 
6730
6567
  | Variable | Default | Description |
@@ -6810,12 +6647,6 @@ Switch the active organization by name (case-insensitive) or ID.
6810
6647
  kici org use <name>
6811
6648
  ```
6812
6649
 
6813
- **Arguments:**
6814
-
6815
- | Argument | Required | Description |
6816
- | -------- | -------- | ----------------------- |
6817
- | `name` | yes | Organization name or ID |
6818
-
6819
6650
  **Examples:**
6820
6651
 
6821
6652
  ```bash
@@ -6851,14 +6682,6 @@ concurrency, and heartbeat age.
6851
6682
  kici diagnostics [options]
6852
6683
  ```
6853
6684
 
6854
- **Options:**
6855
-
6856
- | Option | Default | Description |
6857
- | --------------------- | ------- | --------------------------------------------------- |
6858
- | `--json` | `false` | Machine-readable JSON output |
6859
- | `--verbose` | `false` | Show extended per-agent fields (host, node, memory) |
6860
- | `--orchestrator <id>` | all | Scope the tree to one orchestrator connection id |
6861
-
6862
6685
  **Examples:**
6863
6686
 
6864
6687
  ```bash
@@ -6891,20 +6714,6 @@ branch, trigger, started, duration); pagination is reported at the bottom.
6891
6714
  kici runs list [options]
6892
6715
  ```
6893
6716
 
6894
- **Options:**
6895
-
6896
- | Option | Default | Description |
6897
- | ----------------------- | ------- | --------------------------------------------- |
6898
- | `--status <s>` | all | Filter by run status |
6899
- | `--workflow <w>` | all | Filter by workflow name |
6900
- | `--branch <b>` | all | Filter by branch/ref |
6901
- | `--repo <r>` | all | Filter by repository |
6902
- | `--trigger <t>` | all | Filter by trigger type |
6903
- | `--source <routingKey>` | all | Filter by source routing key |
6904
- | `--since <ts>` | none | Only runs since this ISO-8601 or epoch ms |
6905
- | `--page <n>` | `1` | Page number (server page size is fixed at 20) |
6906
- | `--json` | `false` | Machine-readable JSON output |
6907
-
6908
6717
  ```bash
6909
6718
  kici runs list
6910
6719
  kici runs list --status running
@@ -6916,16 +6725,12 @@ kici runs list --json | jq '.runs[].runId'
6916
6725
 
6917
6726
  Show a run's summary header plus its jobs-and-steps tree (name, status,
6918
6727
  duration, exit code). If the run id is not on the Platform but exists in your
6919
- local run history (from `kici run local`), the local record is shown instead.
6728
+ local run history (from `kici run <event> --local`), the local record is shown instead.
6920
6729
 
6921
6730
  ```bash
6922
6731
  kici runs show <run-id> [options]
6923
6732
  ```
6924
6733
 
6925
- | Option | Default | Description |
6926
- | -------- | ------- | ---------------------------- |
6927
- | `--json` | `false` | Machine-readable JSON output |
6928
-
6929
6734
  ```bash
6930
6735
  kici runs show abc123
6931
6736
  kici runs show abc123 --json
@@ -6939,12 +6744,6 @@ Print each job/step's log lines in order, with headers.
6939
6744
  kici runs logs <run-id> [options]
6940
6745
  ```
6941
6746
 
6942
- | Option | Default | Description |
6943
- | -------------- | ------- | -------------------------------------- |
6944
- | `--job <name>` | all | Only print logs for this job |
6945
- | `-f, --follow` | `false` | Tail logs for a live run until it ends |
6946
- | `--json` | `false` | Machine-readable JSON output |
6947
-
6948
6747
  ```bash
6949
6748
  kici runs logs abc123
6950
6749
  kici runs logs abc123 --job build
@@ -6960,10 +6759,6 @@ cooldown between reruns of the same run.
6960
6759
  kici runs rerun <run-id> [options]
6961
6760
  ```
6962
6761
 
6963
- | Option | Default | Description |
6964
- | -------- | ------- | ---------------------------- |
6965
- | `--json` | `false` | Machine-readable JSON output |
6966
-
6967
6762
  ```bash
6968
6763
  kici runs rerun abc123
6969
6764
  ```
@@ -6976,15 +6771,6 @@ Cancel a single run, or all in-progress runs on a branch.
6976
6771
  kici runs cancel [run-id] [options]
6977
6772
  ```
6978
6773
 
6979
- | Argument | Required | Description |
6980
- | -------- | -------- | ---------------- |
6981
- | `run-id` | no | Run ID to cancel |
6982
-
6983
- | Option | Default | Description |
6984
- | ----------------- | ------- | ------------------------------------------- |
6985
- | `--force` | `false` | Force cancel (kill immediately, skip hooks) |
6986
- | `--branch <name>` | none | Cancel all in-progress runs on this branch |
6987
-
6988
6774
  ```bash
6989
6775
  kici runs cancel abc123
6990
6776
  kici runs cancel abc123 --force
@@ -7005,19 +6791,6 @@ Approve a held [approval gate](https://docs.kici.dev/user/approvals/) so the run
7005
6791
  kici approve <run-id> [options]
7006
6792
  ```
7007
6793
 
7008
- **Arguments:**
7009
-
7010
- | Argument | Required | Description |
7011
- | -------- | -------- | ---------------------------------- |
7012
- | `run-id` | yes | Run ID holding the gate to approve |
7013
-
7014
- **Options:**
7015
-
7016
- | Option | Default | Description |
7017
- | ---------------- | ------- | ---------------------------------------------------- |
7018
- | `--job <name>` | none | Approve a held job (omit for a workflow-level hold) |
7019
- | `--step <index>` | none | Approve a held step by its index (used with `--job`) |
7020
-
7021
6794
  **Examples:**
7022
6795
 
7023
6796
  ```bash
@@ -7031,7 +6804,7 @@ kici approve abc123 --job deploy-production
7031
6804
  kici approve abc123 --job migrate-and-deploy --step 1
7032
6805
  ```
7033
6806
 
7034
- 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.
6807
+ You must be eligible for at least one unsatisfied clause (a member of a named team, or a named user) and hold the `contexts:write` or `ci_trust:write` permission. The command reports whether the element was released, how many clauses remain, or that it was rejected.
7035
6808
 
7036
6809
  ### kici reject
7037
6810
 
@@ -7041,20 +6814,6 @@ Reject a held [approval gate](https://docs.kici.dev/user/approvals/). A rejectio
7041
6814
  kici reject <run-id> --reason <text> [options]
7042
6815
  ```
7043
6816
 
7044
- **Arguments:**
7045
-
7046
- | Argument | Required | Description |
7047
- | -------- | -------- | --------------------------------- |
7048
- | `run-id` | yes | Run ID holding the gate to reject |
7049
-
7050
- **Options:**
7051
-
7052
- | Option | Default | Description |
7053
- | ----------------- | ------- | --------------------------------------------------- |
7054
- | `--reason <text>` | none | Required. Reason recorded with the rejection |
7055
- | `--job <name>` | none | Reject a held job (omit for a workflow-level hold) |
7056
- | `--step <index>` | none | Reject a held step by its index (used with `--job`) |
7057
-
7058
6817
  **Examples:**
7059
6818
 
7060
6819
  ```bash
@@ -7070,7 +6829,7 @@ List secret contexts available for test runs. Shows context names and key names
7070
6829
  kici secrets list
7071
6830
  ```
7072
6831
 
7073
- 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.
6832
+ Each context corresponds to a context configured on the orchestrator. The output lists every context whose `allowLocalExecution` flag is `true` (the gate that lets CLI-initiated test runs resolve secrets through that context), along with the secret key names reachable from the context's bound scopes.
7074
6833
 
7075
6834
  Only key names are shown — secret values are never returned over this endpoint.
7076
6835
 
@@ -7099,18 +6858,12 @@ See [Drive KiCI from your coding agent](https://docs.kici.dev/user/ai-agents/) f
7099
6858
 
7100
6859
  ### kici types
7101
6860
 
7102
- 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.
6861
+ Generate TypeScript declaration files from orchestrator environment metadata. The generated `.d.ts` file augments the SDK's `KnownSecretKeys` and `ContextSecrets` interfaces, providing compile-time autocomplete and type checking for secret key names.
7103
6862
 
7104
6863
  ```bash
7105
6864
  kici types [options]
7106
6865
  ```
7107
6866
 
7108
- **Options:**
7109
-
7110
- | Option | Default | Description |
7111
- | ------------------- | ------- | ----------------------- |
7112
- | `--kici-dir <path>` | `.kici` | Path to .kici directory |
7113
-
7114
6867
  **Prerequisites:** Must be authenticated via `kici login`.
7115
6868
 
7116
6869
  **Output:** `.kici/types/secrets.d.ts`
@@ -7128,7 +6881,7 @@ kici types --kici-dir packages/app/.kici
7128
6881
  **How it works:**
7129
6882
 
7130
6883
  1. Fetches all environment metadata (environment names and secret key names) from the orchestrator
7131
- 2. Generates a `.d.ts` file that augments `@kici-dev/sdk`'s `KnownSecretKeys` and `EnvironmentSecrets` interfaces
6884
+ 2. Generates a `.d.ts` file that augments `@kici-dev/sdk`'s `KnownSecretKeys` and `ContextSecrets` interfaces
7132
6885
  3. Writes the file to `.kici/types/secrets.d.ts`
7133
6886
 
7134
6887
  After generating types, `ctx.secrets.get('MY_KEY')` and `ctx.secrets.expose('DB_HOST')` gain autocomplete and type checking in your IDE.
@@ -7147,20 +6900,8 @@ Generate a fixture template for an event type. Useful for creating custom test p
7147
6900
  kici fixture <event> [options]
7148
6901
  ```
7149
6902
 
7150
- **Arguments:**
7151
-
7152
- | Argument | Required | Description |
7153
- | -------- | -------- | ----------------------------- |
7154
- | `event` | yes | Event to generate fixture for |
7155
-
7156
6903
  **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>`.
7157
6904
 
7158
- **Options:**
7159
-
7160
- | Option | Default | Description |
7161
- | ----------------- | ------- | ------------------------------- |
7162
- | `--output <path>` | stdout | Write to file instead of stdout |
7163
-
7164
6905
  **Examples:**
7165
6906
 
7166
6907
  ```bash
@@ -7189,19 +6930,6 @@ Initialize a `.kici/` directory with default workflow templates.
7189
6930
  kici init [options]
7190
6931
  ```
7191
6932
 
7192
- **Options:**
7193
-
7194
- | Option | Default | Description |
7195
- | ------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------- |
7196
- | `--force` | `false` | Overwrite existing `.kici/` directory |
7197
- | `--skip-install` | `false` | Create files without installing dependencies |
7198
- | `--package-manager <npm\|pnpm\|yarn>` | auto-detect | Force a package manager for the install step (default: detect from your repo) |
7199
- | `--mjs` | `false` | JavaScript-only mode (no TypeScript, no deps) |
7200
- | `--no-agents-md` | writes `AGENTS.md` | Skip writing `.kici/AGENTS.md` (the LLM authoring context file) |
7201
- | `--private-registry <url>` | none | Scaffold a workflow `registries:` entry pointing at `<url>` (e.g. CodeArtifact, GH Packages, Verdaccio) |
7202
- | `--private-registry-scope <scope>` | none | Optional npm package scope (e.g. `@my-org`) for the private registry |
7203
- | `--private-registry-secret <ref>` | `production:NPM_TOKEN` | Qualified secret reference (`env:NAME`) the private registry token comes from |
7204
-
7205
6933
  **Examples:**
7206
6934
 
7207
6935
  ```bash
@@ -7261,12 +6989,6 @@ Install a pre-commit hook that runs `kici compile` before each commit.
7261
6989
  kici hook install [options]
7262
6990
  ```
7263
6991
 
7264
- **Options:**
7265
-
7266
- | Option | Default | Description |
7267
- | ------- | ------- | ------------------------------------------ |
7268
- | `--git` | `false` | Use raw git hook (`.git/hooks/pre-commit`) |
7269
-
7270
6992
  **Examples:**
7271
6993
 
7272
6994
  ```bash
@@ -7293,12 +7015,6 @@ List all webhook entrypoints for the current project. Reads the compiled lock fi
7293
7015
  kici endpoints [options]
7294
7016
  ```
7295
7017
 
7296
- **Options:**
7297
-
7298
- | Option | Default | Description |
7299
- | ------------------- | ------- | ----------------------- |
7300
- | `--kici-dir <path>` | `.kici` | Path to .kici directory |
7301
-
7302
7018
  **Prerequisites:** Run `kici compile` first to generate the lock file.
7303
7019
 
7304
7020
  **Examples:**
@@ -7319,15 +7035,6 @@ List permanently registered workflows on the orchestrator.
7319
7035
  kici workflows list [options]
7320
7036
  ```
7321
7037
 
7322
- **Options:**
7323
-
7324
- | Option | Default | Description |
7325
- | ----------------------- | ------- | ---------------------------------------------- |
7326
- | `--json` | `false` | Output as JSON |
7327
- | `--stale <duration>` | none | Filter stale registrations (e.g., `30d`, `7d`) |
7328
- | `--trigger-type <type>` | none | Filter by trigger type |
7329
- | `--repo <repo>` | none | Filter by repository |
7330
-
7331
7038
  **Examples:**
7332
7039
 
7333
7040
  ```bash
@@ -7387,12 +7094,6 @@ Trigger graceful drain on a worker instance. Sends a POST request to the worker'
7387
7094
  kici admin drain-worker [options]
7388
7095
  ```
7389
7096
 
7390
- **Options:**
7391
-
7392
- | Option | Required | Description |
7393
- | ------------- | -------- | --------------------------------------------- |
7394
- | `--url <url>` | yes | Worker URL (e.g., `http://worker-host:10143`) |
7395
-
7396
7097
  **Examples:**
7397
7098
 
7398
7099
  ```bash
@@ -7420,21 +7121,6 @@ On success the output prints the **origin org** (the customer's public org id
7420
7121
  kici verify-attestation [artifact] --bundle <path-or-url> [--trust-root <url-or-file>] [options]
7421
7122
  ```
7422
7123
 
7423
- **Arguments:**
7424
-
7425
- | Argument | Required | Description |
7426
- | ------------ | -------- | --------------------------------------------------------------------- |
7427
- | `[artifact]` | no | Artifact path to digest-check against the attestation subject digest. |
7428
-
7429
- **Options:**
7430
-
7431
- | Option | Required | Description |
7432
- | ---------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
7433
- | `--bundle <path-or-url>` | yes | Path or `http(s)` URL to the attestation bundle JSON. |
7434
- | `--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. |
7435
- | `--audience <aud>` | no | Expected token audience (defaults to the KiCI provenance audience). |
7436
- | `--json` | no | Print the structured verification result as JSON instead of human-readable output. |
7437
-
7438
7124
  **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:
7439
7125
 
7440
7126
  - **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`.
@@ -7490,194 +7176,816 @@ the authoritative anchor.
7490
7176
  | 0 | Verified — signature, identity, build context (and digest, if checked) all pass |
7491
7177
  | 1 | Not verified, or an error (missing `--bundle`, unreadable bundle, unreachable trust root) |
7492
7178
 
7493
- ## Workflow discovery
7179
+ ## Command reference
7494
7180
 
7495
- The CLI discovers workflows by scanning `.kici/workflows/*.ts` (or `.mjs` in MJS mode). Each file should `export default` a single workflow:
7181
+ The exhaustive, always-current list of every `kici` command with its arguments and options, generated from the CLI's command tree so it never drifts from the shipped binary. The sections above add concepts and worked examples; the reference below is the authoritative signature list.
7496
7182
 
7497
- ```typescript
7498
- // .kici/workflows/ci.ts
7499
- import { workflow, job, step, pr } from '@kici-dev/sdk';
7183
+ <!-- BEGIN GENERATED: kici-commands (do not edit; run the doc generator) -->
7500
7184
 
7501
- export default workflow('ci', {
7502
- on: pr(),
7503
- jobs: [
7504
- /* ... */
7505
- ],
7506
- });
7507
- ```
7185
+ ### `kici admin`
7508
7186
 
7509
- Multiple workflow files are supported -- each becomes a separate workflow in `kici.lock.json`.
7187
+ Operator-facing commands for running instances
7510
7188
 
7511
- ## Lock file
7189
+ Synopsis: `kici admin`
7512
7190
 
7513
- The `kici compile` command produces `.kici/kici.lock.json` inside the `.kici` directory. This file:
7191
+ ### `kici admin drain-worker`
7514
7192
 
7515
- - Contains all workflow definitions in a portable JSON format
7516
- - Is used by the orchestrator to evaluate triggers without code checkout
7517
- - Should be committed to version control
7518
- - Is regenerated on every `kici compile` run
7193
+ Trigger graceful drain on a worker instance
7519
7194
 
7520
- 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/).
7195
+ Synopsis: `kici admin drain-worker [options]`
7521
7196
 
7522
- ## Exit codes
7197
+ **Options**
7523
7198
 
7524
- All commands follow a consistent exit code convention:
7199
+ | Option | Default | Description |
7200
+ | ------------- | ------- | -------------------------------------------- |
7201
+ | `--url <url>` | | Worker URL (e.g., http://worker-host:<port>) |
7525
7202
 
7526
- | Code | Meaning |
7527
- | ---- | -------------------- |
7528
- | 0 | Success |
7529
- | 1 | Failure (see output) |
7203
+ ### `kici approve`
7530
7204
 
7531
- ## Debug output
7205
+ Approve a held approval gate for a run
7532
7206
 
7533
- Use `--debug` (on `kici run local`, `kici run remote`, `kici preview`) or `--verbose` (on `kici compile`) for detailed output:
7207
+ Synopsis: `kici approve <run-id> [options]`
7534
7208
 
7535
- ```bash
7536
- # Shows trigger matching, rule evaluation, decision traces
7537
- kici run local push --debug
7209
+ **Arguments**
7538
7210
 
7539
- # Shows detailed compilation steps
7540
- kici compile --verbose
7211
+ | Argument | Required | Variadic | Description |
7212
+ | -------- | -------- | -------- | ------------------------------------- |
7213
+ | `run-id` | yes | no | Run ID whose approval gate to approve |
7541
7214
 
7542
- # Shows trigger matching preview
7543
- kici preview pr:open --debug
7544
- ```
7215
+ **Options**
7545
7216
 
7546
- Set `KICI_DEBUG=true` for additional internal debug output across all commands.
7217
+ | Option | Default | Description |
7218
+ | ---------------- | ------- | ------------------------------------------- |
7219
+ | `--job <name>` | | Approve the hold for a specific job |
7220
+ | `--step <index>` | | Approve a step-scoped hold (requires --job) |
7547
7221
 
7548
- ## Environment variables
7222
+ ### `kici compile`
7549
7223
 
7550
- | Variable | Description |
7551
- | ------------ | ----------------------------------------- |
7552
- | `KICI_DEV` | Set to `true` for development mode |
7553
- | `KICI_DEBUG` | Set to `true` for verbose internal output |
7554
- | `CI` | When `true`, disables interactive prompts |
7224
+ Compile workflows from .kici/workflows/ to kici.lock.json
7555
7225
 
7556
- ## See also
7226
+ Synopsis: `kici compile [options]`
7557
7227
 
7558
- - [Getting started](https://docs.kici.dev/user/getting-started/) -- install the SDK and write your first workflow
7559
- - [Testing guide](https://docs.kici.dev/user/testing-guide/) -- writing fixtures, remote test runs, secret contexts, and repo state transfer
7560
- - [SDK reference](https://docs.kici.dev/user/sdk-reference/) -- complete API for the workflow definitions that the CLI compiles
7561
- - [Workflow patterns](https://docs.kici.dev/user/workflow-patterns/) -- example workflows to compile and test with these commands
7228
+ **Options**
7562
7229
 
7563
- ---
7230
+ | Option | Default | Description |
7231
+ | ------------------- | ------- | -------------------------------------------- |
7232
+ | `--check` | `false` | Validate workflows without writing lock file |
7233
+ | `--kici-dir <path>` | `.kici` | Path to .kici directory |
7234
+ | `--verbose` | `false` | Detailed output |
7235
+ | `--watch` | `false` | Watch for changes and recompile |
7564
7236
 
7565
- ## Lifecycle hooks
7237
+ ### `kici diagnostics`
7566
7238
 
7567
- Source: https://docs.kici.dev/user/hooks/
7239
+ Show orchestrators, scalers, and agents (mirrors the dashboard Diagnostics page)
7568
7240
 
7569
- 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.
7241
+ Synopsis: `kici diagnostics [options]`
7570
7242
 
7571
- ## Hook types
7243
+ **Options**
7572
7244
 
7573
- KiCI supports six hook types at three levels (step, job, workflow):
7245
+ | Option | Default | Description |
7246
+ | --------------------- | ------- | ----------------------------------- |
7247
+ | `--json` | `false` | Output raw JSON |
7248
+ | `--verbose` | `false` | Show extended per-agent fields |
7249
+ | `--orchestrator <id>` | | Scope the tree to one connection id |
7574
7250
 
7575
- | Hook | When it runs | Available on |
7576
- | ------------ | ------------------------------------ | ------------------- |
7577
- | `onCancel` | After step/job/workflow is cancelled | Step, Job, Workflow |
7578
- | `cleanup` | Always (success, failure, or cancel) | Step, Job, Workflow |
7579
- | `onSuccess` | After job/workflow succeeds | Job, Workflow |
7580
- | `onFailure` | After job/workflow fails | Job, Workflow |
7581
- | `beforeStep` | Before each step in a job | Job |
7582
- | `afterStep` | After each step in a job | Job |
7251
+ ### `kici docs`
7583
7252
 
7584
- ## Basic usage
7253
+ Open the KiCI documentation site in the default browser
7585
7254
 
7586
- ### Job-level hooks
7255
+ Synopsis: `kici docs [options]`
7587
7256
 
7588
- ```typescript
7589
- import { workflow, job, step, push } from '@kici-dev/sdk';
7257
+ **Options**
7590
7258
 
7591
- export default workflow('deploy', {
7592
- on: push({ branches: ['main'] }),
7593
- jobs: [
7594
- job('deploy-prod', {
7595
- runsOn: 'linux',
7596
- steps: [
7597
- step('deploy', async ({ $ }) => {
7598
- await $`kubectl apply -f manifests/`;
7599
- }),
7600
- ],
7601
- onCancel: async (ctx) => {
7602
- console.log(`Deploy cancelled: ${ctx.outcome.reason}`);
7603
- await ctx.$`kubectl rollout undo deployment/app`;
7604
- },
7605
- cleanup: async (ctx) => {
7606
- // Always runs -- release lock, notify team, etc.
7607
- await ctx.$`curl -X POST https://slack.com/webhook -d '{"text": "Deploy ${ctx.outcome.status}"}'`;
7608
- },
7609
- onSuccess: async (ctx) => {
7610
- console.log(`Deploy succeeded in ${ctx.outcome.duration}ms`);
7611
- },
7612
- onFailure: async (ctx) => {
7613
- console.log(`Deploy failed at step: ${ctx.outcome.failedStep}`);
7614
- },
7615
- gracePeriod: 60, // 60 seconds before SIGKILL on cancel
7616
- }),
7617
- ],
7618
- });
7619
- ```
7259
+ | Option | Default | Description |
7260
+ | ----------- | ------- | ----------------------------------------------- |
7261
+ | `--no-open` | | Print the docs URL instead of opening a browser |
7620
7262
 
7621
- ### Step-level hooks
7263
+ ### `kici docs llm`
7622
7264
 
7623
- ```typescript
7624
- step('download-artifacts', {
7625
- run: async ({ $ }) => {
7626
- await $`wget https://artifacts.example.com/build.tar.gz`;
7627
- },
7628
- onCancel: async (ctx) => {
7629
- // Clean up partial downloads
7630
- await ctx.$`rm -f build.tar.gz`;
7631
- },
7632
- cleanup: async (ctx) => {
7633
- await ctx.$`rm -rf /tmp/staging`;
7634
- },
7635
- });
7636
- ```
7265
+ Print KiCI LLM docs bundles. No topic prints the llms.txt index; <topic> prints a task bundle (e.g. sdk, cli, patterns, features, providers, architecture, getting-started); "full" prints the complete bundle.
7637
7266
 
7638
- ### Workflow-level hooks
7267
+ Synopsis: `kici docs llm [topic] [options]`
7639
7268
 
7640
- ```typescript
7641
- workflow('ci', {
7642
- on: push({ branches: ['main'] }),
7643
- jobs: [
7644
- /* ... */
7645
- ],
7646
- onCancel: async (ctx) => {
7647
- // Notify when any job in the workflow is cancelled
7648
- console.log('CI workflow cancelled');
7649
- },
7650
- cleanup: async (ctx) => {
7651
- // Always runs after all jobs complete
7652
- console.log(`CI workflow finished with status: ${ctx.outcome.status}`);
7653
- },
7654
- });
7655
- ```
7269
+ **Arguments**
7656
7270
 
7657
- ## Hook context
7271
+ | Argument | Required | Variadic | Description |
7272
+ | -------- | -------- | -------- | ----------- |
7273
+ | `topic` | no | no | |
7658
7274
 
7659
- Hook functions receive the same `StepContext` as regular steps (`$`, `ctx`, `log`, `env`), plus an `outcome` object with metadata about the execution result.
7275
+ **Options**
7660
7276
 
7661
- ### ctx.outcome
7277
+ | Option | Default | Description |
7278
+ | -------------- | ------- | -------------------------------------------- |
7279
+ | `--out <path>` | | Write the bundle to a file instead of stdout |
7662
7280
 
7663
- ```typescript
7664
- interface OutcomeMetadata {
7665
- /** Final status of the job/workflow. */
7666
- status: 'cancelled' | 'success' | 'failed';
7667
- /** Reason for cancellation (e.g., "User requested", "Superseded by run #42"). */
7668
- reason?: string;
7669
- /** Name of the step that caused failure (for onFailure hooks). */
7670
- failedStep?: string;
7671
- /** Outputs from all completed steps. */
7672
- stepOutputs: Record<string, unknown>;
7673
- /** Total execution duration in milliseconds. */
7674
- duration: number;
7675
- }
7676
- ```
7281
+ ### `kici endpoints`
7677
7282
 
7678
- ### Capabilities
7283
+ List all webhook entrypoints for the current project
7679
7284
 
7680
- Hooks can do everything regular steps can:
7285
+ Synopsis: `kici endpoints [options]`
7286
+
7287
+ **Options**
7288
+
7289
+ | Option | Default | Description |
7290
+ | ------------------- | ------- | ----------------------- |
7291
+ | `--kici-dir <path>` | `.kici` | Path to .kici directory |
7292
+
7293
+ ### `kici fixture`
7294
+
7295
+ Generate fixture template for event type
7296
+
7297
+ Synopsis: `kici fixture <event> [options]`
7298
+
7299
+ **Arguments**
7300
+
7301
+ | Argument | Required | Variadic | Description |
7302
+ | -------- | -------- | -------- | ------------------------------------------------------------------------------------------ |
7303
+ | `event` | yes | no | Event to generate fixture for (e.g., pr:open, push, schedule, lifecycle:workflow_complete) |
7304
+
7305
+ **Options**
7306
+
7307
+ | Option | Default | Description |
7308
+ | ----------------- | ------- | ------------------------------- |
7309
+ | `--output <path>` | | Write to file instead of stdout |
7310
+
7311
+ ### `kici hook`
7312
+
7313
+ Manage pre-commit hooks
7314
+
7315
+ Synopsis: `kici hook`
7316
+
7317
+ ### `kici hook install`
7318
+
7319
+ Install kici compile pre-commit hook
7320
+
7321
+ Synopsis: `kici hook install [options]`
7322
+
7323
+ **Options**
7324
+
7325
+ | Option | Default | Description |
7326
+ | ------- | ------- | ---------------------------------------- |
7327
+ | `--git` | `false` | Use raw git hook (.git/hooks/pre-commit) |
7328
+
7329
+ ### `kici init`
7330
+
7331
+ Initialize .kici/ directory with default workflows
7332
+
7333
+ Synopsis: `kici init [options]`
7334
+
7335
+ **Options**
7336
+
7337
+ | Option | Default | Description |
7338
+ | ---------------------------------- | ---------------------- | --------------------------------------------------------------------------- | --- | ------------------------------------------------------------------- |
7339
+ | `--force` | `false` | Overwrite existing .kici/ directory |
7340
+ | `--skip-install` | `false` | Create files without installing dependencies |
7341
+ | `--package-manager <npm | pnpm | yarn>` | | Force a package manager for the install step (default: auto-detect) |
7342
+ | `--mjs` | `false` | JavaScript-only mode (no TypeScript, no dependencies) |
7343
+ | `--no-agents-md` | | Skip writing .kici/AGENTS.md (LLM authoring context) |
7344
+ | `--private-registry <url>` | | Scaffold a workflow registries: entry pointing at <url> |
7345
+ | `--private-registry-scope <scope>` | | Optional npm package scope (e.g. @my-org) for the private registry |
7346
+ | `--private-registry-secret <ref>` | `production:NPM_TOKEN` | Qualified secret reference (env:NAME) the private registry token comes from |
7347
+ | `--use-verdaccio-local` | `false` | |
7348
+
7349
+ ### `kici local`
7350
+
7351
+ Manage the local dev orchestrator plane
7352
+
7353
+ Synopsis: `kici local`
7354
+
7355
+ ### `kici local attach`
7356
+
7357
+ Attach the local dev plane to the Platform (hybrid)
7358
+
7359
+ Synopsis: `kici local attach`
7360
+
7361
+ ### `kici local detach`
7362
+
7363
+ Detach the local dev plane from the Platform (offline)
7364
+
7365
+ Synopsis: `kici local detach`
7366
+
7367
+ ### `kici local down`
7368
+
7369
+ Stop the local dev plane
7370
+
7371
+ Synopsis: `kici local down`
7372
+
7373
+ ### `kici local logs`
7374
+
7375
+ Print the local dev plane orchestrator log path
7376
+
7377
+ Synopsis: `kici local logs`
7378
+
7379
+ ### `kici local status`
7380
+
7381
+ Show local dev plane status and control commands
7382
+
7383
+ Synopsis: `kici local status`
7384
+
7385
+ ### `kici local trust-root`
7386
+
7387
+ Export the offline dev-signed identity trust root ({ issuer, jwks }) to a file
7388
+
7389
+ Synopsis: `kici local trust-root <file>`
7390
+
7391
+ **Arguments**
7392
+
7393
+ | Argument | Required | Variadic | Description |
7394
+ | -------- | -------- | -------- | ---------------------------------------------------- |
7395
+ | `file` | yes | no | Output path for the { issuer, jwks } trust-root JSON |
7396
+
7397
+ ### `kici local up`
7398
+
7399
+ Start (or reuse) the local dev plane
7400
+
7401
+ Synopsis: `kici local up [options]`
7402
+
7403
+ **Options**
7404
+
7405
+ | Option | Default | Description |
7406
+ | ------------- | ------- | ---------------------------------------------------------------------------- |
7407
+ | `--offline` | `false` | Force the independent (offline) plane (does not clear the attachment record) |
7408
+ | `--connected` | `false` | Force the connected/hybrid plane (requires an attached, reachable Platform) |
7409
+
7410
+ ### `kici login`
7411
+
7412
+ Authenticate with KiCI via browser OAuth (default) or API key (--token)
7413
+
7414
+ Synopsis: `kici login [options]`
7415
+
7416
+ **Options**
7417
+
7418
+ | Option | Default | Description |
7419
+ | --------------------------- | ------- | ----------------------------------------------------------------------------------- |
7420
+ | `--token <key>` | | API key for direct authentication (legacy) |
7421
+ | `--device` | | Force device authorization flow (for headless/SSH environments) |
7422
+ | `--platform-endpoint <url>` | | Platform relay URL |
7423
+ | `--oidc-issuer <url>` | | OIDC issuer URL (defaults to the hosted KiCI IdP unless a flag/env selects another) |
7424
+ | `--routing-key <key>` | | Routing key for webhook source identification |
7425
+ | `--no-attach` | | Skip the post-login prompt to attach the local dev plane |
7426
+
7427
+ ### `kici logout`
7428
+
7429
+ Revoke PAT and clear local credentials
7430
+
7431
+ Synopsis: `kici logout`
7432
+
7433
+ ### `kici orchestrators`
7434
+
7435
+ Inspect the org's orchestrator clusters and pick a default for run remote
7436
+
7437
+ Synopsis: `kici orchestrators`
7438
+
7439
+ ### `kici orchestrators list`
7440
+
7441
+ List the connected orchestrator clusters for the active org
7442
+
7443
+ Synopsis: `kici orchestrators list [options]`
7444
+
7445
+ **Options**
7446
+
7447
+ | Option | Default | Description |
7448
+ | ------------ | ------- | ---------------------------------------------- |
7449
+ | `--org <id>` | | Target organization (overrides the active org) |
7450
+
7451
+ ### `kici orchestrators use`
7452
+
7453
+ Set the per-org default orchestrator cluster for run remote
7454
+
7455
+ Synopsis: `kici orchestrators use <name> [options]`
7456
+
7457
+ **Arguments**
7458
+
7459
+ | Argument | Required | Variadic | Description |
7460
+ | -------- | -------- | -------- | ------------------------- |
7461
+ | `name` | yes | no | Orchestrator cluster name |
7462
+
7463
+ **Options**
7464
+
7465
+ | Option | Default | Description |
7466
+ | ------------ | ------- | ---------------------------------------------- |
7467
+ | `--org <id>` | | Target organization (overrides the active org) |
7468
+
7469
+ ### `kici org`
7470
+
7471
+ Manage organizations
7472
+
7473
+ Synopsis: `kici org`
7474
+
7475
+ ### `kici org current`
7476
+
7477
+ Show current active organization
7478
+
7479
+ Synopsis: `kici org current`
7480
+
7481
+ ### `kici org list`
7482
+
7483
+ List organizations you belong to
7484
+
7485
+ Synopsis: `kici org list`
7486
+
7487
+ ### `kici org use`
7488
+
7489
+ Switch active organization
7490
+
7491
+ Synopsis: `kici org use <name>`
7492
+
7493
+ **Arguments**
7494
+
7495
+ | Argument | Required | Variadic | Description |
7496
+ | -------- | -------- | -------- | ----------------------- |
7497
+ | `name` | yes | no | Organization name or ID |
7498
+
7499
+ ### `kici pat`
7500
+
7501
+ Manage personal access tokens
7502
+
7503
+ Synopsis: `kici pat`
7504
+
7505
+ ### `kici pat create`
7506
+
7507
+ Mint a personal access token (use --agent for a coding-agent token)
7508
+
7509
+ Synopsis: `kici pat create [options]`
7510
+
7511
+ **Options**
7512
+
7513
+ | Option | Default | Description |
7514
+ | ----------------------- | ------- | ---------------------------------------------- |
7515
+ | `--name <name>` | | Token name (defaults to the agent label) |
7516
+ | `--agent` | `false` | Mint an agent-kind PAT for the KiCI MCP server |
7517
+ | `--expires-in-days <n>` | | Custom expiry in days |
7518
+
7519
+ ### `kici preview`
7520
+
7521
+ Preview which workflows match a trigger event (no execution)
7522
+
7523
+ Synopsis: `kici preview [event] [options]`
7524
+
7525
+ **Arguments**
7526
+
7527
+ | Argument | Required | Variadic | Description |
7528
+ | -------- | -------- | -------- | ----------------------------------------------------- |
7529
+ | `event` | no | no | Event type to preview (e.g., push, pr:open, schedule) |
7530
+
7531
+ **Options**
7532
+
7533
+ | Option | Default | Description |
7534
+ | --------------------------- | ------- | ------------------------------------------------------------ |
7535
+ | `--branch <name>` | | Override target branch for trigger matching (default: main) |
7536
+ | `--sha <hash>` | | Override commit SHA |
7537
+ | `--workflow <name>` | | Filter to specific workflow in display |
7538
+ | `--job <name>` | | Filter to specific job in display |
7539
+ | `--debug` | `false` | Verbose internals |
7540
+ | `--kici-dir <path>` | `.kici` | Path to .kici directory |
7541
+ | `--files <path>` | | Simulate changed file path for trigger matching (repeatable) |
7542
+ | `--secret <key=value>` | | Inject flat secret (repeatable) |
7543
+ | `--context <ctx.key=value>` | | Inject context secret (repeatable) |
7544
+
7545
+ ### `kici reject`
7546
+
7547
+ Reject a held approval gate for a run
7548
+
7549
+ Synopsis: `kici reject <run-id> [options]`
7550
+
7551
+ **Arguments**
7552
+
7553
+ | Argument | Required | Variadic | Description |
7554
+ | -------- | -------- | -------- | ------------------------------------ |
7555
+ | `run-id` | yes | no | Run ID whose approval gate to reject |
7556
+
7557
+ **Options**
7558
+
7559
+ | Option | Default | Description |
7560
+ | ----------------- | ------- | ------------------------------------------ |
7561
+ | `--job <name>` | | Reject the hold for a specific job |
7562
+ | `--step <index>` | | Reject a step-scoped hold (requires --job) |
7563
+ | `--reason <text>` | | Reason for the rejection |
7564
+
7565
+ ### `kici run`
7566
+
7567
+ Execute workflows locally or remotely
7568
+
7569
+ Synopsis: `kici run [event] [options]`
7570
+
7571
+ **Arguments**
7572
+
7573
+ | Argument | Required | Variadic | Description |
7574
+ | -------- | -------- | -------- | ------------------------------------------------------ |
7575
+ | `event` | no | no | Event type for a routed local run (e.g. push, pr:open) |
7576
+
7577
+ **Options**
7578
+
7579
+ | Option | Default | Description |
7580
+ | ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
7581
+ | `--local` | `false` | Route the run with this machine as the ephemeral agent |
7582
+ | `--offline` | `false` | Force the throwaway/independent plane (offline) |
7583
+ | `--connected` | `false` | Force the connected/hybrid plane (requires attachment) |
7584
+ | `--in-place` | `false` | Reuse the working tree directly instead of an isolated clone |
7585
+ | `--trusted` | `false` | Route to the trusted fleet agent profile: steps see the ambient host env (minus the agent identity). Alias: --no-sandbox |
7586
+ | `--no-sandbox` | | Alias for --trusted (the bwrap sandbox is already off by default) |
7587
+ | `--env <KEY=VALUE>` | | Per-run secret (repeatable) |
7588
+ | `--payload <path>` | | Dispatch payload JSON { action?, client_payload? } for a routed dispatch run |
7589
+ | `--kici-dir <path>` | `.kici` | Path to .kici directory |
7590
+ | `--quiet` | `false` | Suppress the banner + streaming output |
7591
+ | `--debug` | `false` | Verbose internals |
7592
+
7593
+ ### `kici run remote`
7594
+
7595
+ Execute fixtures remotely via orchestrator
7596
+
7597
+ Synopsis: `kici run remote [fixture] [options]`
7598
+
7599
+ **Arguments**
7600
+
7601
+ | Argument | Required | Variadic | Description |
7602
+ | --------- | -------- | -------- | ----------------------------------------------------- |
7603
+ | `fixture` | no | no | Fixture name or glob pattern (omit to list available) |
7604
+
7605
+ **Options**
7606
+
7607
+ | Option | Default | Description |
7608
+ | --------------------------- | ------- | ------------------------------------------------------------------------------------------- |
7609
+ | `--workflow <name>` | | Run a specific workflow directly (bypass triggers) |
7610
+ | `--all` | `false` | Run all available fixtures |
7611
+ | `-p, --pick` | `false` | Interactively pick fixtures to run |
7612
+ | `--parallel` | `false` | Run matching fixtures concurrently |
7613
+ | `--no-wait` | | Fire and forget (print runIds, don't stream) |
7614
+ | `--quiet` | `false` | Suppress output except final result |
7615
+ | `--json` | `false` | Output structured JSON result |
7616
+ | `--junit <path>` | | Output JUnit XML result |
7617
+ | `--history` | `false` | Show recent run history |
7618
+ | `--routing-key <key>` | | Override routing key for this run |
7619
+ | `--org <id>` | | Target organization (overrides the active org) |
7620
+ | `--orchestrator <name>` | | Target orchestrator cluster (overrides the per-org default) |
7621
+ | `--debug` | `false` | Verbose internals |
7622
+ | `--kici-dir <path>` | `.kici` | Path to .kici directory |
7623
+ | `--context <ctx.key=value>` | | Inject a namespaced context secret, uploaded encrypted to the orchestrator (repeatable) |
7624
+ | `--env <KEY=VALUE>` | | Provide a per-run secret (repeatable); uploaded encrypted to the orchestrator |
7625
+ | `--check` | `false` | Run in check mode: report drift, change nothing |
7626
+ | `--fail-on-drift` | `false` | In check mode, exit non-zero if any step reports drift |
7627
+ | `--target <selector>` | | Narrow runsOnAll jobs to hosts matching this label selector (repeatable, AND-combined) |
7628
+ | `--target-allow-empty` | `false` | A --target that narrows a runsOnAll job to zero hosts skips it instead of failing |
7629
+ | `--input <KEY=VALUE>` | | Typed workflow-dispatch input (repeatable) |
7630
+ | `--yes, --approve-all` | `false` | Auto-approve every approval gate this run holds on (run-scoped; eligibility still enforced) |
7631
+
7632
+ ### `kici runs`
7633
+
7634
+ Inspect and manage execution runs
7635
+
7636
+ Synopsis: `kici runs`
7637
+
7638
+ ### `kici runs cancel`
7639
+
7640
+ Cancel a run, or all in-progress runs on a branch
7641
+
7642
+ Synopsis: `kici runs cancel [run-id] [options]`
7643
+
7644
+ **Arguments**
7645
+
7646
+ | Argument | Required | Variadic | Description |
7647
+ | -------- | -------- | -------- | ---------------- |
7648
+ | `run-id` | no | no | Run ID to cancel |
7649
+
7650
+ **Options**
7651
+
7652
+ | Option | Default | Description |
7653
+ | ----------------- | ------- | ------------------------------------------- |
7654
+ | `--force` | `false` | Force cancel (kill immediately, skip hooks) |
7655
+ | `--branch <name>` | | Cancel all in-progress runs on this branch |
7656
+
7657
+ ### `kici runs list`
7658
+
7659
+ List execution runs (mirrors the dashboard Runs page)
7660
+
7661
+ Synopsis: `kici runs list [options]`
7662
+
7663
+ **Options**
7664
+
7665
+ | Option | Default | Description |
7666
+ | ----------------------- | ------- | -------------------------------------- |
7667
+ | `--status <s>` | | Filter by status |
7668
+ | `--workflow <w>` | | Filter by workflow name |
7669
+ | `--branch <b>` | | Filter by branch/ref |
7670
+ | `--repo <r>` | | Filter by repository |
7671
+ | `--trigger <t>` | | Filter by trigger type |
7672
+ | `--source <routingKey>` | | Filter by source routing key |
7673
+ | `--since <ts>` | | Only runs since (ISO-8601 or epoch ms) |
7674
+ | `--page <n>` | | Page number |
7675
+ | `--json` | `false` | Output raw JSON |
7676
+
7677
+ ### `kici runs logs`
7678
+
7679
+ Print step logs for a run
7680
+
7681
+ Synopsis: `kici runs logs <run-id> [options]`
7682
+
7683
+ **Arguments**
7684
+
7685
+ | Argument | Required | Variadic | Description |
7686
+ | -------- | -------- | -------- | ----------- |
7687
+ | `run-id` | yes | no | Run ID |
7688
+
7689
+ **Options**
7690
+
7691
+ | Option | Default | Description |
7692
+ | -------------- | ------- | ------------------------ |
7693
+ | `--job <name>` | | Only logs for this job |
7694
+ | `-f, --follow` | `false` | Tail logs for a live run |
7695
+ | `--json` | `false` | Output raw JSON |
7696
+
7697
+ ### `kici runs rerun`
7698
+
7699
+ Re-trigger a run
7700
+
7701
+ Synopsis: `kici runs rerun <run-id> [options]`
7702
+
7703
+ **Arguments**
7704
+
7705
+ | Argument | Required | Variadic | Description |
7706
+ | -------- | -------- | -------- | --------------- |
7707
+ | `run-id` | yes | no | Run ID to rerun |
7708
+
7709
+ **Options**
7710
+
7711
+ | Option | Default | Description |
7712
+ | -------- | ------- | --------------- |
7713
+ | `--json` | `false` | Output raw JSON |
7714
+
7715
+ ### `kici runs show`
7716
+
7717
+ Show a run summary with its jobs and steps
7718
+
7719
+ Synopsis: `kici runs show <run-id> [options]`
7720
+
7721
+ **Arguments**
7722
+
7723
+ | Argument | Required | Variadic | Description |
7724
+ | -------- | -------- | -------- | ----------------- |
7725
+ | `run-id` | yes | no | Run ID to inspect |
7726
+
7727
+ **Options**
7728
+
7729
+ | Option | Default | Description |
7730
+ | -------- | ------- | --------------- |
7731
+ | `--json` | `false` | Output raw JSON |
7732
+
7733
+ ### `kici secrets`
7734
+
7735
+ Manage secrets
7736
+
7737
+ Synopsis: `kici secrets`
7738
+
7739
+ ### `kici secrets list`
7740
+
7741
+ List test-available secret contexts
7742
+
7743
+ Synopsis: `kici secrets list`
7744
+
7745
+ ### `kici types`
7746
+
7747
+ Generate TypeScript declarations for secret contexts
7748
+
7749
+ Synopsis: `kici types [options]`
7750
+
7751
+ **Options**
7752
+
7753
+ | Option | Default | Description |
7754
+ | ------------------- | ------- | ----------------------- |
7755
+ | `--kici-dir <path>` | `.kici` | Path to .kici directory |
7756
+
7757
+ ### `kici verify-attestation`
7758
+
7759
+ Verify a KiCI provenance attestation bundle offline
7760
+
7761
+ Synopsis: `kici verify-attestation [artifact] [options]`
7762
+
7763
+ **Arguments**
7764
+
7765
+ | Argument | Required | Variadic | Description |
7766
+ | ---------- | -------- | -------- | ------------------------------------------------------------------------ |
7767
+ | `artifact` | no | no | Artifact path to digest-check against the attestation subject (optional) |
7768
+
7769
+ **Options**
7770
+
7771
+ | Option | Default | Description |
7772
+ | ---------------------------- | ------- | --------------------------------------------------------------------------------------------- |
7773
+ | `--bundle <path>` | | Path or URL to the attestation bundle JSON |
7774
+ | `--trust-root <url-or-file>` | | Trusted issuer URL, or a self-contained { issuer, jwks } file (default: hosted KiCI platform) |
7775
+ | `--audience <aud>` | | Expected token audience |
7776
+ | `--json` | `false` | Output structured JSON result |
7777
+
7778
+ ### `kici workflows`
7779
+
7780
+ Manage workflow registrations
7781
+
7782
+ Synopsis: `kici workflows`
7783
+
7784
+ ### `kici workflows list`
7785
+
7786
+ List permanently registered workflows
7787
+
7788
+ Synopsis: `kici workflows list [options]`
7789
+
7790
+ **Options**
7791
+
7792
+ | Option | Default | Description |
7793
+ | ----------------------- | ------- | ------------------------------------------ |
7794
+ | `--json` | `false` | Output as JSON |
7795
+ | `--stale <duration>` | | Filter stale registrations (e.g., 30d, 7d) |
7796
+ | `--trigger-type <type>` | | Filter by trigger type |
7797
+ | `--repo <repo>` | | Filter by repository |
7798
+
7799
+ <!-- END GENERATED: kici-commands -->
7800
+
7801
+ ## Workflow discovery
7802
+
7803
+ The CLI discovers workflows by scanning `.kici/workflows/*.ts` (or `.mjs` in MJS mode). Each file should `export default` a single workflow:
7804
+
7805
+ ```typescript
7806
+ // .kici/workflows/ci.ts
7807
+ import { workflow, job, step, pr } from '@kici-dev/sdk';
7808
+
7809
+ export default workflow('ci', {
7810
+ on: pr(),
7811
+ jobs: [
7812
+ /* ... */
7813
+ ],
7814
+ });
7815
+ ```
7816
+
7817
+ Multiple workflow files are supported -- each becomes a separate workflow in `kici.lock.json`.
7818
+
7819
+ ## Lock file
7820
+
7821
+ The `kici compile` command produces `.kici/kici.lock.json` inside the `.kici` directory. This file:
7822
+
7823
+ - Contains all workflow definitions in a portable JSON format
7824
+ - Is used by the orchestrator to evaluate triggers without code checkout
7825
+ - Should be committed to version control
7826
+ - Is regenerated on every `kici compile` run
7827
+
7828
+ 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/).
7829
+
7830
+ ## Exit codes
7831
+
7832
+ All commands follow a consistent exit code convention:
7833
+
7834
+ | Code | Meaning |
7835
+ | ---- | -------------------- |
7836
+ | 0 | Success |
7837
+ | 1 | Failure (see output) |
7838
+
7839
+ ## Debug output
7840
+
7841
+ Use `--debug` (on `kici run <event> --local`, `kici run remote`, `kici preview`) or `--verbose` (on `kici compile`) for detailed output:
7842
+
7843
+ ```bash
7844
+ # Shows trigger matching, rule evaluation, decision traces
7845
+ kici run push --local --debug
7846
+
7847
+ # Shows detailed compilation steps
7848
+ kici compile --verbose
7849
+
7850
+ # Shows trigger matching preview
7851
+ kici preview pr:open --debug
7852
+ ```
7853
+
7854
+ Set `KICI_DEBUG=true` for additional internal debug output across all commands.
7855
+
7856
+ ## Environment variables
7857
+
7858
+ | Variable | Description |
7859
+ | ------------ | ----------------------------------------- |
7860
+ | `KICI_DEV` | Set to `true` for development mode |
7861
+ | `KICI_DEBUG` | Set to `true` for verbose internal output |
7862
+ | `CI` | When `true`, disables interactive prompts |
7863
+
7864
+ ## See also
7865
+
7866
+ - [Getting started](https://docs.kici.dev/user/getting-started/) -- install the SDK and write your first workflow
7867
+ - [Testing guide](https://docs.kici.dev/user/testing-guide/) -- writing fixtures, remote test runs, secret contexts, and repo state transfer
7868
+ - [SDK reference](https://docs.kici.dev/user/sdk-reference/) -- complete API for the workflow definitions that the CLI compiles
7869
+ - [Workflow patterns](https://docs.kici.dev/user/workflow-patterns/) -- example workflows to compile and test with these commands
7870
+
7871
+ ---
7872
+
7873
+ ## Lifecycle hooks
7874
+
7875
+ Source: https://docs.kici.dev/user/hooks/
7876
+
7877
+ 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.
7878
+
7879
+ ## Hook types
7880
+
7881
+ KiCI supports six hook types at three levels (step, job, workflow):
7882
+
7883
+ | Hook | When it runs | Available on |
7884
+ | ------------ | ------------------------------------ | ------------------- |
7885
+ | `onCancel` | After step/job/workflow is cancelled | Step, Job, Workflow |
7886
+ | `cleanup` | Always (success, failure, or cancel) | Step, Job, Workflow |
7887
+ | `onSuccess` | After job/workflow succeeds | Job, Workflow |
7888
+ | `onFailure` | After job/workflow fails | Job, Workflow |
7889
+ | `beforeStep` | Before each step in a job | Job |
7890
+ | `afterStep` | After each step in a job | Job |
7891
+
7892
+ ## Basic usage
7893
+
7894
+ ### Job-level hooks
7895
+
7896
+ ```typescript
7897
+ import { workflow, job, step, push } from '@kici-dev/sdk';
7898
+
7899
+ export default workflow('deploy', {
7900
+ on: push({ branches: ['main'] }),
7901
+ jobs: [
7902
+ job('deploy-prod', {
7903
+ runsOn: 'linux',
7904
+ steps: [
7905
+ step('deploy', async ({ $ }) => {
7906
+ await $`kubectl apply -f manifests/`;
7907
+ }),
7908
+ ],
7909
+ onCancel: async (ctx) => {
7910
+ console.log(`Deploy cancelled: ${ctx.outcome.reason}`);
7911
+ await ctx.$`kubectl rollout undo deployment/app`;
7912
+ },
7913
+ cleanup: async (ctx) => {
7914
+ // Always runs -- release lock, notify team, etc.
7915
+ await ctx.$`curl -X POST https://slack.com/webhook -d '{"text": "Deploy ${ctx.outcome.status}"}'`;
7916
+ },
7917
+ onSuccess: async (ctx) => {
7918
+ console.log(`Deploy succeeded in ${ctx.outcome.duration}ms`);
7919
+ },
7920
+ onFailure: async (ctx) => {
7921
+ console.log(`Deploy failed at step: ${ctx.outcome.failedStep}`);
7922
+ },
7923
+ gracePeriod: 60, // 60 seconds before SIGKILL on cancel
7924
+ }),
7925
+ ],
7926
+ });
7927
+ ```
7928
+
7929
+ ### Step-level hooks
7930
+
7931
+ ```typescript
7932
+ step('download-artifacts', {
7933
+ run: async ({ $ }) => {
7934
+ await $`wget https://artifacts.example.com/build.tar.gz`;
7935
+ },
7936
+ onCancel: async (ctx) => {
7937
+ // Clean up partial downloads
7938
+ await ctx.$`rm -f build.tar.gz`;
7939
+ },
7940
+ cleanup: async (ctx) => {
7941
+ await ctx.$`rm -rf /tmp/staging`;
7942
+ },
7943
+ });
7944
+ ```
7945
+
7946
+ ### Workflow-level hooks
7947
+
7948
+ ```typescript
7949
+ workflow('ci', {
7950
+ on: push({ branches: ['main'] }),
7951
+ jobs: [
7952
+ /* ... */
7953
+ ],
7954
+ onCancel: async (ctx) => {
7955
+ // Notify when any job in the workflow is cancelled
7956
+ console.log('CI workflow cancelled');
7957
+ },
7958
+ cleanup: async (ctx) => {
7959
+ // Always runs after all jobs complete
7960
+ console.log(`CI workflow finished with status: ${ctx.outcome.status}`);
7961
+ },
7962
+ });
7963
+ ```
7964
+
7965
+ ## Hook context
7966
+
7967
+ Hook functions receive the same `StepContext` as regular steps (`$`, `ctx`, `log`, `env`), plus an `outcome` object with metadata about the execution result.
7968
+
7969
+ ### ctx.outcome
7970
+
7971
+ ```typescript
7972
+ interface OutcomeMetadata {
7973
+ /** Final status of the job/workflow. */
7974
+ status: 'cancelled' | 'success' | 'failed';
7975
+ /** Reason for cancellation (e.g., "User requested", "Superseded by run #42"). */
7976
+ reason?: string;
7977
+ /** Name of the step that caused failure (for onFailure hooks). */
7978
+ failedStep?: string;
7979
+ /** Outputs from all completed steps. */
7980
+ stepOutputs: Record<string, unknown>;
7981
+ /** Total execution duration in milliseconds. */
7982
+ duration: number;
7983
+ }
7984
+ ```
7985
+
7986
+ ### Capabilities
7987
+
7988
+ Hooks can do everything regular steps can:
7681
7989
 
7682
7990
  - Run shell commands via `$`
7683
7991
  - Set environment variables via `ctx.setEnv()` and prepend to `PATH` via `ctx.addPath()`
@@ -7836,14 +8144,14 @@ Each workflow entry includes:
7836
8144
  | `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. |
7837
8145
  | `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. |
7838
8146
  | `triggers` | Trigger definitions extracted from the workflow (used by the orchestrator for event matching). |
7839
- | `jobs` | Job definitions with scheduling metadata (runsOn, needs, matrix, environment, concurrency, container, checkout, gracePeriod, label routing, dynamic fields, etc.). |
8147
+ | `jobs` | Job definitions with scheduling metadata (runsOn, needs, matrix, contexts, concurrency, container, checkout, gracePeriod, label routing, dynamic fields, etc.). |
7840
8148
  | `rules` | Workflow-level conditional rules (optional). Stored as dynamic references since rule functions cannot be serialized. |
7841
8149
  | `description` | Optional workflow description. |
7842
8150
  | `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). |
7843
8151
  | `resolvedHashFiles` | Resolved file paths from `hashFiles` at compile time (optional). Recorded so the agent can verify without re-discovering. |
7844
8152
  | `contexts` | Secret contexts declared by the workflow (optional). The orchestrator validates access to each context before dispatch. |
7845
8153
  | `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/). |
7846
- | `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/). |
8154
+ | `installEnv` | Extra qualified secret refs (`<context>:<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/). |
7847
8155
  | `concurrency` | Workflow-level concurrency config: `hasGroup`, `cancelInProgress`, `max` (optional). See [concurrency groups](https://docs.kici.dev/user/concurrency/). |
7848
8156
  | `timeout` | Whole-run wall-clock timeout in milliseconds (optional). The orchestrator reads this at run creation to set the run deadline. |
7849
8157
  | Hook flags | Boolean flags (`hasOnCancel`, `hasCleanup`, `hasOnSuccess`, `hasOnFailure`) indicating which lifecycle hooks are defined. Job entries additionally have `hasBeforeStep` and `hasAfterStep`. |
@@ -8071,12 +8379,12 @@ export const pushWithSecrets = fixture('push-with-secrets', {
8071
8379
 
8072
8380
  This maps the `db` secret context to the `test-database` context, and `api` to `test-api-keys`.
8073
8381
 
8074
- This mapping is honored by **both** `kici run local` and `kici run remote`:
8382
+ This mapping is honored by **both** `kici run <event> --local` and `kici run remote`:
8075
8383
 
8076
- - 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).
8077
- - 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).
8384
+ - For a local **`kici run <event> --local`** (see [`kici run <event> --local`](https://docs.kici.dev/user/cli-reference/#kici-run-event---local)), each named context is resolved from your local secret files (`.kici/.secrets`, `.env.local`, `secrets.yaml`, and `--env` flags).
8385
+ - For **`kici run remote`**, each named context maps to an orchestrator **context**, and the orchestrator resolves that context's secrets for the run. The target context must be flagged `allowLocalExecution: true` — mapping a context to a missing or non-test context rejects the run (see [Secret contexts for testing](https://docs.kici.dev/user/testing-guide/#secret-contexts-for-testing) below).
8078
8386
 
8079
- **A fixture `secrets:` mapping is fail-closed; a job's bound `environment:` is not.** The reject above applies only to the fixture `secrets:` mapping — an explicit request for that environment's secrets. A job's own bound `environment:` (`job('deploy', { environment: 'production', … })`) is treated differently on a test run: if it resolves to a non-test or unconfigured environment it is **skipped with a warning**, not rejected, so a job that deploys to production in real runs stays locally testable for its non-secret logic. `kici run remote` prints a warning naming the skipped environment(s), and the dashboard run view shows the same notice. See [Skip-on-test](https://docs.kici.dev/user/environments/#multiple-environments-per-job) in the environments guide.
8387
+ **A fixture `secrets:` mapping is fail-closed; a job's bound `context:` is not.** The reject above applies only to the fixture `secrets:` mapping — an explicit request for that context's secrets. A job's own bound `context:` (`job('deploy', { context: 'production', … })`) is treated differently on a test run: if it resolves to a non-test or unconfigured context it is **skipped with a warning**, not rejected, so a job that deploys to production in real runs stays locally testable for its non-secret logic. `kici run remote` prints a warning naming the skipped context(s), and the dashboard run view shows the same notice. See [Skip-on-test](https://docs.kici.dev/user/contexts/#multiple-contexts-per-job) in the contexts guide.
8080
8388
 
8081
8389
  ### Async fixtures
8082
8390
 
@@ -8211,14 +8519,14 @@ The goal of the test-secret model is to let test runs reach **test-only credenti
8211
8519
 
8212
8520
  ### CLI-uploaded local secrets
8213
8521
 
8214
- `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.
8522
+ `kici run remote` collects the same local secret values that `kici run <event> --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.
8215
8523
 
8216
8524
  ```bash
8217
8525
  # Provide an ad-hoc test value for a single remote run
8218
8526
  kici run remote push-main --env KICI_DATABASE_URL=postgresql://localhost/test
8219
8527
  ```
8220
8528
 
8221
- `--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.
8529
+ `--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-context secret on a key collision.
8222
8530
 
8223
8531
  ```bash
8224
8532
  # Provide a namespaced per-run value under the 'db' context
@@ -8227,14 +8535,14 @@ kici run remote push-db --context db.KICI_DATABASE_URL=postgresql://localhost/te
8227
8535
 
8228
8536
  Because these values originate on your machine, they are the natural place to put throwaway test credentials without touching any orchestrator-stored secret.
8229
8537
 
8230
- ### Orchestrator test-environment secrets
8538
+ ### Orchestrator test-context secrets
8231
8539
 
8232
8540
  In addition to your uploaded values, the orchestrator resolves test-scoped secrets from its own store for a remote test run:
8233
8541
 
8234
- - 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.
8235
- - Each fixture `secrets: { ctx: envName }` mapping resolves the named environment's secrets under the namespaced context `ctx`.
8542
+ - The job's own declared `context` contributes its resolved secrets (flat). Static strings and **pure dynamic functions** both participate: a pure `context:` 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.
8543
+ - Each fixture `secrets: { ctx: envName }` mapping resolves the named context's secrets under the namespaced context `ctx`.
8236
8544
 
8237
- Both paths are restricted to environments flagged `allowLocalExecution: true`. A production environment left at the default `false` is never resolvable for a test run.
8545
+ Both paths are restricted to contexts flagged `allowLocalExecution: true`. A production context left at the default `false` is never resolvable for a test run.
8238
8546
 
8239
8547
  ```typescript
8240
8548
  export const pushWithDb = fixture('push-db', {
@@ -8252,31 +8560,31 @@ step('migrate', async (ctx) => {
8252
8560
 
8253
8561
  ### Precedence: CLI values win
8254
8562
 
8255
- 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.
8563
+ When a key exists in both sources, the **CLI-uploaded local value wins** over the orchestrator test-context 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 context's value for just that run, without changing anything on the orchestrator.
8256
8564
 
8257
- ### Fail-closed on non-test environments
8565
+ ### Fail-closed on non-test contexts
8258
8566
 
8259
8567
  Test-run secret resolution is fail-closed:
8260
8568
 
8261
- - If a fixture maps a context to an environment that does not exist, the run is **rejected**.
8262
- - If a fixture maps a context to an environment whose `allowLocalExecution` is `false`, the run is **rejected**.
8263
- - 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.
8569
+ - If a fixture maps to a context that does not exist, the run is **rejected**.
8570
+ - If a fixture maps to a context whose `allowLocalExecution` is `false`, the run is **rejected**.
8571
+ - The `allowLocalExecution` gate applies to **all** remote test runs: a run whose matched workflow targets a context with the flag off is rejected, so a test run can never resolve production secrets.
8264
8572
 
8265
- ### The `allowLocalExecution` environment flag
8573
+ ### The `allowLocalExecution` context flag
8266
8574
 
8267
- 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.
8575
+ Each context carries an `allowLocalExecution` flag (default `false`) that controls test-run access to that context and to its secrets. Production contexts should leave it at `false`; create a dedicated test context with `allowLocalExecution: true` that binds only test-only secret scopes for the jobs you want test runs to use.
8268
8576
 
8269
8577
  The flag is set by the orchestrator operator, either via the CLI:
8270
8578
 
8271
8579
  ```bash
8272
- kici-admin environment set-policy --env test-database --allow-local-execution true
8580
+ kici-admin context set-policy --env test-database --allow-local-execution true
8273
8581
  ```
8274
8582
 
8275
- 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.
8583
+ or via the dashboard's "Test runs" toggle on the context detail page. `kici secrets list` only surfaces contexts whose `allowLocalExecution` is `true`, so production contexts are never advertised as test-accessible.
8276
8584
 
8277
8585
  ### Local execution as an alternative
8278
8586
 
8279
- `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.
8587
+ `kici run <event> --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 <event> --local`](https://docs.kici.dev/user/cli-reference/#kici-run-event---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.
8280
8588
 
8281
8589
  ### Discovering available contexts
8282
8590
 
@@ -8491,7 +8799,7 @@ job('deploy', {
8491
8799
  });
8492
8800
  ```
8493
8801
 
8494
- `approval: true` holds the element until **any** org member who can act on approvals signs off — anyone with the `environments:write` or `ci_trust:write` permission. Use it when you want a manual gate without restricting who may release it.
8802
+ `approval: true` holds the element until **any** org member who can act on approvals signs off — anyone with the `contexts:write` or `ci_trust:write` permission. Use it when you want a manual gate without restricting who may release it.
8495
8803
 
8496
8804
  ### Approver list (AND)
8497
8805
 
@@ -8612,7 +8920,7 @@ A `when: 'drift'` gate on a step without a `check` facet, or at job/workflow sco
8612
8920
 
8613
8921
  ## Mandatory vs. explicit gates
8614
8922
 
8615
- `approval` is the **explicit** gate — a deliberate "pause for a human here" written by the workflow author. It composes with the **mandatory** gate an operator can attach to a protected environment via required reviewers (see [Environments](https://docs.kici.dev/user/environments/#required-reviewers)). When both apply to the same job, all clauses from both sources must be satisfied before the job is released. The two funnel into one held-element mechanism, so the dashboard queue and `kici approve` work the same way regardless of which source held the element.
8923
+ `approval` is the **explicit** gate — a deliberate "pause for a human here" written by the workflow author. It composes with the **mandatory** gate an operator can attach to a protected context via required reviewers (see [Contexts](https://docs.kici.dev/user/contexts/#required-reviewers)). When both apply to the same job, all clauses from both sources must be satisfied before the job is released. The two funnel into one held-element mechanism, so the dashboard queue and `kici approve` work the same way regardless of which source held the element.
8616
8924
 
8617
8925
  ## Approving from the CLI
8618
8926
 
@@ -8646,12 +8954,14 @@ kici run remote deploy-prod --approve-all
8646
8954
 
8647
8955
  `--approve-all` is **run-scoped** — it only auto-approves holds belonging to the run this invocation dispatched; there is no fleet-wide or account-wide auto-approve. Eligibility is still enforced per hold: if you are not eligible for a gate, that gate still blocks. Each auto-approved gate prints its payload before resolving and is recorded distinctly in the audit trail (`held_run.auto_approve`).
8648
8956
 
8649
- You can also approve from the dashboard approval queue. See [Dashboard](https://docs.kici.dev/user/dashboard/environments-and-secrets/#approval-queue).
8957
+ `--approve-all` is honored in non-interactive runs too: with `--json` or `--quiet` (no TTY), the flag still auto-approves each eligible gate as it appears instead of hanging or printing out-of-band instructions. Hold notices are routed to stderr so `--json` stdout stays pure machine-readable output.
8958
+
8959
+ You can also approve from the dashboard approval queue. See [Dashboard](https://docs.kici.dev/user/dashboard/contexts-and-secrets/#approval-queue).
8650
8960
 
8651
8961
  ## See also
8652
8962
 
8653
8963
  - [Idempotent steps](https://docs.kici.dev/user/idempotent-steps/) — the check/apply step facet that drift gates build on.
8654
- - [Environments](https://docs.kici.dev/user/environments/) — operator-required reviewers on protected environments.
8964
+ - [Contexts](https://docs.kici.dev/user/contexts/) — operator-required reviewers on protected contexts.
8655
8965
  - [Approval gates (operator guide)](https://docs.kici.dev/operator/approvals/) — teams, the approval queue, expiry, and self-approval.
8656
8966
  - [Approval gates (architecture)](https://docs.kici.dev/architecture/approvals/) — the unified hold model and the step-level round-trip.
8657
8967
 
@@ -8713,7 +9023,7 @@ group: () => 'deploy';
8713
9023
  group: (ctx) => `deploy-${ctx.event.targetBranch ?? 'default'}`;
8714
9024
  ```
8715
9025
 
8716
- The workflow-level group function is always evaluated **agent-side** at runtime -- the lock file records only that a group function exists (`hasGroup: true`), not the function itself. The agent loads the workflow source, calls the group function with `{ branch, event }`, and reports the evaluated key back to the orchestrator before step execution begins. This differs from job-level `concurrencyGroup` (see [Environments](https://docs.kici.dev/user/environments/#concurrency-groups)), where the compiler performs purity analysis and can inline pure functions for orchestrator-side evaluation.
9026
+ The workflow-level group function is always evaluated **agent-side** at runtime -- the lock file records only that a group function exists (`hasGroup: true`), not the function itself. The agent loads the workflow source, calls the group function with `{ branch, event }`, and reports the evaluated key back to the orchestrator before step execution begins. This differs from job-level `concurrencyGroup` (see [Contexts](https://docs.kici.dev/user/contexts/#concurrency-groups)), where the compiler performs purity analysis and can inline pure functions for orchestrator-side evaluation.
8717
9027
 
8718
9028
  ## cancelInProgress mode
8719
9029
 
@@ -8806,7 +9116,7 @@ workflow('deploy', {
8806
9116
  jobs: [
8807
9117
  job('deploy-staging', {
8808
9118
  runsOn: 'linux',
8809
- environment: 'staging',
9119
+ context: 'staging',
8810
9120
  steps: [
8811
9121
  /* ... */
8812
9122
  ],
@@ -8837,284 +9147,62 @@ workflow('migrate', {
8837
9147
  workflow('deploy', {
8838
9148
  concurrency: {
8839
9149
  group: (ctx) => {
8840
- const env = ctx.branch === 'main' ? 'production' : 'staging';
8841
- return `deploy-${env}`;
8842
- },
8843
- cancelInProgress: true,
8844
- },
8845
- jobs: [
8846
- /* ... */
8847
- ],
8848
- });
8849
- ```
8850
-
8851
- ## Interaction with environment protection
8852
-
8853
- When a workflow has both `concurrency` and `environment` protection rules:
8854
-
8855
- 1. Environment protection gates (required reviewers, wait timer) apply first
8856
- 2. Concurrency group check happens after protection gates pass
8857
- 3. If the run is queued by concurrency, it keeps its protection approval
8858
-
8859
- This means a run that passed approval won't need re-approval if it gets queued by concurrency.
8860
-
8861
- ## Cancelling queued runs
8862
-
8863
- Queued runs can be cancelled before they start executing. The cancel request removes them from the queue immediately -- they don't go through the grace period since no step is running.
8864
-
8865
- ## Job-level concurrency groups
8866
-
8867
- In addition to workflow-level concurrency, individual jobs can define their own concurrency group via the `concurrencyGroup` property. This controls concurrent execution at the job level rather than the workflow level. See [Environments — concurrency groups](https://docs.kici.dev/user/environments/#concurrency-groups) for details.
8868
-
8869
- ## Local execution
8870
-
8871
- `kici run local` honors workflow-level `concurrency` per-machine, per-user. The `group` callback is evaluated against the simulated event identically to the remote orchestrator path; `cancelInProgress` carries the same semantics — `true` interrupts the holder via `SIGTERM` (escalating to `SIGKILL` after a grace window) and proceeds with the new run, while `false` queues the new invocation in FIFO order until the holder finishes.
8872
-
8873
- Coordination is local only. Running the same workflow on two different machines does not serialize across them — that requires the orchestrator. For full cross-host enforcement (queueing across agents, dashboard visibility, `max > 1`), use `kici run remote` against a deployed orchestrator.
8874
-
8875
- Lock files live under `$XDG_RUNTIME_DIR/kici-local-locks/` on Linux, falling back to `os.tmpdir()/kici-local-locks-<uid>/`. A workflow whose `group` callback throws aborts the run with a clear error rather than running unprotected. See [`kici run local` — Concurrency enforcement](https://docs.kici.dev/user/cli-reference/#concurrency-enforcement) for the `KICI_LOCAL_LOCK_KILL_GRACE_MS` override and the diagnostic output emitted while contending on a busy lock.
8876
-
8877
- The `kici run local --concurrency <n>` flag is a separate concept — it caps **job-level** parallelism within a single run (how many jobs from one workflow run at once), not cross-run serialization.
8878
-
8879
- ---
8880
-
8881
- _Source: `packages/sdk/src/types.ts` (WorkflowOptions.concurrency, JobOptions.concurrencyGroup)_
8882
-
8883
- ---
8884
-
8885
- ## Dashboard
8886
-
8887
- Source: https://docs.kici.dev/user/dashboard/
8888
-
8889
- The KiCI dashboard is the browser interface for monitoring workflow runs, inspecting jobs and logs, and managing your organization. It signs in via OIDC and talks to KiCI over its API.
8890
-
8891
- This guide is split across the following pages:
8892
-
8893
- | Page | Covers |
8894
- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
8895
- | [Getting started](https://docs.kici.dev/user/dashboard/getting-started/) | Onboarding checklist and your organizations list |
8896
- | [Navigation and layout](https://docs.kici.dev/user/dashboard/navigation/) | Sidebar, mobile nav, theme, time display, shortcuts, error pages |
8897
- | [Runs and logs](https://docs.kici.dev/user/dashboard/runs/) | Run list, run detail, the log viewer |
8898
- | [Settings](https://docs.kici.dev/user/dashboard/settings/) | Members, roles, teams, keys, sources, billing, security, support access |
8899
- | [Workflows, diagnostics, and orchestrators](https://docs.kici.dev/user/dashboard/workflows-and-diagnostics/) | Registered workflows, infra health, per-cluster views |
8900
- | [Environments, secrets, and approvals](https://docs.kici.dev/user/dashboard/environments-and-secrets/) | Environments, secret scopes, approval queue |
8901
- | [Activity and DLQ](https://docs.kici.dev/user/dashboard/activity-and-dlq/) | Forensic activity log and dead-letter queue |
8902
- | [Account](https://docs.kici.dev/user/dashboard/account/) | Personal account settings |
8903
-
8904
- ---
8905
-
8906
- ## Dynamic values
8907
-
8908
- Source: https://docs.kici.dev/user/dynamic-values/
8909
-
8910
- Dynamic values let you compute `environment`, `env`, and `concurrencyGroup` at runtime based on the incoming event. Instead of hardcoding static strings, you pass a function that receives the normalized event envelope and returns the resolved value.
8911
-
8912
- ```typescript
8913
- job('deploy', {
8914
- runsOn: ['default'],
8915
- environment: (event) => event.targetBranch,
8916
- env: (event) => ({ BRANCH: event.targetBranch }),
8917
- concurrencyGroup: (event) => `deploy-${event.targetBranch}`,
8918
- steps: [
8919
- /* ... */
8920
- ],
8921
- });
8922
- ```
8923
-
8924
- ```typescript
8925
- job('deploy', {
8926
- runsOn: 'default',
8927
- // One shape everywhere: branch on the normalized event type.
8928
- environment: (event) => (event.type === 'pull_request' ? 'preview' : 'production'),
8929
- steps: [
8930
- /* ... */
8931
- ],
8932
- });
8933
- ```
8934
-
8935
- ## How it works
8936
-
8937
- When you define a dynamic value as a function, the compiler analyzes it at compile time to determine whether it is **pure** (can be evaluated without cloning the repo or running an init job).
8938
-
8939
- ### Pure functions (inline evaluation)
8940
-
8941
- A pure function is one that:
8942
-
8943
- - Is synchronous (no `async`/`await`)
8944
- - Only references its parameters and local variables
8945
- - Does not import or require external modules
8946
- - Does not access globals like `process`, `fetch`, `console`, `setTimeout`, etc.
8947
- - Uses only safe built-in constructors: `String`, `Number`, `Boolean`, `Array`, `Object`, `JSON`, `Math`, `parseInt`, `parseFloat`, `isNaN`, `isFinite`, `encodeURIComponent`, `decodeURIComponent`, `encodeURI`, `decodeURI`
8948
- - Does not use `this`, `new`, `class`, `throw`, `try`/`catch`, `delete`, `var`, `yield`, or mutation operators (`++`, `--`, `+=`, etc.)
8949
-
8950
- When the compiler detects a pure function, it serializes the function source directly into the lock file as an inline expression. At dispatch time, the orchestrator evaluates the expression in a sandboxed VM context (~0ms overhead) instead of dispatching an init job.
8951
-
8952
- **Examples of pure functions:**
8953
-
8954
- ```typescript
8955
- // Simple branch extraction
8956
- environment: (event) => event.targetBranch;
8957
-
8958
- // Object literal with string operations
8959
- env: (event) => ({ BRANCH: event.targetBranch });
8960
-
8961
- // Concatenation with event data
8962
- concurrencyGroup: (event) => `deploy-${event.targetBranch}`;
8963
-
8964
- // Using safe globals
8965
- env: (event) => ({ UPPER: String(event.targetBranch).toUpperCase() });
8966
-
8967
- // Local variables are fine
8968
- environment: (event) => {
8969
- const parts = event.targetBranch.split('/');
8970
- return parts[parts.length - 1];
8971
- };
8972
- ```
8973
-
8974
- ### Impure functions (init-job evaluation)
8975
-
8976
- If the compiler determines a function is impure, it emits a warning during compilation and falls back to the two-phase init model. This means:
8977
-
8978
- 1. The orchestrator dispatches a special `__init__` job to a builder agent
8979
- 2. The builder agent clones the repository and evaluates the function
8980
- 3. The resolved values are sent back to the orchestrator
8981
- 4. The orchestrator dispatches the real execution job with the resolved values
8982
-
8983
- This adds approximately 5-10 seconds of overhead for cloning and evaluation.
8984
-
8985
- **Examples of impure functions (will use init job):**
8986
-
8987
- ```typescript
8988
- // Async functions cannot be inlined
8989
- environment: async (event) => await lookupEnv(event.targetBranch);
8990
-
8991
- // External module references
8992
- env: (event) => {
8993
- const config = require('./config');
8994
- return config.env;
8995
- };
8996
-
8997
- // Process/global access
8998
- environment: (event) => process.env.DEFAULT_ENV || 'staging';
8999
-
9000
- // Dynamic imports
9001
- env: async (event) => {
9002
- const m = await import('./config.js');
9003
- return m.default;
9004
- };
9005
- ```
9006
-
9007
- ## Performance comparison
9008
-
9009
- | Evaluation path | Overhead | When used |
9010
- | ------------------------------------ | -------- | ------------------------------------------------------------------- |
9011
- | Static value (string/object literal) | ~0ms | `environment: 'staging'` |
9012
- | Inline expression (pure function) | ~0ms | `environment: (event) => event.targetBranch` |
9013
- | Init job (impure function) | ~5-10s | `environment: async (event) => await lookupEnv(event.targetBranch)` |
9014
-
9015
- ## Tips
9016
-
9017
- - **Write pure functions whenever possible** to avoid the init-job delay. Most environment and env computations only need the event payload data.
9018
- - **Check compiler warnings** -- the compiler tells you when a function is classified as impure and explains why.
9019
- - **Runtime errors in inline expressions cause immediate job failure.** There is no fallback to the init-job path. If your pure function throws at runtime (e.g., accessing a property on `undefined`), the job fails immediately.
9020
- - **The event parameter is the normalized event envelope** — the same shape rules receive as `ctx.event`: `{ type, action, targetBranch, sourceBranch, changedFiles, payload, … }` (see the [event payload reference](https://docs.kici.dev/user/sdk/event-payloads/) for the complete schema). Narrow on `event.type` (`'push'`, `'pull_request'`, `'tag'`, …) to branch per trigger kind. The raw provider webhook body is nested at `event.payload` (for GitHub pushes: `payload.ref`, `payload.after`, `payload.repository`, …).
9021
-
9022
- ---
9023
-
9024
- ## Environment variables
9025
-
9026
- Source: https://docs.kici.dev/user/env-vars/
9027
-
9028
- The KiCI CLI reads the following environment variables to customize its behavior. OAuth login (`kici login` without `--token`) defaults `KICI_PLATFORM_URL`, `KICI_OIDC_ISSUER`, and `KICI_OIDC_CLIENT_ID` to the hosted KiCI Platform, so `kici login` works with no configuration. Set them only to target another KiCI environment (e.g. a testing instance) or a custom OIDC provider.
9029
-
9030
- ## Authentication
9031
-
9032
- | Variable | Description | Default |
9033
- | --------------------- | -------------------------------------- | -------------------------------------------- |
9034
- | `KICI_OIDC_ISSUER` | OIDC issuer URL for authentication | `https://auth.kici.dev/realms/kici-internal` |
9035
- | `KICI_OIDC_CLIENT_ID` | OIDC client ID for the CLI application | `kici-cli` |
9036
- | `KICI_PLATFORM_URL` | Platform API base URL | `https://api.kici.dev` |
9037
- | `KICI_CONFIG_DIR` | Override the KiCI config directory | `~/.kici` |
9038
-
9039
- ## Browser behavior
9040
-
9041
- | Variable | Description | Default |
9042
- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
9043
- | `KICI_BROWSER_CMD` | Custom browser command for OAuth login. Supports `{url}` placeholder. Set to `none` to suppress browser opening and print the URL to stdout instead. | Uses the system default browser via the `open` package |
9044
- | `KICI_CALLBACK_PORT` | Fixed port for the OAuth PKCE callback server. Useful when firewall rules require a known port. | Random available port |
9045
-
9046
- ## Development
9047
-
9048
- | Variable | Description | Default |
9049
- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
9050
- | `KICI_DEV` | Enable development mode. When `true`, uses prerelease-compatible version ranges (`>=0.0.1-0`) for dev dependencies and skips npm version resolution. | unset |
9051
- | `KICI_DEBUG` | Enable debug logging. When `true`, prints verbose diagnostics (SDK alias resolution, step-level debug logs, stack traces on errors). Equivalent to the `--debug` CLI flag. | unset |
9052
-
9053
- ## Usage examples
9054
-
9055
- ### CI/CD environment
9056
-
9057
- Authenticate with a pre-existing API key (no browser needed):
9058
-
9059
- ```bash
9060
- kici login --token <<< "$KICI_API_KEY"
9150
+ const env = ctx.branch === 'main' ? 'production' : 'staging';
9151
+ return `deploy-${env}`;
9152
+ },
9153
+ cancelInProgress: true,
9154
+ },
9155
+ jobs: [
9156
+ /* ... */
9157
+ ],
9158
+ });
9061
9159
  ```
9062
9160
 
9063
- ### Targeting another environment or custom OIDC provider
9161
+ ## Interaction with context protection
9064
9162
 
9065
- `kici login` targets the hosted KiCI Platform by default. To point the CLI at another KiCI environment (e.g. a testing instance) or a custom OIDC provider, override the defaults:
9163
+ When a workflow has both `concurrency` and `context` protection rules:
9066
9164
 
9067
- ```bash
9068
- export KICI_OIDC_ISSUER=https://your-idp.example.com
9069
- export KICI_OIDC_CLIENT_ID=your-client-id
9070
- export KICI_PLATFORM_URL=https://your-platform.example.com
9071
- kici login
9072
- ```
9165
+ 1. Context protection gates (required reviewers, wait timer) apply first
9166
+ 2. Concurrency group check happens after protection gates pass
9167
+ 3. If the run is queued by concurrency, it keeps its protection approval
9073
9168
 
9074
- ### Headless SSH session
9169
+ This means a run that passed approval won't need re-approval if it gets queued by concurrency.
9075
9170
 
9076
- The CLI auto-detects headless environments and uses the device flow. To force PKCE with URL output instead:
9171
+ ## Cancelling queued runs
9077
9172
 
9078
- ```bash
9079
- export KICI_BROWSER_CMD=none
9080
- kici login
9081
- ```
9173
+ Queued runs can be cancelled before they start executing. The cancel request removes them from the queue immediately -- they don't go through the grace period since no step is running.
9082
9174
 
9083
- This prints the authorization URL to stdout as `KICI_AUTH_URL=<url>`. Open the URL in any browser to complete authentication.
9175
+ ## Job-level concurrency groups
9084
9176
 
9085
- ### Fixed callback port
9177
+ In addition to workflow-level concurrency, individual jobs can define their own concurrency group via the `concurrencyGroup` property. This controls concurrent execution at the job level rather than the workflow level. See [Contexts — concurrency groups](https://docs.kici.dev/user/contexts/#concurrency-groups) for details.
9086
9178
 
9087
- When behind a firewall or using port forwarding:
9179
+ ## Local execution
9088
9180
 
9089
- ```bash
9090
- export KICI_CALLBACK_PORT=19876
9091
- kici login
9092
- ```
9181
+ `kici run <event> --local` honors workflow-level `concurrency` per-machine, per-user. The `group` callback is evaluated against the simulated event identically to the remote orchestrator path; `cancelInProgress` carries the same semantics — `true` interrupts the holder via `SIGTERM` (escalating to `SIGKILL` after a grace window) and proceeds with the new run, while `false` queues the new invocation in FIFO order until the holder finishes.
9093
9182
 
9094
- ### Custom config location
9183
+ Coordination is local only. Running the same workflow on two different machines does not serialize across them — that requires the orchestrator. For full cross-host enforcement (queueing across agents, dashboard visibility, `max > 1`), use `kici run remote` against a deployed orchestrator.
9095
9184
 
9096
- Store the KiCI config in a non-default location:
9185
+ Lock files live under `$XDG_RUNTIME_DIR/kici-local-locks/` on Linux, falling back to `os.tmpdir()/kici-local-locks-<uid>/`. A workflow whose `group` callback throws aborts the run with a clear error rather than running unprotected. See [`kici run <event> --local` — Concurrency enforcement](https://docs.kici.dev/user/cli-reference/#concurrency-enforcement) for the `KICI_LOCAL_LOCK_KILL_GRACE_MS` override and the diagnostic output emitted while contending on a busy lock.
9097
9186
 
9098
- ```bash
9099
- export KICI_CONFIG_DIR=/tmp/kici-test
9100
- kici login
9101
- ```
9187
+ ---
9188
+
9189
+ _Source: `packages/sdk/src/types.ts` (WorkflowOptions.concurrency, JobOptions.concurrencyGroup)_
9102
9190
 
9103
9191
  ---
9104
9192
 
9105
- ## Environments
9193
+ ## Contexts
9106
9194
 
9107
- Source: https://docs.kici.dev/user/environments/
9195
+ Source: https://docs.kici.dev/user/contexts/
9108
9196
 
9109
- <!-- help:environments-list#overview -->
9197
+ <!-- help:contexts-list#overview -->
9110
9198
 
9111
- Environments are named deployment targets (like staging or production) that control where your workflow jobs run. Each environment can have its own variables, secrets, and protection rules to gate deployments.
9199
+ Contexts are named deployment targets (like staging or production) that control where your workflow jobs run. Each context can have its own variables, secrets, and protection rules to gate deployments.
9112
9200
 
9113
- <!-- /help:environments-list -->
9201
+ <!-- /help:contexts-list -->
9114
9202
 
9115
- <!-- help:environments-protection#protection-rules -->
9203
+ <!-- help:contexts-protection#protection-rules -->
9116
9204
 
9117
- Protection rules control when jobs targeting an environment can execute.
9205
+ Protection rules control when jobs targeting a context can execute.
9118
9206
 
9119
9207
  Available rules:
9120
9208
 
@@ -9123,24 +9211,24 @@ Available rules:
9123
9211
  - **Wait timers** — delay execution for a fixed period.
9124
9212
  - **Concurrency limits** — prevent collisions between parallel deployments.
9125
9213
 
9126
- <!-- /help:environments-protection -->
9214
+ <!-- /help:contexts-protection -->
9127
9215
 
9128
- Environments represent deployment targets like `staging`, `production`, or `review/PR-*`. Each environment can have its own variables, bound secrets, and protection rules that control when and how jobs targeting that environment can execute.
9216
+ Contexts represent deployment targets like `staging`, `production`, or `review/PR-*`. Each context can have its own variables, bound secrets, and protection rules that control when and how jobs targeting that context can execute.
9129
9217
 
9130
9218
  ## Overview
9131
9219
 
9132
- An environment in KiCI provides:
9220
+ A context in KiCI provides:
9133
9221
 
9134
9222
  - **Variables** -- non-secret key-value configuration (e.g., `API_URL`, `CLUSTER_NAME`)
9135
- - **Scoped secrets** -- encrypted values bound to the environment via scope bindings
9223
+ - **Scoped secrets** -- encrypted values bound to the context via scope bindings
9136
9224
  - **Protection rules** -- branch restrictions, required reviewers, wait timers, and concurrency limits
9137
9225
  - **Per-source overrides** -- repositories can override unlocked variables for their own deployments
9138
9226
 
9139
9227
  ## SDK API
9140
9228
 
9141
- ### Job-level environment property
9229
+ ### Job-level context property
9142
9230
 
9143
- The `environment` property is set on a job, not a workflow or step:
9231
+ The `context` property is set on a job, not a workflow or step:
9144
9232
 
9145
9233
  ```typescript
9146
9234
  import { workflow, job, step, push } from '@kici-dev/sdk';
@@ -9150,16 +9238,16 @@ export default workflow('deploy', {
9150
9238
  jobs: [
9151
9239
  job('deploy-staging', {
9152
9240
  runsOn: 'default',
9153
- environment: 'staging',
9241
+ context: 'staging',
9154
9242
  steps: [
9155
9243
  step('deploy', async (ctx) => {
9156
- // ctx.environment is the resolved environment name
9157
- console.log(`Deploying to ${ctx.environment}`);
9158
- // ctx.secrets provides async get/expose/has methods for environment-bound secrets
9244
+ // ctx.context is the resolved context name
9245
+ console.log(`Deploying to ${ctx.context}`);
9246
+ // ctx.secrets provides async get/expose/has methods for context-bound secrets
9159
9247
  const dbPassword = await ctx.secrets.get('DB_PASSWORD');
9160
9248
  // Environment variables are in ctx.env
9161
9249
  const apiUrl = ctx.env.API_URL;
9162
- await ctx.$`deploy --target ${ctx.environment}`;
9250
+ await ctx.$`deploy --target ${ctx.context}`;
9163
9251
  }),
9164
9252
  ],
9165
9253
  }),
@@ -9167,54 +9255,54 @@ export default workflow('deploy', {
9167
9255
  });
9168
9256
  ```
9169
9257
 
9170
- ### Dynamic environments
9258
+ ### Dynamic contexts
9171
9259
 
9172
- The environment name can be a string or a function (sync or async) for dynamic environments (e.g., per-PR review environments). The function receives the normalized event envelope, with the raw provider body nested at `event.payload`:
9260
+ The context name can be a string or a function (sync or async) for dynamic contexts (e.g., per-PR review contexts). The function receives the normalized event envelope, with the raw provider body nested at `event.payload`:
9173
9261
 
9174
9262
  ```typescript
9175
9263
  job('deploy-review', {
9176
9264
  runsOn: 'default',
9177
- environment: (event) => `review/PR-${event.payload.pull_request.number}`,
9265
+ context: (event) => `review/PR-${event.payload.pull_request.number}`,
9178
9266
  steps: [
9179
9267
  step('deploy', async (ctx) => {
9180
- // ctx.environment is 'review/PR-123' (resolved at runtime)
9181
- await ctx.$`deploy-preview --env ${ctx.environment}`;
9268
+ // ctx.context is 'review/PR-123' (resolved at runtime)
9269
+ await ctx.$`deploy-preview --env ${ctx.context}`;
9182
9270
  }),
9183
9271
  ],
9184
9272
  });
9185
9273
  ```
9186
9274
 
9187
- A pure function like the one above (see [Dynamic values](https://docs.kici.dev/user/dynamic-values/)) is evaluated inline at dispatch with no init-job overhead. Dynamic environments that match a glob pattern (e.g., `review/*`) inherit the pattern's configuration, variables, and protection rules.
9275
+ A pure function like the one above (see [Dynamic values](https://docs.kici.dev/user/dynamic-values/)) is evaluated inline at dispatch with no init-job overhead. Dynamic contexts that match a glob pattern (e.g., `review/*`) inherit the pattern's configuration, variables, and protection rules.
9188
9276
 
9189
- ### Multiple environments per job
9277
+ ### Multiple contexts per job
9190
9278
 
9191
- A job can bind more than one environment with `environments`, an ordered array. This lets a single job draw secrets and variables from several environments at once — for example a shared `staging` environment plus a `my-testing` environment that carries test-only variables:
9279
+ A job can bind more than one context with `contexts`, an ordered array. This lets a single job draw secrets and variables from several contexts at once — for example a shared `staging` context plus a `my-testing` context that carries test-only variables:
9192
9280
 
9193
9281
  ```typescript
9194
9282
  job('deploy', {
9195
9283
  runsOn: 'default',
9196
- environments: ['staging', 'my-testing'],
9284
+ contexts: ['staging', 'my-testing'],
9197
9285
  steps: [
9198
9286
  step('deploy', async (ctx) => {
9199
- // ctx.secrets and ctx.env carry the merged set from both environments
9287
+ // ctx.secrets and ctx.env carry the merged set from both contexts
9200
9288
  const dbUrl = await ctx.secrets.get('DB_URL');
9201
9289
  }),
9202
9290
  ],
9203
9291
  });
9204
9292
  ```
9205
9293
 
9206
- - `environment` (singular) and `environments` (array) are mutually exclusive — setting both is a compile error. `environment: 'staging'` is exactly equivalent to `environments: ['staging']`.
9207
- - Each array entry is a static name or a function of the event, resolved per element exactly like a single dynamic environment.
9294
+ - `context` (singular) and `contexts` (array) are mutually exclusive — setting both is a compile error. `context: 'staging'` is exactly equivalent to `contexts: ['staging']`.
9295
+ - Each array entry is a static name or a function of the event, resolved per element exactly like a single dynamic context.
9208
9296
 
9209
- **Merge order — last wins.** All bound environments are resolved on every dispatch (webhook, scheduled, and test runs alike) and merged in array order. When the same secret or variable key is defined in more than one environment, the later entry in the array wins. With `environments: ['staging', 'my-testing']`, a key defined in both resolves to `my-testing`'s value; keys defined in only one are preserved. The longest-scope-path-wins rule still applies _within_ each environment.
9297
+ **Merge order — last wins.** All bound contexts are resolved on every dispatch (webhook, scheduled, and test runs alike) and merged in array order. When the same secret or variable key is defined in more than one context, the later entry in the array wins. With `contexts: ['staging', 'my-testing']`, a key defined in both resolves to `my-testing`'s value; keys defined in only one are preserved. The longest-scope-path-wins rule still applies _within_ each context.
9210
9298
 
9211
- **Protection rules combine all-must-pass.** A job must satisfy **every** bound environment's gates — adding an environment can never loosen access. Branch restrictions, trigger-type filters, and repo patterns must pass for all environments; the minimum trust tier is the most restrictive across them; required reviewers are the union of all environments' reviewers; the wait timer is the longest; and the hold expiry is the shortest. If a run is gated out, the rejection names which environment and which rule rejected it (visible via `kici runs show <run-id>` and the run's rejection reason), so a mutually-exclusive set of rules surfaces as a clear failure rather than a silent perpetual rejection.
9299
+ **Protection rules combine all-must-pass.** A job must satisfy **every** bound context's gates — adding a context can never loosen access. Branch restrictions, trigger-type filters, and repo patterns must pass for all contexts; the minimum trust tier is the most restrictive across them; required reviewers are the union of all contexts' reviewers; the wait timer is the longest; and the hold expiry is the shortest. If a run is gated out, the rejection names which context and which rule rejected it (visible via `kici runs show <run-id>` and the run's rejection reason), so a mutually-exclusive set of rules surfaces as a clear failure rather than a silent perpetual rejection.
9212
9300
 
9213
- **Skip-on-test (allow-and-warn).** On a test or local run (`kici run remote`, `kici run local`), a bound environment never rejects the run. Any bound environment that disallows local execution (`allowLocalExecution: false`) — or that is not configured — is **skipped**: its variables and secrets are omitted from the merge and its gates are not evaluated. The run proceeds, and a user-visible warning naming the skipped environment(s) is shown both on the `kici run remote` CLI output and on the dashboard run view. This makes the test-only-variables pattern work: with `environments: ['staging', 'my-testing']` where only `my-testing` allows local execution, a test run resolves just `my-testing`'s variables and warns that `staging` was skipped. If every bound environment is skipped, the job runs with no environment variables. This is intentionally different from a fixture `secrets:` mapping, which is fail-closed — see the [testing guide](https://docs.kici.dev/user/testing-guide/).
9301
+ **Skip-on-test (allow-and-warn).** On a test or local run (`kici run remote`, `kici run <event> --local`), a bound context never rejects the run. Any bound context that disallows local execution (`allowLocalExecution: false`) — or that is not configured — is **skipped**: its variables and secrets are omitted from the merge and its gates are not evaluated. The run proceeds, and a user-visible warning naming the skipped context(s) is shown both on the `kici run remote` CLI output and on the dashboard run view. This makes the test-only-variables pattern work: with `contexts: ['staging', 'my-testing']` where only `my-testing` allows local execution, a test run resolves just `my-testing`'s variables and warns that `staging` was skipped. If every bound context is skipped, the job runs with no environment variables. This is intentionally different from a fixture `secrets:` mapping, which is fail-closed — see the [testing guide](https://docs.kici.dev/user/testing-guide/).
9214
9302
 
9215
- **Unconfigured environments contribute nothing at dispatch.** At dispatch time a bound environment name with no matching configured environment (and no matching glob environment) simply adds no variables, secrets, or protection rules — the job still runs, exactly as a single dynamic environment resolving to an as-yet-unconfigured name does today.
9303
+ **Unconfigured contexts contribute nothing at dispatch.** At dispatch time a bound context name with no matching configured context (and no matching glob context) simply adds no variables, secrets, or protection rules — the job still runs, exactly as a single dynamic context resolving to an as-yet-unconfigured name does today.
9216
9304
 
9217
- **Registration rejects a provably-unsatisfiable binding.** When a workflow is registered, KiCI statically checks every multi-environment binding: a bound environment that does not exist, a disabled one, or two environments with mutually-exclusive fixed branch / trigger-type / repository restrictions (no value can satisfy both) makes the binding provably unsatisfiable, and the registration is rejected with a precise message naming the job, the environments, and the rule — for example `unsatisfiable environment binding: job 'deploy' binds environments [staging, my-testing] with mutually exclusive branch restrictions (no value satisfies all bound environments)`. Bindings whose restrictions use globs are undecidable at registration and fall through to the dispatch-time gate check instead.
9305
+ **Registration rejects a provably-unsatisfiable binding.** When a workflow is registered, KiCI statically checks every multi-context binding: a bound context that does not exist, a disabled one, or two contexts with mutually-exclusive fixed branch / trigger-type / repository restrictions (no value can satisfy both) makes the binding provably unsatisfiable, and the registration is rejected with a precise message naming the job, the contexts, and the rule — for example `unsatisfiable context binding: job 'deploy' binds contexts [staging, my-testing] with mutually exclusive branch restrictions (no value satisfies all bound contexts)`. Bindings whose restrictions use globs are undecidable at registration and fall through to the dispatch-time gate check instead.
9218
9306
 
9219
9307
  ### Job-level environment variables
9220
9308
 
@@ -9223,7 +9311,7 @@ The `env` property on a job provides static or dynamic environment variables:
9223
9311
  ```typescript
9224
9312
  job('deploy', {
9225
9313
  runsOn: 'default',
9226
- environment: 'production',
9314
+ context: 'production',
9227
9315
  env: { DEPLOY_TARGET: 'us-east-1' },
9228
9316
  // Or dynamic:
9229
9317
  // env: (event) => ({ DEPLOY_SHA: event.payload.after?.slice(0, 7) }),
@@ -9238,14 +9326,14 @@ job('deploy', {
9238
9326
 
9239
9327
  ### Concurrency groups
9240
9328
 
9241
- Jobs can define their own concurrency groups to control concurrent execution within an environment. For workflow-level concurrency (which applies to all jobs in a workflow), see [Concurrency groups](https://docs.kici.dev/user/concurrency/).
9329
+ Jobs can define their own concurrency groups to control concurrent execution within a context. For workflow-level concurrency (which applies to all jobs in a workflow), see [Concurrency groups](https://docs.kici.dev/user/concurrency/).
9242
9330
 
9243
- Control concurrent deployments to the same environment:
9331
+ Control concurrent deployments to the same context:
9244
9332
 
9245
9333
  ```typescript
9246
9334
  job('deploy', {
9247
9335
  runsOn: 'default',
9248
- environment: 'production',
9336
+ context: 'production',
9249
9337
  concurrencyGroup: 'production-api',
9250
9338
  // Or dynamic:
9251
9339
  // concurrencyGroup: (event) => `review-${event.payload.pull_request.number}`,
@@ -9255,17 +9343,17 @@ job('deploy', {
9255
9343
  });
9256
9344
  ```
9257
9345
 
9258
- If no `concurrencyGroup` is specified, the environment name is used as the default concurrency group. For a job bound to multiple environments, the default is the **first** bound environment's name.
9346
+ If no `concurrencyGroup` is specified, the context name is used as the default concurrency group. For a job bound to multiple contexts, the default is the **first** bound context's name.
9259
9347
 
9260
9348
  ### Step context
9261
9349
 
9262
9350
  Inside a step, the `ctx` object provides:
9263
9351
 
9264
- | Property | Type | Description |
9265
- | ----------------- | ------------------------------------- | ----------------------------------------------------------------------------------------- |
9266
- | `ctx.environment` | `string \| undefined` | Resolved environment name (undefined for jobs without environment) |
9267
- | `ctx.env` | `Record<string, string \| undefined>` | Environment variables (merged from system, org, source, and job-level `env`) |
9268
- | `ctx.secrets` | `StepSecretsTyped` | Async accessor for bound secrets (get, expose, has, getMeta, list, mountFile, exposeFile) |
9352
+ | Property | Type | Description |
9353
+ | ------------- | ------------------------------------- | ----------------------------------------------------------------------------------------- |
9354
+ | `ctx.context` | `string \| undefined` | Resolved context name (undefined for jobs without context) |
9355
+ | `ctx.env` | `Record<string, string \| undefined>` | Environment variables (merged from system, org, source, and job-level `env`) |
9356
+ | `ctx.secrets` | `StepSecretsTyped` | Async accessor for bound secrets (get, expose, has, getMeta, list, mountFile, exposeFile) |
9269
9357
 
9270
9358
  | Method | Returns | Description |
9271
9359
  | -------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -9282,25 +9370,25 @@ The full secrets API — including `SecretFileOptions`, log masking, and the can
9282
9370
 
9283
9371
  ## Environment variable merge precedence
9284
9372
 
9285
- When a job targets an environment, variables are merged in this order (last wins):
9373
+ When a job targets a context, variables are merged in this order (last wins):
9286
9374
 
9287
9375
  1. **Allowed system vars** -- `PATH`, `HOME`, etc. from the agent process
9288
9376
  2. **Sandbox defaults** -- `FORCE_COLOR=1`
9289
9377
  3. **KICI\_\* system vars** -- orchestrator-generated metadata
9290
- 4. **Org-level environment vars** -- from the dashboard, managed per-environment
9378
+ 4. **Org-level context vars** -- from the dashboard, managed per-context
9291
9379
  5. **Source-level overrides** -- per-repository overrides (skips locked vars)
9292
9380
  6. **Job env** -- from the `env` property in the SDK
9293
9381
  7. **`setEnv()` calls** -- runtime modifications within steps
9294
9382
 
9295
- > **Note:** Secrets are NOT part of the environment variable merge. They are delivered to the step context via IPC and accessed through `ctx.secrets`, not through `process.env`. See the [step context](https://docs.kici.dev/user/environments/#step-context) section above.
9383
+ > **Note:** Secrets are NOT part of the environment variable merge. They are delivered to the step context via IPC and accessed through `ctx.secrets`, not through `process.env`. See the [step context](https://docs.kici.dev/user/contexts/#step-context) section above.
9296
9384
 
9297
9385
  ## Protection rules
9298
9386
 
9299
- Environments can have protection rules that gate job execution:
9387
+ Contexts can have protection rules that gate job execution:
9300
9388
 
9301
9389
  ### Branch restrictions
9302
9390
 
9303
- Limit which branches can deploy to an environment:
9391
+ Limit which branches can deploy to a context:
9304
9392
 
9305
9393
  ```
9306
9394
  Allowed branches: main, release/*
@@ -9357,7 +9445,7 @@ See the [CI security architecture docs](https://docs.kici.dev/architecture/secur
9357
9445
 
9358
9446
  ### Security approval queue
9359
9447
 
9360
- When a PR is held for security review (unknown contributor, workflow modification, or trust policy violation), it enters the security approval queue. This is separate from environment-level approval queues.
9448
+ When a PR is held for security review (unknown contributor, workflow modification, or trust policy violation), it enters the security approval queue. This is separate from context-level approval queues.
9361
9449
 
9362
9450
  Held runs can be approved:
9363
9451
 
@@ -9368,7 +9456,7 @@ Security holds expire after a configurable timeout (default 1 hour).
9368
9456
 
9369
9457
  ### Concurrency limits
9370
9458
 
9371
- Control how many jobs can run simultaneously in an environment:
9459
+ Control how many jobs can run simultaneously in a context:
9372
9460
 
9373
9461
  ```
9374
9462
  Concurrency limit: 1
@@ -9380,18 +9468,18 @@ Strategy: queue (or cancel-pending)
9380
9468
 
9381
9469
  ## Dashboard management
9382
9470
 
9383
- ### Creating environments
9471
+ ### Creating contexts
9384
9472
 
9385
- Navigate to **Settings > Environments** in the dashboard. Click **New environment** to choose the environment name and type (Fixed or Glob).
9473
+ Navigate to **Settings > Contexts** in the dashboard. Click **New context** to choose the context name and type (Fixed or Glob).
9386
9474
 
9387
- - **Fixed** -- applies to jobs that declare exactly this environment name, like `staging` or `production`
9388
- - **Glob** -- applies to any environment name a job declares that matches the pattern, e.g. `review/*` matches a job with `environment: 'review/PR-123'`
9475
+ - **Fixed** -- applies to jobs that declare exactly this context name, like `staging` or `production`
9476
+ - **Glob** -- applies to any context name a job declares that matches the pattern, e.g. `review/*` matches a job with `context: 'review/PR-123'`
9389
9477
 
9390
- The environments list shows each environment's type, whether test runs may use it (the `allowLocalExecution` flag -- see the [testing guide](https://docs.kici.dev/user/testing-guide/)), and whether it is enabled.
9478
+ The contexts list shows each context's type, whether test runs may use it (the `allowLocalExecution` flag -- see the [testing guide](https://docs.kici.dev/user/testing-guide/)), and whether it is enabled.
9391
9479
 
9392
- ### Environment detail page
9480
+ ### Context detail page
9393
9481
 
9394
- Each environment has four tabs:
9482
+ Each context has four tabs:
9395
9483
 
9396
9484
  1. **Variables** -- manage key-value pairs with lock toggles. Locked variables cannot be overridden by source-level overrides. Source overrides are managed in a sub-tab.
9397
9485
 
@@ -9399,26 +9487,26 @@ Each environment has four tabs:
9399
9487
 
9400
9488
  3. **Protection** -- configure branch restrictions, required reviewers, wait timers, and concurrency limits with enable toggles for each section.
9401
9489
 
9402
- 4. **History** -- view filtered runs targeting this environment.
9490
+ 4. **History** -- view filtered runs targeting this context.
9403
9491
 
9404
- ### Bound environments on runs
9492
+ ### Bound contexts on runs
9405
9493
 
9406
- A job's bound deployment environments are shown as chips on the run detail page (in the job metadata panel) in the order the job declared them, and the distinct set across a run's jobs appears as compact chips on the run list. For a multi-environment job the chips read left-to-right in merge order — later environments override earlier ones on key collisions. A `(dynamic)` chip marks an environment whose name is computed at runtime; it resolves to the real name once the run starts. A job that binds a single environment shows one chip; a job that binds none shows no chip.
9494
+ A job's bound deployment contexts are shown as chips on the run detail page (in the job metadata panel) in the order the job declared them, and the distinct set across a run's jobs appears as compact chips on the run list. For a multi-context job the chips read left-to-right in merge order — later contexts override earlier ones on key collisions. A `(dynamic)` chip marks a context whose name is computed at runtime; it resolves to the real name once the run starts. A job that binds a single context shows one chip; a job that binds none shows no chip.
9407
9495
 
9408
- If a multi-environment binding is gated out, the run's failure banner names which environment and which rule rejected it (the same all-must-pass detail surfaced by `kici runs show <run-id>`).
9496
+ If a multi-context binding is gated out, the run's failure banner names which context and which rule rejected it (the same all-must-pass detail surfaced by `kici runs show <run-id>`).
9409
9497
 
9410
9498
  ### Secrets management
9411
9499
 
9412
- Secrets are individual encrypted values organized by scope paths (e.g., `aws/prod`, `databases/postgres`). Scopes are bound to environments via bindings:
9500
+ Secrets are individual encrypted values organized by scope paths (e.g., `aws/prod`, `databases/postgres`). Scopes are bound to contexts via bindings:
9413
9501
 
9414
- - **Scope-centric view** (Secrets page): tree view of scopes with per-scope environment binding checkboxes
9415
- - **Environment-centric view** (inside environment detail): bound scopes, resolved secrets, add binding
9502
+ - **Scope-centric view** (Secrets page): tree view of scopes with per-scope context binding checkboxes
9503
+ - **Context-centric view** (inside context detail): bound scopes, resolved secrets, add binding
9416
9504
 
9417
9505
  When scope paths collide on the same key name, the longer (more specific) path wins.
9418
9506
 
9419
9507
  ## Type generation
9420
9508
 
9421
- Running `kici types` generates two augmented interfaces: `KnownSecretKeys` (union of all secret keys across all environments) and `EnvironmentSecrets` (per-environment key unions):
9509
+ Running `kici types` generates two augmented interfaces: `KnownSecretKeys` (union of all secret keys across all contexts) and `ContextSecrets` (per-context key unions):
9422
9510
 
9423
9511
  ```typescript
9424
9512
  interface KnownSecretKeys {
@@ -9426,13 +9514,233 @@ interface KnownSecretKeys {
9426
9514
  API_KEY: string;
9427
9515
  }
9428
9516
 
9429
- interface EnvironmentSecrets {
9517
+ interface ContextSecrets {
9430
9518
  production: 'DB_PASSWORD' | 'API_KEY';
9431
9519
  staging: 'DB_PASSWORD';
9432
9520
  }
9433
9521
  ```
9434
9522
 
9435
- `KnownSecretKeys` narrows `ctx.secrets.get()` and `ctx.secrets.expose()` key parameters to valid key names. `EnvironmentSecrets` maps each environment to its available secret key names as a string union. Dynamic environments fall back to the full `KnownSecretKeys` union.
9523
+ `KnownSecretKeys` narrows `ctx.secrets.get()` and `ctx.secrets.expose()` key parameters to valid key names. `ContextSecrets` maps each context to its available secret key names as a string union. Dynamic contexts fall back to the full `KnownSecretKeys` union.
9524
+
9525
+ ---
9526
+
9527
+ ## Dashboard
9528
+
9529
+ Source: https://docs.kici.dev/user/dashboard/
9530
+
9531
+ The KiCI dashboard is the browser interface for monitoring workflow runs, inspecting jobs and logs, and managing your organization. It signs in via OIDC and talks to KiCI over its API.
9532
+
9533
+ This guide is split across the following pages:
9534
+
9535
+ | Page | Covers |
9536
+ | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
9537
+ | [Getting started](https://docs.kici.dev/user/dashboard/getting-started/) | Onboarding checklist and your organizations list |
9538
+ | [Navigation and layout](https://docs.kici.dev/user/dashboard/navigation/) | Sidebar, mobile nav, theme, time display, shortcuts, error pages |
9539
+ | [Runs and logs](https://docs.kici.dev/user/dashboard/runs/) | Run list, run detail, the log viewer |
9540
+ | [Settings](https://docs.kici.dev/user/dashboard/settings/) | Members, roles, teams, keys, sources, billing, security, support access |
9541
+ | [Workflows, diagnostics, and orchestrators](https://docs.kici.dev/user/dashboard/workflows-and-diagnostics/) | Registered workflows, infra health, per-cluster views |
9542
+ | [Contexts, secrets, and approvals](https://docs.kici.dev/user/dashboard/contexts-and-secrets/) | Contexts, secret scopes, approval queue |
9543
+ | [Activity and DLQ](https://docs.kici.dev/user/dashboard/activity-and-dlq/) | Forensic activity log and dead-letter queue |
9544
+ | [Account](https://docs.kici.dev/user/dashboard/account/) | Personal account settings |
9545
+
9546
+ ---
9547
+
9548
+ ## Dynamic values
9549
+
9550
+ Source: https://docs.kici.dev/user/dynamic-values/
9551
+
9552
+ Dynamic values let you compute `context`, `env`, and `concurrencyGroup` at runtime based on the incoming event. Instead of hardcoding static strings, you pass a function that receives the normalized event envelope and returns the resolved value.
9553
+
9554
+ ```typescript
9555
+ job('deploy', {
9556
+ runsOn: ['default'],
9557
+ context: (event) => event.targetBranch,
9558
+ env: (event) => ({ BRANCH: event.targetBranch }),
9559
+ concurrencyGroup: (event) => `deploy-${event.targetBranch}`,
9560
+ steps: [
9561
+ /* ... */
9562
+ ],
9563
+ });
9564
+ ```
9565
+
9566
+ ```typescript
9567
+ job('deploy', {
9568
+ runsOn: 'default',
9569
+ // One shape everywhere: branch on the normalized event type.
9570
+ context: (event) => (event.type === 'pull_request' ? 'preview' : 'production'),
9571
+ steps: [
9572
+ /* ... */
9573
+ ],
9574
+ });
9575
+ ```
9576
+
9577
+ ## How it works
9578
+
9579
+ When you define a dynamic value as a function, the compiler analyzes it at compile time to determine whether it is **pure** (can be evaluated without cloning the repo or running an init job).
9580
+
9581
+ ### Pure functions (inline evaluation)
9582
+
9583
+ A pure function is one that:
9584
+
9585
+ - Is synchronous (no `async`/`await`)
9586
+ - Only references its parameters and local variables
9587
+ - Does not import or require external modules
9588
+ - Does not access globals like `process`, `fetch`, `console`, `setTimeout`, etc.
9589
+ - Uses only safe built-in constructors: `String`, `Number`, `Boolean`, `Array`, `Object`, `JSON`, `Math`, `parseInt`, `parseFloat`, `isNaN`, `isFinite`, `encodeURIComponent`, `decodeURIComponent`, `encodeURI`, `decodeURI`
9590
+ - Does not use `this`, `new`, `class`, `throw`, `try`/`catch`, `delete`, `var`, `yield`, or mutation operators (`++`, `--`, `+=`, etc.)
9591
+
9592
+ When the compiler detects a pure function, it serializes the function source directly into the lock file as an inline expression. At dispatch time, the orchestrator evaluates the expression in a sandboxed VM context (~0ms overhead) instead of dispatching an init job.
9593
+
9594
+ **Examples of pure functions:**
9595
+
9596
+ ```typescript
9597
+ // Simple branch extraction
9598
+ context: (event) => event.targetBranch;
9599
+
9600
+ // Object literal with string operations
9601
+ env: (event) => ({ BRANCH: event.targetBranch });
9602
+
9603
+ // Concatenation with event data
9604
+ concurrencyGroup: (event) => `deploy-${event.targetBranch}`;
9605
+
9606
+ // Using safe globals
9607
+ env: (event) => ({ UPPER: String(event.targetBranch).toUpperCase() });
9608
+
9609
+ // Local variables are fine
9610
+ context: (event) => {
9611
+ const parts = event.targetBranch.split('/');
9612
+ return parts[parts.length - 1];
9613
+ };
9614
+ ```
9615
+
9616
+ ### Impure functions (init-job evaluation)
9617
+
9618
+ If the compiler determines a function is impure, it emits a warning during compilation and falls back to the two-phase init model. This means:
9619
+
9620
+ 1. The orchestrator dispatches a special `__init__` job to a builder agent
9621
+ 2. The builder agent clones the repository and evaluates the function
9622
+ 3. The resolved values are sent back to the orchestrator
9623
+ 4. The orchestrator dispatches the real execution job with the resolved values
9624
+
9625
+ This adds approximately 5-10 seconds of overhead for cloning and evaluation.
9626
+
9627
+ **Examples of impure functions (will use init job):**
9628
+
9629
+ ```typescript
9630
+ // Async functions cannot be inlined
9631
+ context: async (event) => await lookupEnv(event.targetBranch);
9632
+
9633
+ // External module references
9634
+ env: (event) => {
9635
+ const config = require('./config');
9636
+ return config.env;
9637
+ };
9638
+
9639
+ // Process/global access
9640
+ context: (event) => process.env.DEFAULT_ENV || 'staging';
9641
+
9642
+ // Dynamic imports
9643
+ env: async (event) => {
9644
+ const m = await import('./config.js');
9645
+ return m.default;
9646
+ };
9647
+ ```
9648
+
9649
+ ## Performance comparison
9650
+
9651
+ | Evaluation path | Overhead | When used |
9652
+ | ------------------------------------ | -------- | --------------------------------------------------------------- |
9653
+ | Static value (string/object literal) | ~0ms | `context: 'staging'` |
9654
+ | Inline expression (pure function) | ~0ms | `context: (event) => event.targetBranch` |
9655
+ | Init job (impure function) | ~5-10s | `context: async (event) => await lookupEnv(event.targetBranch)` |
9656
+
9657
+ ## Tips
9658
+
9659
+ - **Write pure functions whenever possible** to avoid the init-job delay. Most context and env computations only need the event payload data.
9660
+ - **Check compiler warnings** -- the compiler tells you when a function is classified as impure and explains why.
9661
+ - **Runtime errors in inline expressions cause immediate job failure.** There is no fallback to the init-job path. If your pure function throws at runtime (e.g., accessing a property on `undefined`), the job fails immediately.
9662
+ - **The event parameter is the normalized event envelope** — the same shape rules receive as `ctx.event`: `{ type, action, targetBranch, sourceBranch, changedFiles, payload, … }` (see the [event payload reference](https://docs.kici.dev/user/sdk/event-payloads/) for the complete schema). Narrow on `event.type` (`'push'`, `'pull_request'`, `'tag'`, …) to branch per trigger kind. The raw provider webhook body is nested at `event.payload` (for GitHub pushes: `payload.ref`, `payload.after`, `payload.repository`, …).
9663
+
9664
+ ---
9665
+
9666
+ ## Environment variables
9667
+
9668
+ Source: https://docs.kici.dev/user/env-vars/
9669
+
9670
+ The KiCI CLI reads the following environment variables to customize its behavior. OAuth login (`kici login` without `--token`) defaults `KICI_PLATFORM_URL`, `KICI_OIDC_ISSUER`, and `KICI_OIDC_CLIENT_ID` to the hosted KiCI Platform, so `kici login` works with no configuration. Set them only to target another KiCI environment (e.g. a testing instance) or a custom OIDC provider.
9671
+
9672
+ ## Authentication
9673
+
9674
+ | Variable | Description | Default |
9675
+ | --------------------- | -------------------------------------- | -------------------------------------------- |
9676
+ | `KICI_OIDC_ISSUER` | OIDC issuer URL for authentication | `https://auth.kici.dev/realms/kici-internal` |
9677
+ | `KICI_OIDC_CLIENT_ID` | OIDC client ID for the CLI application | `kici-cli` |
9678
+ | `KICI_PLATFORM_URL` | Platform API base URL | `https://api.kici.dev` |
9679
+ | `KICI_CONFIG_DIR` | Override the KiCI config directory | `~/.kici` |
9680
+
9681
+ ## Browser behavior
9682
+
9683
+ | Variable | Description | Default |
9684
+ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
9685
+ | `KICI_BROWSER_CMD` | Custom browser command for OAuth login. Supports `{url}` placeholder. Set to `none` to suppress browser opening and print the URL to stdout instead. | Uses the system default browser via the `open` package |
9686
+ | `KICI_CALLBACK_PORT` | Fixed port for the OAuth PKCE callback server. Useful when firewall rules require a known port. | Random available port |
9687
+
9688
+ ## Development
9689
+
9690
+ | Variable | Description | Default |
9691
+ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
9692
+ | `KICI_DEV` | Enable development mode. When `true`, uses prerelease-compatible version ranges (`>=0.0.1-0`) for dev dependencies and skips npm version resolution. | unset |
9693
+ | `KICI_DEBUG` | Enable debug logging. When `true`, prints verbose diagnostics (SDK alias resolution, step-level debug logs, stack traces on errors). Equivalent to the `--debug` CLI flag. | unset |
9694
+
9695
+ ## Usage examples
9696
+
9697
+ ### CI/CD environment
9698
+
9699
+ Authenticate with a pre-existing API key (no browser needed):
9700
+
9701
+ ```bash
9702
+ kici login --token <<< "$KICI_API_KEY"
9703
+ ```
9704
+
9705
+ ### Targeting another environment or custom OIDC provider
9706
+
9707
+ `kici login` targets the hosted KiCI Platform by default. To point the CLI at another KiCI environment (e.g. a testing instance) or a custom OIDC provider, override the defaults:
9708
+
9709
+ ```bash
9710
+ export KICI_OIDC_ISSUER=https://your-idp.example.com
9711
+ export KICI_OIDC_CLIENT_ID=your-client-id
9712
+ export KICI_PLATFORM_URL=https://your-platform.example.com
9713
+ kici login
9714
+ ```
9715
+
9716
+ ### Headless SSH session
9717
+
9718
+ The CLI auto-detects headless environments and uses the device flow. To force PKCE with URL output instead:
9719
+
9720
+ ```bash
9721
+ export KICI_BROWSER_CMD=none
9722
+ kici login
9723
+ ```
9724
+
9725
+ This prints the authorization URL to stdout as `KICI_AUTH_URL=<url>`. Open the URL in any browser to complete authentication.
9726
+
9727
+ ### Fixed callback port
9728
+
9729
+ When behind a firewall or using port forwarding:
9730
+
9731
+ ```bash
9732
+ export KICI_CALLBACK_PORT=19876
9733
+ kici login
9734
+ ```
9735
+
9736
+ ### Custom config location
9737
+
9738
+ Store the KiCI config in a non-default location:
9739
+
9740
+ ```bash
9741
+ export KICI_CONFIG_DIR=/tmp/kici-test
9742
+ kici login
9743
+ ```
9436
9744
 
9437
9745
  ---
9438
9746
 
@@ -10027,21 +10335,19 @@ guaranteed side-effect-free.
10027
10335
 
10028
10336
  ## Running in check mode
10029
10337
 
10030
- `--check` and `--fail-on-drift` work on both local and remote runs:
10338
+ `--check` and `--fail-on-drift` control drift reporting on `kici run remote`:
10031
10339
 
10032
10340
  ```bash
10033
10341
  # Apply (default): converge the workflow.
10034
- kici run local push
10342
+ kici run push --local
10035
10343
  kici run remote my-fixture
10036
10344
 
10037
10345
  # Check: report drift, change nothing. Always exits 0.
10038
- kici run local push --check
10039
10346
  kici run remote my-fixture --check
10040
10347
 
10041
- # Check + fail on drift: exit non-zero (2) locally, or fail the run remotely,
10042
- # when any step reports drift. Use this as a CI gate ("fail the build if prod
10043
- # has drifted").
10044
- kici run local push --check --fail-on-drift
10348
+ # Check + fail on drift: fail the run when any step reports drift. Use this as a
10349
+ # CI gate ("fail the build if prod has drifted").
10350
+ kici run remote my-fixture --check --fail-on-drift
10045
10351
  ```
10046
10352
 
10047
10353
  `--fail-on-drift` only modifies check mode — passing it without `--check` is an
@@ -10080,7 +10386,7 @@ The two channels (Option A and Option C) compose. If you declare both, the agent
10080
10386
 
10081
10387
  ## Option A — `registries:` block
10082
10388
 
10083
- Declare the registry in your workflow file and point its `tokenSecret` at a scoped secret using the qualified `<environment>:<secret-name>` syntax. The orchestrator resolves the token at dispatch time and the agent applies it for one `npm install` only.
10389
+ Declare the registry in your workflow file and point its `tokenSecret` at a scoped secret using the qualified `<context>:<secret-name>` syntax. The orchestrator resolves the token at dispatch time and the agent applies it for one `npm install` only.
10084
10390
 
10085
10391
  ```typescript
10086
10392
  import { workflow, job, step, push } from '@kici-dev/sdk';
@@ -10097,7 +10403,7 @@ export default workflow('build', {
10097
10403
  jobs: [
10098
10404
  job('build', {
10099
10405
  runsOn: 'default',
10100
- environment: 'production',
10406
+ context: 'production',
10101
10407
  steps: [
10102
10408
  step('install-and-build', async (ctx) => {
10103
10409
  // .kici/package.json can now reference @my-org/* packages
@@ -10113,7 +10419,7 @@ Per-field rules:
10113
10419
 
10114
10420
  - **`url`** — Must be HTTPS. HTTP is permitted only for `localhost` / `127.0.0.0/8` / `::1` / `*.local` hosts, or when an operator has flipped the org-level `allow_http_npm_registries` toggle (see [`kici-admin org-settings allow-http-npm`](https://docs.kici.dev/operator/kici-admin-cli#allow-http-npm--permit-non-https-private-npm-registries)).
10115
10421
  - **`scope`** — Optional. When present, the registry serves only that scope (`@my-org`). When absent, this entry becomes the **default** registry — at most one entry may omit `scope`.
10116
- - **`tokenSecret`** — Mandatory `<environment>:<secret-name>`. The orchestrator looks up the secret in the named environment via the per-environment secret resolver. The bare name **must not** contain a colon.
10422
+ - **`tokenSecret`** — Mandatory `<context>:<secret-name>`. The orchestrator looks up the secret in the named context via the per-context secret resolver. The bare name **must not** contain a colon.
10117
10423
  - **`alwaysAuth`** — Defaults to `true`. Forces npm to send the token on every request (even GETs), which is what most managed-registry providers require.
10118
10424
 
10119
10425
  ### How tokens reach `npm install`
@@ -10143,14 +10449,14 @@ export default workflow('build', {
10143
10449
  jobs: [
10144
10450
  job('build', {
10145
10451
  runsOn: 'default',
10146
- environment: 'production',
10452
+ context: 'production',
10147
10453
  steps: [step('build', async (ctx) => ctx.$`npm run build`)],
10148
10454
  }),
10149
10455
  ],
10150
10456
  });
10151
10457
  ```
10152
10458
 
10153
- The orchestrator resolves `MY_NPM_TOKEN` from the `production` environment's secret store and seeds it as `MY_NPM_TOKEN` (bare name) in the install subprocess. Your committed `.npmrc` reads it through `${MY_NPM_TOKEN}`.
10459
+ The orchestrator resolves `MY_NPM_TOKEN` from the `production` context's secret store and seeds it as `MY_NPM_TOKEN` (bare name) in the install subprocess. Your committed `.npmrc` reads it through `${MY_NPM_TOKEN}`.
10154
10460
 
10155
10461
  This path is the right answer when:
10156
10462
 
@@ -10172,7 +10478,7 @@ export default workflow('build', {
10172
10478
  jobs: [
10173
10479
  job('mint-codeartifact-token', {
10174
10480
  runsOn: 'default',
10175
- environment: 'production',
10481
+ context: 'production',
10176
10482
  steps: [
10177
10483
  step('mint', async (ctx) => {
10178
10484
  const awsKey = await ctx.secrets.get('AWS_ACCESS_KEY_ID');
@@ -10197,7 +10503,7 @@ export default workflow('build', {
10197
10503
  }),
10198
10504
  job('build', {
10199
10505
  runsOn: 'default',
10200
- environment: 'production',
10506
+ context: 'production',
10201
10507
  needs: ['mint-codeartifact-token'],
10202
10508
  steps: [step('build', async (ctx) => ctx.$`npm run build`)],
10203
10509
  }),
@@ -10221,7 +10527,7 @@ registries: [
10221
10527
  ],
10222
10528
  ```
10223
10529
 
10224
- Mint the token from a fine-grained PAT with `read:packages` scope, store it as a scoped secret in the `production` environment.
10530
+ Mint the token from a fine-grained PAT with `read:packages` scope, store it as a scoped secret in the `production` context.
10225
10531
 
10226
10532
  ### GitLab Packages
10227
10533
 
@@ -10276,8 +10582,8 @@ registries: [
10276
10582
 
10277
10583
  ## Security model
10278
10584
 
10279
- - **Per-environment scoping.** Every `tokenSecret` and `installEnv` entry is qualified with an environment name. The orchestrator runs the same protection-rule pipeline (branch / trust / concurrency / reviewer / wait-timer) against each named environment **before** resolving any secret, so a workflow that wants a `production` token from a feature branch is rejected exactly like a job that tries to deploy to `production` from a feature branch. A reviewer-gated install environment **pauses** the whole workflow dispatch as a workflow-scoped held run instead of resolving the token — see [Reviewer-gated installs](https://docs.kici.dev/user/private-registries/#reviewer-gated-installs) below.
10280
- - **Untrusted contributors get no tokens.** When a fork PR is dispatched and the contributor-trust resolution returns anything other than `trusted`, the orchestrator strips both `npmRegistries` and `installEnvSecrets` out of the dispatch. The install runs without auth and fails naturally on the first private dep — fork PRs cannot ever observe a registry token, even if a misconfigured environment lacks an explicit `requiredTrustTier`.
10585
+ - **Per-context scoping.** Every `tokenSecret` and `installEnv` entry is qualified with a context name. The orchestrator runs the same protection-rule pipeline (branch / trust / concurrency / reviewer / wait-timer) against each named context **before** resolving any secret, so a workflow that wants a `production` token from a feature branch is rejected exactly like a job that tries to deploy to `production` from a feature branch. A reviewer-gated install context **pauses** the whole workflow dispatch as a workflow-scoped held run instead of resolving the token — see [Reviewer-gated installs](https://docs.kici.dev/user/private-registries/#reviewer-gated-installs) below.
10586
+ - **Untrusted contributors get no tokens.** When a fork PR is dispatched and the contributor-trust resolution returns anything other than `trusted`, the orchestrator strips both `npmRegistries` and `installEnvSecrets` out of the dispatch. The install runs without auth and fails naturally on the first private dep — fork PRs cannot ever observe a registry token, even if a misconfigured context lacks an explicit `requiredTrustTier`.
10281
10587
  - **Lifecycle scripts disabled.** Whenever a private registry is in scope, the agent runs the install with `--ignore-scripts` (npm or pnpm alike). A malicious `preinstall` / `postinstall` hook in committed `package.json` cannot read the synthesized token env vars, even though they exist in the install subprocess. For a pnpm workspace, the agent builds your in-repo dependency closure as a separate step **after** the install's auth is torn down, so build scripts never see the tokens either.
10282
10588
  - **Stderr is redacted.** If the install fails, the agent masks every token literal out of the surfaced stderr / stdout chunks before logging.
10283
10589
  - **Job-scoped env-var names.** The synthesized auth env var is `KICI_NPM_TOKEN_<jobIdShort>_<i>` where `jobIdShort` is the first 8 chars of the dispatched job id. The name is unguessable from outside the install subprocess and not reused across jobs.
@@ -10285,12 +10591,12 @@ registries: [
10285
10591
 
10286
10592
  ## Reviewer-gated installs
10287
10593
 
10288
- When the named install environment carries a protection rule that holds — a required reviewer (`hold`) or a wait timer (`wait`) — the install gate **pauses the whole workflow dispatch** instead of rejecting it. The run is created in the `held` state, no jobs are queued, and a workflow-scoped row appears on the held-runs page with a `Workflow` scope badge.
10594
+ When the named install context carries a protection rule that holds — a required reviewer (`hold`) or a wait timer (`wait`) — the install gate **pauses the whole workflow dispatch** instead of rejecting it. The run is created in the `held` state, no jobs are queued, and a workflow-scoped row appears on the held-runs page with a `Workflow` scope badge.
10289
10595
 
10290
10596
  - **Reviewer hold:** the run waits for an approver. On approval the dispatch resumes from the install gate, resolves the token, and dispatches its jobs as a normal run. On rejection the run transitions to `cancelled` — no jobs ever run.
10291
10597
  - **Wait timer:** the run waits out the timer and resumes automatically when it elapses.
10292
10598
 
10293
- A `reject` protection outcome (for example a disabled environment or a branch the environment forbids) still fails the dispatch loudly with a clear reason, exactly as before — the orchestrator never dispatches a run with an unresolved install token.
10599
+ A `reject` protection outcome (for example a disabled context or a branch the context forbids) still fails the dispatch loudly with a clear reason, exactly as before — the orchestrator never dispatches a run with an unresolved install token.
10294
10600
 
10295
10601
  ## Limitations
10296
10602
 
@@ -10301,19 +10607,19 @@ A `reject` protection outcome (for example a disabled environment or a branch th
10301
10607
 
10302
10608
  The orchestrator exposes Prometheus counters and a histogram under the `kici_orch_install_secrets_*` prefix on its `/metrics` endpoint. They populate the **Install secrets resolution** Grafana dashboard and let operators graph install-secrets activity without digging through Loki.
10303
10609
 
10304
- | Metric | Type | Labels | What it tells you |
10305
- | ------------------------------------------------------------- | --------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
10306
- | `kici_orch_install_secrets_decisions_total` | Counter | `decision`, `reason` | Pass / reject / hold volume. `decision=hold` (reason `held`) counts dispatches paused at a reviewer-gated install environment. Reject reasons enumerate the failure mode: `malformed_ref`, `invalid_url_scheme`, `env_not_found`, `protection_rule_block`, `missing_token`, `missing_install_env`, etc. |
10307
- | `kici_orch_install_secrets_npm_registry_used_total` | Counter | `channel`, `provider`, `scope` | Per-channel + per-scope usage. `channel=registries` is Option A, `channel=install_env` is Option C. `scope=default` marks a no-scope default registry; `scope=-` marks Option C entries. |
10308
- | `kici_orch_install_secrets_contributor_stripped_total` | Counter | `trust_tier` | Number of dispatches where registry tokens were stripped because the contributor tier wasn't `trusted` (fork PRs from unknown / known contributors). Expected to be 0 in single-tenant orgs. |
10309
- | `kici_orch_install_secrets_token_resolution_duration_seconds` | Histogram | `environment` | Latency of per-environment secret resolution. Pathological tails (>500ms) usually mean a Vault timeout or a slow Postgres replica. |
10610
+ | Metric | Type | Labels | What it tells you |
10611
+ | ------------------------------------------------------------- | --------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
10612
+ | `kici_orch_install_secrets_decisions_total` | Counter | `decision`, `reason` | Pass / reject / hold volume. `decision=hold` (reason `held`) counts dispatches paused at a reviewer-gated install context. Reject reasons enumerate the failure mode: `malformed_ref`, `invalid_url_scheme`, `env_not_found`, `protection_rule_block`, `missing_token`, `missing_install_env`, etc. |
10613
+ | `kici_orch_install_secrets_npm_registry_used_total` | Counter | `channel`, `provider`, `scope` | Per-channel + per-scope usage. `channel=registries` is Option A, `channel=install_env` is Option C. `scope=default` marks a no-scope default registry; `scope=-` marks Option C entries. |
10614
+ | `kici_orch_install_secrets_contributor_stripped_total` | Counter | `trust_tier` | Number of dispatches where registry tokens were stripped because the contributor tier wasn't `trusted` (fork PRs from unknown / known contributors). Expected to be 0 in single-tenant orgs. |
10615
+ | `kici_orch_install_secrets_token_resolution_duration_seconds` | Histogram | `environment` | Latency of per-environment secret resolution. Pathological tails (>500ms) usually mean a Vault timeout or a slow Postgres replica. |
10310
10616
 
10311
10617
  The dashboard JSON lives at `infra/terraform/modules/grafana/dashboards/install-secrets.json`; if you maintain your own monitoring stack, you can import it directly.
10312
10618
 
10313
10619
  ## See also
10314
10620
 
10315
- - [Secrets](https://docs.kici.dev/user/secrets/) — how to seed the `<environment>:<secret-name>` values referenced by `tokenSecret` / `installEnv`.
10316
- - [Environments](https://docs.kici.dev/user/environments/) — protection rules (`branch_restrictions`, `requires_review`, `minimum_trust`) that the install gate inherits.
10621
+ - [Secrets](https://docs.kici.dev/user/secrets/) — how to seed the `<context>:<secret-name>` values referenced by `tokenSecret` / `installEnv`.
10622
+ - [Contexts](https://docs.kici.dev/user/contexts/) — protection rules (`branch_restrictions`, `requires_review`, `minimum_trust`) that the install gate inherits.
10317
10623
  - [Operator: `kici-admin org-settings`](https://docs.kici.dev/operator/kici-admin-cli#org-settings----org-level-security-policy) — the `allow_http_npm_registries` toggle and other org-scoped knobs.
10318
10624
 
10319
10625
  ---
@@ -10582,11 +10888,11 @@ KiCI provides an explicit secrets API that gives workflow steps controlled acces
10582
10888
 
10583
10889
  ## Overview
10584
10890
 
10585
- Secrets are managed per-environment in the orchestrator (see [operator docs](https://docs.kici.dev/operator/orchestrator/configuration) for setup). When a job runs with an `environment` binding, the agent receives the secret keys available for that environment but does **not** inject their values into the step's process environment. Instead, steps access secrets through the `ctx.secrets` API.
10891
+ Secrets are managed per-context in the orchestrator (see [operator docs](https://docs.kici.dev/operator/orchestrator/configuration) for setup). When a job runs with a `context` binding, the agent receives the secret keys available for that context but does **not** inject their values into the step's process environment. Instead, steps access secrets through the `ctx.secrets` API.
10586
10892
 
10587
10893
  This design prevents accidental secret leakage through child processes, log output, or error messages. Only secrets you explicitly request are loaded into memory.
10588
10894
 
10589
- A job can bind several environments with `environments: ['staging', 'my-testing']`; the secret keys from all bound environments are merged in array order, with a later environment's value winning on a key collision. See [Multiple environments per job](https://docs.kici.dev/user/environments/#multiple-environments-per-job).
10895
+ A job can bind several contexts with `contexts: ['staging', 'my-testing']`; the secret keys from all bound contexts are merged in array order, with a later context's value winning on a key collision. See [Multiple contexts per job](https://docs.kici.dev/user/contexts/#multiple-contexts-per-job).
10590
10896
 
10591
10897
  ## Where secret values come from
10592
10898
 
@@ -10654,7 +10960,7 @@ export default workflow('deploy', {
10654
10960
  jobs: [
10655
10961
  job('deploy', {
10656
10962
  runsOn: 'default',
10657
- environment: 'production',
10963
+ context: 'production',
10658
10964
  steps: [
10659
10965
  step('deploy', async (ctx) => {
10660
10966
  const token = await ctx.secrets.get('DEPLOY_TOKEN');
@@ -10778,7 +11084,7 @@ export default workflow('deploy', {
10778
11084
  jobs: [
10779
11085
  job('decrypt-and-deploy', {
10780
11086
  runsOn: 'default',
10781
- environment: 'production',
11087
+ context: 'production',
10782
11088
  steps: [
10783
11089
  step('decrypt', async (ctx) => {
10784
11090
  const ageKeys = ctx.secrets.list().filter((k) => k.startsWith('AGE_KEY_'));
@@ -10810,7 +11116,7 @@ export default workflow('deploy', {
10810
11116
  jobs: [
10811
11117
  job('decrypt-and-deploy', {
10812
11118
  runsOn: 'default',
10813
- environment: 'production',
11119
+ context: 'production',
10814
11120
  steps: [
10815
11121
  step('decrypt-to-env', async (ctx) => {
10816
11122
  await ctx.secrets.exposeFile('SOPS_AGE_KEY_FILE', {
@@ -10889,7 +11195,9 @@ The **GitHub App** is KiCI's flagship source. A single App:
10889
11195
 
10890
11196
  1. receives `push`, `pull_request`, and related events from every repo it's installed on,
10891
11197
  2. clones repos with a short-lived installation token (no deploy key to manage),
10892
- 3. posts workflow / job / step Check runs back to the pull request (see
11198
+ 3. posts workflow / job / step Check runs back to the commit visible on
11199
+ the commit view for a plain `push` and on the **Checks** tab of any pull
11200
+ request that contains it (see
10893
11201
  [GitHub checks architecture](https://docs.kici.dev/architecture/webhooks/github-checks/)).
10894
11202
 
10895
11203
  You don't need an App for every scenario — if you only care about `push`
@@ -11226,6 +11534,13 @@ driven by the orchestrator's reporting module; no workflow
11226
11534
  configuration is required beyond installing the App with the
11227
11535
  `checks: write` permission.
11228
11536
 
11537
+ Check runs are keyed to the head commit SHA, so they surface whether or
11538
+ not a pull request exists: after a `push` you see the pass/fail directly
11539
+ on the commit (a red ✗ / green ✓ next to the SHA in GitHub's commit list
11540
+ and on the commit page), and the same checks roll up into the **Checks**
11541
+ tab of any pull request that later includes the commit. A failing run is
11542
+ therefore visible in GitHub itself without opening the KiCI dashboard.
11543
+
11229
11544
  For architecture details see
11230
11545
  [GitHub checks architecture](https://docs.kici.dev/architecture/webhooks/github-checks/).
11231
11546
 
@@ -12162,7 +12477,7 @@ The working-tree overlay tarball uploads **directly** from the developer machine
12162
12477
 
12163
12478
  The run is dispatched to the developer's active organization (selected with `kici org use`, or overridden per-run). The orchestrator anchors its bound organization with a system-managed **remote source** (routing key `remote:<orgId>`) that it auto-provisions — no manual webhook source is required, so a zero-source org is immediately routable for remote runs. The Platform forces the run's routing key to `remote:<orgId>` server-side; the developer never sets a routing key. When an org has more than one connected orchestrator cluster, the CLI selects the target cluster explicitly (or relies on the per-org default), and a single connected cluster is auto-selected.
12164
12479
 
12165
- Remote runs are offered by the Platform; an orchestrator with no Platform connection cannot serve them. Executing workflow steps on the developer machine with no orchestrator is the separate `kici run local` path.
12480
+ Remote runs are offered by the Platform; an orchestrator with no Platform connection cannot serve them. Executing workflow steps on the developer machine with no orchestrator is the separate `kici run <event> --local` path.
12166
12481
 
12167
12482
  ## Source and dependency caching flow
12168
12483
 
@@ -12833,7 +13148,7 @@ The compiler processes the workflow definition:
12833
13148
 
12834
13149
  ### Execution time (local test runner)
12835
13150
 
12836
- When `kici run local` runs a workflow:
13151
+ When `kici run <event> --local` runs a workflow:
12837
13152
 
12838
13153
  1. **SDK module resolution:** The runner resolves `setStepOutputsMap` / `setJobOutputsMap` from the same `@kici-dev/sdk` module instance that the workflow uses (ensures the proxy reads from the same map)
12839
13154
  2. **Map injection:** Fresh `OutputsMap` and `StepRefMap` are created and injected via `setStepOutputsMap()` / `setStepRefMap()` before each job