@kici-dev/engine 0.12.0 → 0.14.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.
@@ -2985,6 +2985,7 @@ export declare const platformToOrchestratorMessageSchema: z.ZodDiscriminatedUnio
2985
2985
  "held_run.request": "held_run.request";
2986
2986
  "job.cancel": "job.cancel";
2987
2987
  "org_settings.dashboard_write_policy.update": "org_settings.dashboard_write_policy.update";
2988
+ "peer.forget": "peer.forget";
2988
2989
  purge_chunk: "purge_chunk";
2989
2990
  "registration.delete": "registration.delete";
2990
2991
  "registration.disable": "registration.disable";
@@ -3001,6 +3002,8 @@ export declare const platformToOrchestratorMessageSchema: z.ZodDiscriminatedUnio
3001
3002
  "runs.list.read": "runs.list.read";
3002
3003
  "scaler.agents.read": "scaler.agents.read";
3003
3004
  "scaler.capacity.read": "scaler.capacity.read";
3005
+ "scaler.orphan.stop": "scaler.orphan.stop";
3006
+ "scaler.orphans.read": "scaler.orphans.read";
3004
3007
  "secret.delete": "secret.delete";
3005
3008
  "secret.list.read": "secret.list.read";
3006
3009
  "secret.reveal": "secret.reveal";
@@ -30,6 +30,12 @@ export interface WorkflowDecision {
30
30
  checks: TraceEntry[];
31
31
  /** Summary reason */
32
32
  summary: string;
33
+ /**
34
+ * The `paths` lists of triggers that matched only because the changed files
35
+ * were unavailable while a git range existed. Set only when no trigger
36
+ * matched outright; the agent decides these from its clone.
37
+ */
38
+ deferredPaths?: string[][];
33
39
  }
34
40
  /**
35
41
  * Max characters of any free-text field a trace entry carries, so an
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Which files an event changed, expressed as a git range.
3
+ *
4
+ * The one definition the orchestrator's changed-files fetchers and the agent's
5
+ * clone diff share, so both layers reach the same answer from the same event.
6
+ * Pure — no I/O and no `node:*` import — because the engine barrel is
7
+ * browser-safe.
8
+ */
9
+ import { z } from 'zod';
10
+ export declare const diffRangeKindSchema: z.ZodEnum<{
11
+ deleted: "deleted";
12
+ "new-branch": "new-branch";
13
+ none: "none";
14
+ pr: "pr";
15
+ "two-dot": "two-dot";
16
+ }>;
17
+ export type DiffRangeKind = z.infer<typeof diffRangeKindSchema>;
18
+ declare const Kind: {
19
+ deleted: "deleted";
20
+ "new-branch": "new-branch";
21
+ none: "none";
22
+ pr: "pr";
23
+ "two-dot": "two-dot";
24
+ };
25
+ export type DiffRange =
26
+ /** `git diff base..head` — a push to an existing branch. */
27
+ {
28
+ kind: (typeof Kind)['two-dot'];
29
+ base: string;
30
+ head: string;
31
+ }
32
+ /** Three-dot `defaultBranch...head` — the files a new branch adds. */
33
+ | {
34
+ kind: (typeof Kind)['new-branch'];
35
+ defaultBranch: string;
36
+ head: string;
37
+ }
38
+ /** A deleted branch: no files changed. */
39
+ | {
40
+ kind: typeof Kind.deleted;
41
+ }
42
+ /** Three-dot `base...HEAD` — a pull request. */
43
+ | {
44
+ kind: typeof Kind.pr;
45
+ base: string;
46
+ }
47
+ /** No range exists: nobody can compute a diff for this event. */
48
+ | {
49
+ kind: typeof Kind.none;
50
+ };
51
+ /** The event fields a range is read from; `SimulatedEvent` and the SDK `EventPayload` both fit. */
52
+ export interface DiffRangeEvent {
53
+ type: string;
54
+ targetBranch?: string;
55
+ baseBranch?: string;
56
+ defaultBranch?: string;
57
+ payload?: Record<string, unknown>;
58
+ }
59
+ /** Whether a SHA is git's all-zero placeholder (a created or deleted ref). */
60
+ export declare function isZeroSha(sha: string): boolean;
61
+ /** The git range an event changed, or `none` when no diff exists anywhere. */
62
+ export declare function resolveDiffRange(event: DiffRangeEvent): DiffRange;
63
+ /** Whether a range lets an agent compute the diff the orchestrator could not. */
64
+ export declare function isDeferrableRange(range: DiffRange): boolean;
65
+ export {};
66
+ //# sourceMappingURL=diff-range.d.ts.map
@@ -0,0 +1,68 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ import { z } from "zod";
3
+ //#region src/trigger/diff-range.ts
4
+ /**
5
+ * Which files an event changed, expressed as a git range.
6
+ *
7
+ * The one definition the orchestrator's changed-files fetchers and the agent's
8
+ * clone diff share, so both layers reach the same answer from the same event.
9
+ * Pure — no I/O and no `node:*` import — because the engine barrel is
10
+ * browser-safe.
11
+ */
12
+ const diffRangeKindSchema = z.enum([
13
+ "two-dot",
14
+ "new-branch",
15
+ "deleted",
16
+ "pr",
17
+ "none"
18
+ ]);
19
+ const Kind = diffRangeKindSchema.enum;
20
+ const ZERO_SHA_RE = /^0+$/;
21
+ /** Whether a SHA is git's all-zero placeholder (a created or deleted ref). */
22
+ function isZeroSha(sha) {
23
+ return ZERO_SHA_RE.test(sha);
24
+ }
25
+ function nonEmptyString(value) {
26
+ return typeof value === "string" && value.length > 0 ? value : void 0;
27
+ }
28
+ function defaultBranchOf(event) {
29
+ if (event.defaultBranch) return event.defaultBranch;
30
+ const repository = event.payload?.repository;
31
+ if (repository === null || typeof repository !== "object") return void 0;
32
+ return nonEmptyString(repository.default_branch);
33
+ }
34
+ /** The git range an event changed, or `none` when no diff exists anywhere. */
35
+ function resolveDiffRange(event) {
36
+ if (event.type === "pull_request") {
37
+ const base = event.baseBranch ?? event.targetBranch;
38
+ return base ? {
39
+ kind: Kind.pr,
40
+ base
41
+ } : { kind: Kind.none };
42
+ }
43
+ if (event.type !== "push") return { kind: Kind.none };
44
+ const before = nonEmptyString(event.payload?.before);
45
+ const after = nonEmptyString(event.payload?.after);
46
+ if (!before || !after) return { kind: Kind.none };
47
+ if (isZeroSha(after)) return { kind: Kind.deleted };
48
+ if (!isZeroSha(before)) return {
49
+ kind: Kind["two-dot"],
50
+ base: before,
51
+ head: after
52
+ };
53
+ const defaultBranch = defaultBranchOf(event);
54
+ if (!defaultBranch || defaultBranch === event.targetBranch) return { kind: Kind.none };
55
+ return {
56
+ kind: Kind["new-branch"],
57
+ defaultBranch,
58
+ head: after
59
+ };
60
+ }
61
+ /** Whether a range lets an agent compute the diff the orchestrator could not. */
62
+ function isDeferrableRange(range) {
63
+ return range.kind === Kind["two-dot"] || range.kind === Kind["new-branch"] || range.kind === Kind.pr;
64
+ }
65
+ //#endregion
66
+ export { diffRangeKindSchema, isDeferrableRange, isZeroSha, resolveDiffRange };
67
+
68
+ //# sourceMappingURL=diff-range.js.map
@@ -17,10 +17,23 @@ export declare function matchPathPatterns(paths: readonly string[], changedFiles
17
17
  * Empty array = no filtering (matches all repos).
18
18
  */
19
19
  export declare function matchRepoPatterns(repos: readonly LockBranchPattern[], sourceRepo: string): boolean;
20
+ /** Set by a push/pr trigger whose `paths` check passed only because the diff was unavailable. */
21
+ /** Trace value of a `paths` check that read an unavailable diff. */
22
+ export declare const UnavailablePathsTrace: {
23
+ /** A git range exists: the agent decides from its clone. */
24
+ readonly DecidedOnAgent: '[unavailable — decided on agent]';
25
+ /** No range the agent can diff: the workflow runs conservatively. */
26
+ readonly Conservative: '[unavailable — matched conservatively]';
27
+ };
28
+ /** Appended to a matched decision's summary when its paths are left to the agent. */
29
+ export declare const DEFERRED_PATHS_SUMMARY_SUFFIX = " \u2014 paths decided on the agent";
30
+ export interface TriggerMatchNotes {
31
+ conservativePaths?: readonly string[];
32
+ }
20
33
  /**
21
34
  * Match a trigger against a simulated event.
22
35
  */
23
- export declare function matchTrigger(trigger: LockTrigger, event: SimulatedEvent, traces: TraceEntry[]): boolean;
36
+ export declare function matchTrigger(trigger: LockTrigger, event: SimulatedEvent, traces: TraceEntry[], notes?: TriggerMatchNotes): boolean;
24
37
  /**
25
38
  * Match all triggers for a workflow against an event.
26
39
  * Returns WorkflowDecision with full trace.
@@ -2,6 +2,7 @@ import "../rolldown-runtime-ClRpJifh.js";
2
2
  import { evaluateTextMatch } from "./text-match.js";
3
3
  import { TraceCheck, createCommitMessageTraceEntry, createTraceEntry, createWorkflowDecision } from "./decision-trace.js";
4
4
  import { getCompiledRegex, getGlobMatcher, getRepoGlobMatcher } from "./compiled-matchers.js";
5
+ import { isDeferrableRange, resolveDiffRange } from "./diff-range.js";
5
6
  import { matchJsonPath, matchJsonPathNot } from "./jsonpath-matcher.js";
6
7
  //#region src/trigger/matcher.ts
7
8
  /**
@@ -142,10 +143,37 @@ function evaluateCommitMessageFilter(trigger, event, traces) {
142
143
  }));
143
144
  return result.pass;
144
145
  }
146
+ /** Set by a push/pr trigger whose `paths` check passed only because the diff was unavailable. */
147
+ /** Trace value of a `paths` check that read an unavailable diff. */
148
+ const UnavailablePathsTrace = {
149
+ /** A git range exists: the agent decides from its clone. */
150
+ DecidedOnAgent: "[unavailable — decided on agent]",
151
+ /** No range the agent can diff: the workflow runs conservatively. */
152
+ Conservative: "[unavailable — matched conservatively]"
153
+ };
154
+ /** Appended to a matched decision's summary when its paths are left to the agent. */
155
+ const DEFERRED_PATHS_SUMMARY_SUFFIX = " — paths decided on the agent";
156
+ /**
157
+ * Evaluate a push/pr trigger's `paths` list and trace it. An `unavailable`
158
+ * diff matches; when the event still has a git range, the match is recorded in
159
+ * `notes` so the workflow's path decision moves to the agent's clone.
160
+ */
161
+ function checkTriggerPaths(paths, event, traces, notes) {
162
+ if (paths.length === 0) return true;
163
+ const changedFiles = event.changedFiles ?? [];
164
+ const { include, exclude } = splitStringPatterns(paths);
165
+ const unavailable = event.changedFilesStatus === "unavailable";
166
+ const deferrable = unavailable && isDeferrableRange(resolveDiffRange(event));
167
+ const matches = matchPathPatterns(paths, changedFiles, event.changedFilesStatus);
168
+ const value = !unavailable ? `[${changedFiles.join(", ")}]` : deferrable ? UnavailablePathsTrace.DecidedOnAgent : UnavailablePathsTrace.Conservative;
169
+ traces.push(createTraceEntry("paths", `include: [${include.join(", ")}] exclude: [${exclude.join(", ")}]`, value, matches));
170
+ if (deferrable && notes) notes.conservativePaths = paths;
171
+ return matches;
172
+ }
145
173
  /**
146
174
  * Match a PR trigger against a simulated event.
147
175
  */
148
- function matchPrTrigger(trigger, event, traces) {
176
+ function matchPrTrigger(trigger, event, traces, notes) {
149
177
  if (event.type !== "pull_request") {
150
178
  traces.push(createTraceEntry("event type", "pull_request", event.type, false));
151
179
  return false;
@@ -171,13 +199,7 @@ function matchPrTrigger(trigger, event, traces) {
171
199
  traces.push(createTraceEntry("source branch", trigger.sourceBranches.map((p) => p.pattern).join("|"), event.sourceBranch, matches));
172
200
  if (!matches) return false;
173
201
  }
174
- if (trigger.paths.length > 0) {
175
- const changedFiles = event.changedFiles ?? [];
176
- const { include, exclude } = splitStringPatterns(trigger.paths);
177
- const matches = matchPathPatterns(trigger.paths, changedFiles, event.changedFilesStatus);
178
- traces.push(createTraceEntry("paths", `include: [${include.join(", ")}] exclude: [${exclude.join(", ")}]`, event.changedFilesStatus === "unavailable" ? "[unavailable — matched conservatively]" : `[${changedFiles.join(", ")}]`, matches));
179
- if (!matches) return false;
180
- }
202
+ if (!checkTriggerPaths(trigger.paths, event, traces, notes)) return false;
181
203
  if (!evaluateCommitMessageFilter(trigger, event, traces)) return false;
182
204
  if (!evaluateRepoFilter(trigger, event, traces)) return false;
183
205
  return true;
@@ -185,7 +207,7 @@ function matchPrTrigger(trigger, event, traces) {
185
207
  /**
186
208
  * Match a push trigger against a simulated event.
187
209
  */
188
- function matchPushTrigger(trigger, event, traces) {
210
+ function matchPushTrigger(trigger, event, traces, notes) {
189
211
  if (event.type !== "push") {
190
212
  traces.push(createTraceEntry("event type", "push", event.type, false));
191
213
  return false;
@@ -196,13 +218,7 @@ function matchPushTrigger(trigger, event, traces) {
196
218
  traces.push(createTraceEntry("branch", trigger.branches.map((p) => p.pattern).join("|"), event.targetBranch, matches));
197
219
  if (!matches) return false;
198
220
  }
199
- if (trigger.paths.length > 0) {
200
- const changedFiles = event.changedFiles ?? [];
201
- const { include, exclude } = splitStringPatterns(trigger.paths);
202
- const matches = matchPathPatterns(trigger.paths, changedFiles, event.changedFilesStatus);
203
- traces.push(createTraceEntry("paths", `include: [${include.join(", ")}] exclude: [${exclude.join(", ")}]`, event.changedFilesStatus === "unavailable" ? "[unavailable — matched conservatively]" : `[${changedFiles.join(", ")}]`, matches));
204
- if (!matches) return false;
205
- }
221
+ if (!checkTriggerPaths(trigger.paths, event, traces, notes)) return false;
206
222
  if (!evaluateCommitMessageFilter(trigger, event, traces)) return false;
207
223
  if (!evaluateRepoFilter(trigger, event, traces)) return false;
208
224
  return true;
@@ -737,10 +753,10 @@ function matchLifecycleTrigger(trigger, event, traces) {
737
753
  /**
738
754
  * Match a trigger against a simulated event.
739
755
  */
740
- function matchTrigger(trigger, event, traces) {
756
+ function matchTrigger(trigger, event, traces, notes) {
741
757
  switch (trigger._type) {
742
- case "pr": return matchPrTrigger(trigger, event, traces);
743
- case "push": return matchPushTrigger(trigger, event, traces);
758
+ case "pr": return matchPrTrigger(trigger, event, traces, notes);
759
+ case "push": return matchPushTrigger(trigger, event, traces, notes);
744
760
  case "tag": return matchTagTrigger(trigger, event, traces);
745
761
  case "comment": return matchCommentTrigger(trigger, event, traces);
746
762
  case "review": return matchReviewTrigger(trigger, event, traces);
@@ -772,13 +788,29 @@ function matchTrigger(trigger, event, traces) {
772
788
  function matchWorkflowTriggers(workflow, event) {
773
789
  const allTraces = [];
774
790
  if (workflow.triggers.length === 0) return createWorkflowDecision(workflow.name, false, [], void 0, "No triggers defined");
791
+ let firstDeferred;
792
+ const deferredPaths = [];
775
793
  for (let i = 0; i < workflow.triggers.length; i++) {
776
794
  const trigger = workflow.triggers[i];
777
795
  if (trigger == null) continue;
778
796
  const triggerTraces = [];
779
- if (matchTrigger(trigger, event, triggerTraces)) return createWorkflowDecision(workflow.name, true, triggerTraces, i, `Matched trigger ${i + 1} (${trigger._type})`);
797
+ const notes = {};
798
+ if (matchTrigger(trigger, event, triggerTraces, notes)) {
799
+ if (notes.conservativePaths === void 0) return createWorkflowDecision(workflow.name, true, triggerTraces, i, `Matched trigger ${i + 1} (${trigger._type})`);
800
+ deferredPaths.push([...notes.conservativePaths]);
801
+ firstDeferred ??= {
802
+ index: i,
803
+ traces: triggerTraces,
804
+ type: trigger._type
805
+ };
806
+ continue;
807
+ }
780
808
  allTraces.push(...triggerTraces);
781
809
  }
810
+ if (firstDeferred) return {
811
+ ...createWorkflowDecision(workflow.name, true, firstDeferred.traces, firstDeferred.index, `Matched trigger ${firstDeferred.index + 1} (${firstDeferred.type})${DEFERRED_PATHS_SUMMARY_SUFFIX}`),
812
+ deferredPaths
813
+ };
782
814
  return createWorkflowDecision(workflow.name, false, allTraces, void 0, "No triggers matched");
783
815
  }
784
816
  /**
@@ -788,6 +820,6 @@ function matchAllWorkflows(workflows, event) {
788
820
  return workflows.map((w) => matchWorkflowTriggers(w, event));
789
821
  }
790
822
  //#endregion
791
- export { matchAllWorkflows, matchBranchPattern, matchPathPatterns, matchRepoPatterns, matchTrigger, matchWorkflowTriggers };
823
+ export { DEFERRED_PATHS_SUMMARY_SUFFIX, UnavailablePathsTrace, matchAllWorkflows, matchBranchPattern, matchPathPatterns, matchRepoPatterns, matchTrigger, matchWorkflowTriggers };
792
824
 
793
825
  //# sourceMappingURL=matcher.js.map
@@ -1131,6 +1131,12 @@ export interface SimulatedEvent {
1131
1131
  isForkPR?: boolean;
1132
1132
  /** Base branch ref for PR events (the branch being merged into). */
1133
1133
  baseBranch?: string;
1134
+ /**
1135
+ * The repository's default branch, when the delivery names it. A new-branch
1136
+ * push diffs against it (`resolveDiffRange`); absent, the range falls back to
1137
+ * `payload.repository.default_branch`.
1138
+ */
1139
+ defaultBranch?: string;
1134
1140
  /** Sender username from the webhook payload (e.g. GitHub login). */
1135
1141
  senderUsername?: string;
1136
1142
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kici-dev/engine",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "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.",
5
5
  "keywords": [
6
6
  "ci",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "license": "AGPL-3.0-only",
25
25
  "engines": {
26
- "node": ">=24"
26
+ "node": ">=24.5.0"
27
27
  },
28
28
  "type": "module",
29
29
  "files": [
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/engine@0.12.0",
6
- "documentNamespace": "https://kici.dev/sbom/%40kici-dev%2Fengine/0.12.0/26df8fdf-b46b-49db-86d9-b586bd3124fa",
5
+ "name": "@kici-dev/engine@0.14.0",
6
+ "documentNamespace": "https://kici.dev/sbom/%40kici-dev%2Fengine/0.14.0/1e6771dc-3781-402f-8aa7-93c6edb56fcc",
7
7
  "creationInfo": {
8
- "created": "2026-09-28T05:10:36Z",
8
+ "created": "2026-10-02T14:42:42Z",
9
9
  "creators": [
10
10
  "Tool: kici-sbom-generator"
11
11
  ]
@@ -54,7 +54,7 @@
54
54
  {
55
55
  "SPDXID": "SPDXRef-RootPackage",
56
56
  "name": "@kici-dev/engine",
57
- "versionInfo": "0.12.0",
57
+ "versionInfo": "0.14.0",
58
58
  "downloadLocation": "NOASSERTION",
59
59
  "filesAnalyzed": false,
60
60
  "licenseConcluded": "NOASSERTION",
@@ -65,7 +65,7 @@
65
65
  {
66
66
  "referenceCategory": "PACKAGE-MANAGER",
67
67
  "referenceType": "purl",
68
- "referenceLocator": "pkg:npm/%40kici-dev/engine@0.12.0"
68
+ "referenceLocator": "pkg:npm/%40kici-dev/engine@0.14.0"
69
69
  }
70
70
  ],
71
71
  "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.",