@kici-dev/sdk 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ import type { $ as Shell } from 'zx';
2
+ import type { ChangedFilesStatus } from '@kici-dev/engine';
3
+ import type { EventPayload } from './events/event-payloads.js';
4
+ import type { RepoInfo } from './context.js';
5
+ import type { FilterContext } from './filter.js';
6
+ /** Input for {@link createFilterContext}. */
7
+ export interface CreateFilterContextInput {
8
+ /** The repo whose event triggered this evaluation. */
9
+ sourceRepo: RepoInfo;
10
+ /** The repo that registered the workflow. */
11
+ workflowRepo: RepoInfo;
12
+ event: EventPayload | Record<string, unknown>;
13
+ changedFiles?: string[];
14
+ /** Defaults to `'fetched'` — a caller that passes a real list needs no status. */
15
+ changedFilesStatus?: ChangedFilesStatus;
16
+ env?: Record<string, string | undefined>;
17
+ /** zx shell handed to the filter. Defaults to the ambient `$`. */
18
+ $?: typeof Shell;
19
+ }
20
+ /**
21
+ * Build a `FilterContext` — the single construction site for the context a
22
+ * workflow's `filter` predicate receives, mirroring {@link createRuleContext}.
23
+ *
24
+ * `changedFiles` is installed through the shared accessor, so it throws
25
+ * `ChangedFilesUnavailableError` when the diff is unavailable instead of
26
+ * reading as an empty list. That matters more here than in a rule: a `false`
27
+ * verdict dispatches none of the workflow's own jobs, and on the
28
+ * organization-wide path it produces no run at all — so a silently-empty diff
29
+ * would suppress the workflow with nothing left to inspect.
30
+ */
31
+ export declare function createFilterContext(input: CreateFilterContextInput): FilterContext;
32
+ //# sourceMappingURL=filter-context.d.ts.map
@@ -0,0 +1,36 @@
1
+ import "./rolldown-runtime-ClRpJifh.js";
2
+ import { defineChangedFilesGetter, eventTypeOf } from "./rules/changed-files.js";
3
+ import { $ } from "zx";
4
+ //#region src/filter-context.ts
5
+ /**
6
+ * Build a `FilterContext` — the single construction site for the context a
7
+ * workflow's `filter` predicate receives, mirroring {@link createRuleContext}.
8
+ *
9
+ * `changedFiles` is installed through the shared accessor, so it throws
10
+ * `ChangedFilesUnavailableError` when the diff is unavailable instead of
11
+ * reading as an empty list. That matters more here than in a rule: a `false`
12
+ * verdict dispatches none of the workflow's own jobs, and on the
13
+ * organization-wide path it produces no run at all — so a silently-empty diff
14
+ * would suppress the workflow with nothing left to inspect.
15
+ */
16
+ function createFilterContext(input) {
17
+ const status = input.changedFilesStatus ?? "fetched";
18
+ const base = {
19
+ sourceRepo: input.sourceRepo,
20
+ workflowRepo: input.workflowRepo,
21
+ event: input.event,
22
+ changedFilesStatus: status,
23
+ env: input.env ?? {},
24
+ $: input.$ ?? $
25
+ };
26
+ defineChangedFilesGetter(base, {
27
+ files: input.changedFiles ?? [],
28
+ status,
29
+ eventType: eventTypeOf(input.event)
30
+ });
31
+ return base;
32
+ }
33
+ //#endregion
34
+ export { createFilterContext };
35
+
36
+ //# sourceMappingURL=filter-context.js.map
@@ -0,0 +1,81 @@
1
+ import type { $ as Shell } from 'zx';
2
+ import type { ChangedFilesStatus } from '@kici-dev/engine';
3
+ import type { EventPayload } from './events/event-payloads.js';
4
+ import type { RepoInfo } from './context.js';
5
+ /**
6
+ * Context handed to a workflow's `filter`, on an evaluating agent with the
7
+ * tree(s) on disk. An organization-wide workflow is evaluated once per
8
+ * (event × workflow repo), before any run row exists; a same-repo workflow is
9
+ * evaluated once per dispatch-bound job and once per generator, where
10
+ * `sourceRepo` and `workflowRepo` are the same repo. See `FilterFn` for what
11
+ * that means for the predicate you write, and which jobs it skips.
12
+ *
13
+ * **The context carries no secrets.** Not because nothing has been resolved
14
+ * yet — on the same-repo path a job's bound contexts, their vars and scoped
15
+ * secrets, its context rules and its approval hold are all resolved BEFORE the
16
+ * evaluating job is queued — but because this context deliberately does not
17
+ * carry them. `filter` sees no `scoped_secrets` and no source-repo credentials
18
+ * beyond the clone token, whichever path it runs on.
19
+ *
20
+ * `sourceRepo.path` is an absolute path on the evaluating agent. Its CONTENTS
21
+ * are stable across evaluations and the later sandbox run; the path itself is
22
+ * not (different workDir, possibly a different machine). Read through it; never
23
+ * embed it in a job name, an output, or anything compared across calls.
24
+ *
25
+ * `sourceRepo.ref` and `sourceRepo.sha` are optional on `RepoInfo` and may be
26
+ * absent for an event that carries no single ref — guard before reading them.
27
+ */
28
+ export interface FilterContext {
29
+ /** The repo whose push triggered this evaluation. */
30
+ sourceRepo: RepoInfo;
31
+ /** The repo that registered the workflow. Identical to `sourceRepo` for a non-global workflow. */
32
+ workflowRepo: RepoInfo;
33
+ /** Normalized event envelope that triggered this evaluation. */
34
+ event: EventPayload;
35
+ /**
36
+ * Files changed in this event (push / pull_request diff, computed from the
37
+ * checkout). Reading this throws `ChangedFilesUnavailableError` when the diff
38
+ * is not available (`changedFilesStatus !== 'fetched'`) — e.g. a
39
+ * schedule/tag/manual event. Guard with `changedFilesStatus` first when a
40
+ * filter runs on such events:
41
+ * `if (ctx.changedFilesStatus !== 'fetched') return true`.
42
+ *
43
+ * The throw is deliberate and mirrors `RuleContext.changedFiles`: a `false`
44
+ * verdict dispatches none of the workflow's own jobs, so a silently-empty
45
+ * list would make a path-based gate suppress it invisibly. How invisibly
46
+ * depends on the path — an organization-wide workflow leaves no run at all,
47
+ * a same-repo one leaves a `success` run carrying only its `__init__*`
48
+ * evaluation jobs, whose log records the verdict.
49
+ */
50
+ changedFiles: string[];
51
+ /** Availability of `changedFiles` (see `changedFiles`). */
52
+ changedFilesStatus: ChangedFilesStatus;
53
+ /** Environment variables. */
54
+ env: Record<string, string | undefined>;
55
+ /** zx shell executor. */
56
+ $: typeof Shell;
57
+ }
58
+ /**
59
+ * A workflow-level pre-dispatch predicate.
60
+ *
61
+ * **It must be pure and deterministic.** For an organization-wide workflow it is
62
+ * called once per (event × workflow repo). For a same-repo workflow it is called
63
+ * once for every job that reaches dispatch and once for every job generator, so
64
+ * a workflow with ten jobs calls it ten times for one event, each on its own
65
+ * agent with its own checkout and its own `ctx.$` shell. Two consequences to
66
+ * design for: anything the predicate does — an API call, a shell command, a
67
+ * write — happens that many times, so keep it cheap and side-effect free; and if
68
+ * it can return different answers for the same event, the workflow will
69
+ * partially dispatch, running some jobs and not others.
70
+ *
71
+ * "Reaches dispatch" excludes two same-repo cases: a job **held for approval**
72
+ * and a job **rejected by a context rule** are never filtered. Each already has
73
+ * a gate — the hold or the rule — so an approved job dispatches with no filter
74
+ * verdict having been taken. A path filter therefore cannot stop an approval
75
+ * request for a job the change does not concern.
76
+ *
77
+ * Decide from `ctx` alone — the event, the changed files, and the checked-out
78
+ * tree — and the same event always yields the same verdict.
79
+ */
80
+ export type FilterFn = (ctx: FilterContext) => boolean | Promise<boolean>;
81
+ //# sourceMappingURL=filter.d.ts.map
package/dist/filter.js ADDED
@@ -0,0 +1,2 @@
1
+ import "./rolldown-runtime-ClRpJifh.js";
2
+ export {};
package/dist/index.d.ts CHANGED
@@ -33,6 +33,9 @@ export { dynamicGroup, isDynamicGroupRef, DYNAMIC_GROUP_TAG } from './dynamic-gr
33
33
  export type { DynamicGroupRef } from './dynamic-group.js';
34
34
  export type { StepContext, Logger, WorkflowInfo, JobInfo, AgentInfo, MatrixJobOutputs, HostJobOutputs, RepoInfo, StepSecretsTyped, KnownSecretKeys, } from './context.js';
35
35
  export { isMatrixJobOutputs, isHostJobOutputs } from './context.js';
36
+ export type { FilterContext, FilterFn } from './filter.js';
37
+ export { createFilterContext } from './filter-context.js';
38
+ export type { CreateFilterContextInput } from './filter-context.js';
36
39
  export type { FanoutPosition } from './fanout-context.js';
37
40
  export { buildKiciApi } from './api-types.js';
38
41
  export type { KiciApi, KiciApiTransport, InfrastructureApi, InfrastructureListResult, InventoryApi, HostApi, HostInventoryEntry, InventorySelector, } from './api-types.js';
package/dist/index.js CHANGED
@@ -6,6 +6,8 @@ import { CacheSpecSchema, normalizeCacheSpecs } from "./cache-types.js";
6
6
  import { isHostJobOutputs, isMatrixJobOutputs } from "./context.js";
7
7
  import { DYNAMIC_GROUP_TAG, dynamicGroup, isDynamicGroupRef } from "./dynamic-group.js";
8
8
  import { SecretNotFoundError } from "./errors.js";
9
+ import { ChangedFilesUnavailableError } from "./rules/changed-files.js";
10
+ import { createFilterContext } from "./filter-context.js";
9
11
  import { fixture } from "./fixture.js";
10
12
  import { createJobOutputProxy, createSnapshotOutputProxy, createStepOutputProxy, getJobOutputsMap, getStepOutputsMap, getStepRefMap, resolveJobOutputs, resolveStepOutputs, setJobOutputsMap, setStepOutputsMap, setStepRefMap } from "./outputs.js";
11
13
  import { step } from "./step.js";
@@ -42,7 +44,7 @@ import { defineDispatchInputs } from "./triggers/dispatch-inputs.js";
42
44
  import "./triggers/index.js";
43
45
  import { afterStep, beforeStep, cleanup, onCancel, onFailure, onSuccess } from "./hooks/index.js";
44
46
  import { onlyOnFanoutIndex, onlyOnFirstHost, onlyOnLastHost, rule, skip } from "./rules/rule.js";
45
- import { ChangedFilesUnavailableError, createRuleContext } from "./rules/context.js";
47
+ import { createRuleContext } from "./rules/context.js";
46
48
  import { evaluateRules } from "./rules/evaluator.js";
47
49
  import { isEventType } from "./events/event-payloads.js";
48
50
  import "./rules/index.js";
@@ -59,4 +61,4 @@ import { defineEvent, isEventDefinition } from "./events/define-event.js";
59
61
  import "./events/index.js";
60
62
  import { agentVersionConverge } from "./fleet/agent-version-converge.js";
61
63
  import { z } from "zod";
62
- export { ARTIFACT_NAME_MAX_LENGTH, ArtifactNameSchema, CacheSpecSchema, ChangedFilesUnavailableError, DYNAMIC_GROUP_TAG, DYNAMIC_JOB_GROUP_TAG, DYNAMIC_JOB_NEEDS_TAG, SecretNotFoundError, WaitForTimeoutError, afterStep, agentVersionConverge, applyIncludeExclude, beforeStep, buildKiciApi, buildNeedsContext, checkStep, cleanup, comment, create, createJobOutputProxy, createRuleContext, createSnapshotOutputProxy, createStepOutputProxy, createStepSecrets, defineDispatchInputs, defineEvent, del as delete, dispatch, dynamicGroup, dynamicJob, evaluateRules, expandMatrix, fixture, flattenStepInputs, fork, genericWebhook, getDynamicJobGroup, getDynamicJobNeeds, getJobOutputsMap, getStepOutputsMap, getStepRefMap, idempotent, idempotentStep, isDynamicFunction, isDynamicGroupRef, isDynamicJobFn, isEventDefinition, isEventType, isHostJobOutputs, isMatrixJobOutputs, isParallelGroup, isStaticArray, isStaticObject, job, jobComplete, kiciEvent, lifecycle, normalizeApproval, normalizeCacheSpecs, onCancel, onFailure, onSuccess, onlyOnFanoutIndex, onlyOnFirstHost, onlyOnLastHost, parallel, pr, provenanceSubjectIsPath, push, release, resolveJobOutputs, resolveStepOutputs, restartHost, review, reviewComment, rule, schedule, setJobOutputsMap, setStepOutputsMap, setStepRefMap, skip, star, status, step, tag, validateDag, waitFor, waitForHostAlive, waitForStep, watch, webhook, workflow, workflowComplete, workflowRun, workflowsFailedBatch, z };
64
+ export { ARTIFACT_NAME_MAX_LENGTH, ArtifactNameSchema, CacheSpecSchema, ChangedFilesUnavailableError, DYNAMIC_GROUP_TAG, DYNAMIC_JOB_GROUP_TAG, DYNAMIC_JOB_NEEDS_TAG, SecretNotFoundError, WaitForTimeoutError, afterStep, agentVersionConverge, applyIncludeExclude, beforeStep, buildKiciApi, buildNeedsContext, checkStep, cleanup, comment, create, createFilterContext, createJobOutputProxy, createRuleContext, createSnapshotOutputProxy, createStepOutputProxy, createStepSecrets, defineDispatchInputs, defineEvent, del as delete, dispatch, dynamicGroup, dynamicJob, evaluateRules, expandMatrix, fixture, flattenStepInputs, fork, genericWebhook, getDynamicJobGroup, getDynamicJobNeeds, getJobOutputsMap, getStepOutputsMap, getStepRefMap, idempotent, idempotentStep, isDynamicFunction, isDynamicGroupRef, isDynamicJobFn, isEventDefinition, isEventType, isHostJobOutputs, isMatrixJobOutputs, isParallelGroup, isStaticArray, isStaticObject, job, jobComplete, kiciEvent, lifecycle, normalizeApproval, normalizeCacheSpecs, onCancel, onFailure, onSuccess, onlyOnFanoutIndex, onlyOnFirstHost, onlyOnLastHost, parallel, pr, provenanceSubjectIsPath, push, release, resolveJobOutputs, resolveStepOutputs, restartHost, review, reviewComment, rule, schedule, setJobOutputsMap, setStepOutputsMap, setStepRefMap, skip, star, status, step, tag, validateDag, waitFor, waitForHostAlive, waitForStep, watch, webhook, workflow, workflowComplete, workflowRun, workflowsFailedBatch, z };
@@ -0,0 +1,37 @@
1
+ import type { ChangedFilesStatus } from '@kici-dev/engine';
2
+ /**
3
+ * Thrown when a rule or a workflow `filter` reads `ctx.changedFiles` but the
4
+ * diff is not available (`changedFilesStatus !== 'fetched'`). `evaluateRules`
5
+ * re-throws this rather than folding it into a `passed=false` skip, so the job
6
+ * fails loudly instead of silently mis-evaluating a path-based gate; the global
7
+ * eval round reports the candidate indeterminate for the same reason.
8
+ */
9
+ export declare class ChangedFilesUnavailableError extends Error {
10
+ readonly changedFilesStatus: ChangedFilesStatus;
11
+ readonly eventType?: string;
12
+ constructor(status: ChangedFilesStatus, eventType?: string);
13
+ }
14
+ /** Read an event's `type` discriminant, when it carries a string one. */
15
+ export declare function eventTypeOf(event: unknown): string | undefined;
16
+ /**
17
+ * Install the throwing `changedFiles` accessor shared by `RuleContext` and
18
+ * `FilterContext`.
19
+ *
20
+ * Both contexts contract `changedFiles` to **throw** when the diff is
21
+ * unavailable rather than read as an empty list: a path-based gate that
22
+ * silently sees no changes suppresses work invisibly, and for a `filter` it
23
+ * suppresses every job the workflow declares. On the organization-wide path
24
+ * that leaves no run row at all; on the same-repo path it leaves a `success`
25
+ * run carrying only the workflow's `__init__*` evaluation jobs.
26
+ *
27
+ * The behaviour lives here, in one `Object.defineProperty`, precisely because a
28
+ * plain `{ changedFiles: [] }` property satisfies both context types with no
29
+ * error anywhere: a second construction site could violate the contract and
30
+ * still typecheck. Every builder of either context calls this.
31
+ */
32
+ export declare function defineChangedFilesGetter(target: object, input: {
33
+ files: string[];
34
+ status: ChangedFilesStatus;
35
+ eventType?: string;
36
+ }): void;
37
+ //# sourceMappingURL=changed-files.d.ts.map
@@ -0,0 +1,54 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ //#region src/rules/changed-files.ts
3
+ /**
4
+ * Thrown when a rule or a workflow `filter` reads `ctx.changedFiles` but the
5
+ * diff is not available (`changedFilesStatus !== 'fetched'`). `evaluateRules`
6
+ * re-throws this rather than folding it into a `passed=false` skip, so the job
7
+ * fails loudly instead of silently mis-evaluating a path-based gate; the global
8
+ * eval round reports the candidate indeterminate for the same reason.
9
+ */
10
+ var ChangedFilesUnavailableError = class extends Error {
11
+ changedFilesStatus;
12
+ eventType;
13
+ constructor(status, eventType) {
14
+ super(`ctx.changedFiles is not available (status: ${status}` + (eventType ? `, event: ${eventType}` : "") + "). Changed files are only defined for push / pull_request events with a computable diff. Guard with ctx.changedFilesStatus before accessing, e.g. `if (ctx.changedFilesStatus !== 'fetched') return true`.");
15
+ this.name = "ChangedFilesUnavailableError";
16
+ this.changedFilesStatus = status;
17
+ this.eventType = eventType;
18
+ }
19
+ };
20
+ /** Read an event's `type` discriminant, when it carries a string one. */
21
+ function eventTypeOf(event) {
22
+ const type = event?.type;
23
+ return typeof type === "string" ? type : void 0;
24
+ }
25
+ /**
26
+ * Install the throwing `changedFiles` accessor shared by `RuleContext` and
27
+ * `FilterContext`.
28
+ *
29
+ * Both contexts contract `changedFiles` to **throw** when the diff is
30
+ * unavailable rather than read as an empty list: a path-based gate that
31
+ * silently sees no changes suppresses work invisibly, and for a `filter` it
32
+ * suppresses every job the workflow declares. On the organization-wide path
33
+ * that leaves no run row at all; on the same-repo path it leaves a `success`
34
+ * run carrying only the workflow's `__init__*` evaluation jobs.
35
+ *
36
+ * The behaviour lives here, in one `Object.defineProperty`, precisely because a
37
+ * plain `{ changedFiles: [] }` property satisfies both context types with no
38
+ * error anywhere: a second construction site could violate the contract and
39
+ * still typecheck. Every builder of either context calls this.
40
+ */
41
+ function defineChangedFilesGetter(target, input) {
42
+ Object.defineProperty(target, "changedFiles", {
43
+ enumerable: true,
44
+ configurable: true,
45
+ get() {
46
+ if (input.status !== "fetched") throw new ChangedFilesUnavailableError(input.status, input.eventType);
47
+ return input.files;
48
+ }
49
+ });
50
+ }
51
+ //#endregion
52
+ export { ChangedFilesUnavailableError, defineChangedFilesGetter, eventTypeOf };
53
+
54
+ //# sourceMappingURL=changed-files.js.map
@@ -1,18 +1,9 @@
1
1
  import type { ChangedFilesStatus } from '@kici-dev/engine';
2
2
  import type { EventPayload } from '../events/event-payloads.js';
3
3
  import type { FanoutPosition } from '../fanout-context.js';
4
+ import type { RepoInfo } from '../context.js';
4
5
  import type { RuleContext } from './types.js';
5
- /**
6
- * Thrown when a rule reads `ctx.changedFiles` but the diff is not available
7
- * (`changedFilesStatus !== 'fetched'`). `evaluateRules` re-throws this rather
8
- * than folding it into a `passed=false` skip, so the job fails loudly instead
9
- * of silently mis-evaluating a path-based gate.
10
- */
11
- export declare class ChangedFilesUnavailableError extends Error {
12
- readonly changedFilesStatus: ChangedFilesStatus;
13
- readonly eventType?: string;
14
- constructor(status: ChangedFilesStatus, eventType?: string);
15
- }
6
+ export { ChangedFilesUnavailableError } from './changed-files.js';
16
7
  /** Input for {@link createRuleContext}. */
17
8
  export interface CreateRuleContextInput {
18
9
  event: EventPayload | Record<string, unknown>;
@@ -22,6 +13,16 @@ export interface CreateRuleContextInput {
22
13
  env?: Record<string, string | undefined>;
23
14
  dispatchInputs?: Readonly<Record<string, string | number | boolean | null>>;
24
15
  fanout?: FanoutPosition;
16
+ /**
17
+ * The repo whose event triggered this run. Supplied for a global workflow.
18
+ *
19
+ * `path` is an absolute path into the evaluating machine's work directory and
20
+ * is NOT stable across evaluations — read the tree through it, never compare
21
+ * it. `ref` / `sha` are optional on `RepoInfo`; do not assume either is set.
22
+ */
23
+ sourceRepo?: RepoInfo;
24
+ /** The repo that registered the workflow. Identical to `sourceRepo` outside a global workflow. */
25
+ workflowRepo?: RepoInfo;
25
26
  }
26
27
  /**
27
28
  * Build a RuleContext. `changedFiles` is exposed as a getter: it returns the
@@ -1,23 +1,8 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
+ import { ChangedFilesUnavailableError, defineChangedFilesGetter, eventTypeOf } from "./changed-files.js";
2
3
  import { $ } from "zx";
3
4
  //#region src/rules/context.ts
4
5
  /**
5
- * Thrown when a rule reads `ctx.changedFiles` but the diff is not available
6
- * (`changedFilesStatus !== 'fetched'`). `evaluateRules` re-throws this rather
7
- * than folding it into a `passed=false` skip, so the job fails loudly instead
8
- * of silently mis-evaluating a path-based gate.
9
- */
10
- var ChangedFilesUnavailableError = class extends Error {
11
- changedFilesStatus;
12
- eventType;
13
- constructor(status, eventType) {
14
- super(`ctx.changedFiles is not available (status: ${status}` + (eventType ? `, event: ${eventType}` : "") + "). Changed files are only defined for push / pull_request events with a computable diff. Guard with ctx.changedFilesStatus before accessing, e.g. `if (ctx.changedFilesStatus !== 'fetched') return true`.");
15
- this.name = "ChangedFilesUnavailableError";
16
- this.changedFilesStatus = status;
17
- this.eventType = eventType;
18
- }
19
- };
20
- /**
21
6
  * Build a RuleContext. `changedFiles` is exposed as a getter: it returns the
22
7
  * list when `changedFilesStatus === 'fetched'`, otherwise it throws
23
8
  * `ChangedFilesUnavailableError`. This is the single construction site for a
@@ -25,23 +10,20 @@ var ChangedFilesUnavailableError = class extends Error {
25
10
  */
26
11
  function createRuleContext(input) {
27
12
  const status = input.changedFilesStatus ?? "fetched";
28
- const files = input.changedFiles ?? [];
29
- const eventType = typeof input.event.type === "string" ? input.event.type : void 0;
30
13
  const base = {
31
14
  event: input.event,
32
15
  changedFilesStatus: status,
33
16
  env: input.env ?? {},
34
17
  dispatchInputs: input.dispatchInputs ?? {},
35
18
  ...input.fanout && { fanout: input.fanout },
19
+ ...input.sourceRepo && { sourceRepo: input.sourceRepo },
20
+ ...input.workflowRepo && { workflowRepo: input.workflowRepo },
36
21
  $
37
22
  };
38
- Object.defineProperty(base, "changedFiles", {
39
- enumerable: true,
40
- configurable: true,
41
- get() {
42
- if (status !== "fetched") throw new ChangedFilesUnavailableError(status, eventType);
43
- return files;
44
- }
23
+ defineChangedFilesGetter(base, {
24
+ files: input.changedFiles ?? [],
25
+ status,
26
+ eventType: eventTypeOf(input.event)
45
27
  });
46
28
  return base;
47
29
  }
@@ -1,5 +1,6 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
- import { ChangedFilesUnavailableError } from "./context.js";
2
+ import { ChangedFilesUnavailableError } from "./changed-files.js";
3
+ import "./context.js";
3
4
  import { toErrorMessage } from "@kici-dev/core";
4
5
  //#region src/rules/evaluator.ts
5
6
  /**
@@ -1,6 +1,7 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
+ import { ChangedFilesUnavailableError } from "./changed-files.js";
2
3
  import { onlyOnFanoutIndex, onlyOnFirstHost, onlyOnLastHost, rule, skip } from "./rule.js";
3
- import { ChangedFilesUnavailableError, createRuleContext } from "./context.js";
4
+ import { createRuleContext } from "./context.js";
4
5
  import { evaluateRules } from "./evaluator.js";
5
6
  import { isEventType } from "../events/event-payloads.js";
6
7
  export { ChangedFilesUnavailableError, createRuleContext, evaluateRules, isEventType, onlyOnFanoutIndex, onlyOnFirstHost, onlyOnLastHost, rule, skip };
@@ -1,6 +1,7 @@
1
1
  import type { $ as Shell } from 'zx';
2
2
  import type { EventPayload } from '../events/event-payloads.js';
3
3
  import type { FanoutPosition } from '../fanout-context.js';
4
+ import type { RepoInfo } from '../context.js';
4
5
  export type { EventPayload } from '../events/event-payloads.js';
5
6
  /**
6
7
  * Context passed to rule check functions.
@@ -21,6 +22,14 @@ export interface RuleContext {
21
22
  changedFiles: string[];
22
23
  /** Availability of `changedFiles` (see `changedFiles`). */
23
24
  changedFilesStatus: import('@kici-dev/engine').ChangedFilesStatus;
25
+ /**
26
+ * The repo whose event triggered this run. Present for a global workflow and
27
+ * for any evaluation that runs with a checkout; undefined otherwise.
28
+ * Read through `.path`; never embed it in a job name (see FilterContext).
29
+ */
30
+ sourceRepo?: RepoInfo;
31
+ /** The repo that registered the workflow. Identical to `sourceRepo` outside a global workflow. */
32
+ workflowRepo?: RepoInfo;
24
33
  /** Environment variables */
25
34
  env: Record<string, string | undefined>;
26
35
  /** Operator-supplied, validated + coerced workflow-dispatch inputs. Empty when none declared. */
@@ -2,8 +2,8 @@ import "../rolldown-runtime-ClRpJifh.js";
2
2
  import { resolveJobOutputs, resolveStepOutputs } from "../outputs.js";
3
3
  import { createStepSecrets } from "../secrets.js";
4
4
  import { isEventDefinition } from "../events/define-event.js";
5
- import { initZx } from "@kici-dev/core";
6
5
  import { $ } from "zx";
6
+ import { initZx } from "@kici-dev/core";
7
7
  import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
8
8
  import { tmpdir } from "node:os";
9
9
  import { dirname, join } from "node:path";
@@ -38,6 +38,8 @@ function pr(config) {
38
38
  sourceBranches: Object.freeze([...sourceBranches]),
39
39
  paths: Object.freeze([...paths]),
40
40
  repos: Object.freeze([...repos]),
41
+ ...config?.requires !== void 0 && { requires: Object.freeze([...config.requires]) },
42
+ ...config?.commitMessage !== void 0 && { commitMessage: config.commitMessage },
41
43
  ...config?.description !== void 0 && { description: config.description }
42
44
  };
43
45
  return Object.freeze(result);
@@ -38,6 +38,8 @@ function push(config) {
38
38
  tags: Object.freeze([...tags]),
39
39
  paths: Object.freeze([...paths]),
40
40
  repos: Object.freeze([...repos]),
41
+ ...config?.requires !== void 0 && { requires: Object.freeze([...config.requires]) },
42
+ ...config?.commitMessage !== void 0 && { commitMessage: config.commitMessage },
41
43
  ...config?.description !== void 0 && { description: config.description }
42
44
  };
43
45
  return Object.freeze(result);
@@ -21,6 +21,8 @@ function tag(config) {
21
21
  _tag: "TagTrigger",
22
22
  patterns: Object.freeze([...patterns]),
23
23
  repos: Object.freeze([...repos]),
24
+ ...config?.requires !== void 0 && { requires: Object.freeze([...config.requires]) },
25
+ ...config?.commitMessage !== void 0 && { commitMessage: config.commitMessage },
24
26
  ...config?.description !== void 0 && { description: config.description }
25
27
  };
26
28
  return Object.freeze(result);
@@ -3,6 +3,8 @@
3
3
  * Supports both glob patterns and regex patterns for branch/path matching.
4
4
  */
5
5
  import type { z } from 'zod';
6
+ import type { ContentRequirement, TextMatch } from '@kici-dev/engine';
7
+ export type { ContentRequirement, ContentFormat, TextMatch } from '@kici-dev/engine';
6
8
  /**
7
9
  * A declared map of typed workflow-dispatch inputs: `{ name: ZodSchema }`.
8
10
  * Each schema must fall within the closed dispatch-input subset (extracted by
@@ -44,6 +46,10 @@ export interface PrTriggerConfig {
44
46
  readonly sourceBranches: readonly BranchPattern[];
45
47
  readonly paths: readonly string[];
46
48
  readonly repos: readonly BranchPattern[];
49
+ /** Declarative static content filter over source files at the event ref (AND-ed). */
50
+ readonly requires?: readonly ContentRequirement[];
51
+ /** Declarative static filter over the event's commit message / PR title+body. */
52
+ readonly commitMessage?: TextMatch;
47
53
  readonly description?: string;
48
54
  }
49
55
  /**
@@ -55,6 +61,10 @@ export interface PushTriggerConfig {
55
61
  readonly tags: readonly BranchPattern[];
56
62
  readonly paths: readonly string[];
57
63
  readonly repos: readonly BranchPattern[];
64
+ /** Declarative static content filter over source files at the event ref (AND-ed). */
65
+ readonly requires?: readonly ContentRequirement[];
66
+ /** Declarative static filter over the event's commit message / PR title+body. */
67
+ readonly commitMessage?: TextMatch;
58
68
  readonly description?: string;
59
69
  }
60
70
  /**
@@ -64,6 +74,10 @@ export interface TagTriggerConfig {
64
74
  readonly _tag: 'TagTrigger';
65
75
  readonly patterns: readonly BranchPattern[];
66
76
  readonly repos: readonly BranchPattern[];
77
+ /** Declarative static content filter over source files at the event ref (AND-ed). */
78
+ readonly requires?: readonly ContentRequirement[];
79
+ /** Declarative static filter over the event's commit message / PR title+body. */
80
+ readonly commitMessage?: TextMatch;
67
81
  readonly description?: string;
68
82
  }
69
83
  /**
@@ -72,6 +86,10 @@ export interface TagTriggerConfig {
72
86
  export interface TagConfigInput {
73
87
  readonly patterns?: string | RegExp | (string | RegExp)[];
74
88
  readonly repos?: string | RegExp | (string | RegExp)[];
89
+ /** Declarative static content filter over source files at the event ref (AND-ed). */
90
+ readonly requires?: readonly ContentRequirement[];
91
+ /** Declarative static filter over the event's commit message / PR title+body. */
92
+ readonly commitMessage?: TextMatch;
75
93
  readonly description?: string;
76
94
  }
77
95
  export type CommentAction = 'created' | 'edited' | 'deleted';
@@ -558,6 +576,10 @@ export interface PrConfigInput {
558
576
  readonly source?: string | RegExp | (string | RegExp)[];
559
577
  readonly paths?: string[];
560
578
  readonly repos?: string | RegExp | (string | RegExp)[];
579
+ /** Declarative static content filter over source files at the event ref (AND-ed). */
580
+ readonly requires?: readonly ContentRequirement[];
581
+ /** Declarative static filter over the event's commit message / PR title+body. */
582
+ readonly commitMessage?: TextMatch;
561
583
  readonly description?: string;
562
584
  }
563
585
  /**
@@ -569,6 +591,10 @@ export interface PushConfigInput {
569
591
  readonly tags?: string | RegExp | (string | RegExp)[];
570
592
  readonly paths?: string[];
571
593
  readonly repos?: string | RegExp | (string | RegExp)[];
594
+ /** Declarative static content filter over source files at the event ref (AND-ed). */
595
+ readonly requires?: readonly ContentRequirement[];
596
+ /** Declarative static filter over the event's commit message / PR title+body. */
597
+ readonly commitMessage?: TextMatch;
572
598
  readonly description?: string;
573
599
  }
574
600
  /**
package/dist/types.d.ts CHANGED
@@ -2,7 +2,8 @@ import type { z } from 'zod';
2
2
  import type { $ as Shell } from 'zx';
3
3
  import type { RetryBackoff } from '@kici-dev/core';
4
4
  import type { ResourceRequest, RunsOnAllInput, OnUnreachableMode, NeedsWhen, ExecutionJobStatus } from '@kici-dev/engine';
5
- import type { StepContext, Logger } from './context.js';
5
+ import type { StepContext, Logger, RepoInfo } from './context.js';
6
+ import type { FilterFn } from './filter.js';
6
7
  import type { SingleNeedEntry, NeedsContext } from './needs-context.js';
7
8
  import type { TriggerConfig } from './triggers/types.js';
8
9
  import type { Rule } from './rules/types.js';
@@ -308,6 +309,14 @@ export interface DynamicJobContext {
308
309
  env: Record<string, string | undefined>;
309
310
  /** Typed KiCI API — orchestrator queries over WS (e.g., kici.infrastructure.list()) */
310
311
  kici: KiciApi;
312
+ /**
313
+ * The repo whose event triggered this run. Present for a global workflow and
314
+ * for any evaluation that runs with a checkout; undefined otherwise.
315
+ * Read through `.path`; never embed it in a job name (see FilterContext).
316
+ */
317
+ sourceRepo?: RepoInfo;
318
+ /** The repo that registered the workflow. Identical to `sourceRepo` outside a global workflow. */
319
+ workflowRepo?: RepoInfo;
311
320
  }
312
321
  /**
313
322
  * Async function that generates jobs dynamically at runtime.
@@ -845,6 +854,23 @@ export interface Workflow {
845
854
  readonly on?: TriggerConfig[];
846
855
  /** Rules for conditional execution */
847
856
  readonly rules?: Rule[];
857
+ /**
858
+ * Pre-dispatch predicate deciding whether this workflow applies to the event's
859
+ * source repo. A `false` result suppresses the workflow's jobs — none is
860
+ * dispatched, and none is reported as skipped.
861
+ *
862
+ * An organization-wide workflow is evaluated once per (event × workflow repo)
863
+ * before any run row is created, so a `false` verdict produces no run. A
864
+ * same-repo workflow is evaluated once per job and once per job generator,
865
+ * after the run row exists — so a run is recorded showing the evaluation and
866
+ * none of the workflow's own jobs. Because it runs once per job there, the
867
+ * predicate must be cheap, side-effect free, and deterministic; see `FilterFn`.
868
+ *
869
+ * One same-repo exception: a job **held for approval** or **rejected by a
870
+ * context rule** is not filtered at all. The hold or the rule is its gate, so
871
+ * an approved job dispatches with no filter verdict having been taken.
872
+ */
873
+ readonly filter?: FilterFn;
848
874
  /** Optional description */
849
875
  readonly description?: string;
850
876
  /**
@@ -899,6 +925,26 @@ export interface WorkflowOptions {
899
925
  on?: Trigger | Trigger[];
900
926
  /** Rules that must pass for workflow to execute */
901
927
  rules?: Rule[];
928
+ /**
929
+ * Pre-dispatch predicate deciding whether this workflow applies to the event's
930
+ * source repo. Runs on an evaluating agent with the tree(s) on disk, and a
931
+ * `false` result suppresses the workflow's jobs — none is dispatched, and none
932
+ * is reported as skipped.
933
+ *
934
+ * An organization-wide workflow is evaluated once per (event × workflow repo)
935
+ * before any run row is created, so a `false` verdict produces no run. A
936
+ * same-repo workflow is evaluated once per job and once per job generator,
937
+ * after the run row exists — so a run is recorded showing the evaluation and
938
+ * none of the workflow's own jobs. Because it runs once per job there, the
939
+ * predicate must be cheap, side-effect free, and deterministic; see `FilterFn`.
940
+ *
941
+ * One same-repo exception: a job **held for approval** or **rejected by a
942
+ * context rule** is not filtered at all. The hold or the rule is its gate, so
943
+ * an approved job dispatches with no filter verdict having been taken.
944
+ *
945
+ * See `FilterContext` for what it receives and what it deliberately does not.
946
+ */
947
+ filter?: FilterFn;
902
948
  /** Optional description for documentation */
903
949
  description?: string;
904
950
  /**
package/dist/workflow.js CHANGED
@@ -60,12 +60,14 @@ function workflow(name, options) {
60
60
  if (options.installEnv) {
61
61
  for (const [i, ref] of options.installEnv.entries()) if (!isQualifiedSecretRef(ref)) throw new Error(`workflow('${name}'): installEnv[${i}] must use qualified <context>:<secret-name> syntax (got: ${ref})`);
62
62
  }
63
+ if (options.filter !== void 0 && typeof options.filter !== "function") throw new Error(`workflow('${name}'): filter must be a function (got: ${typeof options.filter})`);
63
64
  return {
64
65
  _tag: "Workflow",
65
66
  name,
66
67
  jobs: options.jobs,
67
68
  on: normalizeTriggers(options.on),
68
69
  rules: options.rules,
70
+ filter: options.filter,
69
71
  description: options.description,
70
72
  hashFiles: options.hashFiles,
71
73
  registries: options.registries,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kici-dev/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
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",
@@ -54,8 +54,8 @@
54
54
  "micromatch": "^4.0.8",
55
55
  "zod": "^4.4.3",
56
56
  "zx": "^8.8.5",
57
- "@kici-dev/core": "0.3.0",
58
- "@kici-dev/engine": "0.3.0"
57
+ "@kici-dev/core": "0.5.0",
58
+ "@kici-dev/engine": "0.5.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/micromatch": "^4.0.10"
package/sbom.spdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "spdxVersion": "SPDX-2.3",
3
3
  "dataLicense": "CC0-1.0",
4
4
  "SPDXID": "SPDXRef-DOCUMENT",
5
- "name": "@kici-dev/sdk@0.3.0",
6
- "documentNamespace": "https://kici.dev/sbom/%40kici-dev%2Fsdk/0.3.0/d6c98e6b-cc60-4e6c-a027-ad3272eb60b7",
5
+ "name": "@kici-dev/sdk@0.5.0",
6
+ "documentNamespace": "https://kici.dev/sbom/%40kici-dev%2Fsdk/0.5.0/ce1a88a9-f83c-40fc-b642-cfdfb3ab5f7c",
7
7
  "creationInfo": {
8
- "created": "2026-08-09T01:30:17Z",
8
+ "created": "2026-08-15T19:15:41Z",
9
9
  "creators": [
10
10
  "Tool: kici-sbom-generator"
11
11
  ]
@@ -146,9 +146,9 @@
146
146
  "homepage": "https://ericsmekens.github.io/jsep/tree/master/packages/regex#readme"
147
147
  },
148
148
  {
149
- "SPDXID": "SPDXRef-Package--kici-dev-core-0.3.0",
149
+ "SPDXID": "SPDXRef-Package--kici-dev-core-0.5.0",
150
150
  "name": "@kici-dev/core",
151
- "versionInfo": "0.3.0",
151
+ "versionInfo": "0.5.0",
152
152
  "downloadLocation": "NOASSERTION",
153
153
  "filesAnalyzed": false,
154
154
  "licenseConcluded": "NOASSERTION",
@@ -159,16 +159,16 @@
159
159
  {
160
160
  "referenceCategory": "PACKAGE-MANAGER",
161
161
  "referenceType": "purl",
162
- "referenceLocator": "pkg:npm/%40kici-dev/core@0.3.0"
162
+ "referenceLocator": "pkg:npm/%40kici-dev/core@0.5.0"
163
163
  }
164
164
  ],
165
165
  "description": "Light shared utilities for the KiCI stack (logging, errors, formatting, crypto, zx init, the TypeScript ESM loader hook). No server-side dependencies.",
166
166
  "homepage": "https://kici.dev"
167
167
  },
168
168
  {
169
- "SPDXID": "SPDXRef-Package--kici-dev-engine-0.3.0",
169
+ "SPDXID": "SPDXRef-Package--kici-dev-engine-0.5.0",
170
170
  "name": "@kici-dev/engine",
171
- "versionInfo": "0.3.0",
171
+ "versionInfo": "0.5.0",
172
172
  "downloadLocation": "NOASSERTION",
173
173
  "filesAnalyzed": false,
174
174
  "licenseConcluded": "NOASSERTION",
@@ -179,7 +179,7 @@
179
179
  {
180
180
  "referenceCategory": "PACKAGE-MANAGER",
181
181
  "referenceType": "purl",
182
- "referenceLocator": "pkg:npm/%40kici-dev/engine@0.3.0"
182
+ "referenceLocator": "pkg:npm/%40kici-dev/engine@0.5.0"
183
183
  }
184
184
  ],
185
185
  "description": "Shared business logic for the KiCI CI/CD stack: protocol, triggers, state machine, and provider interfaces used by the Platform relay, orchestrator, and compiler.",
@@ -188,7 +188,7 @@
188
188
  {
189
189
  "SPDXID": "SPDXRef-RootPackage",
190
190
  "name": "@kici-dev/sdk",
191
- "versionInfo": "0.3.0",
191
+ "versionInfo": "0.5.0",
192
192
  "downloadLocation": "NOASSERTION",
193
193
  "filesAnalyzed": false,
194
194
  "licenseConcluded": "NOASSERTION",
@@ -199,7 +199,7 @@
199
199
  {
200
200
  "referenceCategory": "PACKAGE-MANAGER",
201
201
  "referenceType": "purl",
202
- "referenceLocator": "pkg:npm/%40kici-dev/sdk@0.3.0"
202
+ "referenceLocator": "pkg:npm/%40kici-dev/sdk@0.5.0"
203
203
  }
204
204
  ],
205
205
  "description": "TypeScript SDK for defining KiCI workflows. Import into `.kici/workflows/*.ts` to declare workflows, jobs, steps, triggers, rules, and matrix configurations.",
@@ -1491,6 +1491,26 @@
1491
1491
  ],
1492
1492
  "description": "A logger for just about everything."
1493
1493
  },
1494
+ {
1495
+ "SPDXID": "SPDXRef-Package-yaml-2.9.0",
1496
+ "name": "yaml",
1497
+ "versionInfo": "2.9.0",
1498
+ "downloadLocation": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
1499
+ "filesAnalyzed": false,
1500
+ "licenseConcluded": "NOASSERTION",
1501
+ "licenseDeclared": "ISC",
1502
+ "copyrightText": "NOASSERTION",
1503
+ "supplier": "NOASSERTION",
1504
+ "externalRefs": [
1505
+ {
1506
+ "referenceCategory": "PACKAGE-MANAGER",
1507
+ "referenceType": "purl",
1508
+ "referenceLocator": "pkg:npm/yaml@2.9.0"
1509
+ }
1510
+ ],
1511
+ "description": "JavaScript parser and stringifier for YAML",
1512
+ "homepage": "https://eemeli.org/yaml/"
1513
+ },
1494
1514
  {
1495
1515
  "SPDXID": "SPDXRef-Package-zod-4.4.3",
1496
1516
  "name": "zod",
@@ -1584,68 +1604,73 @@
1584
1604
  "relationshipType": "DEPENDS_ON"
1585
1605
  },
1586
1606
  {
1587
- "spdxElementId": "SPDXRef-Package--kici-dev-core-0.3.0",
1607
+ "spdxElementId": "SPDXRef-Package--kici-dev-core-0.5.0",
1588
1608
  "relatedSpdxElement": "SPDXRef-Package-oxc-transform-0.140.0",
1589
1609
  "relationshipType": "DEPENDS_ON"
1590
1610
  },
1591
1611
  {
1592
- "spdxElementId": "SPDXRef-Package--kici-dev-core-0.3.0",
1612
+ "spdxElementId": "SPDXRef-Package--kici-dev-core-0.5.0",
1593
1613
  "relatedSpdxElement": "SPDXRef-Package-picocolors-1.1.1",
1594
1614
  "relationshipType": "DEPENDS_ON"
1595
1615
  },
1596
1616
  {
1597
- "spdxElementId": "SPDXRef-Package--kici-dev-core-0.3.0",
1617
+ "spdxElementId": "SPDXRef-Package--kici-dev-core-0.5.0",
1598
1618
  "relatedSpdxElement": "SPDXRef-Package-winston-daily-rotate-file-5.0.0",
1599
1619
  "relationshipType": "DEPENDS_ON"
1600
1620
  },
1601
1621
  {
1602
- "spdxElementId": "SPDXRef-Package--kici-dev-core-0.3.0",
1622
+ "spdxElementId": "SPDXRef-Package--kici-dev-core-0.5.0",
1603
1623
  "relatedSpdxElement": "SPDXRef-Package-winston-3.19.0",
1604
1624
  "relationshipType": "DEPENDS_ON"
1605
1625
  },
1606
1626
  {
1607
- "spdxElementId": "SPDXRef-Package--kici-dev-core-0.3.0",
1627
+ "spdxElementId": "SPDXRef-Package--kici-dev-core-0.5.0",
1608
1628
  "relatedSpdxElement": "SPDXRef-Package-zod-4.4.3",
1609
1629
  "relationshipType": "DEPENDS_ON"
1610
1630
  },
1611
1631
  {
1612
- "spdxElementId": "SPDXRef-Package--kici-dev-core-0.3.0",
1632
+ "spdxElementId": "SPDXRef-Package--kici-dev-core-0.5.0",
1613
1633
  "relatedSpdxElement": "SPDXRef-Package-zx-8.8.5",
1614
1634
  "relationshipType": "DEPENDS_ON"
1615
1635
  },
1616
1636
  {
1617
- "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.3.0",
1637
+ "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.5.0",
1618
1638
  "relatedSpdxElement": "SPDXRef-Package-jose-6.2.3",
1619
1639
  "relationshipType": "DEPENDS_ON"
1620
1640
  },
1621
1641
  {
1622
- "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.3.0",
1642
+ "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.5.0",
1623
1643
  "relatedSpdxElement": "SPDXRef-Package-jsonpath-plus-10.4.0",
1624
1644
  "relationshipType": "DEPENDS_ON"
1625
1645
  },
1626
1646
  {
1627
- "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.3.0",
1647
+ "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.5.0",
1628
1648
  "relatedSpdxElement": "SPDXRef-Package-picomatch-4.0.5",
1629
1649
  "relationshipType": "DEPENDS_ON"
1630
1650
  },
1631
1651
  {
1632
- "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.3.0",
1652
+ "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.5.0",
1633
1653
  "relatedSpdxElement": "SPDXRef-Package-safe-regex-2.1.1",
1634
1654
  "relationshipType": "DEPENDS_ON"
1635
1655
  },
1636
1656
  {
1637
- "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.3.0",
1657
+ "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.5.0",
1658
+ "relatedSpdxElement": "SPDXRef-Package-yaml-2.9.0",
1659
+ "relationshipType": "DEPENDS_ON"
1660
+ },
1661
+ {
1662
+ "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.5.0",
1638
1663
  "relatedSpdxElement": "SPDXRef-Package-zod-4.4.3",
1639
1664
  "relationshipType": "DEPENDS_ON"
1640
1665
  },
1641
1666
  {
1642
1667
  "spdxElementId": "SPDXRef-RootPackage",
1643
- "relatedSpdxElement": "SPDXRef-Package--kici-dev-core-0.3.0",
1668
+ "relatedSpdxElement": "SPDXRef-Package--kici-dev-core-0.5.0",
1644
1669
  "relationshipType": "DEPENDS_ON"
1645
1670
  },
1646
1671
  {
1647
1672
  "spdxElementId": "SPDXRef-RootPackage",
1648
- "relatedSpdxElement": "SPDXRef-Package--kici-dev-engine-0.3.0",
1673
+ "relatedSpdxElement": "SPDXRef-Package--kici-dev-engine-0.5.0",
1649
1674
  "relationshipType": "DEPENDS_ON"
1650
1675
  },
1651
1676
  {