@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.
- package/dist/cli.js +37 -7
- package/dist/commands/approve.d.ts +12 -0
- package/dist/commands/approve.js +5 -2
- package/dist/commands/compile.js +5 -15
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/endpoints.js +4 -6
- package/dist/commands/held-run-client.d.ts +21 -1
- package/dist/commands/held-run-client.js +34 -15
- package/dist/commands/hook.js +22 -20
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +2 -1
- package/dist/commands/init.d.ts +9 -2
- package/dist/commands/init.js +43 -16
- package/dist/commands/login.js +1 -1
- package/dist/commands/orchestrators.js +3 -2
- package/dist/commands/preview.js +1 -8
- package/dist/commands/reject.d.ts +12 -0
- package/dist/commands/reject.js +5 -2
- package/dist/commands/report/collect.d.ts +82 -0
- package/dist/commands/report/collect.js +234 -0
- package/dist/commands/report/identity.d.ts +48 -0
- package/dist/commands/report/identity.js +49 -0
- package/dist/commands/report/index.d.ts +63 -0
- package/dist/commands/report/index.js +119 -0
- package/dist/commands/report/upload.d.ts +38 -0
- package/dist/commands/report/upload.js +64 -0
- package/dist/commands/run-hold-watch.js +2 -2
- package/dist/commands/run.js +6 -3
- package/dist/commands/runs/show.js +80 -1
- package/dist/commands/types.js +51 -9
- package/dist/errors/formatter.d.ts +2 -4
- package/dist/errors/formatter.js +1 -3
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +2 -2
- package/dist/execution/sdk-alias.js +4 -2
- package/dist/fixtures/compiler.js +2 -1
- package/dist/format.js +3 -3
- package/dist/generators/secrets-dts.d.ts +8 -3
- package/dist/generators/secrets-dts.js +3 -3
- package/dist/hooks/installer.js +2 -1
- package/dist/llm-context/llms-architecture.txt +59 -23
- package/dist/llm-context/llms-cli.txt +168 -34
- package/dist/llm-context/llms-features-execution.txt +2017 -0
- package/dist/llm-context/llms-features.txt +96 -1100
- package/dist/llm-context/llms-full.txt +3131 -1407
- package/dist/llm-context/llms-getting-started.txt +164 -31
- package/dist/llm-context/llms-patterns.txt +186 -7
- package/dist/llm-context/llms-providers.txt +15 -33
- package/dist/llm-context/llms-sdk-runtime.txt +62 -40
- package/dist/llm-context/llms-sdk.txt +284 -58
- package/dist/llm-context/llms.txt +23 -15
- package/dist/local-plane/paths.d.ts +15 -0
- package/dist/local-plane/paths.js +22 -1
- package/dist/local-plane/plane-manager.js +2 -2
- package/dist/local-plane/port-holder.js +1 -1
- package/dist/local-plane/postgres.d.ts +3 -16
- package/dist/local-plane/postgres.js +10 -15
- package/dist/lockfile/generator.d.ts +12 -0
- package/dist/lockfile/generator.js +182 -54
- package/dist/lockfile/index.d.ts +0 -2
- package/dist/lockfile/index.js +1 -2
- package/dist/postinstall.js +2 -1
- package/dist/remote/config.d.ts +2 -15
- package/dist/remote/config.js +2 -16
- package/dist/remote/dashboard-client.d.ts +39 -0
- package/dist/remote/dashboard-client.js +41 -0
- package/dist/remote/oauth.js +7 -5
- package/dist/remote/uploader.js +2 -2
- package/dist/templates/package-json.js +1 -1
- package/dist/test-runner/dry-run.d.ts +1 -2
- package/dist/test-runner/dry-run.js +5 -20
- package/dist/test-runner/git-detector.js +2 -1
- package/dist/test-runner/job-executor.js +2 -1
- package/dist/test-runner/payload-builder.js +11 -17
- package/dist/types.d.ts +64 -10
- package/dist/types.js +7 -1
- package/dist/validation/validator.js +63 -6
- package/package.json +16 -11
- package/sbom.spdx.json +957 -900
- package/dist/lockfile/purity-analyzer.d.ts +0 -25
- package/dist/lockfile/purity-analyzer.js +0 -204
- package/dist/lockfile/purity-diagnostics.d.ts +0 -31
- 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
|
|
38
|
-
| ---------------- | ---------------------------------------- |
|
|
39
|
-
| **Compile** | Your dev machine or CI (`kici compile`) | Load your workflow modules, validate the DAG, assign step IDs,
|
|
40
|
-
| **Orchestrator** | Your orchestrator (no repo clone) | Match triggers against the lock
|
|
41
|
-
| **Agent** | An ephemeral agent (fresh clone per job) | Load the workflow module, evaluate job and step rules, run step bodies and hooks, run
|
|
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`, …),
|
|
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
|
|
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
|
-
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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. **
|
|
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
|
-
|
|
|
96
|
-
|
|
|
97
|
-
|
|
|
98
|
-
|
|
|
99
|
-
| `dynamicJob` (
|
|
100
|
-
| `
|
|
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
|
|
136
|
-
|
|
|
137
|
-
| A top-level `let seen = 0` (or a cache filled in job A) is empty in job B
|
|
138
|
-
|
|
|
139
|
-
|
|
|
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
|
|
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
|
-
| `
|
|
312
|
-
|
|
|
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
|
|
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
|
|
151
|
-
| Repository -> Pull requests | Read | Match `pull_request` triggers
|
|
152
|
-
| Repository -> Checks | Read & write | Post KiCI's enriched Check runs
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
316
|
-
kici-admin
|
|
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
|
|
753
|
-
kici-admin
|
|
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 \
|