@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.
Files changed (41) hide show
  1. package/dist/cli.js +15 -5
  2. package/dist/commands/index.d.ts +4 -2
  3. package/dist/commands/index.js +3 -2
  4. package/dist/commands/init.js +2 -2
  5. package/dist/commands/pat.d.ts +27 -0
  6. package/dist/commands/pat.js +76 -0
  7. package/dist/commands/preview.d.ts +88 -0
  8. package/dist/commands/{test.js → preview.js} +15 -14
  9. package/dist/commands/run.d.ts +11 -1
  10. package/dist/commands/run.js +34 -7
  11. package/dist/commands/verify-attestation.d.ts +4 -1
  12. package/dist/commands/verify-attestation.js +26 -10
  13. package/dist/generators/secrets-dts.js +2 -0
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.js +2 -2
  16. package/dist/llm-context/llms-architecture.txt +3 -3
  17. package/dist/llm-context/llms-cli.txt +149 -26
  18. package/dist/llm-context/llms-features.txt +107 -5
  19. package/dist/llm-context/llms-full.txt +390 -46
  20. package/dist/llm-context/llms-getting-started.txt +6 -6
  21. package/dist/llm-context/llms-sdk.txt +125 -6
  22. package/dist/llm-context/llms.txt +7 -5
  23. package/dist/local-executor/index.js +2 -1
  24. package/dist/local-executor/job-runner.js +3 -3
  25. package/dist/lockfile/generator.d.ts +10 -2
  26. package/dist/lockfile/generator.js +106 -52
  27. package/dist/remote/history.d.ts +1 -1
  28. package/dist/remote/history.js +1 -1
  29. package/dist/remote/local-repo-identity.d.ts +32 -0
  30. package/dist/remote/local-repo-identity.js +74 -0
  31. package/dist/remote/prod-defaults.d.ts +8 -0
  32. package/dist/remote/prod-defaults.js +9 -1
  33. package/dist/templates/agents-md.d.ts +1 -1
  34. package/dist/templates/agents-md.js +2 -2
  35. package/dist/templates/package-json.js +1 -1
  36. package/dist/test-runner/step-context.d.ts +1 -1
  37. package/dist/test-runner/step-context.js +2 -1
  38. package/dist/types.d.ts +33 -6
  39. package/dist/types.js +5 -1
  40. package/package.json +4 -7
  41. 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 test` (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.
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 test` to preview which workflows match a trigger event (dry-run, no execution):
250
+ Use `kici preview` to preview which workflows match a trigger event (dry-run, no execution):
251
251
 
252
252
  ```bash
253
- npx kici test pr:open
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 test` 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.
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 test`, `kici run local`, `kici docs llm`)
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 test` and `kici run local` instead.
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 test mode (`kici test`) 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.
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 test` (remote test run) */
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 test`, you can provide secrets locally without an orchestrator.
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 test push --secret DEPLOY_TOKEN=my-token --secret API_KEY=my-key
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 test push --context production.DB_PASSWORD=prod-secret --context npm-publish.NPM_TOKEN=abc123
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 five pages by topic.
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) (185 KB) — Authoring API: workflow/job/step factories, triggers, rules, matrix, runtime, caching
14
- - [cli](https://docs.kici.dev/llms-cli.txt) (117 KB) — Running the CLI: compile, test, run local/remote, auth, hooks, lock-file drift
15
- - [features](https://docs.kici.dev/llms-features.txt) (116 KB) — Workflow features: concurrency, environments, secrets, approvals, provenance, events
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) (88 KB) — How the runtime works: three-tier relay model, data flows, configuration
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, test, login, logout, org, diagnostics, runs (list/show/logs/rerun/cancel), secrets, types, fixture, init, hook, endpoints, workflows, docs, admin
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
- if (job.environment !== void 0) {
496
- if (typeof job.environment === "function") {
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
- let stepCounter = 0;
647
- return steps.map((stepOrFn) => {
648
- if (typeof stepOrFn === "function") {
649
- stepCounter++;
650
- return {
651
- name: `step-${stepCounter}`,
652
- hasOutputs: false
653
- };
654
- }
655
- const step = stepOrFn;
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: step.name || `step-${++stepCounter}`,
658
- hasOutputs: !!step.outputs && Object.keys(step.outputs).length > 0,
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
@@ -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 test --history) and inspect specific runs
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 */
@@ -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 test --history) and inspect specific runs
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