@kici-dev/sdk 0.1.21 → 0.1.23

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/job.js CHANGED
@@ -35,12 +35,14 @@ function job(nameOrOptions, maybeOptions) {
35
35
  if (options.runsOn !== void 0 && options.runsOnAll !== void 0) throw new Error(`job('${name}'): runsOn and runsOnAll are mutually exclusive`);
36
36
  if (options.runsOn === void 0 && options.runsOnAll === void 0) throw new Error(`job('${name}'): one of runsOn or runsOnAll is required`);
37
37
  if (options.onUnreachable !== void 0 && options.runsOnAll === void 0) console.warn(`[kici] job('${name}'): onUnreachable is ignored without runsOnAll`);
38
+ if (options.includeUninitialized !== void 0 && options.runsOnAll === void 0) console.warn(`[kici] job('${name}'): includeUninitialized is ignored without runsOnAll`);
38
39
  return {
39
40
  _tag: "Job",
40
41
  name,
41
42
  ...options.runsOn !== void 0 && { runsOn: options.runsOn },
42
43
  ...options.runsOnAll !== void 0 && { runsOnAll: options.runsOnAll },
43
44
  ...options.onUnreachable !== void 0 && { onUnreachable: options.onUnreachable },
45
+ ...options.includeUninitialized !== void 0 && { includeUninitialized: options.includeUninitialized },
44
46
  ...options.maxParallel !== void 0 && { maxParallel: options.maxParallel },
45
47
  ...options.failFast !== void 0 && { failFast: options.failFast },
46
48
  steps,
@@ -66,7 +68,7 @@ function job(nameOrOptions, maybeOptions) {
66
68
  resources: options.resources,
67
69
  init: options.init,
68
70
  ...options.cache !== void 0 && { cache: options.cache },
69
- ...options.requireApproval !== void 0 && { requireApproval: options.requireApproval },
71
+ ...options.approval !== void 0 && { approval: options.approval },
70
72
  result: createJobOutputProxy(name)
71
73
  };
72
74
  }
@@ -1,31 +1,38 @@
1
1
  import type { OutputProxy, DynamicJobNeed } from './types.js';
2
+ import type { ExecutionJobStatus } from '@kici-dev/engine';
2
3
  /**
3
- * Frozen snapshot of upstream outputs, captured once at first eval of a
4
- * result-aware dynamic generator and replayed unchanged on re-eval.
4
+ * Frozen snapshot of upstream outputs + statuses, captured once at first eval of
5
+ * a result-aware dynamic generator and replayed unchanged on re-eval.
5
6
  *
6
7
  * - `jobs` maps an upstream job name to its outputs record.
7
8
  * - `groups` maps a dynamic group name to its ordered member job names.
9
+ * - `statuses` maps an upstream job name to its terminal status. Absent entries
10
+ * default to `success` (the only status that satisfies a default needs edge,
11
+ * so an upstream resolved into the snapshot is success unless told otherwise).
8
12
  */
9
13
  export interface UpstreamSnapshot {
10
14
  jobs: Record<string, Record<string, unknown>>;
11
15
  groups: Record<string, string[]>;
16
+ statuses?: Record<string, ExecutionJobStatus>;
12
17
  }
13
18
  /** One entry in the array exposed for a `dynamicGroup(...)` need. */
14
19
  export interface GroupNeedEntry {
15
20
  name: string;
16
21
  result: OutputProxy<any>;
22
+ status: ExecutionJobStatus;
17
23
  }
18
- /** A single-job need exposes `{ result }`; a group need exposes an ordered array. */
24
+ /** A single-job need exposes `{ result, status }`; a group need exposes an ordered array. */
19
25
  export type NeedEntry = {
20
26
  result: OutputProxy<any>;
27
+ status: ExecutionJobStatus;
21
28
  } | GroupNeedEntry[];
22
29
  /** The resolved `ctx.needs` map keyed by job name or group name. */
23
30
  export type NeedsContext = Record<string, NeedEntry>;
24
31
  /**
25
32
  * Resolve declared needs against a frozen snapshot into the `ctx.needs` map.
26
33
  *
27
- * - A single static/named-job need resolves to `{ result: <proxy over jobs[name]> }`.
28
- * - A `dynamicGroup(...)` need resolves to an ordered array of `{ name, result }`,
34
+ * - A single static/named-job need resolves to `{ result: <proxy over jobs[name]>, status }`.
35
+ * - A `dynamicGroup(...)` need resolves to an ordered array of `{ name, result, status }`,
29
36
  * one entry per group member in the snapshot's deterministic eval order.
30
37
  */
31
38
  export declare function buildNeedsContext(snapshot: UpstreamSnapshot, declaredNeeds: ReadonlyArray<DynamicJobNeed>): NeedsContext;
@@ -19,11 +19,15 @@ function needKey(need) {
19
19
  key: need.name
20
20
  };
21
21
  }
22
+ /** Read an upstream's terminal status from the snapshot, defaulting to success. */
23
+ function statusFor(snapshot, name) {
24
+ return snapshot.statuses?.[name] ?? "success";
25
+ }
22
26
  /**
23
27
  * Resolve declared needs against a frozen snapshot into the `ctx.needs` map.
24
28
  *
25
- * - A single static/named-job need resolves to `{ result: <proxy over jobs[name]> }`.
26
- * - A `dynamicGroup(...)` need resolves to an ordered array of `{ name, result }`,
29
+ * - A single static/named-job need resolves to `{ result: <proxy over jobs[name]>, status }`.
30
+ * - A `dynamicGroup(...)` need resolves to an ordered array of `{ name, result, status }`,
27
31
  * one entry per group member in the snapshot's deterministic eval order.
28
32
  */
29
33
  function buildNeedsContext(snapshot, declaredNeeds) {
@@ -32,9 +36,13 @@ function buildNeedsContext(snapshot, declaredNeeds) {
32
36
  const { kind, key } = needKey(need);
33
37
  if (kind === "group") out[key] = (snapshot.groups[key] ?? []).map((name) => ({
34
38
  name,
35
- result: createSnapshotOutputProxy(name, snapshot.jobs[name])
39
+ result: createSnapshotOutputProxy(name, snapshot.jobs[name]),
40
+ status: statusFor(snapshot, name)
36
41
  }));
37
- else out[key] = { result: createSnapshotOutputProxy(key, snapshot.jobs[key]) };
42
+ else out[key] = {
43
+ result: createSnapshotOutputProxy(key, snapshot.jobs[key]),
44
+ status: statusFor(snapshot, key)
45
+ };
38
46
  }
39
47
  return out;
40
48
  }
@@ -1,4 +1,4 @@
1
- export { rule, skip } from './rule.js';
1
+ export { rule, skip, onlyOnFirstHost, onlyOnLastHost, onlyOnFanoutIndex } from './rule.js';
2
2
  export { evaluateRules, type RuleEvaluationResult } from './evaluator.js';
3
3
  export type { Rule, RuleCheckFn, RuleContext, RuleResult, EventPayload } from './types.js';
4
4
  export { isEventType } from '../events/event-payloads.js';
@@ -1,5 +1,5 @@
1
1
  import "../chunk-BTugEXQM.js";
2
- import { rule, skip } from "./rule.js";
2
+ import { onlyOnFanoutIndex, onlyOnFirstHost, onlyOnLastHost, rule, skip } from "./rule.js";
3
3
  import { evaluateRules } from "./evaluator.js";
4
4
  import { isEventType } from "../events/event-payloads.js";
5
- export { evaluateRules, isEventType, rule, skip };
5
+ export { evaluateRules, isEventType, onlyOnFanoutIndex, onlyOnFirstHost, onlyOnLastHost, rule, skip };
@@ -31,4 +31,24 @@ export declare function rule(label: string, check: RuleCheckFn): Rule;
31
31
  * });
32
32
  */
33
33
  export declare function skip(label: string, check: RuleCheckFn): Rule;
34
+ /**
35
+ * Run a step only on the first fan-out child (the lowest-`agentId` host, or the
36
+ * first matrix variant). KiCI's `run_once`-on-the-first-host primitive.
37
+ *
38
+ * A non-fan-out job is treated as a single implicit child at index 0, so a step
39
+ * gated this way runs normally there (there is exactly one host, which is first).
40
+ *
41
+ * @example
42
+ * step('enable sync mode', async (ctx) => { ... }, { rules: [onlyOnFirstHost()] })
43
+ */
44
+ export declare function onlyOnFirstHost(): Rule;
45
+ /**
46
+ * Run a step only on the last fan-out child. Runs normally when not fanned out.
47
+ */
48
+ export declare function onlyOnLastHost(): Rule;
49
+ /**
50
+ * Run a step only on the fan-out child at index `n`. A non-fan-out job is the
51
+ * implicit child at index 0, so `onlyOnFanoutIndex(0)` runs normally there.
52
+ */
53
+ export declare function onlyOnFanoutIndex(n: number): Rule;
34
54
  //# sourceMappingURL=rule.d.ts.map
@@ -31,7 +31,33 @@ function skip(label, check) {
31
31
  check: async (ctx) => !await check(ctx)
32
32
  };
33
33
  }
34
+ /**
35
+ * Run a step only on the first fan-out child (the lowest-`agentId` host, or the
36
+ * first matrix variant). KiCI's `run_once`-on-the-first-host primitive.
37
+ *
38
+ * A non-fan-out job is treated as a single implicit child at index 0, so a step
39
+ * gated this way runs normally there (there is exactly one host, which is first).
40
+ *
41
+ * @example
42
+ * step('enable sync mode', async (ctx) => { ... }, { rules: [onlyOnFirstHost()] })
43
+ */
44
+ function onlyOnFirstHost() {
45
+ return rule("fanout: first host only", (ctx) => ctx.fanout === void 0 || ctx.fanout.first);
46
+ }
47
+ /**
48
+ * Run a step only on the last fan-out child. Runs normally when not fanned out.
49
+ */
50
+ function onlyOnLastHost() {
51
+ return rule("fanout: last host only", (ctx) => ctx.fanout === void 0 || ctx.fanout.last);
52
+ }
53
+ /**
54
+ * Run a step only on the fan-out child at index `n`. A non-fan-out job is the
55
+ * implicit child at index 0, so `onlyOnFanoutIndex(0)` runs normally there.
56
+ */
57
+ function onlyOnFanoutIndex(n) {
58
+ return rule(`fanout: index ${n} only`, (ctx) => (ctx.fanout?.index ?? 0) === n);
59
+ }
34
60
  //#endregion
35
- export { rule, skip };
61
+ export { onlyOnFanoutIndex, onlyOnFirstHost, onlyOnLastHost, rule, skip };
36
62
 
37
63
  //# sourceMappingURL=rule.js.map
@@ -1,5 +1,6 @@
1
1
  import type { $ as Shell } from 'zx';
2
2
  import type { EventPayload } from '../events/event-payloads.js';
3
+ import type { FanoutPosition } from '../fanout-context.js';
3
4
  export type { EventPayload } from '../events/event-payloads.js';
4
5
  /**
5
6
  * Context passed to rule check functions.
@@ -12,6 +13,14 @@ export interface RuleContext {
12
13
  changedFiles: string[];
13
14
  /** Environment variables */
14
15
  env: Record<string, string | undefined>;
16
+ /** Operator-supplied, validated + coerced workflow-dispatch inputs. Empty when none declared. */
17
+ dispatchInputs: Readonly<Record<string, string | number | boolean | null>>;
18
+ /**
19
+ * Position of this child within its fan-out (a `runsOnAll` host or a matrix
20
+ * combination); undefined on a non-fan-out job. Read by the run-once rule
21
+ * helpers (`onlyOnFirstHost` / `onlyOnLastHost` / `onlyOnFanoutIndex`).
22
+ */
23
+ fanout?: FanoutPosition;
15
24
  /** zx shell executor for running commands */
16
25
  $: typeof Shell;
17
26
  }
package/dist/step.js CHANGED
@@ -1,7 +1,24 @@
1
1
  import "./chunk-BTugEXQM.js";
2
+ import { normalizeApproval } from "./approval.js";
2
3
  import { createStepOutputProxy } from "./outputs.js";
3
4
  //#region src/step.ts
4
5
  /**
6
+ * Fill retry defaults and expand the `retry: N` shorthand into a
7
+ * {@link NormalizedRetry}. `retryIf` is carried through unchanged (it is
8
+ * execution-only and never serialized).
9
+ */
10
+ function normalizeRetry(retry) {
11
+ if (retry === void 0) return void 0;
12
+ const cfg = typeof retry === "number" ? { maxAttempts: retry } : retry;
13
+ return {
14
+ maxAttempts: cfg.maxAttempts,
15
+ delayMs: cfg.delayMs ?? 1e3,
16
+ backoff: cfg.backoff ?? "exponential",
17
+ maxDelayMs: cfg.maxDelayMs ?? 3e4,
18
+ ...cfg.retryIf && { retryIf: cfg.retryIf }
19
+ };
20
+ }
21
+ /**
5
22
  * Capture the call-site source location of step() using the V8 stack trace API.
6
23
  * Uses Error.captureStackTrace with the `step` function as the constructor argument
7
24
  * so the stack starts from step()'s caller.
@@ -59,6 +76,8 @@ function step(nameOrRunOrOptions, runOrOptions) {
59
76
  options = nameOrRunOrOptions;
60
77
  }
61
78
  if (options.check && !options.summarize) throw new Error("summarize is required when check is set");
79
+ if (options.approval !== void 0 && normalizeApproval(options.approval).when === "drift" && !options.check) throw new Error("approval.when \"drift\" requires a check facet");
80
+ const retry = normalizeRetry(options.retry);
62
81
  return {
63
82
  _tag: "Step",
64
83
  name,
@@ -70,11 +89,12 @@ function step(nameOrRunOrOptions, runOrOptions) {
70
89
  ...options.whenInSync !== void 0 && { whenInSync: options.whenInSync },
71
90
  continueOnError: options.continueOnError,
72
91
  timeout: options.timeout,
92
+ ...retry !== void 0 && { retry },
73
93
  ...options.cache !== void 0 && { cache: options.cache },
74
94
  rules: options.rules,
75
95
  onCancel: options.onCancel,
76
96
  cleanup: options.cleanup,
77
- ...options.requireApproval !== void 0 && { requireApproval: options.requireApproval },
97
+ ...options.approval !== void 0 && { approval: options.approval },
78
98
  _sourceLocation,
79
99
  result: createStepOutputProxy(name)
80
100
  };
@@ -0,0 +1,32 @@
1
+ import type { z } from 'zod';
2
+ import type { DispatchInputsMap } from './types.js';
3
+ /** The per-key inferred output type of a declared dispatch-inputs map. */
4
+ export type InferDispatchInputs<TMap extends DispatchInputsMap> = {
5
+ [K in keyof TMap]: z.infer<TMap[K]>;
6
+ };
7
+ /** A context that may carry validated, coerced dispatch inputs. */
8
+ interface DispatchInputsCarrier {
9
+ dispatchInputs?: Record<string, unknown>;
10
+ }
11
+ /**
12
+ * A branded handle returned by `defineDispatchInputs`. It is accepted directly
13
+ * by `dispatch({ inputs })` and exposes typed `.from(ctx)` / `.fromRule(ctx)`
14
+ * readers over `ctx.dispatchInputs`, typed per declared key.
15
+ */
16
+ export interface DefinedDispatchInputs<TMap extends DispatchInputsMap> {
17
+ readonly __kiciDispatchInputs: true;
18
+ readonly map: TMap;
19
+ /** Read the validated, coerced dispatch inputs from a step context, typed per declared key. */
20
+ from(ctx: DispatchInputsCarrier): InferDispatchInputs<TMap>;
21
+ /** Same, from a rule context. */
22
+ fromRule(ctx: DispatchInputsCarrier): InferDispatchInputs<TMap>;
23
+ }
24
+ /**
25
+ * Declare a typed workflow-dispatch inputs map once and get back a handle that
26
+ * both `dispatch({ inputs })` accepts and exposes a typed `.from(ctx)` reader —
27
+ * no double type annotation. Type safety comes via Standard-Schema inference
28
+ * (Zod 4 implements `~standard`), without a builder-generics refactor.
29
+ */
30
+ export declare function defineDispatchInputs<TMap extends DispatchInputsMap>(map: TMap): DefinedDispatchInputs<TMap>;
31
+ export {};
32
+ //# sourceMappingURL=dispatch-inputs.d.ts.map
@@ -0,0 +1,21 @@
1
+ import "../chunk-BTugEXQM.js";
2
+ //#region src/triggers/dispatch-inputs.ts
3
+ /**
4
+ * Declare a typed workflow-dispatch inputs map once and get back a handle that
5
+ * both `dispatch({ inputs })` accepts and exposes a typed `.from(ctx)` reader —
6
+ * no double type annotation. Type safety comes via Standard-Schema inference
7
+ * (Zod 4 implements `~standard`), without a builder-generics refactor.
8
+ */
9
+ function defineDispatchInputs(map) {
10
+ const read = (ctx) => ctx.dispatchInputs ?? {};
11
+ return Object.freeze({
12
+ __kiciDispatchInputs: true,
13
+ map: Object.freeze({ ...map }),
14
+ from: read,
15
+ fromRule: read
16
+ });
17
+ }
18
+ //#endregion
19
+ export { defineDispatchInputs };
20
+
21
+ //# sourceMappingURL=dispatch-inputs.js.map
@@ -13,11 +13,15 @@ import { asArray, toBranchPattern } from "./types.js";
13
13
  */
14
14
  function dispatch(config) {
15
15
  const repos = config?.repos ? asArray(config.repos).map(toBranchPattern) : [];
16
+ const rawInputs = config?.inputs;
17
+ let inputsMap;
18
+ if (rawInputs) inputsMap = "__kiciDispatchInputs" in rawInputs ? rawInputs.map : rawInputs;
16
19
  const result = {
17
20
  _tag: "DispatchTrigger",
18
21
  types: Object.freeze(config?.types ? [...config.types] : []),
19
22
  repos: Object.freeze([...repos]),
20
- ...config?.description !== void 0 && { description: config.description }
23
+ ...config?.description !== void 0 && { description: config.description },
24
+ ...inputsMap && { inputs: Object.freeze({ ...inputsMap }) }
21
25
  };
22
26
  return Object.freeze(result);
23
27
  }
@@ -23,6 +23,8 @@ export { jobComplete } from './job-complete.js';
23
23
  export { genericWebhook } from './generic-webhook.js';
24
24
  export { schedule } from './schedule.js';
25
25
  export { lifecycle } from './lifecycle.js';
26
- export type { BranchPattern, BodyMatchPattern, PrEvent, PushEvent, PrTriggerConfig, PushTriggerConfig, TagTriggerConfig, CommentTriggerConfig, ReviewTriggerConfig, ReviewCommentTriggerConfig, ReleaseTriggerConfig, DispatchTriggerConfig, CreateTriggerConfig, DeleteTriggerConfig, StatusTriggerConfig, WorkflowRunTriggerConfig, ForkTriggerConfig, StarTriggerConfig, WatchTriggerConfig, WebhookTriggerConfig, TriggerConfig, PrConfigInput, PushConfigInput, TagConfigInput, CommentConfigInput, CommentAction, CommentSource, ReviewConfigInput, ReviewAction, ReviewState, ReviewCommentConfigInput, ReviewCommentAction, ReleaseConfigInput, ReleaseAction, DispatchConfigInput, CreateConfigInput, DeleteConfigInput, RefType, StatusConfigInput, StatusState, WorkflowRunConfigInput, WorkflowRunAction, ForkConfigInput, StarConfigInput, StarAction, WatchConfigInput, WatchAction, WebhookConfigInput, KiciEventConfigInput, KiciEventTriggerConfig, WorkflowCompleteConfigInput, WorkflowCompleteTriggerConfig, WorkflowCompleteStatus, JobCompleteConfigInput, JobCompleteTriggerConfig, JobCompleteStatus, GenericWebhookConfigInput, GenericWebhookTriggerConfig, GenericWebhookAuthMethod, GenericWebhookHmacAuth, GenericWebhookApiKeyAuth, GenericWebhookAuth, ScheduleConfigInput, ScheduleTriggerConfig, LifecycleEvent, LifecycleConfigInput, LifecycleTriggerConfig, } from './types.js';
26
+ export { defineDispatchInputs } from './dispatch-inputs.js';
27
+ export type { DefinedDispatchInputs, InferDispatchInputs } from './dispatch-inputs.js';
28
+ export type { DispatchInputsMap, BranchPattern, BodyMatchPattern, PrEvent, PushEvent, PrTriggerConfig, PushTriggerConfig, TagTriggerConfig, CommentTriggerConfig, ReviewTriggerConfig, ReviewCommentTriggerConfig, ReleaseTriggerConfig, DispatchTriggerConfig, CreateTriggerConfig, DeleteTriggerConfig, StatusTriggerConfig, WorkflowRunTriggerConfig, ForkTriggerConfig, StarTriggerConfig, WatchTriggerConfig, WebhookTriggerConfig, TriggerConfig, PrConfigInput, PushConfigInput, TagConfigInput, CommentConfigInput, CommentAction, CommentSource, ReviewConfigInput, ReviewAction, ReviewState, ReviewCommentConfigInput, ReviewCommentAction, ReleaseConfigInput, ReleaseAction, DispatchConfigInput, CreateConfigInput, DeleteConfigInput, RefType, StatusConfigInput, StatusState, WorkflowRunConfigInput, WorkflowRunAction, ForkConfigInput, StarConfigInput, StarAction, WatchConfigInput, WatchAction, WebhookConfigInput, KiciEventConfigInput, KiciEventTriggerConfig, WorkflowCompleteConfigInput, WorkflowCompleteTriggerConfig, WorkflowCompleteStatus, JobCompleteConfigInput, JobCompleteTriggerConfig, JobCompleteStatus, GenericWebhookConfigInput, GenericWebhookTriggerConfig, GenericWebhookAuthMethod, GenericWebhookHmacAuth, GenericWebhookApiKeyAuth, GenericWebhookAuth, ScheduleConfigInput, ScheduleTriggerConfig, LifecycleEvent, LifecycleConfigInput, LifecycleTriggerConfig, } from './types.js';
27
29
  export { DEFAULT_PR_EVENTS, toBranchPattern } from './types.js';
28
30
  //# sourceMappingURL=index.d.ts.map
@@ -22,4 +22,5 @@ import { jobComplete } from "./job-complete.js";
22
22
  import { genericWebhook } from "./generic-webhook.js";
23
23
  import { schedule } from "./schedule.js";
24
24
  import { lifecycle } from "./lifecycle.js";
25
- export { DEFAULT_PR_EVENTS, comment, create, del as delete, dispatch, fork, genericWebhook, jobComplete, kiciEvent, lifecycle, pr, push, release, review, reviewComment, schedule, star, status, tag, toBranchPattern, watch, webhook, workflowComplete, workflowRun };
25
+ import { defineDispatchInputs } from "./dispatch-inputs.js";
26
+ export { DEFAULT_PR_EVENTS, comment, create, defineDispatchInputs, del as delete, dispatch, fork, genericWebhook, jobComplete, kiciEvent, lifecycle, pr, push, release, review, reviewComment, schedule, star, status, tag, toBranchPattern, watch, webhook, workflowComplete, workflowRun };
@@ -2,6 +2,14 @@
2
2
  * Trigger types and interfaces for pr() and push() trigger helpers.
3
3
  * Supports both glob patterns and regex patterns for branch/path matching.
4
4
  */
5
+ import type { z } from 'zod';
6
+ /**
7
+ * A declared map of typed workflow-dispatch inputs: `{ name: ZodSchema }`.
8
+ * Each schema must fall within the closed dispatch-input subset (extracted by
9
+ * the compiler) — z.string/number/boolean/enum/literal plus
10
+ * .optional/.nullable/.default/.min/.max/.regex/.int.
11
+ */
12
+ export type DispatchInputsMap = Record<string, z.ZodType>;
5
13
  /**
6
14
  * Branch pattern - discriminated union supporting both glob and regex patterns.
7
15
  * Glob patterns use micromatch syntax, regex patterns use standard JS regex.
@@ -165,6 +173,8 @@ export interface DispatchTriggerConfig {
165
173
  readonly types: readonly string[];
166
174
  readonly repos: readonly BranchPattern[];
167
175
  readonly description?: string;
176
+ /** Declared, typed workflow-dispatch inputs (frozen `{ name: ZodSchema }`). */
177
+ readonly inputs?: DispatchInputsMap;
168
178
  }
169
179
  /**
170
180
  * Input configuration for dispatch() factory function.
@@ -173,6 +183,16 @@ export interface DispatchConfigInput {
173
183
  readonly types?: string[];
174
184
  readonly repos?: string | RegExp | (string | RegExp)[];
175
185
  readonly description?: string;
186
+ /**
187
+ * Typed workflow-dispatch inputs. Accepts a bare `{ name: ZodSchema }` map or
188
+ * a `defineDispatchInputs(...)` branded handle (which also exposes a typed
189
+ * `.from(ctx)` accessor). The compiler validates each schema against the
190
+ * closed dispatch-input subset.
191
+ */
192
+ readonly inputs?: DispatchInputsMap | {
193
+ readonly __kiciDispatchInputs: true;
194
+ readonly map: DispatchInputsMap;
195
+ };
176
196
  }
177
197
  export type RefType = 'branch' | 'tag';
178
198
  /**
package/dist/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { z } from 'zod';
2
2
  import type { $ as Shell } from 'zx';
3
- import type { ResourceRequest, RunsOnAllInput, OnUnreachableMode } from '@kici-dev/engine';
3
+ import type { RetryBackoff } from '@kici-dev/core';
4
+ import type { ResourceRequest, RunsOnAllInput, OnUnreachableMode, NeedsWhen, ExecutionJobStatus } from '@kici-dev/engine';
4
5
  import type { StepContext, Logger } from './context.js';
5
6
  import type { TriggerConfig } from './triggers/types.js';
6
7
  import type { Rule } from './rules/types.js';
@@ -9,8 +10,15 @@ import type { HookInput } from './hooks/types.js';
9
10
  import type { KiciApi } from './api-types.js';
10
11
  import type { DynamicGroupRef } from './dynamic-group.js';
11
12
  import type { EventPayload } from './events/event-payloads.js';
12
- import type { RequireApproval } from './approval.js';
13
- export type { ResourceRequest, ResourceSpec, RunsOnAllInput, OnUnreachableMode, } from '@kici-dev/engine';
13
+ import type { ApprovalConfig } from './approval.js';
14
+ export type { ResourceRequest, ResourceSpec, RunsOnAllInput, OnUnreachableMode, NeedsWhen, } from '@kici-dev/engine';
15
+ /**
16
+ * Author-facing run condition for a `needs` edge: keyword sugar
17
+ * (`'on-success'` | `'always'` | `'on-skip'` | `'on-failure'`) or a raw set of
18
+ * upstream terminal statuses. Resolved to a normalized status-set at compile
19
+ * time; the downstream runs when the upstream's terminal status is a member.
20
+ */
21
+ export type NeedsWhenInput = NeedsWhen | ExecutionJobStatus[];
14
22
  /** Source location captured at a step() call site. */
15
23
  export interface SourceLocation {
16
24
  readonly file: string;
@@ -57,6 +65,8 @@ export interface Step<TResult = void> {
57
65
  readonly continueOnError?: boolean;
58
66
  /** Step-level timeout in milliseconds. Overrides the agent's default (30 minutes). */
59
67
  readonly timeout?: number;
68
+ /** Normalized retry policy (defaults filled, shorthand expanded). `retryIf` is execution-only. */
69
+ readonly retry?: NormalizedRetry;
60
70
  /** Declarative cache: restored before this step, saved after on key miss. */
61
71
  readonly cache?: import('./cache-types.js').CacheInput;
62
72
  /** Step-level conditional rules (evaluated agent-side). */
@@ -65,8 +75,8 @@ export interface Step<TResult = void> {
65
75
  readonly onCancel?: HookInput;
66
76
  /** Always runs after step (success, failure, or cancel). */
67
77
  readonly cleanup?: HookInput;
68
- /** Pause for a manual human approval before this step runs. */
69
- readonly requireApproval?: RequireApproval;
78
+ /** Pause for a manual human approval (before this step, or on drift). */
79
+ readonly approval?: ApprovalConfig;
70
80
  /** Internal: source location captured at step() call site. Not part of public API. */
71
81
  readonly _sourceLocation?: SourceLocation;
72
82
  /**
@@ -88,6 +98,33 @@ export type BareStepFn<TResult = void> = (ctx: StepContext) => Promise<TResult>;
88
98
  export type StepInput = Step<any> | BareStepFn<any>;
89
99
  /** Options for step() factory - simple form (just async function) */
90
100
  export type StepRunFn = (ctx: StepContext) => Promise<void>;
101
+ /**
102
+ * Author-supplied retry policy for a step. A thrown attempt is re-run while
103
+ * attempts remain and `retryIf(err)` is true.
104
+ */
105
+ export interface RetryConfig {
106
+ /** Total attempts incl. the first; `maxAttempts: 3` ⇒ up to 3 runs. Must be >= 1. */
107
+ maxAttempts: number;
108
+ /** Base delay between attempts, ms. Default 1000. */
109
+ delayMs?: number;
110
+ /** Delay growth. Default 'exponential'. */
111
+ backoff?: RetryBackoff;
112
+ /** Cap for exponential backoff, ms. Default 30000. */
113
+ maxDelayMs?: number;
114
+ /** Retry only when this returns true for the thrown error. Default: retry on any throw. */
115
+ retryIf?: (err: unknown) => boolean;
116
+ }
117
+ /**
118
+ * Retry policy with defaults filled in, carried on the built {@link Step}.
119
+ * `retryIf` rides along on the in-memory step (it is never serialized).
120
+ */
121
+ export interface NormalizedRetry {
122
+ maxAttempts: number;
123
+ delayMs: number;
124
+ backoff: RetryBackoff;
125
+ maxDelayMs: number;
126
+ retryIf?: (err: unknown) => boolean;
127
+ }
91
128
  /**
92
129
  * Facets shared by both the plain and the check variant of {@link StepOptions}.
93
130
  * These compose unchanged whether or not a step declares a `check` facet.
@@ -99,6 +136,8 @@ export interface StepOptionsBase {
99
136
  continueOnError?: boolean;
100
137
  /** Step-level timeout in milliseconds. Overrides the agent's default (30 minutes). */
101
138
  timeout?: number;
139
+ /** Retry policy: re-run a thrown step with backoff. `retry: N` ⇒ `{ maxAttempts: N }`. */
140
+ retry?: number | RetryConfig;
102
141
  /** Declarative cache: restored before this step, saved after on key miss. */
103
142
  cache?: import('./cache-types.js').CacheInput;
104
143
  /** Step-level conditional rules (evaluated agent-side). */
@@ -107,8 +146,8 @@ export interface StepOptionsBase {
107
146
  onCancel?: HookInput;
108
147
  /** Always runs after step (success, failure, or cancel). */
109
148
  cleanup?: HookInput;
110
- /** Pause for a manual human approval before this step runs. */
111
- requireApproval?: RequireApproval;
149
+ /** Pause for a manual human approval (before this step, or on drift). */
150
+ approval?: ApprovalConfig;
112
151
  }
113
152
  /**
114
153
  * Plain step options: `run` takes only the context — the existing, fully
@@ -249,10 +288,10 @@ declare const DYNAMIC_JOB_NEEDS_TAG: unique symbol;
249
288
  */
250
289
  export type DynamicJobNeed = Job | string | DynamicGroupRef | {
251
290
  name: string;
252
- ifFailed?: 'skip' | 'run';
291
+ when?: NeedsWhenInput;
253
292
  } | {
254
293
  group: string;
255
- ifFailed?: 'skip' | 'run';
294
+ when?: NeedsWhenInput;
256
295
  };
257
296
  /**
258
297
  * Options-object form of {@link dynamicJob}: a result-aware generator that is
@@ -354,7 +393,21 @@ export type InitConfig = InitItem | InitItem[] | 'auto' | false;
354
393
  export interface RunsOnSelector {
355
394
  labels: string | RegExp | (string | RegExp)[];
356
395
  exclude?: string | RegExp | (string | RegExp)[];
396
+ /**
397
+ * How to pick the single agent when more than one matches.
398
+ *
399
+ * - `'deterministic'` (default) — sort matching candidates by `agentId` and
400
+ * pick the lowest, so a run-once-on-one-host job (a migration, a dump) lands
401
+ * on the same host across re-runs. Can hot-spot equivalent agents.
402
+ * - `'any'` — pick any available agent (load spread). Opt out of determinism
403
+ * for jobs that don't need a stable host.
404
+ *
405
+ * The string / array shorthand `runsOn` forms imply `'deterministic'` too.
406
+ */
407
+ pick?: RunsOnPick;
357
408
  }
409
+ /** Single-agent selection policy when multiple agents match a `runsOn` selector. */
410
+ export type RunsOnPick = 'deterministic' | 'any';
358
411
  /**
359
412
  * Polymorphic runsOn type: string shorthand, array shorthand, or full selector object.
360
413
  * - `'kici:os:linux'` — single label shorthand (targets any linux agent)
@@ -384,6 +437,14 @@ export interface Job {
384
437
  readonly runsOnAll?: RunsOnAllInput;
385
438
  /** Failure policy for unreachable durable hosts when using `runsOnAll`. */
386
439
  readonly onUnreachable?: OnUnreachableMode;
440
+ /**
441
+ * Widen a `runsOnAll` fan-out to declared-but-un-agented hosts: each matching
442
+ * host that has no live agent gets a temporary init-runner brought up over SSH
443
+ * and its steps run on it (fresh-box bootstrap convergence). Already-live hosts
444
+ * run on their own agent. Default `false` (only live hosts run). Only
445
+ * meaningful alongside `runsOnAll`.
446
+ */
447
+ readonly includeUninitialized?: boolean;
387
448
  /** Fan-out concurrency width (sliding window; `1` = serial). Applies to matrix and `runsOnAll`. */
388
449
  readonly maxParallel?: number;
389
450
  /** Halt the fan-out on first child failure, skipping the remainder. Default `false`. */
@@ -391,10 +452,10 @@ export interface Job {
391
452
  readonly steps: readonly StepInput[];
392
453
  readonly needs?: ReadonlyArray<Job | string | DynamicGroupRef | {
393
454
  name: string;
394
- ifFailed: 'skip' | 'run';
455
+ when?: NeedsWhenInput;
395
456
  } | {
396
457
  group: string;
397
- ifFailed: 'skip' | 'run';
458
+ when?: NeedsWhenInput;
398
459
  }>;
399
460
  /** Rules for conditional execution */
400
461
  readonly rules?: Rule[];
@@ -457,7 +518,7 @@ export interface Job {
457
518
  /** Declarative cache: restored before steps, saved after the job on key miss. */
458
519
  readonly cache?: import('./cache-types.js').CacheInput;
459
520
  /** Pause for a manual human approval before this job dispatches. */
460
- readonly requireApproval?: RequireApproval;
521
+ readonly approval?: ApprovalConfig;
461
522
  /**
462
523
  * Type-safe proxy for accessing this job's outputs.
463
524
  * For multi-step jobs: jobRef.result.stepName.field
@@ -484,6 +545,13 @@ export interface JobOptions {
484
545
  * pinned child and waits. Only meaningful alongside `runsOnAll`.
485
546
  */
486
547
  onUnreachable?: OnUnreachableMode;
548
+ /**
549
+ * Widen a `runsOnAll` fan-out to declared-but-un-agented hosts: a matching host
550
+ * with no live agent gets a temporary init-runner brought up over SSH and its
551
+ * steps run on it (fresh-box bootstrap convergence); already-live hosts run on
552
+ * their own agent. Default `false`. Only meaningful alongside `runsOnAll`.
553
+ */
554
+ includeUninitialized?: boolean;
487
555
  /**
488
556
  * Fan-out concurrency width: the maximum number of fan-out children (matrix
489
557
  * combinations or `runsOnAll` hosts) that run at once. A sliding window —
@@ -510,10 +578,10 @@ export interface JobOptions {
510
578
  run?: (ctx: StepContext) => Promise<any>;
511
579
  needs?: Array<Job | string | DynamicGroupRef | {
512
580
  name: string;
513
- ifFailed: 'skip' | 'run';
581
+ when?: NeedsWhenInput;
514
582
  } | {
515
583
  group: string;
516
- ifFailed: 'skip' | 'run';
584
+ when?: NeedsWhenInput;
517
585
  }>;
518
586
  /** Rules that must pass for job to execute */
519
587
  rules?: Rule[];
@@ -591,7 +659,7 @@ export interface JobOptions {
591
659
  /** Declarative cache: restored before steps, saved after the job on key miss. */
592
660
  cache?: import('./cache-types.js').CacheInput;
593
661
  /** Pause for a manual human approval before this job dispatches. */
594
- requireApproval?: RequireApproval;
662
+ approval?: ApprovalConfig;
595
663
  }
596
664
  /**
597
665
  * Private npm registry declaration. Tells the agent to authenticate against
@@ -670,7 +738,7 @@ export interface Workflow {
670
738
  readonly max?: number;
671
739
  };
672
740
  /** Pause for a manual human approval before the whole workflow dispatches. */
673
- readonly requireApproval?: RequireApproval;
741
+ readonly approval?: ApprovalConfig;
674
742
  }
675
743
  /** Options for workflow() factory */
676
744
  export interface WorkflowOptions {
@@ -723,7 +791,7 @@ export interface WorkflowOptions {
723
791
  max?: number;
724
792
  };
725
793
  /** Pause for a manual human approval before the whole workflow dispatches. */
726
- requireApproval?: RequireApproval;
794
+ approval?: ApprovalConfig;
727
795
  }
728
796
  export type { TriggerConfig, PrTriggerConfig, PushTriggerConfig } from './triggers/types.js';
729
797
  export type { Rule, RuleContext, RuleCheckFn, RuleResult } from './rules/types.js';
package/dist/workflow.js CHANGED
@@ -76,7 +76,7 @@ function workflow(name, options) {
76
76
  onSuccess: options.onSuccess,
77
77
  onFailure: options.onFailure,
78
78
  concurrency: options.concurrency,
79
- requireApproval: options.requireApproval
79
+ approval: options.approval
80
80
  };
81
81
  }
82
82
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kici-dev/sdk",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "TypeScript SDK for defining KiCI workflows. Import into `.kici/workflows/*.ts` to declare workflows, jobs, steps, triggers, rules, and matrix configurations.",
5
5
  "keywords": [
6
6
  "ci",
@@ -49,8 +49,8 @@
49
49
  "micromatch": "^4.0.8",
50
50
  "zod": "^4.4.3",
51
51
  "zx": "^8.8.5",
52
- "@kici-dev/core": "0.1.21",
53
- "@kici-dev/engine": "0.1.21"
52
+ "@kici-dev/core": "0.1.23",
53
+ "@kici-dev/engine": "0.1.23"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/micromatch": "^4.0.10"