@kici-dev/compiler 0.1.23 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +15 -5
- package/dist/commands/index.d.ts +4 -2
- package/dist/commands/index.js +3 -2
- package/dist/commands/init.js +2 -2
- package/dist/commands/pat.d.ts +27 -0
- package/dist/commands/pat.js +76 -0
- package/dist/commands/preview.d.ts +88 -0
- package/dist/commands/{test.js → preview.js} +15 -14
- package/dist/commands/run.d.ts +11 -1
- package/dist/commands/run.js +34 -7
- package/dist/commands/verify-attestation.d.ts +4 -1
- package/dist/commands/verify-attestation.js +26 -10
- package/dist/generators/secrets-dts.js +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/llm-context/llms-architecture.txt +3 -3
- package/dist/llm-context/llms-cli.txt +149 -26
- package/dist/llm-context/llms-features.txt +107 -5
- package/dist/llm-context/llms-full.txt +390 -46
- package/dist/llm-context/llms-getting-started.txt +6 -6
- package/dist/llm-context/llms-sdk.txt +125 -6
- package/dist/llm-context/llms.txt +7 -5
- package/dist/local-executor/index.js +2 -1
- package/dist/local-executor/job-runner.js +3 -3
- package/dist/lockfile/generator.d.ts +10 -2
- package/dist/lockfile/generator.js +106 -52
- package/dist/remote/history.d.ts +1 -1
- package/dist/remote/history.js +1 -1
- package/dist/remote/local-repo-identity.d.ts +32 -0
- package/dist/remote/local-repo-identity.js +74 -0
- package/dist/remote/prod-defaults.d.ts +8 -0
- package/dist/remote/prod-defaults.js +9 -1
- package/dist/templates/agents-md.d.ts +1 -1
- package/dist/templates/agents-md.js +2 -2
- package/dist/templates/package-json.js +1 -1
- package/dist/test-runner/step-context.d.ts +1 -1
- package/dist/test-runner/step-context.js +2 -1
- package/dist/types.d.ts +33 -6
- package/dist/types.js +5 -1
- package/package.json +4 -7
- package/sbom.spdx.json +35 -35
|
@@ -24,7 +24,7 @@ Why the lock file must stay in sync with workflow source, how to commit both tog
|
|
|
24
24
|
|
|
25
25
|
### [CLI reference](https://docs.kici.dev/user/cli-reference/)
|
|
26
26
|
|
|
27
|
-
All CLI commands provided by `@kici-dev/compiler`: `kici compile` (with watch mode and check mode), `kici run` (local and remote execution), `kici
|
|
27
|
+
All CLI commands provided by `@kici-dev/compiler`: `kici compile` (with watch mode and check mode), `kici run` (local and remote execution), `kici preview` (event simulation with dry-run, filtering, debug output, and custom payloads), `kici login`/`logout`/`org` (authentication and org management), `kici diagnostics` (infrastructure tree) and `kici runs` (`list`/`show`/`logs`/`rerun`/`cancel` — run management), `kici secrets` (secret listing), `kici types` (type generation), `kici fixture` (generate test payloads), `kici init` (interactive project scaffolding), `kici hook` (pre-commit hook installation), `kici endpoints` (webhook entrypoints), and `kici workflows` (workflow listing). Includes environment variables and exit codes.
|
|
28
28
|
|
|
29
29
|
### [Workflow patterns](https://docs.kici.dev/user/workflow-patterns/)
|
|
30
30
|
|
|
@@ -247,10 +247,10 @@ The lock file (`kici.lock.json`) is a JSON representation of your workflow that
|
|
|
247
247
|
|
|
248
248
|
## Preview trigger matching
|
|
249
249
|
|
|
250
|
-
Use `kici
|
|
250
|
+
Use `kici preview` to preview which workflows match a trigger event (dry-run, no execution):
|
|
251
251
|
|
|
252
252
|
```bash
|
|
253
|
-
npx kici
|
|
253
|
+
npx kici preview pr:open
|
|
254
254
|
```
|
|
255
255
|
|
|
256
256
|
Expected output (simplified):
|
|
@@ -416,14 +416,14 @@ Or add the flag to your root `package.json`:
|
|
|
416
416
|
|
|
417
417
|
## Authoring KiCI workflows with LLM coding agents
|
|
418
418
|
|
|
419
|
-
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
|
|
419
|
+
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.
|
|
420
420
|
|
|
421
421
|
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:
|
|
422
422
|
|
|
423
423
|
- where the SDK type declarations live (`node_modules/@kici-dev/sdk/dist/index.d.ts`)
|
|
424
424
|
- the five canonical authoring patterns with runnable examples
|
|
425
425
|
- the anti-patterns that catch agents off-guard (no YAML, no `/dist/...` imports, no top-level `await`)
|
|
426
|
-
- the local commands the agent should drive (`kici compile --check`, `kici
|
|
426
|
+
- the local commands the agent should drive (`kici compile --check`, `kici preview`, `kici run local`, `kici docs llm`)
|
|
427
427
|
|
|
428
428
|
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.
|
|
429
429
|
|
|
@@ -514,6 +514,6 @@ If you're not sure, pick Docker / Podman.
|
|
|
514
514
|
|
|
515
515
|
## Looking for the laptop-only path?
|
|
516
516
|
|
|
517
|
-
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
|
|
517
|
+
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.
|
|
518
518
|
|
|
519
519
|
---
|
|
@@ -807,7 +807,7 @@ const deploy = job('deploy', {
|
|
|
807
807
|
|
|
808
808
|
**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`.
|
|
809
809
|
|
|
810
|
-
Cross-job output chaining works in both local
|
|
810
|
+
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.
|
|
811
811
|
|
|
812
812
|
### Job dependencies (`needs`)
|
|
813
813
|
|
|
@@ -1473,6 +1473,88 @@ The caller never has to branch on outcome — `result.result` is always a `Bucke
|
|
|
1473
1473
|
|
|
1474
1474
|
---
|
|
1475
1475
|
|
|
1476
|
+
## SDK reference: parallel
|
|
1477
|
+
|
|
1478
|
+
Source: https://docs.kici.dev/user/sdk/parallel/
|
|
1479
|
+
|
|
1480
|
+
`parallel([...steps], opts?)` runs a group of independent steps **concurrently**
|
|
1481
|
+
within one job, behind a join barrier: execution continues past the group only
|
|
1482
|
+
once every child has settled. Each child is its own observable step — it gets its
|
|
1483
|
+
own logs, status, timing, and retry — instead of being hidden inside one step's
|
|
1484
|
+
`Promise.all`.
|
|
1485
|
+
|
|
1486
|
+
```ts
|
|
1487
|
+
import { workflow, job, step, parallel, push } from '@kici-dev/sdk';
|
|
1488
|
+
|
|
1489
|
+
export default workflow('ci', {
|
|
1490
|
+
on: push(),
|
|
1491
|
+
jobs: [
|
|
1492
|
+
job('checks', {
|
|
1493
|
+
runsOn: 'kici:os:linux',
|
|
1494
|
+
steps: [
|
|
1495
|
+
checkout,
|
|
1496
|
+
// lint, typecheck, and the unit tests have no ordering between them,
|
|
1497
|
+
// so they run together — the job's wall-clock is the slowest child,
|
|
1498
|
+
// not the sum of all three.
|
|
1499
|
+
parallel([lint, typecheck, unitTests], { failFast: true }),
|
|
1500
|
+
deploy,
|
|
1501
|
+
],
|
|
1502
|
+
}),
|
|
1503
|
+
],
|
|
1504
|
+
});
|
|
1505
|
+
```
|
|
1506
|
+
|
|
1507
|
+
`parallel(...)` returns a `ParallelGroup` that sits in the ordinary flat
|
|
1508
|
+
`steps: [...]` array — there is no new `job` field. A group's children are
|
|
1509
|
+
**sequential steps only**; groups cannot be nested.
|
|
1510
|
+
|
|
1511
|
+
## Options
|
|
1512
|
+
|
|
1513
|
+
`parallel(steps, opts?)` accepts:
|
|
1514
|
+
|
|
1515
|
+
- **`failFast?: boolean`** — default `true`. When a child fails, the in-flight
|
|
1516
|
+
siblings are cancelled immediately and the job fails. With `failFast: false`
|
|
1517
|
+
every child runs to completion first, then the job fails if any child failed.
|
|
1518
|
+
- **`maxParallel?: number`** — default unlimited. Caps how many children run at
|
|
1519
|
+
once; children waiting for a slot report a `pending` status until they launch.
|
|
1520
|
+
- **`name?: string`** — a label for the group's dashboard band.
|
|
1521
|
+
|
|
1522
|
+
A child marked `continueOnError: true` never trips fail-fast and never fails the
|
|
1523
|
+
job — it still shows a `failed` status badge, but the group treats it as
|
|
1524
|
+
non-fatal.
|
|
1525
|
+
|
|
1526
|
+
## Statuses
|
|
1527
|
+
|
|
1528
|
+
Parallel steps introduce two step statuses:
|
|
1529
|
+
|
|
1530
|
+
- **`pending`** — a child queued behind `maxParallel`, not yet launched.
|
|
1531
|
+
- **`cancelled`** — a sibling aborted by fail-fast. A cancelled step is **not** a
|
|
1532
|
+
failure: only the child that actually failed fails the job; the cancelled
|
|
1533
|
+
siblings render in gray (distinct from the red failing step) on the dashboard.
|
|
1534
|
+
|
|
1535
|
+
Children may also complete **out of order** — the fastest child finishes first
|
|
1536
|
+
regardless of array position. A later sequential step can read a parallel child's
|
|
1537
|
+
`.result` after the barrier; children within a group cannot read each other's
|
|
1538
|
+
results (there is no ordering inside the group).
|
|
1539
|
+
|
|
1540
|
+
## Scope: nests inside job-level fan-out
|
|
1541
|
+
|
|
1542
|
+
`parallel()`'s `failFast` / `maxParallel` are **step-group** scopes — they govern
|
|
1543
|
+
only the steps inside the group. They are a different layer from the **job-level**
|
|
1544
|
+
`failFast` / `maxParallel` on a matrix / `runsOnAll` fan-out, which govern how a
|
|
1545
|
+
job's child _jobs_ spread across the matrix or host roster. A `parallel()` group
|
|
1546
|
+
inside a fan-out job nests its concurrency inside each fan-out child.
|
|
1547
|
+
|
|
1548
|
+
## Local vs remote execution
|
|
1549
|
+
|
|
1550
|
+
Run remotely (the orchestrator + agent), parallel children execute concurrently
|
|
1551
|
+
and each surfaces as its own dashboard step. `kici run local` executes the same
|
|
1552
|
+
children in array order in its single-process model — the results are identical,
|
|
1553
|
+
only the wall-clock and the live fail-fast cancellation differ. Use a remote run
|
|
1554
|
+
to observe the concurrent timeline.
|
|
1555
|
+
|
|
1556
|
+
---
|
|
1557
|
+
|
|
1476
1558
|
## SDK reference: rules, matrix, dynamic jobs
|
|
1477
1559
|
|
|
1478
1560
|
Source: https://docs.kici.dev/user/sdk/rules-matrix-dynamic/
|
|
@@ -2371,7 +2453,7 @@ interface StepContext<TInputs = Record<string, unknown>> {
|
|
|
2371
2453
|
rawPayload?: Record<string, unknown>;
|
|
2372
2454
|
/** Which git provider triggered this workflow (e.g. 'github', 'gitlab') */
|
|
2373
2455
|
provider?: string;
|
|
2374
|
-
/** Whether this execution was triggered by `kici
|
|
2456
|
+
/** Whether this execution was triggered by `kici run remote` (developer-initiated remote run) */
|
|
2375
2457
|
isTestRun: boolean;
|
|
2376
2458
|
/** The resolved deployment environment name for this job (undefined without environment) */
|
|
2377
2459
|
environment?: string;
|
|
@@ -2768,7 +2850,7 @@ Tools that require a file path on disk (sops `SOPS_AGE_KEY_FILE`, kubectl `KUBEC
|
|
|
2768
2850
|
|
|
2769
2851
|
### Local test mode secrets
|
|
2770
2852
|
|
|
2771
|
-
When running `kici
|
|
2853
|
+
When running `kici preview`, you can provide secrets locally without an orchestrator.
|
|
2772
2854
|
|
|
2773
2855
|
#### .kici/.secrets file
|
|
2774
2856
|
|
|
@@ -2796,10 +2878,10 @@ Override or supplement file-based secrets with CLI flags:
|
|
|
2796
2878
|
|
|
2797
2879
|
```bash
|
|
2798
2880
|
# Inject flat secrets (repeatable)
|
|
2799
|
-
kici
|
|
2881
|
+
kici preview push --secret DEPLOY_TOKEN=my-token --secret API_KEY=my-key
|
|
2800
2882
|
|
|
2801
2883
|
# Inject context-scoped secrets (repeatable)
|
|
2802
|
-
kici
|
|
2884
|
+
kici preview push --context production.DB_PASSWORD=prod-secret --context npm-publish.NPM_TOKEN=abc123
|
|
2803
2885
|
```
|
|
2804
2886
|
|
|
2805
2887
|
**Precedence:** CLI flags override `.kici/.secrets` file values. Context secrets are auto-flattened into `ctx.secrets` using the same merge logic as production (last context wins).
|
|
@@ -3354,6 +3436,7 @@ interface ScheduleConfigInput {
|
|
|
3354
3436
|
cron: string; // Required: cron expression (5-field)
|
|
3355
3437
|
timezone?: string; // Timezone for cron evaluation (default: 'UTC')
|
|
3356
3438
|
description?: string; // Human-readable description of the schedule
|
|
3439
|
+
inputs?: DispatchInputsMap; // Optional: defaults-only typed inputs (see below)
|
|
3357
3440
|
}
|
|
3358
3441
|
```
|
|
3359
3442
|
|
|
@@ -3364,6 +3447,41 @@ schedule({ cron: '0 9 * * 1', timezone: 'America/New_York' }); // Monday 9am ET
|
|
|
3364
3447
|
schedule({ cron: '*/15 * * * *', description: 'health check every 15 min' });
|
|
3365
3448
|
```
|
|
3366
3449
|
|
|
3450
|
+
#### Schedule inputs (defaults-only)
|
|
3451
|
+
|
|
3452
|
+
A `schedule()` trigger may declare typed `inputs`. A cron or dashboard
|
|
3453
|
+
"run now" fire carries **no operator-supplied values**, so each input resolves
|
|
3454
|
+
from its declared **default** and is exposed to steps and rules as
|
|
3455
|
+
`ctx.dispatchInputs` — the same surface as [typed dispatch inputs](https://docs.kici.dev/user/sdk/triggers/#typed-dispatch-inputs).
|
|
3456
|
+
|
|
3457
|
+
Because there is no operator to supply a value, every schedule input must
|
|
3458
|
+
declare a `.default()` **or** be `.optional()`. An input that is neither is
|
|
3459
|
+
rejected at `kici compile` time.
|
|
3460
|
+
|
|
3461
|
+
```typescript
|
|
3462
|
+
import { workflow, job, schedule, z } from '@kici-dev/sdk';
|
|
3463
|
+
|
|
3464
|
+
export default workflow('nightly', {
|
|
3465
|
+
on: schedule({
|
|
3466
|
+
cron: '0 3 * * *',
|
|
3467
|
+
inputs: { mode: z.enum(['full', 'quick']).default('full') },
|
|
3468
|
+
}),
|
|
3469
|
+
jobs: [
|
|
3470
|
+
job('build', {
|
|
3471
|
+
runsOn: 'default',
|
|
3472
|
+
run: async (ctx) => {
|
|
3473
|
+
ctx.log(`mode = ${ctx.dispatchInputs.mode}`); // "full" on every fire
|
|
3474
|
+
},
|
|
3475
|
+
}),
|
|
3476
|
+
],
|
|
3477
|
+
});
|
|
3478
|
+
```
|
|
3479
|
+
|
|
3480
|
+
You can also share a typed handle via `defineDispatchInputs(...)` and read it
|
|
3481
|
+
back with `.from(ctx)`, exactly as with `dispatch()`. The allowed input types
|
|
3482
|
+
are the same closed subset documented under
|
|
3483
|
+
[typed dispatch inputs](https://docs.kici.dev/user/sdk/triggers/#typed-dispatch-inputs).
|
|
3484
|
+
|
|
3367
3485
|
### lifecycle()
|
|
3368
3486
|
|
|
3369
3487
|
Create a lifecycle trigger for cross-workflow orchestration events. Returns a frozen `LifecycleTriggerConfig`.
|
|
@@ -3692,7 +3810,7 @@ If `check()` throws while polling, the error is logged and polling continues —
|
|
|
3692
3810
|
|
|
3693
3811
|
Source: https://docs.kici.dev/user/sdk-reference/
|
|
3694
3812
|
|
|
3695
|
-
Reference documentation for `@kici-dev/sdk`. The reference is split across
|
|
3813
|
+
Reference documentation for `@kici-dev/sdk`. The reference is split across the per-topic pages below.
|
|
3696
3814
|
|
|
3697
3815
|
| Page | Covers |
|
|
3698
3816
|
| ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -3704,6 +3822,7 @@ Reference documentation for `@kici-dev/sdk`. The reference is split across five
|
|
|
3704
3822
|
| [Runtime](https://docs.kici.dev/user/sdk/runtime/) | Types index, `StepContext`, secrets, and fixtures. |
|
|
3705
3823
|
| [Idempotent helpers](https://docs.kici.dev/user/sdk/idempotent/) | `idempotent()`, `idempotentStep()`, and the check-mode-aware `checkStep()` — check / apply pattern with typed results on both the skipped and applied branches. |
|
|
3706
3824
|
| [Wait-for helpers](https://docs.kici.dev/user/sdk/wait-for/) | `waitFor()` and `waitForStep()` — poll a condition on an interval, run an optional success action, recover gracefully on timeout. |
|
|
3825
|
+
| [Parallel steps](https://docs.kici.dev/user/sdk/parallel/) | `parallel()` — run independent steps concurrently within one job behind a join barrier, each as its own observable step, with `failFast` and `maxParallel` controls. |
|
|
3707
3826
|
|
|
3708
3827
|
The `@kici-dev/sdk` package re-exports the entire surface from a single entry point. Pick what you need:
|
|
3709
3828
|
|
|
@@ -10,11 +10,11 @@ Each bundle below is a self-contained markdown file for one authoring task. Fetc
|
|
|
10
10
|
|
|
11
11
|
- [getting-started](https://docs.kici.dev/llms-getting-started.txt) (25 KB) — Install the SDK, write your first workflow, compile and test locally
|
|
12
12
|
- [patterns](https://docs.kici.dev/llms-patterns.txt) (49 KB) — Copy-paste workflow recipes: triggers, conditionals, matrix, scheduling, integrations
|
|
13
|
-
- [sdk](https://docs.kici.dev/llms-sdk.txt) (
|
|
14
|
-
- [cli](https://docs.kici.dev/llms-cli.txt) (
|
|
15
|
-
- [features](https://docs.kici.dev/llms-features.txt) (
|
|
13
|
+
- [sdk](https://docs.kici.dev/llms-sdk.txt) (190 KB) — Authoring API: workflow/job/step factories, triggers, rules, matrix, runtime, caching
|
|
14
|
+
- [cli](https://docs.kici.dev/llms-cli.txt) (124 KB) — Running the CLI: compile, test, run local/remote, auth, hooks, lock-file drift
|
|
15
|
+
- [features](https://docs.kici.dev/llms-features.txt) (124 KB) — Workflow features: concurrency, environments, secrets, approvals, provenance, events
|
|
16
16
|
- [providers](https://docs.kici.dev/llms-providers.txt) (36 KB) — Connecting sources: GitHub App, universal-git (Forgejo/Gitea/GitLab), local file://
|
|
17
|
-
- [architecture](https://docs.kici.dev/llms-architecture.txt) (
|
|
17
|
+
- [architecture](https://docs.kici.dev/llms-architecture.txt) (87 KB) — How the runtime works: three-tier relay model, data flows, configuration
|
|
18
18
|
|
|
19
19
|
## Getting started
|
|
20
20
|
|
|
@@ -37,6 +37,7 @@ Each bundle below is a self-contained markdown file for one authoring task. Fetc
|
|
|
37
37
|
- [SDK reference: core](https://docs.kici.dev/user/sdk/core/): Factory functions (workflow, job, step) and authoring patterns: needs, output chaining, dynamic groups
|
|
38
38
|
- [Event payload reference](https://docs.kici.dev/user/sdk/event-payloads/): Generated schema of the normalized event envelope passed to rules and dynamic functions.
|
|
39
39
|
- [SDK reference: idempotent](https://docs.kici.dev/user/sdk/idempotent/): Idempotent helpers for declarative check / apply patterns inside workflow steps
|
|
40
|
+
- [SDK reference: parallel](https://docs.kici.dev/user/sdk/parallel/): Run independent steps concurrently within a single job, each as its own observable step
|
|
40
41
|
- [SDK reference: rules, matrix, dynamic jobs](https://docs.kici.dev/user/sdk/rules-matrix-dynamic/): rule(), skip(), matrix builds (static + dynamic), and dynamicJob / dynamicGroup
|
|
41
42
|
- [SDK reference: runsOnAll host fan-out](https://docs.kici.dev/user/sdk/runs-on-all/): Fan one job out to every matching connected host, one pinned execution per host
|
|
42
43
|
- [SDK reference: runtime](https://docs.kici.dev/user/sdk/runtime/): Types index, StepContext, secrets, fixtures
|
|
@@ -47,8 +48,9 @@ Each bundle below is a self-contained markdown file for one authoring task. Fetc
|
|
|
47
48
|
|
|
48
49
|
## CLI and authoring
|
|
49
50
|
|
|
51
|
+
- [Drive KiCI from your coding agent](https://docs.kici.dev/user/ai-agents/): Point a coding agent at KiCI's MCP server and let it trigger, read, and re-run your CI under your own identity.
|
|
50
52
|
- [CLI authentication](https://docs.kici.dev/user/cli-auth/): Authenticate the KiCI CLI with browser OAuth, device flow, or API key paste
|
|
51
|
-
- [CLI reference](https://docs.kici.dev/user/cli-reference/): All CLI commands: compile, run (local/remote), orchestrators,
|
|
53
|
+
- [CLI reference](https://docs.kici.dev/user/cli-reference/): All CLI commands: compile, run (local/remote), orchestrators, preview, login, logout, org, diagnostics, runs (list/show/logs/rerun/cancel), secrets, types, fixture, init, hook, endpoints, workflows, docs, admin
|
|
52
54
|
- [Lifecycle hooks](https://docs.kici.dev/user/hooks/): SDK hook API for cancel, cleanup, success, failure, and step-level callbacks
|
|
53
55
|
- [Lock file and workflow drift](https://docs.kici.dev/user/lock-file-and-drift/): Keep the lock file in sync with your workflow source and avoid drift
|
|
54
56
|
- [Testing guide](https://docs.kici.dev/user/testing-guide/): Running remote test fixtures with kici run remote
|
|
@@ -14,6 +14,7 @@ import { ConcurrencyKeyEvaluationError, acquireWorkflowLock } from "./workflow-l
|
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import pc from "picocolors";
|
|
16
16
|
import { writeFile } from "node:fs/promises";
|
|
17
|
+
import { flattenStepInputs } from "@kici-dev/sdk";
|
|
17
18
|
import { logger } from "@kici-dev/core";
|
|
18
19
|
import { CheckMode, CheckStepOutcome, coerceDispatchInputs, matchAllWorkflows, parseInputPairs } from "@kici-dev/engine";
|
|
19
20
|
import os from "node:os";
|
|
@@ -55,7 +56,7 @@ function workflowsToLockFormat(workflows) {
|
|
|
55
56
|
name: j.name,
|
|
56
57
|
runsOn: j.runsOn,
|
|
57
58
|
needs: j.needs?.map((n) => typeof n === "string" ? n : n.name) ?? [],
|
|
58
|
-
steps: j.steps.map((s) => {
|
|
59
|
+
steps: flattenStepInputs(j.steps).map((s) => {
|
|
59
60
|
if (typeof s === "function") return {
|
|
60
61
|
name: "",
|
|
61
62
|
hasOutputs: false
|
|
@@ -7,7 +7,7 @@ import { localRunsOnString } from "./runs-on-display.js";
|
|
|
7
7
|
import { toEventPayload } from "./to-event-payload.js";
|
|
8
8
|
import { pathToFileURL } from "node:url";
|
|
9
9
|
import path from "node:path";
|
|
10
|
-
import { applyIncludeExclude, expandMatrix, isDynamicJobFn, setJobOutputsMap, setStepOutputsMap, setStepRefMap } from "@kici-dev/sdk";
|
|
10
|
+
import { applyIncludeExclude, expandMatrix, flattenStepInputs, isDynamicJobFn, setJobOutputsMap, setStepOutputsMap, setStepRefMap } from "@kici-dev/sdk";
|
|
11
11
|
import { computeBackoffDelay } from "@kici-dev/core";
|
|
12
12
|
import { CheckMode, CheckStepOutcome, formatMatrixSuffix } from "@kici-dev/engine";
|
|
13
13
|
import { runIdempotentStep } from "@kici-dev/core/idempotency";
|
|
@@ -314,10 +314,10 @@ async function executeResolvedJobInner(resolvedJob, context, startTime) {
|
|
|
314
314
|
const stepCtx = createStepContext({ name: context.workflowName }, {
|
|
315
315
|
name: expandedName,
|
|
316
316
|
runsOn: localRunsOnString(job.runsOn)
|
|
317
|
-
}, repoRoot, void 0, hasMatrix ? matrixValues : void 0, context.secrets, void 0, context.event.payload, context.event.provider, context.dispatchInputs ?? {});
|
|
317
|
+
}, repoRoot, void 0, hasMatrix ? matrixValues : void 0, context.secrets, void 0, context.event.payload, context.event.provider, context.dispatchInputs ?? {}, context.signal);
|
|
318
318
|
const stepResults = [];
|
|
319
319
|
let stepCounter = 0;
|
|
320
|
-
for (const stepOrFn of job.steps) {
|
|
320
|
+
for (const stepOrFn of flattenStepInputs(job.steps)) {
|
|
321
321
|
if (context.signal.aborted) return {
|
|
322
322
|
name: expandedName,
|
|
323
323
|
status: "cancelled",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { TriggerConfig } from '@kici-dev/sdk';
|
|
2
|
-
import { type LockFile, type LockTrigger, type WorkflowWithSource } from '../types.js';
|
|
1
|
+
import type { StepInput, TriggerConfig } from '@kici-dev/sdk';
|
|
2
|
+
import { type LockFile, type LockTrigger, type LockStepEntry, type WorkflowWithSource } from '../types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Detect git repository root by running `git rev-parse --show-toplevel`.
|
|
5
5
|
* Falls back to cwd if not in a git repo.
|
|
@@ -40,6 +40,14 @@ export declare function generateLockFile(workflowsWithSource: WorkflowWithSource
|
|
|
40
40
|
* Exported for reuse in the test runner's in-memory lock format conversion.
|
|
41
41
|
*/
|
|
42
42
|
export declare function transformTriggers(triggers?: TriggerConfig[]): readonly LockTrigger[];
|
|
43
|
+
/**
|
|
44
|
+
* Transform a job's `steps` array into lock-file entries. Sequential steps and
|
|
45
|
+
* parallel-group children share one flat `step-N` counter (anonymous steps are
|
|
46
|
+
* numbered across the whole flattened sequence, parallel children inline) so the
|
|
47
|
+
* compiler's naming matches the agent's `extractAndNormalizeSteps` enumeration —
|
|
48
|
+
* the flat-stepIndex invariant.
|
|
49
|
+
*/
|
|
50
|
+
export declare function transformSteps(steps: readonly StepInput[], gitRoot: string): readonly LockStepEntry[];
|
|
43
51
|
/**
|
|
44
52
|
* Serialize lock file to JSON string.
|
|
45
53
|
*
|
|
@@ -5,10 +5,10 @@ import { resolveHashFiles } from "./hash-files.js";
|
|
|
5
5
|
import { analyzePurity } from "./purity-analyzer.js";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { readFileSync } from "node:fs";
|
|
8
|
-
import { getDynamicJobGroup, getDynamicJobNeeds, isDynamicFunction, isDynamicGroupRef, isDynamicJobFn, isStaticArray, isStaticObject, normalizeApproval, normalizeCacheSpecs } from "@kici-dev/sdk";
|
|
8
|
+
import { getDynamicJobGroup, getDynamicJobNeeds, isDynamicFunction, isDynamicGroupRef, isDynamicJobFn, isParallelGroup, isStaticArray, isStaticObject, normalizeApproval, normalizeCacheSpecs } from "@kici-dev/sdk";
|
|
9
9
|
import { sha256 } from "@kici-dev/core";
|
|
10
10
|
import { PackageManager, detectPackageManagerSync, detectYarnFlavorSync } from "@kici-dev/core/package-manager";
|
|
11
|
-
import { extractInputsDescriptorMap, resolveWhenToRunOn, validateResourceRequest } from "@kici-dev/engine";
|
|
11
|
+
import { assertScheduleInputsSatisfiable, extractInputsDescriptorMap, resolveWhenToRunOn, validateResourceRequest } from "@kici-dev/engine";
|
|
12
12
|
import { normalizeRunsOnAllToMatchers, normalizeRunsOnToMatchers, runsOnPickFromInput } from "@kici-dev/engine/labels/compile";
|
|
13
13
|
import { execSync } from "node:child_process";
|
|
14
14
|
//#region src/lockfile/generator.ts
|
|
@@ -345,11 +345,14 @@ function toLockGenericWebhook(t) {
|
|
|
345
345
|
};
|
|
346
346
|
}
|
|
347
347
|
function toLockSchedule(t) {
|
|
348
|
+
const inputs = t.inputs ? extractInputsDescriptorMap(t.inputs) : void 0;
|
|
349
|
+
if (inputs) assertScheduleInputsSatisfiable(inputs);
|
|
348
350
|
return {
|
|
349
351
|
_type: "schedule",
|
|
350
352
|
cronExpression: t.cron,
|
|
351
353
|
timezone: t.timezone,
|
|
352
|
-
...t.description && { description: t.description }
|
|
354
|
+
...t.description && { description: t.description },
|
|
355
|
+
...inputs && { inputs }
|
|
353
356
|
};
|
|
354
357
|
}
|
|
355
358
|
function toLockLifecycle(t) {
|
|
@@ -482,6 +485,34 @@ function validateRunsOn(runsOn, jobName) {
|
|
|
482
485
|
if (overlap.length > 0) throw new Error(`Job "${jobName}": labels and exclude overlap on [${overlap.join(", ")}]. A label cannot be both required and excluded.`);
|
|
483
486
|
}
|
|
484
487
|
/**
|
|
488
|
+
* Transform one environment reference (static name or function) into a lock
|
|
489
|
+
* `{ value, dynamic }` entry. A function element is analyzed for purity: a pure
|
|
490
|
+
* function becomes an inline expression resolvable at two-phase eval; an impure
|
|
491
|
+
* one carries only the `dynamic` flag (the agent runs an init job to resolve it).
|
|
492
|
+
*/
|
|
493
|
+
function transformEnvironmentRef(ref, jobName) {
|
|
494
|
+
if (typeof ref === "function") {
|
|
495
|
+
const fnSource = ref.toString();
|
|
496
|
+
const purity = analyzePurity(fnSource);
|
|
497
|
+
if (purity.pure) return {
|
|
498
|
+
value: {
|
|
499
|
+
_type: "inline",
|
|
500
|
+
expression: fnSource
|
|
501
|
+
},
|
|
502
|
+
dynamic: true
|
|
503
|
+
};
|
|
504
|
+
console.warn(`[kici] Job "${jobName}": environment function is not pure (${purity.reason}). An init job will be required, adding ~5-10s delay.`);
|
|
505
|
+
return {
|
|
506
|
+
value: "",
|
|
507
|
+
dynamic: true
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
value: ref,
|
|
512
|
+
dynamic: false
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
485
516
|
* Transform a static job to lock file format.
|
|
486
517
|
*/
|
|
487
518
|
function transformJob(job, configPath, index, gitRoot, uuidToName) {
|
|
@@ -492,18 +523,8 @@ function transformJob(job, configPath, index, gitRoot, uuidToName) {
|
|
|
492
523
|
if (!(job.matrix !== void 0 || job.runsOnAll !== void 0) && (job.maxParallel !== void 0 || job.failFast !== void 0)) console.warn(`[kici] job '${job.name}': maxParallel/failFast are ignored without matrix or runsOnAll (no fan-out to bound)`);
|
|
493
524
|
if (job.runsOn !== void 0) validateRunsOn(job.runsOn, job.name);
|
|
494
525
|
const environmentFields = {};
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const fnSource = job.environment.toString();
|
|
498
|
-
const purity = analyzePurity(fnSource);
|
|
499
|
-
environmentFields.dynamicEnvironment = true;
|
|
500
|
-
if (purity.pure) environmentFields.environment = {
|
|
501
|
-
_type: "inline",
|
|
502
|
-
expression: fnSource
|
|
503
|
-
};
|
|
504
|
-
else console.warn(`[kici] Job "${job.name}": environment function is not pure (${purity.reason}). An init job will be required, adding ~5-10s delay.`);
|
|
505
|
-
} else if (typeof job.environment === "string") environmentFields.environment = job.environment;
|
|
506
|
-
}
|
|
526
|
+
const envRefs = job.environments ?? (job.environment !== void 0 ? [job.environment] : void 0);
|
|
527
|
+
if (envRefs !== void 0 && envRefs.length > 0) environmentFields.environments = envRefs.map((ref) => transformEnvironmentRef(ref, job.name));
|
|
507
528
|
const envFields = {};
|
|
508
529
|
if (job.env !== void 0) {
|
|
509
530
|
if (typeof job.env === "function") {
|
|
@@ -642,45 +663,78 @@ function assertNonStepApprovalScope(c, scope) {
|
|
|
642
663
|
function assertStepApprovalCheckFacet(step) {
|
|
643
664
|
if (step.approval !== void 0 && normalizeApproval(step.approval).when === "drift" && step.check === void 0) throw new Error(`step '${step.name || "(unnamed)"}': approval.when "drift" requires a check facet`);
|
|
644
665
|
}
|
|
666
|
+
/**
|
|
667
|
+
* Transform a job's `steps` array into lock-file entries. Sequential steps and
|
|
668
|
+
* parallel-group children share one flat `step-N` counter (anonymous steps are
|
|
669
|
+
* numbered across the whole flattened sequence, parallel children inline) so the
|
|
670
|
+
* compiler's naming matches the agent's `extractAndNormalizeSteps` enumeration —
|
|
671
|
+
* the flat-stepIndex invariant.
|
|
672
|
+
*/
|
|
645
673
|
function transformSteps(steps, gitRoot) {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
674
|
+
const counter = { n: 0 };
|
|
675
|
+
let groupOrdinal = 0;
|
|
676
|
+
return steps.map((entry) => {
|
|
677
|
+
if (isParallelGroup(entry)) return transformParallelGroup(entry, gitRoot, counter, groupOrdinal++);
|
|
678
|
+
return transformSequentialStep(entry, gitRoot, counter);
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
/** Validate and transform a `ParallelGroup` into a `LockParallelStep`. */
|
|
682
|
+
function transformParallelGroup(group, gitRoot, counter, groupOrdinal) {
|
|
683
|
+
if (group.steps.length === 0) throw new Error("job step: empty parallel group not allowed");
|
|
684
|
+
const seen = /* @__PURE__ */ new Set();
|
|
685
|
+
const children = group.steps.map((child) => {
|
|
686
|
+
if (isParallelGroup(child)) throw new Error("job step: nested parallel groups are not supported");
|
|
687
|
+
const lockChild = transformSequentialStep(child, gitRoot, counter);
|
|
688
|
+
if (seen.has(lockChild.name)) throw new Error(`job step: duplicate step name '${lockChild.name}' in parallel group`);
|
|
689
|
+
seen.add(lockChild.name);
|
|
690
|
+
return lockChild;
|
|
691
|
+
});
|
|
692
|
+
if (children.length === 1) console.warn(`[kici] parallel group with a single step ('${children[0].name}') runs identically to a sequential step`);
|
|
693
|
+
return {
|
|
694
|
+
kind: "parallel",
|
|
695
|
+
name: group.name ?? `parallel-${groupOrdinal}`,
|
|
696
|
+
failFast: group.failFast,
|
|
697
|
+
...group.maxParallel !== void 0 && { maxParallel: group.maxParallel },
|
|
698
|
+
children
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
/** Transform a single sequential step (or bare function) into a `LockStep`. */
|
|
702
|
+
function transformSequentialStep(stepOrFn, gitRoot, counter) {
|
|
703
|
+
if (typeof stepOrFn === "function") {
|
|
704
|
+
counter.n++;
|
|
656
705
|
return {
|
|
657
|
-
name:
|
|
658
|
-
hasOutputs:
|
|
659
|
-
...step.continueOnError !== void 0 && { continueOnError: step.continueOnError },
|
|
660
|
-
...step.timeout !== void 0 && { timeout: step.timeout },
|
|
661
|
-
...step.retry !== void 0 && { retry: {
|
|
662
|
-
maxAttempts: step.retry.maxAttempts,
|
|
663
|
-
delayMs: step.retry.delayMs,
|
|
664
|
-
backoff: step.retry.backoff,
|
|
665
|
-
maxDelayMs: step.retry.maxDelayMs
|
|
666
|
-
} },
|
|
667
|
-
...step.cache !== void 0 && { cache: normalizeCacheSpecs(step.cache) },
|
|
668
|
-
...step._sourceLocation && { sourceLocation: {
|
|
669
|
-
file: makeRelativePath(step._sourceLocation.file, gitRoot),
|
|
670
|
-
line: step._sourceLocation.line,
|
|
671
|
-
column: step._sourceLocation.column
|
|
672
|
-
} },
|
|
673
|
-
...step.rules && step.rules.length > 0 && {
|
|
674
|
-
hasRules: true,
|
|
675
|
-
rules: transformRules(step.rules, makeRelativePath(step._sourceLocation?.file ?? "", gitRoot))
|
|
676
|
-
},
|
|
677
|
-
...step.onCancel !== void 0 && { hasOnCancel: true },
|
|
678
|
-
...step.cleanup !== void 0 && { hasCleanup: true },
|
|
679
|
-
...step.check !== void 0 && { hasCheck: true },
|
|
680
|
-
...step.whenInSync !== void 0 && { hasWhenInSync: true },
|
|
681
|
-
...step.approval !== void 0 && { approval: (assertStepApprovalCheckFacet(step), toLockApproval(step.approval)) }
|
|
706
|
+
name: `step-${counter.n}`,
|
|
707
|
+
hasOutputs: false
|
|
682
708
|
};
|
|
683
|
-
}
|
|
709
|
+
}
|
|
710
|
+
const step = stepOrFn;
|
|
711
|
+
return {
|
|
712
|
+
name: step.name || `step-${++counter.n}`,
|
|
713
|
+
hasOutputs: !!step.outputs && Object.keys(step.outputs).length > 0,
|
|
714
|
+
...step.continueOnError !== void 0 && { continueOnError: step.continueOnError },
|
|
715
|
+
...step.timeout !== void 0 && { timeout: step.timeout },
|
|
716
|
+
...step.retry !== void 0 && { retry: {
|
|
717
|
+
maxAttempts: step.retry.maxAttempts,
|
|
718
|
+
delayMs: step.retry.delayMs,
|
|
719
|
+
backoff: step.retry.backoff,
|
|
720
|
+
maxDelayMs: step.retry.maxDelayMs
|
|
721
|
+
} },
|
|
722
|
+
...step.cache !== void 0 && { cache: normalizeCacheSpecs(step.cache) },
|
|
723
|
+
...step._sourceLocation && { sourceLocation: {
|
|
724
|
+
file: makeRelativePath(step._sourceLocation.file, gitRoot),
|
|
725
|
+
line: step._sourceLocation.line,
|
|
726
|
+
column: step._sourceLocation.column
|
|
727
|
+
} },
|
|
728
|
+
...step.rules && step.rules.length > 0 && {
|
|
729
|
+
hasRules: true,
|
|
730
|
+
rules: transformRules(step.rules, makeRelativePath(step._sourceLocation?.file ?? "", gitRoot))
|
|
731
|
+
},
|
|
732
|
+
...step.onCancel !== void 0 && { hasOnCancel: true },
|
|
733
|
+
...step.cleanup !== void 0 && { hasCleanup: true },
|
|
734
|
+
...step.check !== void 0 && { hasCheck: true },
|
|
735
|
+
...step.whenInSync !== void 0 && { hasWhenInSync: true },
|
|
736
|
+
...step.approval !== void 0 && { approval: (assertStepApprovalCheckFacet(step), toLockApproval(step.approval)) }
|
|
737
|
+
};
|
|
684
738
|
}
|
|
685
739
|
/**
|
|
686
740
|
* Strip the `?t=...` query suffix added by cache-busting `import()` calls.
|
|
@@ -758,6 +812,6 @@ function serializeLockFile(lockFile, pretty = true) {
|
|
|
758
812
|
return JSON.stringify(lockFile, null, pretty ? 2 : void 0);
|
|
759
813
|
}
|
|
760
814
|
//#endregion
|
|
761
|
-
export { computeLockfileHash, detectGitRoot, generateLockFile, serializeLockFile, transformTriggers };
|
|
815
|
+
export { computeLockfileHash, detectGitRoot, generateLockFile, serializeLockFile, transformSteps, transformTriggers };
|
|
762
816
|
|
|
763
817
|
//# sourceMappingURL=generator.js.map
|
package/dist/remote/history.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Local run history manager.
|
|
3
3
|
*
|
|
4
4
|
* Persists test run history in ~/.kici/history/runs.json so developers
|
|
5
|
-
* can review past runs (kici
|
|
5
|
+
* can review past runs (kici run remote --history) and inspect specific runs
|
|
6
6
|
* (kici status <run-id>).
|
|
7
7
|
*/
|
|
8
8
|
/** Status of a test run */
|
package/dist/remote/history.js
CHANGED
|
@@ -11,7 +11,7 @@ import picomatch from "picomatch";
|
|
|
11
11
|
* Local run history manager.
|
|
12
12
|
*
|
|
13
13
|
* Persists test run history in ~/.kici/history/runs.json so developers
|
|
14
|
-
* can review past runs (kici
|
|
14
|
+
* can review past runs (kici run remote --history) and inspect specific runs
|
|
15
15
|
* (kici status <run-id>).
|
|
16
16
|
*/
|
|
17
17
|
/** Maximum number of entries to keep in history */
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Git hosts whose origin URL we can turn into a real `owner/repo` + web link. */
|
|
3
|
+
export declare const RecognizedProvider: z.ZodEnum<{
|
|
4
|
+
github: "github";
|
|
5
|
+
gitlab: "gitlab";
|
|
6
|
+
bitbucket: "bitbucket";
|
|
7
|
+
}>;
|
|
8
|
+
export type RecognizedProvider = z.infer<typeof RecognizedProvider>;
|
|
9
|
+
/** The literal provider value stamped when no recognized git origin exists. */
|
|
10
|
+
export declare const LOCAL_PROVIDER = "local";
|
|
11
|
+
/**
|
|
12
|
+
* Parse a git remote URL into `{ provider, owner/repo }` for a recognized host.
|
|
13
|
+
* Handles scp-style ssh (`git@host:owner/repo.git`), `ssh://`, and `https://`
|
|
14
|
+
* forms; strips a trailing `.git`. Returns null for any unrecognized host or
|
|
15
|
+
* unparseable input.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseGitOrigin(url: string): {
|
|
18
|
+
provider: RecognizedProvider;
|
|
19
|
+
repoIdentifier: string;
|
|
20
|
+
} | null;
|
|
21
|
+
/** Read the `origin` remote URL of the working tree at `repoRoot`, or null. */
|
|
22
|
+
export declare function detectGitOrigin(repoRoot: string): string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Build the repo identity stamped on a `kici run remote` run. A recognized git
|
|
25
|
+
* origin yields the real `owner/repo` + provider so the dashboard can link to
|
|
26
|
+
* it; otherwise a synthetic `local/<basename>` identity with provider `local`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildLocalRepoIdentity(repoRoot: string): {
|
|
29
|
+
repoIdentifier: string;
|
|
30
|
+
provider: string;
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=local-repo-identity.d.ts.map
|