@kici-dev/compiler 0.4.0 → 0.6.0

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 (83) hide show
  1. package/dist/cli.js +37 -7
  2. package/dist/commands/approve.d.ts +12 -0
  3. package/dist/commands/approve.js +5 -2
  4. package/dist/commands/compile.js +5 -15
  5. package/dist/commands/doctor.js +2 -2
  6. package/dist/commands/endpoints.js +4 -6
  7. package/dist/commands/held-run-client.d.ts +21 -1
  8. package/dist/commands/held-run-client.js +34 -15
  9. package/dist/commands/hook.js +22 -20
  10. package/dist/commands/index.d.ts +2 -0
  11. package/dist/commands/index.js +2 -1
  12. package/dist/commands/init.d.ts +9 -2
  13. package/dist/commands/init.js +43 -16
  14. package/dist/commands/login.js +1 -1
  15. package/dist/commands/orchestrators.js +3 -2
  16. package/dist/commands/preview.js +1 -8
  17. package/dist/commands/reject.d.ts +12 -0
  18. package/dist/commands/reject.js +5 -2
  19. package/dist/commands/report/collect.d.ts +82 -0
  20. package/dist/commands/report/collect.js +234 -0
  21. package/dist/commands/report/identity.d.ts +48 -0
  22. package/dist/commands/report/identity.js +49 -0
  23. package/dist/commands/report/index.d.ts +63 -0
  24. package/dist/commands/report/index.js +119 -0
  25. package/dist/commands/report/upload.d.ts +38 -0
  26. package/dist/commands/report/upload.js +64 -0
  27. package/dist/commands/run-hold-watch.js +2 -2
  28. package/dist/commands/run.js +6 -3
  29. package/dist/commands/runs/show.js +80 -1
  30. package/dist/commands/types.js +51 -9
  31. package/dist/errors/formatter.d.ts +2 -4
  32. package/dist/errors/formatter.js +1 -3
  33. package/dist/errors/index.d.ts +1 -1
  34. package/dist/errors/index.js +2 -2
  35. package/dist/execution/sdk-alias.js +4 -2
  36. package/dist/fixtures/compiler.js +2 -1
  37. package/dist/format.js +3 -3
  38. package/dist/generators/secrets-dts.d.ts +8 -3
  39. package/dist/generators/secrets-dts.js +3 -3
  40. package/dist/hooks/installer.js +2 -1
  41. package/dist/llm-context/llms-architecture.txt +59 -23
  42. package/dist/llm-context/llms-cli.txt +168 -34
  43. package/dist/llm-context/llms-features-execution.txt +2017 -0
  44. package/dist/llm-context/llms-features.txt +96 -1100
  45. package/dist/llm-context/llms-full.txt +3131 -1407
  46. package/dist/llm-context/llms-getting-started.txt +164 -31
  47. package/dist/llm-context/llms-patterns.txt +186 -7
  48. package/dist/llm-context/llms-providers.txt +15 -33
  49. package/dist/llm-context/llms-sdk-runtime.txt +62 -40
  50. package/dist/llm-context/llms-sdk.txt +284 -58
  51. package/dist/llm-context/llms.txt +23 -15
  52. package/dist/local-plane/paths.d.ts +15 -0
  53. package/dist/local-plane/paths.js +22 -1
  54. package/dist/local-plane/plane-manager.js +2 -2
  55. package/dist/local-plane/port-holder.js +1 -1
  56. package/dist/local-plane/postgres.d.ts +3 -16
  57. package/dist/local-plane/postgres.js +10 -15
  58. package/dist/lockfile/generator.d.ts +12 -0
  59. package/dist/lockfile/generator.js +182 -54
  60. package/dist/lockfile/index.d.ts +0 -2
  61. package/dist/lockfile/index.js +1 -2
  62. package/dist/postinstall.js +2 -1
  63. package/dist/remote/config.d.ts +2 -15
  64. package/dist/remote/config.js +2 -16
  65. package/dist/remote/dashboard-client.d.ts +39 -0
  66. package/dist/remote/dashboard-client.js +41 -0
  67. package/dist/remote/oauth.js +7 -5
  68. package/dist/remote/uploader.js +2 -2
  69. package/dist/templates/package-json.js +1 -1
  70. package/dist/test-runner/dry-run.d.ts +1 -2
  71. package/dist/test-runner/dry-run.js +5 -20
  72. package/dist/test-runner/git-detector.js +2 -1
  73. package/dist/test-runner/job-executor.js +2 -1
  74. package/dist/test-runner/payload-builder.js +11 -17
  75. package/dist/types.d.ts +64 -10
  76. package/dist/types.js +7 -1
  77. package/dist/validation/validator.js +63 -6
  78. package/package.json +16 -11
  79. package/sbom.spdx.json +957 -900
  80. package/dist/lockfile/purity-analyzer.d.ts +0 -25
  81. package/dist/lockfile/purity-analyzer.js +0 -204
  82. package/dist/lockfile/purity-diagnostics.d.ts +0 -31
  83. package/dist/lockfile/purity-diagnostics.js +0 -52
@@ -34,19 +34,19 @@ Your workflow is plain TypeScript, but different parts of it run at three distin
34
34
 
35
35
  ## The three phases
36
36
 
37
- | Phase | Where it runs | What runs | When |
38
- | ---------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
39
- | **Compile** | Your dev machine or CI (`kici compile`) | Load your workflow modules, validate the DAG, assign step IDs, analyze dynamic-value purity, emit `kici.lock.json` | Before anything is pushed |
40
- | **Orchestrator** | Your orchestrator (no repo clone) | Match triggers against the lock, evaluate **pure** inline dynamic values in a sandboxed JavaScript VM, dispatch jobs | On each incoming event |
41
- | **Agent** | An ephemeral agent (fresh clone per job) | Load the workflow module, evaluate job and step rules, run step bodies and hooks, run impure dynamic-value init jobs and `dynamicJob` generators (both forms) | After dispatch |
37
+ | Phase | Where it runs | What runs | When |
38
+ | ---------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
39
+ | **Compile** | Your dev machine or CI (`kici compile`) | Load your workflow modules, validate the DAG, assign step IDs, emit `kici.lock.json` | Before anything is pushed |
40
+ | **Orchestrator** | Your orchestrator (no repo clone) | Match triggers against the lock and dispatch jobs; it never evaluates workflow code | On each incoming event |
41
+ | **Agent** | An ephemeral agent (fresh clone per job) | Load the workflow module, evaluate job and step rules, run step bodies and hooks, run dynamic-value init steps and `dynamicJob` generators (both forms) | After dispatch |
42
42
 
43
43
  The lock file is the seam. Everything left of it is decided once at compile time and frozen into JSON; everything right of it reads that JSON. See [the lock file and workflow drift](https://docs.kici.dev/user/lock-file-and-drift/) and [the three-tier architecture](https://docs.kici.dev/architecture/overview/) for the wider picture.
44
44
 
45
45
  ## Compile time
46
46
 
47
- `kici compile` loads your `.kici/workflows/*.ts`, validates dependencies (no cycles, no missing `needs`), assigns compile-time step IDs (unnamed steps become `step-1`, `step-2`, …), runs purity analysis over every dynamic-value function, and writes `kici.lock.json`.
47
+ `kici compile` loads your `.kici/workflows/*.ts`, validates dependencies (no cycles, no missing `needs`), assigns compile-time step IDs (unnamed steps become `step-1`, `step-2`, …), and writes `kici.lock.json`.
48
48
 
49
- The compiler runs your module's **top-level code** to build the workflow object — but that execution's side effects and in-memory state do not travel. Only the resulting workflow structure (plus the serialized source of pure dynamic-value functions) lands in the lock. Anything your top-level code computes that isn't part of the returned workflow object simply doesn't exist past this point.
49
+ The compiler runs your module's **top-level code** to build the workflow object — but that execution's side effects and in-memory state do not travel. Only the resulting workflow structure lands in the lock. Anything your top-level code computes that isn't part of the returned workflow object simply doesn't exist past this point.
50
50
 
51
51
  See [compile the workflow](https://docs.kici.dev/user/getting-started/#compile-the-workflow) for the command in context.
52
52
 
@@ -57,7 +57,7 @@ The lock is portable JSON. It carries:
57
57
  - Workflow and trigger metadata.
58
58
  - The job and step DAG, with compile-time step IDs.
59
59
  - Static values, verbatim.
60
- - The **source text** of pure dynamic-value functions, as inline expressions.
60
+ - Markers noting which fields are dynamic, so the orchestrator knows to resolve them on the agent's init step.
61
61
 
62
62
  It does **not** carry:
63
63
 
@@ -70,11 +70,15 @@ The consequence is blunt: if a value isn't in the lock, the orchestrator can't s
70
70
 
71
71
  ## Orchestrator time
72
72
 
73
- On each event the orchestrator matches triggers using only the lock — it never clones your repository. Pure dynamic `context`, `env`, and `concurrencyGroup` functions are evaluated here, as inline expressions in a sandboxed JavaScript VM (~0ms overhead), instead of dispatching a separate job to resolve them.
73
+ On each event the orchestrator matches triggers using only the lock — it never clones your repository and never evaluates workflow code. Dynamic `context`, `env`, and `concurrencyGroup` functions are not run here: the orchestrator dispatches a short init step to an agent to resolve them (see below).
74
74
 
75
- A runtime error in an inline expression fails the job immediately there is no automatic fallback to the clone-and-evaluate path. The orchestrator does **not** run `dynamicJob` generator bodies itself: for the event-only (function) form it dispatches a dedicated dynamic-evaluation job to an agent at event time; the generator function then runs agent-side (see below).
75
+ Trigger matching can query the **contents** of individual source files, not just their paths: a `pr()`, `push()`, or `tag()` trigger with a [`requires`](https://docs.kici.dev/user/sdk/triggers/#content-requirements-requires) filter is matched by reading the named files at the event's commit and evaluating the filter as declarative data still with no repository clone and no workflow code executed. A `requires` regex is checked for catastrophic (ReDoS) shapes at `kici compile` time and rejected there, so only safe patterns reach the orchestrator.
76
76
 
77
- See [dynamic values](https://docs.kici.dev/user/dynamic-values/) for the exact rules that make a function pure or impure.
77
+ The orchestrator also does **not** run `dynamicJob` generator bodies itself: for the event-only (function) form it dispatches a dedicated dynamic-evaluation job to an agent at event time; the generator function then runs agent-side (see below).
78
+
79
+ A workflow-level `filter` predicate is the same shape: the orchestrator sees only the lock's `hasFilter` flag, never the predicate, so it dispatches an evaluation job and lets an agent decide whether the workflow applies. Prefer the declarative filters where they answer the question — `commitMessage` on the trigger and `requires` over source files cost no evaluation job at all.
80
+
81
+ See [dynamic values](https://docs.kici.dev/user/dynamic-values/) for how dynamic `context`, `env`, and `concurrencyGroup` functions resolve.
78
82
 
79
83
  ## Agent time
80
84
 
@@ -82,23 +86,35 @@ After dispatch, each job runs in its own ephemeral agent sandbox: a shallow clon
82
86
 
83
87
  1. **Job-level rules** are evaluated. By this point the agent has already spawned and the source has already been restored, so a job that its rules skip has **still** paid for that spawn and clone; only its steps are avoided.
84
88
  2. **Step-level rules**, then each step's `run()` body and its hooks.
85
- 3. **Impure** dynamic values are resolved here too, via an init job that clones and evaluates the function (~5–10s) before the real job runs.
89
+ 3. **Dynamic values** (`context`, `env`, `concurrencyGroup` functions) are resolved here, via a short `__init__` job that runs the function before the real job runs; this shows in the run timeline as an `Init:` entry.
86
90
  4. **`dynamicJob` generators run here — both forms.** The event-only (function) form runs in a dedicated evaluation job dispatched at event time; the result-aware (options) form is deferred until its declared `needs` complete, then run with the upstream outputs frozen as `ctx.needs`.
91
+ 5. **A workflow-level `filter` predicate runs here too**, before the jobs it gates. A global workflow evaluates it once per (event × workflow repo), before any run row exists, so a `false` verdict leaves no run at all. A same-repo workflow evaluates it once per job that reaches dispatch and once per job generator, after the run row exists, so a `false` verdict leaves a run whose only entries are the evaluation jobs. Keep the predicate cheap, pure, and side-effect free — a ten-job workflow calls it ten times for one event. See [narrowing with a filter](https://docs.kici.dev/user/global-workflows/#narrowing-with-a-filter).
87
92
 
88
93
  See [job execution](https://docs.kici.dev/architecture/execution/job-execution/) and [hooks and rules](https://docs.kici.dev/user/hooks/) for the details.
89
94
 
95
+ ## How a reused agent stays clean between jobs
96
+
97
+ An ephemeral agent is discarded after one job, so residue never matters. A **reused agent** — a long-lived process on a shared host (the bare-metal profile) — serves many jobs in turn. Between them, the agent runs a supervisor-owned cleanup phase so one job's leftovers never reach the next:
98
+
99
+ 1. **It reaps the finished job's process tree.** A step may background a daemon that outlives the job. The agent runs each job's process in its own process group and signals the whole group when the job ends, so a stray daemon does not survive into the next job. Set `KICI_AGENT_ORPHAN_CLEANUP=false` to keep only the runner and leave a backgrounded process alive on purpose.
100
+ 2. **It re-runs declared cleanup after a hard kill.** A job's `cleanup` / `onFailure` hooks normally run in the job process. If that process is killed hard (out of memory, forced stop), the agent re-runs the declared cleanup against the preserved work directory.
101
+ 3. **It deletes the work directory.**
102
+ 4. **It runs an optional operator reset command.** Set `KICI_AGENT_BETWEEN_JOBS_RESET_COMMAND` to a host-reset command (for example, pruning a container cache). It runs after the reap and work-directory deletion. A failure never fails the finished job.
103
+
104
+ This phase is the primary cross-job cleanup. The agent's startup temp-directory sweep stays as a backstop for anything a between-jobs phase missed. See [agent configuration](https://docs.kici.dev/operator/agent/configuration/) for the full env-var reference.
105
+
90
106
  ## What re-evaluates where
91
107
 
92
- | Construct | Runs on | When |
93
- | ---------------------------- | ---------------- | ------------------------------- |
94
- | Static value | Compile → lock | Never re-evaluated |
95
- | Pure dynamic value | Orchestrator VM | Per event |
96
- | Impure dynamic value | Agent init job | Per event |
97
- | Job-level rules | Agent | After clone |
98
- | Step-level rules | Agent | Per step |
99
- | `dynamicJob` (function form) | Agent (eval job) | Dispatched at event time |
100
- | `dynamicJob` (options form) | Agent | Deferred until `needs` complete |
101
- | Step / job body + hooks | Agent | Per job |
108
+ | Construct | Runs on | When |
109
+ | ---------------------------- | ---------------- | ---------------------------------------------------- |
110
+ | Static value | Compile → lock | Never re-evaluated |
111
+ | Dynamic value | Agent init step | Per event |
112
+ | Job-level rules | Agent | After clone |
113
+ | Step-level rules | Agent | Per step |
114
+ | `dynamicJob` (function form) | Agent (eval job) | Dispatched at event time |
115
+ | `dynamicJob` (options form) | Agent | Deferred until `needs` complete |
116
+ | Workflow `filter` predicate | Agent (eval job) | Per event (global) / per job + generator (same-repo) |
117
+ | Step / job body + hooks | Agent | Per job |
102
118
 
103
119
  **Determinism note.** `ctx.event` and `ctx.needs` are frozen snapshots — captured once and replayed unchanged on any re-evaluation. A generator that derives its output from them is stable across re-evaluations; one that reads the wall clock (`Date.now()`) or a random source (`Math.random()`) is not.
104
120
 
@@ -132,12 +148,11 @@ Outputs are typed across the job boundary too: reading `jobRef.result.…` or `c
132
148
 
133
149
  ## Common footguns
134
150
 
135
- | Symptom | Why | Fix |
136
- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
137
- | A top-level `let seen = 0` (or a cache filled in job A) is empty in job B | Each job loads the workflow module fresh in its own agent process after its own clone — there is no shared memory between jobs | Pass data through step/job **outputs** (`OutputProxy` / `needs`), not module variables |
138
- | `context: (event) => event.ref + SUFFIX`, where `SUFFIX` is a module constant, silently falls back to the slower init-job path | Purity analysis only allows the function's own params, locals, and a fixed safe-globals set — a free identifier makes it impure | Inline the constant, or accept the init-job path knowingly. See [pure functions](https://docs.kici.dev/user/dynamic-values/#pure-functions-inline-evaluation) |
139
- | Fan-out job identities shift between re-evaluations | `ctx.event` / `ctx.needs` are frozen and replayed, but `Date.now()` / `Math.random()` are not | Derive job identity only from the frozen event/needs snapshot |
140
- | A rule-skipped job still spawned an agent and cloned | Job-level rules evaluate agent-side, after dispatch and clone — not on the orchestrator | This is by design: rules can read true runtime context (`$`, `changedFiles`, `env`). See [step-level rules](https://docs.kici.dev/user/hooks/#step-level-rules) |
151
+ | Symptom | Why | Fix |
152
+ | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
153
+ | A top-level `let seen = 0` (or a cache filled in job A) is empty in job B | Each job loads the workflow module fresh in its own agent process after its own clone — there is no shared memory between jobs | Pass data through step/job **outputs** (`OutputProxy` / `needs`), not module variables |
154
+ | Fan-out job identities shift between re-evaluations | `ctx.event` / `ctx.needs` are frozen and replayed, but `Date.now()` / `Math.random()` are not | Derive job identity only from the frozen event/needs snapshot |
155
+ | A rule-skipped job still spawned an agent and cloned | Job-level rules evaluate agent-side, after dispatch and clone not on the orchestrator | This is by design: rules can read true runtime context (`$`, `changedFiles`, `env`). See [step-level rules](https://docs.kici.dev/user/hooks/#step-level-rules) |
141
156
 
142
157
  ## See also
143
158
 
@@ -149,6 +164,124 @@ Outputs are typed across the job boundary too: reading `jobRef.result.…` or `c
149
164
 
150
165
  ---
151
166
 
167
+ ## Getting help
168
+
169
+ Source: https://docs.kici.dev/user/getting-help/
170
+
171
+ When something goes wrong, the fastest path to a fix is a report that already
172
+ carries the context. This page covers what to try first, what to send, and how
173
+ to send it privately.
174
+
175
+ ## Diagnose it yourself first
176
+
177
+ Two commands answer most problems without anyone else involved:
178
+
179
+ ```bash
180
+ # Check your own setup: login, org, lock file, orchestrator, agent labels
181
+ kici doctor
182
+
183
+ # Look at the org's infrastructure: orchestrators, scalers, agents
184
+ kici diagnostics
185
+ ```
186
+
187
+ `kici doctor` prints the next command to run for each problem it finds. Work
188
+ down its output before reporting — a stale lock file or an expired login is a
189
+ one-command fix.
190
+
191
+ If your workflow ran and failed, read its logs:
192
+
193
+ ```bash
194
+ kici runs show <run-id>
195
+ kici runs logs <run-id>
196
+ ```
197
+
198
+ [Common failures](https://docs.kici.dev/user/common-failures/) covers the errors people hit most.
199
+
200
+ ## Report a problem
201
+
202
+ When you cannot resolve it yourself, gather a diagnostic bundle:
203
+
204
+ ```bash
205
+ kici report --run <run-id> --message "what you expected, and what happened"
206
+ ```
207
+
208
+ The command writes a ZIP and prints its path and `sha256`. It sends nothing.
209
+ The bundle holds:
210
+
211
+ - your CLI, Node, and orchestrator versions,
212
+ - your redacted KiCI configuration,
213
+ - your project's workflow list and lock-file state,
214
+ - the failing run's detail and logs, when you pass `--run`,
215
+ - a collection report saying which of those the command could and could not
216
+ read.
217
+
218
+ Open the file and read it. It is yours until you decide to share it.
219
+
220
+ ### What gets redacted
221
+
222
+ KiCI removes known secret shapes before anything enters the bundle:
223
+
224
+ - API keys and access tokens (AWS, GitHub, Slack, KiCI agent tokens),
225
+ - `Authorization` headers and JSON web tokens,
226
+ - passwords inside connection URLs,
227
+ - private keys and encrypted-value blocks,
228
+ - values assigned to a secret-named key, such as `api_key=` or `password=`.
229
+
230
+ Configuration is redacted twice: an allowlist keeps only known-safe fields, and
231
+ the free-text scrubber runs over what remains.
232
+
233
+ **Redaction is best effort.** A secret in a format KiCI does not recognize can
234
+ survive it. Review the bundle before you share it. `--no-redact` turns
235
+ redaction off and prints a warning — use it only for a bundle you keep.
236
+
237
+ ## Send it privately
238
+
239
+ Reports contain your data, so there is no public tracker for them. Add
240
+ `--upload` to send the bundle to KiCI directly:
241
+
242
+ ```bash
243
+ kici report --run <run-id> --upload --message "matrix job hangs on macOS"
244
+ ```
245
+
246
+ The command prints a reference id. Quote it in any conversation about the
247
+ problem. The bundle goes straight from your machine to KiCI storage over a
248
+ one-time upload link — it never passes through the dashboard.
249
+
250
+ Add `--email` if you want a reply address attached to the report.
251
+
252
+ ## Manage what you have sent
253
+
254
+ An upload is not permanent, and it is yours to revoke:
255
+
256
+ ```bash
257
+ # See the reports you have uploaded
258
+ kici report list
259
+
260
+ # Delete an uploaded bundle
261
+ kici report withdraw <ref>
262
+ ```
263
+
264
+ Uploaded bundles expire automatically after 90 days.
265
+
266
+ Anyone in your organization can upload a report. By default you see and
267
+ withdraw your own. A member with the `support:admin` permission can manage
268
+ every report in the organization, which is how an owner cleans up on behalf of
269
+ someone who has left.
270
+
271
+ ## Reporting a security issue
272
+
273
+ Do not use `kici report` for a suspected vulnerability. Follow the disclosure
274
+ process in [SECURITY.md](https://github.com/kici-dev/kici/blob/master/SECURITY.md)
275
+ instead.
276
+
277
+ ## See also
278
+
279
+ - [Common failures](https://docs.kici.dev/user/common-failures/) — the errors people hit most, and their fixes
280
+ - [CLI reference](https://docs.kici.dev/user/cli-reference/) — every `kici` command
281
+ - [Dashboard](https://docs.kici.dev/user/dashboard/) — inspecting runs in the browser
282
+
283
+ ---
284
+
152
285
  ## Getting started with workflows
153
286
 
154
287
  Source: https://docs.kici.dev/user/getting-started/
@@ -171,11 +304,11 @@ npx kici init
171
304
 
172
305
  This will:
173
306
 
174
- 1. Create `.kici/` directory with `workflows/`, `tests/`, `types/`, `package.json`, and `tsconfig.json`
307
+ 1. Create `.kici/` directory with `workflows/`, `tests/`, `types/`, `package.json`, and `tsconfig.json`. The `types/` folder holds a local development aid — TypeScript declarations that `kici types` (and an authenticated `kici compile`) generate from your orchestrator's secret contexts. Its content is a snapshot of one org's secret keys, so it is not committed.
175
308
  2. Create a `.kiciignore` file with sensible defaults
176
309
  3. Let you choose from starter workflow templates (hello-world, pr-checks)
177
310
  4. Install dependencies using the package manager detected for your repo (npm, pnpm, or yarn)
178
- 5. Update `.gitignore` to exclude `.kici/node_modules/`
311
+ 5. Update `.gitignore` to exclude `.kici/node_modules/`, and write `.kici/.gitignore` to keep the generated `types/` declarations untracked (`kici.lock.json` stays tracked — the orchestrator fetches it from your repo)
179
312
  6. Optionally install a pre-commit hook to auto-compile workflows
180
313
 
181
314
  The package manager is detected from your repo's `packageManager` field, lockfile, or the manager that invoked `kici`, defaulting to npm. Pass `--package-manager <npm|pnpm|yarn>` to override it.
@@ -496,7 +629,7 @@ For coding agents that want the entire documentation set up front, KiCI follows
496
629
 
497
630
  - `https://kici.dev/llms.txt` — curated link index grouped by SDK / patterns / CLI / architecture.
498
631
  - `https://kici.dev/llms-full.txt` — concatenated markdown of every page indexed above.
499
- - `kici docs llm` — print the same `llms-full.txt` bundle to stdout, offline, straight from the installed `@kici-dev/compiler` package. Add `--index` to print the curated `llms.txt` index instead. The agent can pipe the output into its own context buffer with no network call.
632
+ - `kici docs llm` — print the same curated `llms.txt` index to stdout, offline, straight from the installed `@kici-dev/compiler` package. `kici docs llm <topic>` prints one task bundle (`getting-started`, `patterns`, `sdk`, `sdk-runtime`, `cli`, `features`, `features-execution`, `providers`, `architecture`), and `kici docs llm full` prints the whole `llms-full.txt`. Add `--out <path>` to write to a file. The agent can pipe the output into its own context buffer with no network call.
500
633
  - `kici docs` — open the docs site in your browser.
501
634
 
502
635
  The offline bundle is regenerated from `docs/` every time the package is built, so it always matches the version of KiCI you've installed.
@@ -304,12 +304,16 @@ export default workflow('ci', {
304
304
 
305
305
  Rule check functions receive a `RuleContext` with:
306
306
 
307
- | Property | Type | Description |
308
- | -------------- | ----------------------------------- | ----------------------------------- |
309
- | `event` | `EventPayload` | The triggering event data |
310
- | `changedFiles` | `string[]` | Files changed in this event |
311
- | `env` | `Record<string, string\|undefined>` | Environment variables |
312
- | `$` | zx shell | Shell executor for running commands |
307
+ | Property | Type | Description |
308
+ | -------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------- |
309
+ | `event` | `EventPayload` | The triggering event data |
310
+ | `changedFiles` | `string[]` | Files changed in this event |
311
+ | `sourceRepo` | `RepoInfo \| undefined` | The repo whose event triggered the run, when the evaluation has a checkout |
312
+ | `workflowRepo` | `RepoInfo \| undefined` | The repo that registered the workflow. The same repo as `sourceRepo` outside a global workflow |
313
+ | `env` | `Record<string, string\|undefined>` | Environment variables |
314
+ | `$` | zx shell | Shell executor for running commands |
315
+
316
+ `RepoInfo` carries `path` — an absolute path to that repo's checkout — plus optional `ref` and `sha`. In a [global workflow](https://docs.kici.dev/user/global-workflows/) the two are different repos, which is what lets one rule read the source repo's tree while the workflow lives elsewhere. Read _through_ `path`: its contents are stable, but the path itself differs between the evaluation and the later run.
313
317
 
314
318
  ### Marker rules
315
319
 
@@ -573,6 +577,181 @@ This workflow:
573
577
 
574
578
  ---
575
579
 
580
+ ## Git credentials
581
+
582
+ Source: https://docs.kici.dev/user/patterns/git-credentials/
583
+
584
+ Two facts drive everything on this page, and neither is guessable:
585
+
586
+ - **Cloning your own repository needs no credential.** The framework checks it
587
+ out for you, and the app KiCI installs already holds read access.
588
+ - **Pushing always needs a credential you supply.** The KiCI app holds read
589
+ access only, so every push — including to the job's own repository — needs one.
590
+
591
+ ## Declare credentials once, by name
592
+
593
+ Credentials are declared on the job as a named map. **Every value is the name of
594
+ a secret**, in `<context>:<secret-name>` form — never the credential itself:
595
+
596
+ ```typescript
597
+ job('release', {
598
+ runsOn: 'linux',
599
+ gitCredentials: {
600
+ // `default` is used whenever a call names no credential
601
+ default: {
602
+ kind: 'app',
603
+ appIdSecret: 'ci:ACME_APP_ID',
604
+ installationIdSecret: 'ci:ACME_INSTALL_ID',
605
+ privateKeySecret: 'ci:ACME_APP_KEY',
606
+ },
607
+ forge: { kind: 'token', tokenSecret: 'ci:FORGE_PAT' },
608
+ vendor: { kind: 'ssh', privateKeySecret: 'ci:VENDOR_DEPLOY_KEY' },
609
+ },
610
+ steps: [build, tagAndPush],
611
+ });
612
+ ```
613
+
614
+ Store the secrets first with `kici-admin secret set`. Pasting a private key
615
+ straight into the workflow is rejected when the workflow is defined, naming the
616
+ field — a key written into `.kici/` would be committed to your repository.
617
+
618
+ ## Push
619
+
620
+ Your checkout is read-only by default. Opening a write window is explicit:
621
+
622
+ ```typescript
623
+ step('tag', async ({ $, repo }) => {
624
+ await $`git tag v${version}`;
625
+ await repo.withWrite({ permissions: { contents: 'write' } }, async () => {
626
+ await $`git push origin v${version}`;
627
+ });
628
+ });
629
+ ```
630
+
631
+ Inside the callback, git operations on that repository use a write credential.
632
+ Outside it they do not, so an accidental push elsewhere in the job fails.
633
+
634
+ Pass `credential: 'forge'` to use a named entry instead of `default`.
635
+
636
+ **KiCI never guesses the permission set.** What a push needs depends on what is
637
+ being pushed — changing anything under `.github/workflows/` additionally requires
638
+ `workflows`:
639
+
640
+ ```typescript
641
+ await repo.withWrite({ permissions: { contents: 'write', workflows: 'write' } }, async () => {
642
+ await $`git push origin HEAD`;
643
+ });
644
+ ```
645
+
646
+ If the app was not granted a permission you request, the forge refuses to issue
647
+ the credential at all. The error names the repository and the permissions you
648
+ asked for, **before any git command runs** — not at the end of a long build.
649
+
650
+ ## Clone more than one repository
651
+
652
+ A workflow often needs several repositories, not just its own. Mint one token
653
+ that covers all of them, then clone each with it:
654
+
655
+ ```typescript
656
+ const { token } = await kici.git.github.getToken({
657
+ repositories: ['acme/app', 'acme/shared-lib'],
658
+ permissions: { contents: 'read' },
659
+ });
660
+
661
+ for (const repo of ['acme/app', 'acme/shared-lib']) {
662
+ await $`git clone https://x-access-token:${token}@github.com/${repo}.git`;
663
+ }
664
+ ```
665
+
666
+ A GitHub App token is issued per installation, so one call covers every
667
+ repository you name. Each repository must be inside the app's installation. If
668
+ one is not, the forge refuses the whole request and the error names it.
669
+
670
+ The credential helper is installed on your own checkout only, so a repository
671
+ you clone yourself does not inherit it. That is why this case mints a token
672
+ rather than relying on the helper.
673
+
674
+ ## Call the forge API
675
+
676
+ `gh` does not read git credential helpers, so this is the one case that wants the
677
+ token as a value:
678
+
679
+ ```typescript
680
+ const { token } = await kici.git.github.getToken({
681
+ repositories: ['acme/app'],
682
+ permissions: { contents: 'write' },
683
+ });
684
+ await $({ env: { ...process.env, GH_TOKEN: token } })`gh release create v${version}`;
685
+ ```
686
+
687
+ The token is masked in step logs. Prefer `withWrite` for git itself, which never
688
+ places a credential in the step environment.
689
+
690
+ ## Credentials that only exist at run time
691
+
692
+ A credential fetched during the run — from a vault, or a cloud secret store via
693
+ the job's OIDC identity — cannot be named ahead of time. Use the `*Value` half of
694
+ the pair, which says "this is the credential, not a name for one":
695
+
696
+ ```typescript
697
+ gitCredentials: { default: { kind: 'token', tokenValue: fetchedAtRuntime } }
698
+ ```
699
+
700
+ To pass a derived credential to a **later** job, publish it with
701
+ `ctx.setSecretOutput()` and name it with the reserved `needs:` context — that
702
+ path is encrypted, scoped to the run, and deleted when the run ends:
703
+
704
+ ```typescript
705
+ const mint = job('mint', {
706
+ runsOn: 'linux',
707
+ run: async (ctx) => {
708
+ const token = (await ctx.$`vault write -f auth/token/create`).stdout.trim();
709
+ ctx.setSecretOutput('FORGE_TOKEN', token);
710
+ },
711
+ });
712
+
713
+ const build = job('build', {
714
+ runsOn: 'linux',
715
+ needs: [mint],
716
+ gitCredentials: { default: { kind: 'token', tokenSecret: 'needs:FORGE_TOKEN' } },
717
+ steps: [cloneAndPush],
718
+ });
719
+ ```
720
+
721
+ Never put a credential in a regular job output: regular outputs are not masked,
722
+ are stored, and are shown in the dashboard.
723
+
724
+ For a **minted app token**, prefer re-deriving over transporting — those expire
725
+ after an hour, so one minted in an earlier job is often already dead by the time
726
+ a later job reads it. Have the later job name the same secret, or mint its own.
727
+
728
+ ## How it works, and why long jobs still push
729
+
730
+ An app token expires an hour after it is issued, and cannot be renewed. Rather
731
+ than capture one at checkout time, the agent installs a git credential helper on
732
+ the checkout: git asks it on every network operation, and it obtains a fresh
733
+ credential each time. A push at the end of a three-hour build works exactly as it
734
+ does at the start, and no credential is ever written into `.git/config`, into
735
+ `git remote -v`, or into the step's environment.
736
+
737
+ ## Limits worth knowing
738
+
739
+ - **Container jobs cannot use this yet.** A container job runs git inside the
740
+ container, which has no route to the credential service. Bare-metal jobs are
741
+ unaffected.
742
+ - **The reserved `needs:` context is not resolvable yet** on a deployed
743
+ orchestrator; naming it produces a clear error rather than a wrong credential.
744
+ - **A write window is bounded by the repository and the callback, not the step.**
745
+ Steps running concurrently in the same job can push to the same repository
746
+ while it is open. They cannot reach a different one.
747
+ - **A credential you supply yourself cannot be narrowed.** A personal access
748
+ token or SSH key grants whatever it was created with, so a requested permission
749
+ set is reported as unscoped rather than pretended to be enforced.
750
+ - **Being allowed to push is not the same as the push succeeding.** A branch
751
+ protection rule or repository ruleset can still reject it.
752
+
753
+ ---
754
+
576
755
  ## Host restart & wait-for-alive
577
756
 
578
757
  Source: https://docs.kici.dev/user/patterns/host-restart/
@@ -935,7 +1114,7 @@ export default workflow('on-forgejo-push', {
935
1114
  **Caveat — cloning:** generic webhook sources deliver only the payload; they do not carry a clone token, and KiCI's automatic pre-step clone (`packages/agent/src/checkout/git-clone.ts`) is GitHub-only today (HTTPS + `http.extraHeader` Basic auth with a GitHub installation token). Three practical patterns:
936
1115
 
937
1116
  - **Mirror to GitHub and fan out.** Keep the repo on GitHub, register the workflow via a GitHub default-branch push, and have Gogs/Forgejo webhooks fan out via [cross-source delivery](https://docs.kici.dev/architecture/webhooks/webhook-delivery/#cross-source-delivery). The clone runs against the GitHub mirror using the GitHub App's token.
938
- - **Clone yourself using a secret.** Set `checkout: false` on the job to skip the framework clone, store an SSH private key or forge personal access token as a secret, and run `git clone` explicitly in the first step. This works for any forge the agent can reach, no mirror needed. You still need a way to **register** the workflow — either keep a one-file GitHub repo whose only job is to own the registration, or bootstrap the registration manually against the orchestrator DB.
1117
+ - **Clone yourself using a declared credential.** Set `checkout: false` on the job to skip the framework clone, declare the forge credential on the job as a named `gitCredentials` entry (values are **secret names**, never the credential), and run `git clone` in the first step — the agent authenticates it for you, and the credential never appears in workflow source. See [Git credentials](https://docs.kici.dev/user/patterns/git-credentials/). This works for any forge the agent can reach, no mirror needed. You still need a way to **register** the workflow — either keep a one-file GitHub repo whose only job is to own the registration, or bootstrap the registration manually against the orchestrator DB.
939
1118
  - **Self-contained workflow.** No clone at all. The step reads whatever it needs from `rawPayload` (e.g., `rawPayload.after`, `rawPayload.repository.clone_url`) and drives external systems — notifications, deploys, third-party CI triggers.
940
1119
 
941
1120
  Manual-clone example (pattern 2) using an SSH deploy key:
@@ -144,38 +144,22 @@ the manifest flow — follow these steps.
144
144
 
145
145
  5. **Pick permissions.** Minimum required:
146
146
 
147
- | Scope | Access | Why |
148
- | --------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
149
- | Repository -> Contents | Read | Clone the repo to read the lock file |
150
- | Repository -> Metadata | Read (auto) | Default for every App; also lets KiCI look up a pull-request author's repository access level for CI trust |
151
- | Repository -> Pull requests | Read | Match `pull_request` triggers |
152
- | Repository -> Checks | Read & write | Post KiCI's enriched Check runs |
153
- | Organization -> Members | Read | (optional, org installs) Receive `organization` / `membership` / `team` events so KiCI's CI-trust permission cache invalidates promptly on access changes |
154
-
155
- The first four rows cover the core flow (clone, trigger matching,
156
- Check runs). The **Organization -> Members** row is only relevant if
157
- you use [CI trust tiers](https://docs.kici.dev/architecture/security/ci-security/)
158
- on an org-level install — see the event note below.
147
+ | Scope | Access | Why |
148
+ | --------------------------- | ------------ | ------------------------------------ |
149
+ | Repository -> Contents | Read | Clone the repo to read the lock file |
150
+ | Repository -> Metadata | Read (auto) | Default for every App |
151
+ | Repository -> Pull requests | Read | Match `pull_request` triggers |
152
+ | Repository -> Checks | Read & write | Post KiCI's enriched Check runs |
153
+
154
+ Those four cover the whole flow: clone, trigger matching, Check runs.
155
+ [CI trust](https://docs.kici.dev/architecture/security/ci-security/) needs no
156
+ permission of its own it reads the fork relationship straight out of
157
+ the webhook payload and calls no GitHub API.
159
158
 
160
159
  6. **Subscribe to events.** At minimum: `push`, `pull_request`,
161
160
  `check_run`, `check_suite`. Add others (`issues`, `release`, ...) if
162
161
  your workflows use those triggers.
163
162
 
164
- **For CI trust (optional but recommended on org installs):** also
165
- subscribe to `member`, `organization`, `membership`, and `team`.
166
- KiCI caches each pull-request author's repository access level (used
167
- to decide whether workflow changes take effect immediately or are
168
- held for approval — see
169
- [CI security](https://docs.kici.dev/architecture/security/ci-security/)). These
170
- events let the orchestrator drop stale cache entries the moment a
171
- contributor's access changes. They are not required for correctness:
172
- without them the cache simply ages out on its own 15-minute TTL, so a
173
- permission change can take up to 15 minutes to take effect. The
174
- `organization` / `membership` / `team` events require the
175
- **Organization -> Members** read permission and an org-level
176
- installation; `member` is a repository event covered by the default
177
- Metadata permission.
178
-
179
163
  7. **Generate a private key.** Scroll to the bottom of the App settings
180
164
  and click _Generate a private key_. A `.pem` file downloads —
181
165
  store it safely; you cannot redownload it.
@@ -312,9 +296,8 @@ when you want a list entry pinned to this specific App rather than
312
296
  applying to any source in the org:
313
297
 
314
298
  ```bash
315
- # Enable global workflows for the org
316
- kici-admin org-settings global-workflows set-enabled true \
317
- --customer-id <orgId>
299
+ # Enable global workflows cluster-wide (the master switch is fleet-wide, held by the operator)
300
+ kici-admin cluster-settings set --global-workflows-enabled true
318
301
 
319
302
  # Allow the listed repo as an author for any source in the org
320
303
  kici-admin org-settings global-workflows allow-add 'my-org/ci-workflows/*' \
@@ -749,9 +732,8 @@ org-scoped (one row per `customer_id`); each list entry can optionally
749
732
  pin to a specific source via `--source <routingKey>`:
750
733
 
751
734
  ```bash
752
- # Enable global workflows for the org
753
- kici-admin org-settings global-workflows set-enabled true \
754
- --customer-id <orgId>
735
+ # Enable global workflows cluster-wide (the master switch is fleet-wide, held by the operator)
736
+ kici-admin cluster-settings set --global-workflows-enabled true
755
737
 
756
738
  # Allow authors from any source in the org
757
739
  kici-admin org-settings global-workflows allow-add \