SDK reference: triggers
Triggers
Section titled “Triggers”Triggers define when a workflow runs. KiCI provides 23 trigger types: 16 GitHub webhook triggers and 7 internal/generic triggers for event routing, scheduling, and non-GitHub sources. Each trigger returns a frozen config object with a unique _tag discriminator.
All triggers use a config object form — pass an options object to configure the trigger.
Create a pull request trigger. Returns a frozen PrTriggerConfig directly.
function pr(config?: PrConfigInput): PrTriggerConfig;Config options:
interface PrConfigInput { events?: PrEvent[]; target?: string | RegExp | (string | RegExp)[]; source?: string | RegExp | (string | RegExp)[]; paths?: string[]; // Use '!' prefix for exclusions (e.g., '!docs/**') repos?: string | RegExp | (string | RegExp)[]; // Cross-repo source patterns -- see global-workflows.md description?: string;}PrEvent values: 'opened', 'synchronize', 'reopened', 'closed', 'assigned', 'unassigned', 'labeled', 'unlabeled', 'edited', 'converted_to_draft', 'ready_for_review', 'locked', 'unlocked', 'review_requested', 'review_request_removed', 'auto_merge_enabled', 'auto_merge_disabled'
Default events (when events is not specified): opened, synchronize, reopened, closed
Examples:
// All PRs with default eventspr();
// PRs targeting main with path filterpr({ target: 'main', events: ['opened', 'synchronize'], paths: ['src/**'] });
// Regex branch patternpr({ target: /^release\/v\d+$/ });push()
Section titled “push()”Create a push trigger. Returns a frozen PushTriggerConfig directly.
function push(config?: PushConfigInput): PushTriggerConfig;Config options:
interface PushConfigInput { branches?: string | RegExp | (string | RegExp)[]; tags?: string | RegExp | (string | RegExp)[]; paths?: string[]; // Use '!' prefix for exclusions (e.g., '!docs/**') repos?: string | RegExp | (string | RegExp)[]; // Cross-repo source patterns -- see global-workflows.md description?: string;}Examples:
// Any pushpush();
// Push to main onlypush({ branches: 'main' });
// Push with branch and path filterspush({ branches: ['main', 'develop'], paths: ['src/**'] });
// Tag pushespush({ tags: ['v*'] });Path filter behavior
Section titled “Path filter behavior”A pr() or push() trigger with paths matches the event’s changed files
against your patterns. An available list is matched exactly (an event with no
matching change — including a diff-less branch create or delete — does not run).
When the diff is unavailable (chiefly a universal-git pull-request event,
whose webhook carries no diff), path filters match conservatively so the
workflow runs rather than being silently dropped, and the delivery is recorded
as degraded. GitHub always provides an exact list; a transient API error fails
loudly, not as empty.
Content requirements (requires)
Section titled “Content requirements (requires)”Where paths filters on which files changed, requires filters on what
those files contain. It is a declarative filter on the pr(), push(), and
tag() triggers: a list of queries over the bytes of named source files, read
at the event’s commit. The orchestrator evaluates it as pure data before
dispatching — it reads only the referenced files, never clones the whole
repository, and never runs any of your workflow code. A workflow whose requires
does not pass is not dispatched.
Each entry is a ContentRequirement:
interface ContentRequirement { file: string; // repo-relative path to query format?: 'auto' | 'json' | 'yaml' | 'text'; // how to parse the file (default: 'auto') exists?: string[]; // JSONPath expressions that must each resolve to ≥1 node (json/yaml) match?: Record<string, unknown>; // JSONPath → expected value; every one must match (json/yaml) not?: Record<string, unknown>; // JSONPath → value; passes only when NONE match (json/yaml) contains?: string | string[]; // literal substrings, all of which must appear (text only) notContains?: string | string[]; // literal substrings, none of which may appear (text only) matches?: string | RegExp | (string | RegExp)[]; // regexes, all of which must match (text only) notMatches?: string | RegExp | (string | RegExp)[]; // regexes, none of which may match (text only) ignoreCase?: boolean; // applies to contains/notContains only (default: false) absent?: boolean; // passes only when the file does NOT exist}Format. format: 'auto' (the default) picks the parser by extension:
.json → JSON, .yaml / .yml → YAML, everything else → text. Set format
explicitly to override — e.g. treat an extensionless file as JSON, or read a
.json file as raw text. JSON and YAML both parse to an object, so the JSONPath
keys (exists / match / not) work identically over either; text files are
queried by contains, notContains, matches, and notMatches over the raw
bytes.
Query keys.
exists— an array of JSONPath expressions; each must resolve to at least one node in the parsed document.match— a JSONPath → expected-value map; every expression must match. An expected value is an exact value, a regex string in/pattern/flagsform (against a string node), or an array of acceptable values (any one matches).not— the same map shape, inverted: the entry passes only when none of the expressions match.contains/notContains— literal substrings tested against the raw file text. Every entry must be present (contains) or absent (notContains). No escaping needed (text format only).matches— one or several regexes (aRegExpor/pattern/flagsstring), each of which must match the raw file text (text format only).notMatches— the inverse ofmatches: the entry passes only when none of the regexes match (text format only).ignoreCase— case-insensitivecontains/notContainsonly; a regex carries its own flags. Default false.absent: true— passes only when the file does not exist at the event’s commit. It is mutually exclusive with the query keys above.- A bare
{ file }with no query key requires the file to exist.
The keys inside one entry are AND-ed, and the entries in a requires list are
AND-ed with each other. An empty or absent requires matches everything, exactly
like paths.
Examples:
// Only run CI when package.json declares a `ci` script.push({ branches: 'main', requires: [{ file: 'package.json', exists: ['$.scripts.ci'] }] });
// Deploy only when the service config enables it (YAML, matched by value).push({ branches: 'main', requires: [{ file: 'service.yaml', match: { '$.deploy.enabled': true } }],});
// Only run when the Dockerfile builds from a Node base image (raw-text regex).pr({ requires: [{ file: 'Dockerfile', format: 'text', matches: '/^FROM node:/m' }] });
// Skip the workflow whenever a repo carries an opt-out marker file.push({ requires: [{ file: '.skip-ci', absent: true }] });
// Combine filters: a tag build that requires a version file AND forbids a draft flag.tag({ patterns: ['v*'], requires: [ { file: 'VERSION', matches: '/^\\d+\\.\\d+\\.\\d+$/' }, { file: 'release.json', not: { '$.draft': true } }, ],});Fail-visible evaluation. Files are read at the event’s commit. If a
referenced file is larger than 1 MiB, or fails to parse for its format, the
requirement is indeterminate — the candidate workflow is dropped and does
not run. A requires that cannot be evaluated never silently passes.
Compile-time validation. kici compile rejects a malformed requirement before
it ever reaches the orchestrator: a raw-text key that is invalid or catastrophic
(ReDoS-prone) is rejected by a safe-regex check; a text file cannot carry a
JSON/YAML query key (exists / match / not) and a JSON/YAML file cannot carry a
raw-text key; absent cannot be combined with a query key; and an explicit
format with no query key is rejected as having nothing to check.
Commit-message filters (commitMessage)
Section titled “Commit-message filters (commitMessage)”Where requires filters on what the repository’s files contain,
commitMessage filters on what the event says. It is a declarative filter on
the pr(), push(), and tag() triggers. The orchestrator evaluates it
directly from the webhook payload: no file is fetched, and no repository is
cloned. For an organization-wide workflow it dispatches no evaluation job. It is
the cheapest gate available.
The text it tests is the full head-commit message — subject and body — for
push and tag, and the title plus body for pull-request events.
interface TextMatch { contains?: string | string[]; // every needle must be present notContains?: string | string[]; // no needle may be present matches?: string | RegExp | (string | RegExp)[]; // every regex must match notMatches?: string | RegExp | (string | RegExp)[]; // no regex may match ignoreCase?: boolean; // applies to contains/notContains only (default: false)}Every entry in a list is a conjunct. contains: ['a', 'b'] passes only when
the text contains both, and the keys AND together. To express OR, declare two
triggers — a workflow’s trigger list already matches on the first one that fits:
// AND — one trigger.push({ commitMessage: { contains: ['release:', 'approved'] } });
// OR — two triggers.on: [ push({ branches: 'main', commitMessage: { contains: 'deploy:' } }), push({ branches: 'main', commitMessage: { contains: 'release:' } }),];Needles are literal substrings — no glob, no regex, no escaping, so a needle
containing .* matches only the literal .*. Use matches / notMatches for a
pattern; both accept a RegExp literal, and the m flag reaches the body:
// The single most common use: skip marker commits.push({ branches: 'main', commitMessage: { notContains: ['[skip ci]', '[ci skip]'] } });
// Ignore dependency-bump noise across an organization.push({ commitMessage: { notMatches: /^chore\(deps\):/ } });
// Require a conventional-commit prefix and forbid a WIP marker.pr({ target: 'main', commitMessage: { matches: /^(feat|fix)\(/, notContains: 'WIP' } });
// Match a trailer in the commit body.push({ commitMessage: { matches: /^Fixes: #\d+$/m } });ignoreCase affects contains and notContains only — a regex already carries
its own flags, so write /^feat:/i rather than expecting ignoreCase to reach
it.
Fail-visible evaluation. Some events carry no message at all. A
branch-deletion push has no head commit, and a self-hosted forge may publish
none. The trigger then does not match, and the decision trace records it as
indeterminate rather than as an exclusion. A commitMessage filter that cannot
be evaluated never silently passes.
Compile-time validation. kici compile rejects a malformed matcher. It
refuses:
- a matcher with no query key;
- an
ignoreCasethat would affect nothing; - an empty needle list;
- an empty-string needle (it would match every text);
- a regex that is invalid or catastrophic (ReDoS-prone).
Create a tag trigger. Returns a frozen TagTriggerConfig.
function tag(config?: TagConfigInput): TagTriggerConfig;Config options: patterns (string/RegExp/array), description
tag(); // Any tagtag({ patterns: ['v*'] }); // Semver tagstag({ patterns: /^v\d+\.\d+$/ }); // Regex matchcomment()
Section titled “comment()”Create an issue/PR comment trigger. Returns a frozen CommentTriggerConfig.
function comment(config?: CommentConfigInput): CommentTriggerConfig;Config options: actions (created/edited/deleted), source (issue/pr), bodyMatch (string or RegExp), description
comment(); // Any commentcomment({ bodyMatch: '/deploy' }); // Glob match on bodycomment({ bodyMatch: /^\/deploy/i }); // Regex match on bodycomment({ source: 'pr', actions: ['created'] }); // PR comments onlyreview()
Section titled “review()”Create a pull request review trigger. Returns a frozen ReviewTriggerConfig.
function review(config?: ReviewConfigInput): ReviewTriggerConfig;Config options: actions (submitted/edited/dismissed), states (approved/changes_requested/commented/dismissed), description
review(); // Any reviewreview({ states: ['approved'] }); // Approvals onlyreview({ actions: ['submitted'], states: ['approved'] }); // Submitted approvalsreviewComment()
Section titled “reviewComment()”Create a PR review comment trigger. Returns a frozen ReviewCommentTriggerConfig.
function reviewComment(config?: ReviewCommentConfigInput): ReviewCommentTriggerConfig;Config options: actions (created/edited/deleted), description
reviewComment(); // Any review commentreviewComment({ actions: ['created'] }); // New review comments onlyrelease()
Section titled “release()”Create a release trigger. Returns a frozen ReleaseTriggerConfig.
function release(config?: ReleaseConfigInput): ReleaseTriggerConfig;Config options: actions (published/unpublished/created/edited/deleted/prereleased/released), description
release(); // Any release eventrelease({ actions: ['published'] }); // Published releases onlydispatch()
Section titled “dispatch()”Create a repository_dispatch trigger. Returns a frozen DispatchTriggerConfig.
function dispatch(config?: DispatchConfigInput): DispatchTriggerConfig;Config options: types (string[]), description, inputs (typed dispatch inputs map)
dispatch(); // Any dispatchdispatch({ types: ['deploy', 'rollback'] }); // Specific event typesTyped dispatch inputs
Section titled “Typed dispatch inputs”A dispatch() trigger can declare a typed inputs schema. Operators supply
values with kici run remote --input key=value; KiCI validates, coerces, defaults, and
exposes them to steps and rules as ctx.dispatchInputs. The values are validated
on the orchestrator from the compiled lock file — a missing required input or a
bad value is rejected before any agent runs, without cloning the repository.
import { workflow, job, step, dispatch, defineDispatchInputs, z } from '@kici-dev/sdk';
const inputs = defineDispatchInputs({ target: z.string().optional(), skipCveScan: z.boolean().default(false), skipCveScanReason: z.string().min(1).optional(), mode: z.enum(['full', 'edge-only']).default('full'), retries: z.number().int().min(0).max(10).default(3),});
export default workflow('deploy-prod', { on: dispatch({ types: ['deploy-prod'], inputs }), jobs: [ job('gates', { runsOn: 'kici:group:ops', steps: [ step('cve-gate', async (ctx) => { const i = inputs.from(ctx); // fully typed per declared key if (i.skipCveScan) { ctx.log.warn(`CVE gate skipped: ${i.skipCveScanReason ?? '(no reason)'}`); return; } await ctx.$`pnpm scan:cve:gate`; }), ], }), ],});defineDispatchInputs(map)is the single declaration site. It returns a handle thatdispatch({ inputs })accepts and exposesinputs.from(ctx)— a typed reader overctx.dispatchInputs, typed per declared key.dispatch({ inputs })also accepts a bare{ name: schema }map directly when you don’t need the reader.ctx.dispatchInputsis always present (a validated map ofstring | number | boolean | null), distinct fromctx.inputs(typed outputs fromneedsdependencies). Rules see the same values viactx.dispatchInputs, soskipUnless(ctx => !ctx.dispatchInputs.skipCveScan)works.- Defaults are applied once, on the orchestrator (the authoritative side); the
CLI pre-validates
--inputfor fast feedback and forwards the raw operator pairs.
Allowed input types (closed subset): z.string(), z.number(),
z.boolean(), z.enum([...]), z.literal(v), with the modifiers .optional(),
.nullable(), .default(v), .min(n), .max(n), .regex(re), .int().
Anything outside this set (.refine(), .transform(), .pipe(), z.object(),
z.array(), z.union(), z.record(), z.coerce.*) is a compile error —
the closed set is what guarantees the schema survives the trip to the
orchestrator’s lock file without silently dropping any validation. CLI strings
are coerced for you (--input retries=3 becomes the number 3; booleans accept
true/false/1/0/yes/no), so author your schema with clean types.
create()
Section titled “create()”Create a ref creation trigger (branches/tags). Returns a frozen CreateTriggerConfig.
function create(config?: CreateConfigInput): CreateTriggerConfig;Config options: refTypes (branch/tag), patterns (string/RegExp/array), description
create(); // Any ref creationcreate({ refTypes: ['tag'], patterns: ['v*'] }); // Tag creation onlydelete()
Section titled “delete()”Create a ref deletion trigger (branches/tags). Returns a frozen DeleteTriggerConfig.
Note: Since delete is a JavaScript reserved word, import as del: import { delete as del } from '@kici-dev/sdk'
function del(config?: DeleteConfigInput): DeleteTriggerConfig;Config options: refTypes (branch/tag), patterns (string/RegExp/array), description
del(); // Any ref deletiondel({ refTypes: ['branch'], patterns: ['temp/*'] }); // Temp branch cleanupstatus()
Section titled “status()”Create a commit status trigger. Returns a frozen StatusTriggerConfig.
function status(config?: StatusConfigInput): StatusTriggerConfig;Config options: contexts (glob patterns like ‘ci/*’), states (error/failure/pending/success), description
status(); // Any statusstatus({ contexts: ['ci/*'], states: ['success'] }); // CI successworkflowRun()
Section titled “workflowRun()”Create a workflow_run trigger. Returns a frozen WorkflowRunTriggerConfig.
function workflowRun(config?: WorkflowRunConfigInput): WorkflowRunTriggerConfig;Config options: actions (requested/completed/in_progress), workflows (name filters), conclusions (success/failure/cancelled), description
workflowRun(); // Any workflow runworkflowRun({ workflows: ['CI'], actions: ['completed'], conclusions: ['success'] });fork()
Section titled “fork()”Create a fork trigger. No filter fields. Returns a frozen ForkTriggerConfig.
function fork(config?: ForkConfigInput): ForkTriggerConfig;fork(); // Any fork eventfork({ description: 'Track forks' }); // With descriptionstar()
Section titled “star()”Create a star trigger. Returns a frozen StarTriggerConfig.
function star(config?: StarConfigInput): StarTriggerConfig;Config options: actions (created/deleted), description
star(); // Any star eventstar({ actions: ['created'] }); // New stars onlywatch()
Section titled “watch()”Create a watch trigger. Returns a frozen WatchTriggerConfig.
function watch(config?: WatchConfigInput): WatchTriggerConfig;Config options: actions (started), description
watch(); // Any watch eventwatch({ actions: ['started'] }); // Watch started onlywebhook()
Section titled “webhook()”Create a catch-all webhook trigger for any GitHub event. Returns a frozen WebhookTriggerConfig. Unlike other triggers, events is required — catch-all must specify what to catch.
function webhook(config: WebhookConfigInput): WebhookTriggerConfig;Config options: events (required string[]), actions (optional string[]), repos (optional cross-repo source patterns — see global workflows), description
webhook({ events: ['deployment'] }); // Deployment eventswebhook({ events: ['deployment', 'deployment_status'] }); // Multiple eventswebhook({ events: ['deployment'], actions: ['created'] }); // With action filterCross-source delivery
Section titled “Cross-source delivery”A webhook() trigger fires whenever a matching event arrives via any inbound webhook source within the same org, not just the source the workflow’s repository is bound to. If your repo is registered through a github source and a separate generic source in the same org POSTs an event with a matching name, the workflow still runs.
Two important rules govern the cross-source path:
- The registration’s source owns dispatch credentials. The runtime clone, auth, and check-status posting come from the source the workflow was registered with (via its default-branch push), never from the inbound source. A generic webhook fanning out to a github-registered workflow uses the github bundle’s clone token provider — the generic source contributes only the event payload.
- Org isolation is structural. A webhook delivered to org A can never trigger a workflow registered against org B. The lookup index is keyed on
(customerId, eventName)so cross-org leakage is impossible.
The orchestrator emits kici_cross_source_fanout_size (histogram) per inbound webhook so operators can observe how many workflows each event reaches.
Event triggers
Section titled “Event triggers”The following 7 trigger types support internal event routing, scheduling, lifecycle orchestration, and non-GitHub webhook sources.
kiciEvent()
Section titled “kiciEvent()”Create a custom event trigger. Fires when a named internal event is emitted from a workflow step via ctx.emit(). Returns a frozen KiciEventTriggerConfig.
function kiciEvent(config: KiciEventConfigInput): KiciEventTriggerConfig;Config options:
interface KiciEventConfigInput { name: string; // Required: event name to listen for match?: Record<string, unknown>; // JSONPath payload matching (e.g., { '$.env': 'prod' }) not?: Record<string, unknown>; // Negative JSONPath filter source?: string; // Cross-repo source filter (e.g., 'org/infra-repo') description?: string;}kiciEvent({ name: 'deploy-complete' }); // Match by namekiciEvent({ name: 'deploy-complete', match: { '$.env': 'prod' } }); // With payload filterkiciEvent({ name: 'deploy-complete', not: { '$.env': 'staging' } }); // Negative filterkiciEvent({ name: 'deploy-complete', source: 'org/infra-repo' }); // Cross-repoworkflowComplete()
Section titled “workflowComplete()”Create a workflow completion trigger. Fires automatically when another workflow finishes execution. Returns a frozen WorkflowCompleteTriggerConfig.
function workflowComplete(config?: WorkflowCompleteConfigInput): WorkflowCompleteTriggerConfig;Config options:
interface WorkflowCompleteConfigInput { name?: string; // Filter by workflow name status?: WorkflowCompleteStatus[]; // Filter by completion status source?: string; // Cross-repo source filter description?: string;}type WorkflowCompleteStatus = 'success' | 'failed' | 'cancelled';workflowComplete(); // Any workflow completionworkflowComplete({ name: 'CI' }); // Specific workflowworkflowComplete({ name: 'CI', status: ['success'] }); // Success onlyworkflowComplete({ name: 'CI', status: ['success'], source: 'org/repo' }); // Cross-repoworkflowsFailedBatch()
Section titled “workflowsFailedBatch()”Create a batched failure trigger. Instead of firing once per failed workflow, it accumulates every failed workflow completion over a time window and fires the subscribing workflow once with the whole list — so a mass incident (a bad deploy failing hundreds of runs at once) notifies a single time, not once per failure. Returns a frozen WorkflowsFailedBatchTriggerConfig.
function workflowsFailedBatch( config: WorkflowsFailedBatchConfigInput,): WorkflowsFailedBatchTriggerConfig;Config options:
interface WorkflowsFailedBatchConfigInput { accumulateFor: number; // Accumulation window in milliseconds (opens on the first failure) name?: string; // Filter by failed workflow name source?: string; // Cross-repo source filter description?: string;}The first failure inside the window opens it; when the window closes, the subscribing workflow is dispatched once. The batch is delivered on ctx.event.payload:
// ctx.event.payload for a workflowsFailedBatch dispatch:// {// total: number, // total failures in the window// runs: Array<{ // the failed runs (bounded — the first 200)// runId: string;// repo: string;// workflowName: string;// failureClass?: string; // why the run failed// senderUsername?: string; // triggering actor, when known// }>,// }workflowsFailedBatch({ accumulateFor: 10000 }); // One notification per 10s burst of failuresworkflowsFailedBatch({ accumulateFor: 30000, name: 'CI' }); // Only CI failuresworkflowsFailedBatch({ accumulateFor: 30000, source: 'org/repo' }); // Cross-repo source filterA workflow dispatched by a failure trigger (workflowsFailedBatch, or workflowComplete({ status: ['failed'] })) never re-triggers the same batch on its own failure — a notifier that itself fails cannot loop.
jobComplete()
Section titled “jobComplete()”Create a job completion trigger. Fires automatically when a specific job within a workflow finishes. Returns a frozen JobCompleteTriggerConfig.
function jobComplete(config?: JobCompleteConfigInput): JobCompleteTriggerConfig;Config options:
interface JobCompleteConfigInput { workflow?: string; // Filter by workflow name job?: string; // Filter by job name status?: JobCompleteStatus[]; // Filter by completion status source?: string; // Cross-repo source filter description?: string;}type JobCompleteStatus = 'success' | 'failed' | 'cancelled' | 'skipped';jobComplete(); // Any job completionjobComplete({ workflow: 'CI', job: 'build' }); // Specific workflow + jobjobComplete({ workflow: 'CI', job: 'build', status: ['success'] }); // Success onlyjobComplete({ workflow: 'CI', job: 'build', source: 'org/repo' }); // Cross-repojobComplete() starts a new workflow run that reacts to another job finishing (gated on the prior job’s status). For same-run fan-out — generating follow-up jobs from a prior job’s outputs within the same run — use a result-aware dynamicJob(group, { needs, generate }) instead.
genericWebhook()
Section titled “genericWebhook()”Create a generic webhook trigger. Fires when a non-GitHub webhook is received from an external source configured via the admin API. Returns a frozen GenericWebhookTriggerConfig.
function genericWebhook(config: GenericWebhookConfigInput): GenericWebhookTriggerConfig;Config options:
interface GenericWebhookConfigInput { source: string; // Required: must match `--name` from `kici-admin source add generic` events?: string[]; // Filter by event types match?: Record<string, unknown>; // JSONPath payload matching not?: Record<string, unknown>; // Negative JSONPath filter auth?: GenericWebhookAuth; // HMAC or API key authentication path?: string; // URL path pattern (replaces source for URL matching) description?: string;}genericWebhook({ source: 'argocd' }); // Any event from ArgoCDgenericWebhook({ source: 'argocd', events: ['deploy.success'] }); // Specific eventsgenericWebhook({ source: 'argocd', match: { '$.env': 'prod' } }); // With payload filtergenericWebhook({ source: 'argocd', not: { '$.dry_run': true } }); // Negative filtergenericWebhook({ source: 'stripe', auth: { method: 'hmac-sha256', secret: 'stripe-key', signatureHeader: 'stripe-signature' },}); // HMAC authgenericWebhook({ source: 'slack', auth: { method: 'api-key', secret: 'slack-token' } }); // API key authgenericWebhook({ source: 'stripe', path: 'stripe/payments' }); // URL path patternschedule()
Section titled “schedule()”Create a cron-based schedule trigger. Returns a frozen ScheduleTriggerConfig.
function schedule(config: ScheduleConfigInput): ScheduleTriggerConfig;Config options:
interface ScheduleConfigInput { cron: string; // Required: cron expression (5-field) timezone?: string; // Timezone for cron evaluation (default: 'UTC') description?: string; // Human-readable description of the schedule inputs?: DispatchInputsMap; // Optional: defaults-only typed inputs (see below)}schedule({ cron: '0 * * * *' }); // Every hourschedule({ cron: '0 0 * * *' }); // Daily at midnight UTCschedule({ cron: '0 9 * * 1', timezone: 'America/New_York' }); // Monday 9am ETschedule({ cron: '*/15 * * * *', description: 'health check every 15 min' });A workflow may declare multiple schedule() triggers. Each schedule is
evaluated and fired independently — a Monday 9am schedule and a Friday 6pm
schedule on the same workflow both run at their own times:
on: [schedule({ cron: '0 9 * * 1' }), schedule({ cron: '0 18 * * 5' })];Schedule inputs (defaults-only)
Section titled “Schedule inputs (defaults-only)”A schedule() trigger may declare typed inputs. A cron or dashboard
“run now” fire carries no operator-supplied values, so each input resolves
from its declared default and is exposed to steps and rules as
ctx.dispatchInputs — the same surface as typed dispatch inputs.
Because there is no operator to supply a value, every schedule input must
declare a .default() or be .optional(). An input that is neither is
rejected at kici compile time.
import { workflow, job, schedule, z } from '@kici-dev/sdk';
export default workflow('nightly', { on: schedule({ cron: '0 3 * * *', inputs: { mode: z.enum(['full', 'quick']).default('full') }, }), jobs: [ job('build', { runsOn: 'default', run: async (ctx) => { ctx.log(`mode = ${ctx.dispatchInputs.mode}`); // "full" on every fire }, }), ],});You can also share a typed handle via defineDispatchInputs(...) and read it
back with .from(ctx), exactly as with dispatch(). The allowed input types
are the same closed subset documented under
typed dispatch inputs.
lifecycle()
Section titled “lifecycle()”Create a lifecycle trigger for cross-workflow orchestration events. Returns a frozen LifecycleTriggerConfig.
function lifecycle(config: LifecycleConfigInput): LifecycleTriggerConfig;Config options:
interface LifecycleConfigInput { events: LifecycleEvent[]; // Required: lifecycle events to listen for sources?: string[]; // Optional: filter by source repo (e.g., 'org/repo') description?: string; // Human-readable description}
type LifecycleEvent = 'workflow_complete' | 'job_complete' | 'job_failed' | 'registration_updated';lifecycle({ events: ['workflow_complete'] }); // Any workflow completionlifecycle({ events: ['job_failed'], sources: ['org/deploy-repo'] }); // Job failures from specific repolifecycle({ events: ['registration_updated'] }); // Workflow registration changesBranch patterns
Section titled “Branch patterns”Both pr() and push() (as well as tag(), create(), and delete()) accept glob strings and RegExp literals for pattern matching:
// Glob patterns (micromatch syntax)pr({ target: ['main', 'release/*', 'feature/**'] });
// Regex patternspr({ target: /^release\/v\d+\.\d+$/ });
// Mixedpush({ branches: ['main', /^hotfix\//] });Glob patterns use micromatch syntax. Regex patterns use standard JavaScript RegExp.