@kici-dev/compiler 0.1.26 → 0.2.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.
Files changed (199) hide show
  1. package/dist/auth/headless-detect.d.ts +14 -5
  2. package/dist/auth/headless-detect.js +50 -9
  3. package/dist/cli-banner.d.ts +6 -3
  4. package/dist/cli-banner.js +13 -5
  5. package/dist/cli.js +180 -48
  6. package/dist/commands/check-mode.d.ts +1 -1
  7. package/dist/commands/compile.js +31 -7
  8. package/dist/commands/diagnostics.js +14 -2
  9. package/dist/commands/doctor.d.ts +64 -0
  10. package/dist/commands/doctor.js +413 -0
  11. package/dist/commands/endpoints.js +6 -0
  12. package/dist/commands/fixture.js +13 -0
  13. package/dist/commands/hook.js +3 -2
  14. package/dist/commands/index.d.ts +13 -1
  15. package/dist/commands/index.js +10 -3
  16. package/dist/commands/init-host-os.d.ts +34 -0
  17. package/dist/commands/init-host-os.js +44 -0
  18. package/dist/commands/init.d.ts +4 -0
  19. package/dist/commands/init.js +160 -37
  20. package/dist/commands/local-trust-root.d.ts +6 -0
  21. package/dist/commands/local-trust-root.js +52 -0
  22. package/dist/commands/local.d.ts +65 -0
  23. package/dist/commands/local.js +206 -0
  24. package/dist/commands/login.d.ts +2 -0
  25. package/dist/commands/login.js +41 -1
  26. package/dist/commands/logout.js +7 -0
  27. package/dist/commands/notifications.d.ts +61 -0
  28. package/dist/commands/notifications.js +274 -0
  29. package/dist/commands/preview.js +9 -2
  30. package/dist/commands/run-banner.d.ts +31 -0
  31. package/dist/commands/run-banner.js +25 -0
  32. package/dist/commands/run-routed.d.ts +53 -0
  33. package/dist/commands/run-routed.js +185 -0
  34. package/dist/commands/run.d.ts +0 -17
  35. package/dist/commands/run.js +44 -58
  36. package/dist/commands/runs/artifacts/download.d.ts +24 -0
  37. package/dist/commands/runs/artifacts/download.js +154 -0
  38. package/dist/commands/runs/artifacts/list.d.ts +5 -0
  39. package/dist/commands/runs/artifacts/list.js +47 -0
  40. package/dist/commands/runs/cancel.js +2 -2
  41. package/dist/commands/runs/list.js +8 -2
  42. package/dist/commands/secrets-list.d.ts +2 -2
  43. package/dist/commands/secrets-list.js +4 -4
  44. package/dist/commands/types.d.ts +3 -3
  45. package/dist/commands/types.js +4 -4
  46. package/dist/commands/verify-attestation.d.ts +3 -1
  47. package/dist/commands/verify-attestation.js +18 -7
  48. package/dist/errors/formatter.d.ts +14 -1
  49. package/dist/errors/formatter.js +13 -3
  50. package/dist/errors/index.d.ts +2 -1
  51. package/dist/errors/index.js +3 -2
  52. package/dist/errors/source-location.d.ts +20 -0
  53. package/dist/errors/source-location.js +45 -0
  54. package/dist/execution/executor.js +32 -12
  55. package/dist/fixtures/defaults/index.js +2 -1
  56. package/dist/generators/secrets-dts.d.ts +9 -9
  57. package/dist/generators/secrets-dts.js +12 -12
  58. package/dist/llm-context/llms-architecture.txt +50 -21
  59. package/dist/llm-context/llms-cli.txt +2667 -1454
  60. package/dist/llm-context/llms-features.txt +484 -414
  61. package/dist/llm-context/llms-full.txt +6703 -4687
  62. package/dist/llm-context/llms-getting-started.txt +311 -55
  63. package/dist/llm-context/llms-patterns.txt +17 -29
  64. package/dist/llm-context/llms-providers.txt +10 -1
  65. package/dist/llm-context/llms-sdk-runtime.txt +2013 -0
  66. package/dist/llm-context/llms-sdk.txt +596 -2157
  67. package/dist/llm-context/llms.txt +31 -17
  68. package/dist/local-plane/orchestrator-process.d.ts +70 -0
  69. package/dist/local-plane/orchestrator-process.js +129 -0
  70. package/dist/local-plane/os-mismatch-hint.d.ts +16 -0
  71. package/dist/local-plane/os-mismatch-hint.js +34 -0
  72. package/dist/local-plane/paths.d.ts +39 -0
  73. package/dist/local-plane/paths.js +54 -0
  74. package/dist/local-plane/plane-liveness.d.ts +68 -0
  75. package/dist/local-plane/plane-liveness.js +145 -0
  76. package/dist/local-plane/plane-manager.d.ts +193 -0
  77. package/dist/local-plane/plane-manager.js +480 -0
  78. package/dist/local-plane/plane-seed.d.ts +76 -0
  79. package/dist/local-plane/plane-seed.js +59 -0
  80. package/dist/local-plane/plane-trigger.d.ts +71 -0
  81. package/dist/local-plane/plane-trigger.js +110 -0
  82. package/dist/local-plane/platform-attach.d.ts +53 -0
  83. package/dist/local-plane/platform-attach.js +107 -0
  84. package/dist/local-plane/port-holder.d.ts +78 -0
  85. package/dist/local-plane/port-holder.js +227 -0
  86. package/dist/local-plane/postgres.d.ts +56 -0
  87. package/dist/local-plane/postgres.js +187 -0
  88. package/dist/local-plane/resolve-plane.d.ts +37 -0
  89. package/dist/local-plane/resolve-plane.js +76 -0
  90. package/dist/local-plane/run-follow.d.ts +61 -0
  91. package/dist/local-plane/run-follow.js +151 -0
  92. package/dist/local-plane/scaler-config.d.ts +68 -0
  93. package/dist/local-plane/scaler-config.js +132 -0
  94. package/dist/local-plane/secret-seed.d.ts +59 -0
  95. package/dist/local-plane/secret-seed.js +112 -0
  96. package/dist/local-plane/source-provider.d.ts +43 -0
  97. package/dist/local-plane/source-provider.js +159 -0
  98. package/dist/local-plane/trusted-routing.d.ts +32 -0
  99. package/dist/local-plane/trusted-routing.js +47 -0
  100. package/dist/lockfile/generator.d.ts +14 -1
  101. package/dist/lockfile/generator.js +103 -37
  102. package/dist/lockfile/index.d.ts +3 -1
  103. package/dist/lockfile/index.js +3 -2
  104. package/dist/lockfile/purity-diagnostics.d.ts +31 -0
  105. package/dist/lockfile/purity-diagnostics.js +52 -0
  106. package/dist/postinstall.js +2 -1
  107. package/dist/remote/artifact-extract.d.ts +8 -0
  108. package/dist/remote/artifact-extract.js +58 -0
  109. package/dist/remote/config.d.ts +21 -3
  110. package/dist/remote/config.js +24 -5
  111. package/dist/remote/dashboard-client.d.ts +46 -9
  112. package/dist/remote/dashboard-client.js +54 -11
  113. package/dist/remote/fs-case.d.ts +21 -0
  114. package/dist/remote/fs-case.js +45 -0
  115. package/dist/remote/history.d.ts +9 -3
  116. package/dist/remote/history.js +79 -10
  117. package/dist/remote/local-repo-identity.d.ts +1 -1
  118. package/dist/remote/local-repo-identity.js +2 -1
  119. package/dist/remote/notifications-client.d.ts +149 -0
  120. package/dist/remote/notifications-client.js +103 -0
  121. package/dist/remote/oauth.d.ts +13 -0
  122. package/dist/remote/oauth.js +31 -5
  123. package/dist/remote/output/summary.js +76 -14
  124. package/dist/remote/platform-client.d.ts +1 -1
  125. package/dist/remote/render.js +30 -9
  126. package/dist/{local-executor → remote}/secret-loader.d.ts +1 -1
  127. package/dist/{local-executor → remote}/secret-loader.js +4 -4
  128. package/dist/remote/secret-upload.d.ts +1 -1
  129. package/dist/remote/secret-upload.js +2 -2
  130. package/dist/remote/uploader.d.ts +1 -1
  131. package/dist/remote/uploader.js +7 -6
  132. package/dist/templates/agents-md.d.ts +1 -1
  133. package/dist/templates/agents-md.js +8 -8
  134. package/dist/templates/index.d.ts +1 -1
  135. package/dist/templates/index.js +2 -2
  136. package/dist/templates/package-json.d.ts +15 -0
  137. package/dist/templates/package-json.js +26 -5
  138. package/dist/test-runner/dry-run.d.ts +2 -1
  139. package/dist/test-runner/dry-run.js +18 -1
  140. package/dist/test-runner/event-types.d.ts +2 -0
  141. package/dist/test-runner/event-types.js +3 -0
  142. package/dist/test-runner/job-executor.d.ts +1 -0
  143. package/dist/test-runner/job-executor.js +82 -44
  144. package/dist/test-runner/output-formatter.d.ts +5 -2
  145. package/dist/test-runner/output-formatter.js +6 -4
  146. package/dist/test-runner/payload-builder.d.ts +7 -1
  147. package/dist/test-runner/payload-builder.js +11 -2
  148. package/dist/test-runner/rule-evaluator.d.ts +7 -3
  149. package/dist/test-runner/rule-evaluator.js +13 -12
  150. package/dist/{local-executor → test-runner}/runs-on-display.js +1 -1
  151. package/dist/test-runner/step-context.d.ts +23 -5
  152. package/dist/test-runner/step-context.js +29 -119
  153. package/dist/types.d.ts +26 -7
  154. package/dist/types.js +4 -2
  155. package/dist/validation/index.d.ts +2 -0
  156. package/dist/validation/index.js +2 -1
  157. package/dist/validation/typecheck.d.ts +28 -0
  158. package/dist/validation/typecheck.js +110 -0
  159. package/dist/validation/validator.d.ts +9 -4
  160. package/dist/validation/validator.js +38 -44
  161. package/package.json +13 -13
  162. package/sbom.spdx.json +9311 -1528
  163. package/dist/assets/api-TJJVHrjC.json +0 -118
  164. package/dist/assets/descriptor-BTtjzN9L.json +0 -1382
  165. package/dist/assets/package-BpQF9kR8.json +0 -74
  166. package/dist/assets/package-Ceo2h27X.json +0 -89
  167. package/dist/assets/source_context-D0atuL28.json +0 -20
  168. package/dist/assets/type-BFqO8SCZ.json +0 -202
  169. package/dist/commands/cancel.d.ts +0 -22
  170. package/dist/commands/detect-package-manager.d.ts +0 -42
  171. package/dist/commands/held-run-resolve.d.ts +0 -50
  172. package/dist/commands/status.d.ts +0 -34
  173. package/dist/commands/test.d.ts +0 -88
  174. package/dist/errors/codes.d.ts +0 -25
  175. package/dist/local-executor/dag-scheduler.d.ts +0 -44
  176. package/dist/local-executor/dag-scheduler.js +0 -183
  177. package/dist/local-executor/index.d.ts +0 -23
  178. package/dist/local-executor/index.js +0 -378
  179. package/dist/local-executor/job-runner.d.ts +0 -47
  180. package/dist/local-executor/job-runner.js +0 -411
  181. package/dist/local-executor/materializer.d.ts +0 -44
  182. package/dist/local-executor/materializer.js +0 -132
  183. package/dist/local-executor/output-streamer.d.ts +0 -31
  184. package/dist/local-executor/output-streamer.js +0 -168
  185. package/dist/local-executor/payload-generator.d.ts +0 -16
  186. package/dist/local-executor/payload-generator.js +0 -138
  187. package/dist/local-executor/picker.d.ts +0 -33
  188. package/dist/local-executor/picker.js +0 -109
  189. package/dist/local-executor/to-event-payload.d.ts +0 -16
  190. package/dist/local-executor/to-event-payload.js +0 -21
  191. package/dist/local-executor/types.d.ts +0 -93
  192. package/dist/local-executor/types.js +0 -2
  193. package/dist/local-executor/workflow-lock.d.ts +0 -82
  194. package/dist/local-executor/workflow-lock.js +0 -0
  195. package/dist/package-F7UXSDHW.json +0 -74
  196. package/dist/remote/client.d.ts +0 -210
  197. package/dist/remote/observer.d.ts +0 -81
  198. package/dist/test-runner/summary.d.ts +0 -7
  199. /package/dist/{local-executor → test-runner}/runs-on-display.d.ts +0 -0
@@ -3,7 +3,7 @@ import { loadSecretsFile } from "../test-runner/secrets-file.js";
3
3
  import path from "node:path";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import { parse } from "yaml";
6
- //#region src/local-executor/secret-loader.ts
6
+ //#region src/remote/secret-loader.ts
7
7
  /**
8
8
  * Parse a dotenv-style file (.env.local) into flat key-value pairs.
9
9
  * Lines starting with # are comments. Blank lines are ignored.
@@ -24,8 +24,8 @@ function parseDotenv(content) {
24
24
  }
25
25
  /**
26
26
  * Parse a secrets.yaml file.
27
- * Expected format: top-level keys are environment names, values are key-value maps.
28
- * For local execution, all environments are merged flat (no environment resolution).
27
+ * Expected format: top-level keys are context names, values are key-value maps.
28
+ * For local execution, all contexts are merged flat (no context resolution).
29
29
  */
30
30
  function parseSecretsYaml(content) {
31
31
  const parsed = parse(content);
@@ -66,7 +66,7 @@ async function readFileOrNull(filePath) {
66
66
  * Merge order (lowest to highest priority):
67
67
  * 1. .kici/.secrets (INI-style, backward compat)
68
68
  * 2. .kici/.env.local (dotenv format)
69
- * 3. .kici/secrets.yaml (YAML with environment scopes, merged flat)
69
+ * 3. .kici/secrets.yaml (YAML with context scopes, merged flat)
70
70
  * 4. --env KEY=VALUE CLI flags
71
71
  *
72
72
  * Note: process.env is NOT merged here -- it's handled at step-context level.
@@ -6,7 +6,7 @@
6
6
  */
7
7
  export declare function parseContextFlags(flags: string[] | undefined): Record<string, Record<string, string>>;
8
8
  /**
9
- * Load the developer's local secrets (same sources as `kici run local`: the
9
+ * Load the developer's local secrets (same sources as `kici run --local`: the
10
10
  * `.kici` secret files plus `--env` flat flags) and `--context` namespaced
11
11
  * flags, and encrypt them to the orchestrator's per-upload X25519 public key.
12
12
  * `--context` values override `.kici/.secrets` file contexts for the same key.
@@ -1,5 +1,5 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
- import { loadLocalSecrets } from "../local-executor/secret-loader.js";
2
+ import { loadLocalSecrets } from "./secret-loader.js";
3
3
  import { encryptJson } from "@kici-dev/core";
4
4
  //#region src/remote/secret-upload.ts
5
5
  /**
@@ -25,7 +25,7 @@ function parseContextFlags(flags) {
25
25
  return contexts;
26
26
  }
27
27
  /**
28
- * Load the developer's local secrets (same sources as `kici run local`: the
28
+ * Load the developer's local secrets (same sources as `kici run --local`: the
29
29
  * `.kici` secret files plus `--env` flat flags) and `--context` namespaced
30
30
  * flags, and encrypt them to the orchestrator's per-upload X25519 public key.
31
31
  * `--context` values override `.kici/.secrets` file contexts for the same key.
@@ -82,7 +82,7 @@ export interface OverlaySelection {
82
82
  * When `fullWorkingTree` is set (the `kici run remote` path), the entire
83
83
  * `.git` directory is additively included so the extracted overlay is a real
84
84
  * git repository on the agent — workflow steps that shell out to git then work
85
- * exactly as they do under `kici run local`. The `.git` files are added after
85
+ * exactly as they do under `kici run --local`. The `.git` files are added after
86
86
  * `.kiciignore` filtering (git internals are never subject to working-tree
87
87
  * ignore globs).
88
88
  *
@@ -4,9 +4,9 @@ import path from "node:path";
4
4
  import fs from "node:fs/promises";
5
5
  import { formatBytes, sha256, sha256File } from "@kici-dev/core";
6
6
  import { execSync } from "node:child_process";
7
- import os from "node:os";
8
- import picomatch from "picomatch";
9
7
  import { create } from "tar";
8
+ import { makeTempDir } from "@kici-dev/core/tmp";
9
+ import picomatch from "picomatch";
10
10
  //#region src/remote/uploader.ts
11
11
  /** Size threshold for warning (50MB) */
12
12
  const SIZE_WARN_THRESHOLD = 50 * 1024 * 1024;
@@ -44,7 +44,7 @@ function isGitDirPath(relPath) {
44
44
  * clone on the agent — we enumerate the directory explicitly. Including the
45
45
  * whole `.git` directory (objects, refs, HEAD, index, config, packed-refs)
46
46
  * makes the extracted overlay a real git repository, so workflow steps that
47
- * shell out to git work exactly as they do under `kici run local`.
47
+ * shell out to git work exactly as they do under `kici run --local`.
48
48
  */
49
49
  async function collectGitDirFiles(repoRoot) {
50
50
  const gitRoot = path.join(repoRoot, ".git");
@@ -94,7 +94,7 @@ async function loadKiciIgnore(kiciIgnorePath) {
94
94
  * When `fullWorkingTree` is set (the `kici run remote` path), the entire
95
95
  * `.git` directory is additively included so the extracted overlay is a real
96
96
  * git repository on the agent — workflow steps that shell out to git then work
97
- * exactly as they do under `kici run local`. The `.git` files are added after
97
+ * exactly as they do under `kici run --local`. The `.git` files are added after
98
98
  * `.kiciignore` filtering (git internals are never subject to working-tree
99
99
  * ignore globs).
100
100
  *
@@ -168,14 +168,15 @@ async function createOverlayTarball(repoRoot, options) {
168
168
  const modifiedFiles = workingTreeFiles.filter((f) => !untrackedSet.has(f));
169
169
  const checksums = {};
170
170
  await Promise.all(existingFiles.map(async (file) => {
171
- checksums[file] = await sha256File(path.join(repoRoot, file));
171
+ const fullPath = path.join(repoRoot, file);
172
+ checksums[file] = await sha256File(fullPath);
172
173
  }));
173
174
  const manifest = {
174
175
  sha,
175
176
  deletions: deletedFiles,
176
177
  checksums
177
178
  };
178
- const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "kici-overlay-"));
179
+ const { path: tmpDir } = await makeTempDir("overlay", { persist: true });
179
180
  const manifestPath = path.join(tmpDir, "manifest.json");
180
181
  await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
181
182
  const manifestRelDir = ".kici-overlay-tmp";
@@ -4,5 +4,5 @@
4
4
  * picked up by Claude Code, Cursor, Aider, and other coding agents that scan
5
5
  * the working tree for an authoring context file.
6
6
  */
7
- export declare const agentsMdTemplate = "# KiCI workflow authoring guide\n\nThis project uses KiCI \u2014 a TypeScript-native CI/CD workflow engine \u2014 instead\nof YAML-based CI. Workflows live in `.kici/workflows/*.ts`, are compiled\ninto a portable lock file, and executed by self-hosted agents.\n\n## Where the API surface lives\n\n- Public SDK types: `node_modules/@kici-dev/sdk/dist/index.d.ts` \u2014 read this\n for the canonical signatures of `workflow`, `job`, `step`, `pr`,\n `push`, `schedule`, `matrix`, `rule`, `dynamicJob`, etc.\n- Bundled offline reference for coding agents: `kici docs llm` prints the\n full markdown documentation bundle to stdout. `kici docs llm --index`\n prints just the curated link index (llms.txt format).\n- Online docs:\n - <https://kici.dev/docs/> \u2014 published docs site.\n - <https://kici.dev/llms.txt> \u2014 curated index for LLM consumers.\n - <https://kici.dev/llms-full.txt> \u2014 full markdown bundle.\n - Key pages: `user/sdk-reference`, `user/workflow-patterns`,\n `user/testing-guide`, `user/hooks`, `user/secrets`.\n\n## The five core patterns\n\n1. **Push trigger** \u2014 `on: push({ branches: 'main' })`. Pair with `paths`\n to scope to subtrees.\n\n ```ts\n import { workflow, job, step, push } from '@kici-dev/sdk';\n\n export default workflow('build', {\n on: push({ branches: 'main' }),\n jobs: [\n job('build', {\n runsOn: 'kici:os:linux',\n steps: [step('install', async ({ $ }) => { await $`pnpm install`; })],\n }),\n ],\n });\n ```\n\n `kici:os:linux` targets any agent reporting that OS \u2014 every agent\n self-reports `kici:os:` / `kici:arch:` / `kici:host:`. Use a custom label\n (e.g. `'gpu'`, `'prod-pool'`) to target a specific agent pool your scaler\n defines.\n\n2. **PR + matrix** \u2014 `pr({ target: 'main' })` plus a matrix over node\n versions. The matrix expands at dispatch time.\n\n ```ts\n import { workflow, job, step, pr, matrix } from '@kici-dev/sdk';\n\n export default workflow('test-matrix', {\n on: pr({ target: 'main' }),\n jobs: [\n job('test', {\n runsOn: 'kici:os:linux',\n strategy: { matrix: matrix({ node: ['20', '22', '24'] }) },\n steps: [\n step('test', async ({ $, matrix }) => {\n await $`node --version`;\n await $`pnpm install`;\n await $`pnpm test`;\n return { node: matrix.node };\n }),\n ],\n }),\n ],\n });\n ```\n\n3. **Lifecycle hooks** \u2014 `onFailure` / `onSuccess` / `onCancel` on a\n job or workflow run after the main steps in their own scope.\n\n4. **Secrets** \u2014 declared scopes resolve at dispatch:\n\n ```ts\n step('deploy', async ({ $, secrets }) => {\n await $`./scripts/deploy.sh`.env({ DEPLOY_TOKEN: secrets.production.DEPLOY_TOKEN });\n });\n ```\n\n Run `kici secrets list` to enumerate the contexts available for testing.\n\n5. **Dynamic jobs** \u2014 `dynamicJob` and `dynamicGroup` build the DAG at\n runtime from a step's outputs. Don't try to compute job names at top level;\n the lock file would be wrong.\n\n## Anti-patterns\n\n- **Do NOT write `.yml` / `.yaml` CI files** \u2014 KiCI replaces that entire\n layer. There is no compatibility shim.\n- **Do NOT `import` from any `@kici-dev/*` package's `/dist/...`\n subpath** \u2014 those are not part of the public API and break across versions.\n Import from the package root.\n- **Do NOT `await` outside step bodies.** The top-level workflow file is\n loaded by the compiler synchronously; async I/O at module scope means the\n lock file emits before it resolves and the workflow appears empty.\n- **Do NOT mutate shared variables between jobs.** Each job runs in its own\n agent process. Use `needs` + step outputs to thread values.\n- **Do NOT hand-edit `kici.lock.json`.** Regenerate it via `kici compile`.\n\n## Local commands a coding agent should run\n\n| Command | Purpose |\n| ------------------------------- | ------------------------------------------- |\n| `pnpm kici compile --check` | Validate workflow source without writing. |\n| `pnpm kici preview pr:open --debug` | Preview which workflows match an event. |\n| `pnpm kici run local push` | Execute workflows locally with no orchestrator. |\n| `pnpm kici docs llm` | Print the full LLM documentation bundle. |\n| `pnpm kici docs llm --index` | Print the curated link index. |\n\nIf `pnpm kici` isn't in scripts, fall back to `npx kici`.\n\n## Loop\n\n1. Read the SDK types from `node_modules/@kici-dev/sdk/dist/index.d.ts`.\n2. Pipe `kici docs llm` into the agent's context if it doesn't already have\n the full bundle.\n3. Edit a workflow under `.kici/workflows/`.\n4. Run `kici compile --check` (zero exit means valid).\n5. Run `kici preview <event>` to preview matching.\n6. Run `kici run local <event>` to execute locally before pushing.\n";
7
+ export declare const agentsMdTemplate = "# KiCI workflow authoring guide\n\nThis project uses KiCI \u2014 a TypeScript-native CI/CD workflow engine \u2014 instead\nof YAML-based CI. Workflows live in `.kici/workflows/*.ts`, are compiled\ninto a portable lock file, and executed by self-hosted agents.\n\n## Where the API surface lives\n\n- Public SDK types: `node_modules/@kici-dev/sdk/dist/index.d.ts` \u2014 read this\n for the canonical signatures of `workflow`, `job`, `step`, `pr`,\n `push`, `schedule`, `rule`, `dynamicJob`, etc.\n- Bundled offline reference for coding agents: `kici docs llm` prints the\n full markdown documentation bundle to stdout. `kici docs llm --index`\n prints just the curated link index (llms.txt format).\n- Online docs:\n - <https://kici.dev/docs/> \u2014 published docs site.\n - <https://kici.dev/llms.txt> \u2014 curated index for LLM consumers.\n - <https://kici.dev/llms-full.txt> \u2014 full markdown bundle.\n - Key pages: `user/sdk-reference`, `user/workflow-patterns`,\n `user/testing-guide`, `user/hooks`, `user/secrets`.\n\n## The five core patterns\n\n1. **Push trigger** \u2014 `on: push({ branches: 'main' })`. Pair with `paths`\n to scope to subtrees.\n\n ```ts\n import { workflow, job, step, push } from '@kici-dev/sdk';\n\n export default workflow('build', {\n on: push({ branches: 'main' }),\n jobs: [\n job('build', {\n runsOn: 'kici:os:linux',\n steps: [step('install', async ({ $ }) => { await $`pnpm install`; })],\n }),\n ],\n });\n ```\n\n `kici:os:linux` targets any agent reporting that OS \u2014 every agent\n self-reports `kici:os:` / `kici:arch:` / `kici:host:`. Use a custom label\n (e.g. `'gpu'`, `'prod-pool'`) to target a specific agent pool your scaler\n defines.\n\n2. **PR + matrix** \u2014 `pr({ target: 'main' })` plus a matrix over node\n versions. The matrix expands at dispatch time.\n\n ```ts\n import { workflow, job, step, pr } from '@kici-dev/sdk';\n\n export default workflow('test-matrix', {\n on: pr({ target: 'main' }),\n jobs: [\n job('test', {\n runsOn: 'kici:os:linux',\n matrix: { node: ['20', '22', '24'] },\n steps: [\n step('test', async ({ $, matrix }) => {\n await $`echo testing on node ${matrix!.node}`;\n await $`pnpm install`;\n await $`pnpm test`;\n }),\n ],\n }),\n ],\n });\n ```\n\n3. **Lifecycle hooks** \u2014 `onFailure` / `onSuccess` / `onCancel` on a\n job or workflow run after the main steps in their own scope.\n\n4. **Secrets** \u2014 declared scopes resolve at dispatch:\n\n ```ts\n step('deploy', async ({ $, secrets }) => {\n await secrets.expose('DEPLOY_TOKEN');\n await $`./scripts/deploy.sh`;\n });\n ```\n\n Run `kici secrets list` to enumerate the contexts available for testing.\n\n5. **Dynamic jobs** \u2014 `dynamicJob` and `dynamicGroup` build the DAG at\n runtime from a step's outputs. Don't try to compute job names at top level;\n the lock file would be wrong.\n\n## Anti-patterns\n\n- **Do NOT write `.yml` / `.yaml` CI files** \u2014 KiCI replaces that entire\n layer. There is no compatibility shim.\n- **Do NOT `import` from any `@kici-dev/*` package's `/dist/...`\n subpath** \u2014 those are not part of the public API and break across versions.\n Import from the package root.\n- **Do NOT `await` outside step bodies.** The top-level workflow file is\n loaded by the compiler synchronously; async I/O at module scope means the\n lock file emits before it resolves and the workflow appears empty.\n- **Do NOT mutate shared variables between jobs.** Each job runs in its own\n agent process. Use `needs` + step outputs to thread values.\n- **Do NOT hand-edit `kici.lock.json`.** Regenerate it via `kici compile`.\n\n## Local commands a coding agent should run\n\n| Command | Purpose |\n| ------------------------------- | ------------------------------------------- |\n| `pnpm kici compile --check` | Validate workflow source without writing. |\n| `pnpm kici preview pr:open --debug` | Preview which workflows match an event. |\n| `pnpm kici run push --local` | Execute a workflow locally (this machine as an ephemeral agent). |\n| `pnpm kici docs llm` | Print the full LLM documentation bundle. |\n| `pnpm kici docs llm --index` | Print the curated link index. |\n\nIf `pnpm kici` isn't in scripts, fall back to `npx kici`.\n\n## Loop\n\n1. Read the SDK types from `node_modules/@kici-dev/sdk/dist/index.d.ts`.\n2. Pipe `kici docs llm` into the agent's context if it doesn't already have\n the full bundle.\n3. Edit a workflow under `.kici/workflows/`.\n4. Run `kici compile --check` (zero exit means valid).\n5. Run `kici preview <event>` to preview matching.\n6. Run `kici run <event> --local` to execute locally before pushing.\n";
8
8
  //# sourceMappingURL=agents-md.d.ts.map
@@ -16,7 +16,7 @@ into a portable lock file, and executed by self-hosted agents.
16
16
 
17
17
  - Public SDK types: \`node_modules/@kici-dev/sdk/dist/index.d.ts\` — read this
18
18
  for the canonical signatures of \`workflow\`, \`job\`, \`step\`, \`pr\`,
19
- \`push\`, \`schedule\`, \`matrix\`, \`rule\`, \`dynamicJob\`, etc.
19
+ \`push\`, \`schedule\`, \`rule\`, \`dynamicJob\`, etc.
20
20
  - Bundled offline reference for coding agents: \`kici docs llm\` prints the
21
21
  full markdown documentation bundle to stdout. \`kici docs llm --index\`
22
22
  prints just the curated link index (llms.txt format).
@@ -55,20 +55,19 @@ into a portable lock file, and executed by self-hosted agents.
55
55
  versions. The matrix expands at dispatch time.
56
56
 
57
57
  \`\`\`ts
58
- import { workflow, job, step, pr, matrix } from '@kici-dev/sdk';
58
+ import { workflow, job, step, pr } from '@kici-dev/sdk';
59
59
 
60
60
  export default workflow('test-matrix', {
61
61
  on: pr({ target: 'main' }),
62
62
  jobs: [
63
63
  job('test', {
64
64
  runsOn: 'kici:os:linux',
65
- strategy: { matrix: matrix({ node: ['20', '22', '24'] }) },
65
+ matrix: { node: ['20', '22', '24'] },
66
66
  steps: [
67
67
  step('test', async ({ $, matrix }) => {
68
- await $\`node --version\`;
68
+ await $\`echo testing on node \${matrix!.node}\`;
69
69
  await $\`pnpm install\`;
70
70
  await $\`pnpm test\`;
71
- return { node: matrix.node };
72
71
  }),
73
72
  ],
74
73
  }),
@@ -83,7 +82,8 @@ into a portable lock file, and executed by self-hosted agents.
83
82
 
84
83
  \`\`\`ts
85
84
  step('deploy', async ({ $, secrets }) => {
86
- await $\`./scripts/deploy.sh\`.env({ DEPLOY_TOKEN: secrets.production.DEPLOY_TOKEN });
85
+ await secrets.expose('DEPLOY_TOKEN');
86
+ await $\`./scripts/deploy.sh\`;
87
87
  });
88
88
  \`\`\`
89
89
 
@@ -113,7 +113,7 @@ into a portable lock file, and executed by self-hosted agents.
113
113
  | ------------------------------- | ------------------------------------------- |
114
114
  | \`pnpm kici compile --check\` | Validate workflow source without writing. |
115
115
  | \`pnpm kici preview pr:open --debug\` | Preview which workflows match an event. |
116
- | \`pnpm kici run local push\` | Execute workflows locally with no orchestrator. |
116
+ | \`pnpm kici run push --local\` | Execute a workflow locally (this machine as an ephemeral agent). |
117
117
  | \`pnpm kici docs llm\` | Print the full LLM documentation bundle. |
118
118
  | \`pnpm kici docs llm --index\` | Print the curated link index. |
119
119
 
@@ -127,7 +127,7 @@ If \`pnpm kici\` isn't in scripts, fall back to \`npx kici\`.
127
127
  3. Edit a workflow under \`.kici/workflows/\`.
128
128
  4. Run \`kici compile --check\` (zero exit means valid).
129
129
  5. Run \`kici preview <event>\` to preview matching.
130
- 6. Run \`kici run local <event>\` to execute locally before pushing.
130
+ 6. Run \`kici run <event> --local\` to execute locally before pushing.
131
131
  `;
132
132
  //#endregion
133
133
  export { agentsMdTemplate };
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export { helloWorldWorkflow } from './workflows/hello-world.js';
7
7
  export { prChecksWorkflow } from './workflows/pr-checks.js';
8
- export { generatePackageJson } from './package-json.js';
8
+ export { generatePackageJson, sdkDependencyRange } from './package-json.js';
9
9
  export { tsconfigTemplate } from './tsconfig-json.js';
10
10
  export { agentsMdTemplate } from './agents-md.js';
11
11
  /**
@@ -3,7 +3,7 @@ import { agentsMdTemplate } from "./agents-md.js";
3
3
  import { helloWorldWorkflow } from "./workflows/hello-world.js";
4
4
  import { prChecksWorkflow } from "./workflows/pr-checks.js";
5
5
  import { tsconfigTemplate } from "./tsconfig-json.js";
6
- import { generatePackageJson } from "./package-json.js";
6
+ import { generatePackageJson, sdkDependencyRange } from "./package-json.js";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import path from "node:path";
9
9
  //#region src/templates/index.ts
@@ -43,6 +43,6 @@ const templates = {
43
43
  tsconfig: tsconfigTemplate
44
44
  };
45
45
  //#endregion
46
- export { agentsMdTemplate, generatePackageJson, helloWorldWorkflow, prChecksWorkflow, templates, tsconfigTemplate, workflowPaths, workflows };
46
+ export { agentsMdTemplate, generatePackageJson, helloWorldWorkflow, prChecksWorkflow, sdkDependencyRange, templates, tsconfigTemplate, workflowPaths, workflows };
47
47
 
48
48
  //# sourceMappingURL=index.js.map
@@ -6,6 +6,21 @@
6
6
  * (or a private Verdaccio instance when .npmrc scopes the registry).
7
7
  * The compiler is invoked via npx (not installed as a dependency).
8
8
  */
9
+ /**
10
+ * The npm version range the scaffold pins `@kici-dev/sdk` to.
11
+ *
12
+ * @param devMode - When true, a prerelease-compatible range (`>=0.0.1-0`) so
13
+ * npm resolves Verdaccio's prerelease builds (e.g. 0.0.1-2856). Semver
14
+ * `^0.0.1` does NOT match prereleases, causing 404s on Verdaccio.
15
+ */
16
+ export declare function sdkDependencyRange(devMode?: boolean): string;
17
+ /**
18
+ * The TypeScript range scaffolded into a `.kici` workspace. Pinned to the major
19
+ * the compiler is built against so `kici compile --check` (and the `typecheck`
20
+ * script) run the same tsc the compiler expects. Same range in dev and prod
21
+ * mode — TypeScript is a public npm package, not a Verdaccio prerelease.
22
+ */
23
+ export declare const TYPESCRIPT_RANGE = "^6.0.3";
9
24
  /**
10
25
  * Generate package.json content for .kici/ directory
11
26
  *
@@ -1,6 +1,23 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
2
  //#region src/templates/package-json.ts
3
- const sdkVersion = "0.1.26";
3
+ const sdkVersion = "0.2.0";
4
+ /**
5
+ * The npm version range the scaffold pins `@kici-dev/sdk` to.
6
+ *
7
+ * @param devMode - When true, a prerelease-compatible range (`>=0.0.1-0`) so
8
+ * npm resolves Verdaccio's prerelease builds (e.g. 0.0.1-2856). Semver
9
+ * `^0.0.1` does NOT match prereleases, causing 404s on Verdaccio.
10
+ */
11
+ function sdkDependencyRange(devMode = false) {
12
+ return devMode ? ">=0.0.1-0" : `^${sdkVersion}`;
13
+ }
14
+ /**
15
+ * The TypeScript range scaffolded into a `.kici` workspace. Pinned to the major
16
+ * the compiler is built against so `kici compile --check` (and the `typecheck`
17
+ * script) run the same tsc the compiler expects. Same range in dev and prod
18
+ * mode — TypeScript is a public npm package, not a Verdaccio prerelease.
19
+ */
20
+ const TYPESCRIPT_RANGE = "^6.0.3";
4
21
  /**
5
22
  * Generate package.json content for .kici/ directory
6
23
  *
@@ -10,7 +27,7 @@ const sdkVersion = "0.1.26";
10
27
  * @returns JSON string with proper formatting (2-space indent, trailing newline)
11
28
  */
12
29
  function generatePackageJson(devMode = false) {
13
- return JSON.stringify({
30
+ const pkg = {
14
31
  name: "@kici-dev/workflows",
15
32
  private: true,
16
33
  type: "module",
@@ -18,10 +35,14 @@ function generatePackageJson(devMode = false) {
18
35
  compile: "npx --yes kici@latest compile",
19
36
  typecheck: "tsc --noEmit"
20
37
  },
21
- devDependencies: { "@kici-dev/sdk": devMode ? ">=0.0.1-0" : `^${sdkVersion}` }
22
- }, null, 2) + "\n";
38
+ devDependencies: {
39
+ "@kici-dev/sdk": sdkDependencyRange(devMode),
40
+ typescript: TYPESCRIPT_RANGE
41
+ }
42
+ };
43
+ return JSON.stringify(pkg, null, 2) + "\n";
23
44
  }
24
45
  //#endregion
25
- export { generatePackageJson };
46
+ export { TYPESCRIPT_RANGE, generatePackageJson, sdkDependencyRange };
26
47
 
27
48
  //# sourceMappingURL=package-json.js.map
@@ -1,4 +1,5 @@
1
1
  import type { LockWorkflow } from '../types.js';
2
+ import type { JobPurityWarning } from '../lockfile/purity-diagnostics.js';
2
3
  import type { WorkflowDecision } from '@kici-dev/engine';
3
4
  interface DryRunOptions {
4
5
  workflow?: string;
@@ -7,6 +8,6 @@ interface DryRunOptions {
7
8
  /**
8
9
  * Display dry-run output showing what would execute.
9
10
  */
10
- export declare function displayDryRun(workflows: readonly LockWorkflow[], decisions: WorkflowDecision[], options: DryRunOptions): void;
11
+ export declare function displayDryRun(workflows: readonly LockWorkflow[], decisions: WorkflowDecision[], options: DryRunOptions, purityWarnings?: JobPurityWarning[]): void;
11
12
  export {};
12
13
  //# sourceMappingURL=dry-run.d.ts.map
@@ -3,11 +3,26 @@ import pc from "picocolors";
3
3
  import { logger } from "@kici-dev/core";
4
4
  //#region src/test-runner/dry-run.ts
5
5
  /**
6
+ * Print the injected `__init__` job line for each impure dynamic value on a job,
7
+ * so the ~5-10s init-job cost is visible before the first run. Returns the number
8
+ * of lines rendered so the caller can count the jobs it actually surfaced.
9
+ */
10
+ function renderJobInitWarnings(purityWarnings, workflowName, jobName) {
11
+ let rendered = 0;
12
+ for (const w of purityWarnings) {
13
+ if (w.workflowName !== workflowName || w.jobName !== jobName) continue;
14
+ logger.info(pc.yellow(` ⚠ __init__ job required (~5-10s): ${w.field} is not pure — ${w.reason}`));
15
+ rendered++;
16
+ }
17
+ return rendered;
18
+ }
19
+ /**
6
20
  * Display dry-run output showing what would execute.
7
21
  */
8
- function displayDryRun(workflows, decisions, options) {
22
+ function displayDryRun(workflows, decisions, options, purityWarnings = []) {
9
23
  logger.info(pc.bold("\n🔍 DRY RUN - No commands will be executed\n"));
10
24
  const matchedWorkflows = decisions.filter((d) => d.matched);
25
+ const injectedInitJobs = /* @__PURE__ */ new Set();
11
26
  if (matchedWorkflows.length === 0) {
12
27
  logger.info(pc.yellow("No workflows matched the event.\n"));
13
28
  displayDecisionSummary(decisions);
@@ -33,6 +48,7 @@ function displayDryRun(workflows, decisions, options) {
33
48
  else logger.info(pc.gray(` matrix: [dynamic]`));
34
49
  logger.info(pc.gray(` steps (${job.steps.length}):`));
35
50
  for (const step of job.steps) logger.info(pc.gray(` - ${step.name}`));
51
+ if (renderJobInitWarnings(purityWarnings, decision.workflowName, job.name) > 0) injectedInitJobs.add(`${decision.workflowName} ${job.name}`);
36
52
  }
37
53
  const dynamicJobs = workflow.jobs.filter((j) => j._type === "dynamic");
38
54
  if (dynamicJobs.length > 0) {
@@ -41,6 +57,7 @@ function displayDryRun(workflows, decisions, options) {
41
57
  }
42
58
  logger.info("");
43
59
  }
60
+ if (injectedInitJobs.size > 0) logger.info(pc.yellow(`⚠ ${injectedInitJobs.size} __init__ job(s) will be injected for impure dynamic values (~5-10s each).`));
44
61
  displayDecisionSummary(decisions);
45
62
  logger.info(pc.green("✓ Dry run complete\n"));
46
63
  }
@@ -51,6 +51,8 @@ export type EventType = {
51
51
  type: 'workflow_complete';
52
52
  workflowName: string;
53
53
  status: string;
54
+ } | {
55
+ type: 'workflows_failed_batch';
54
56
  } | {
55
57
  type: 'job_complete';
56
58
  workflowName: string;
@@ -169,6 +169,7 @@ function parseEventArg(arg) {
169
169
  workflowName: "test",
170
170
  status: "cancelled"
171
171
  };
172
+ case "workflows_failed_batch": return { type: "workflows_failed_batch" };
172
173
  case "job_complete": return {
173
174
  type: "job_complete",
174
175
  workflowName: "test",
@@ -256,6 +257,7 @@ function triggerToEventArg(trigger) {
256
257
  const status = trigger.status?.[0];
257
258
  return status ? `workflow_complete:${status}` : "workflow_complete";
258
259
  }
260
+ case "workflows_failed_batch": return "workflows_failed_batch";
259
261
  case "job_complete": {
260
262
  const status = trigger.status?.[0];
261
263
  return status ? `job_complete:${status}` : "job_complete";
@@ -317,6 +319,7 @@ function triggerSummary(trigger) {
317
319
  case "webhook": return `webhook(${joinList(trigger.events)})`;
318
320
  case "kici_event": return `kici_event(${trigger.eventName})`;
319
321
  case "workflow_complete": return `workflow_complete(${trigger.name ? `${trigger.name}` : "*"}${trigger.status?.length ? `, ${joinList(trigger.status)}` : ""})`;
322
+ case "workflows_failed_batch": return `workflows_failed_batch(${trigger.name ? `${trigger.name}` : "*"}, ${trigger.accumulateFor}ms)`;
320
323
  case "job_complete": return `job_complete(${trigger.workflow ?? "*"}${trigger.job ? `.${trigger.job}` : ""}${trigger.status?.length ? `, ${joinList(trigger.status)}` : ""})`;
321
324
  case "generic_webhook": return `webhook(${trigger.source})`;
322
325
  case "schedule": return `schedule('${trigger.cronExpression}')`;
@@ -39,6 +39,7 @@ interface WorkflowResult {
39
39
  durationMs: number;
40
40
  jobs: JobResult[];
41
41
  ruleResults?: RuleResult[];
42
+ error?: Error;
42
43
  }
43
44
  /**
44
45
  * Execute a job sequentially (steps run in order).
@@ -1,8 +1,8 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
2
  import { formatter } from "./output-formatter.js";
3
3
  import { createStepContext } from "./step-context.js";
4
- import { createRuleContext, evaluateRules as evaluateRulesWithFormatting } from "./rule-evaluator.js";
5
- import { localRunsOnString } from "../local-executor/runs-on-display.js";
4
+ import { createRuleContext as createRuleContext$1, evaluateRules as evaluateRulesWithFormatting } from "./rule-evaluator.js";
5
+ import { localRunsOnString } from "./runs-on-display.js";
6
6
  import { pathToFileURL } from "node:url";
7
7
  import path from "node:path";
8
8
  import pc from "picocolors";
@@ -73,11 +73,23 @@ async function executeJob(job, workflowName, event, repoRoot, testSecrets, sdkSe
73
73
  const stepResults = [];
74
74
  let ruleResults;
75
75
  if (job.rules && job.rules.length > 0) {
76
- const ruleContext = createRuleContext(event, event.changedFiles);
76
+ const ruleContext = createRuleContext$1({
77
+ event,
78
+ changedFiles: event.changedFiles,
79
+ changedFilesStatus: event.changedFilesStatus ?? "fetched"
80
+ });
77
81
  const ruleEval = await evaluateRulesWithFormatting(job.rules, ruleContext, job.name);
78
82
  ruleResults = ruleEval.results;
79
83
  if (!ruleEval.allPassed) {
80
84
  const durationMs = Date.now() - startTime;
85
+ if (ruleEval.evaluationError) return {
86
+ name: job.name,
87
+ status: "failure",
88
+ durationMs,
89
+ steps: [],
90
+ ruleResults,
91
+ error: /* @__PURE__ */ new Error(`rule '${ruleEval.evaluationError.label}' errored: ${ruleEval.evaluationError.message}`)
92
+ };
81
93
  return {
82
94
  name: job.name,
83
95
  status: "skipped",
@@ -93,51 +105,61 @@ async function executeJob(job, workflowName, event, repoRoot, testSecrets, sdkSe
93
105
  setters.setStepRefMap(refMap);
94
106
  setStepOutputsMap(outputsMap);
95
107
  setStepRefMap(refMap);
96
- const ctx = createStepContext({ name: workflowName }, {
108
+ const { ctx, dispose } = createStepContext({ name: workflowName }, {
97
109
  name: job.name,
98
110
  runsOn: localRunsOnString(job.runsOn)
99
111
  }, repoRoot, void 0, void 0, testSecrets, void 0, event.payload, event.provider);
100
- let stepCounter = 0;
101
- for (const stepOrFn of job.steps) {
102
- let normalizedStep;
103
- if (typeof stepOrFn === "function") {
104
- stepCounter++;
105
- const name = `step-${stepCounter}`;
106
- refMap.set(stepOrFn, name);
107
- normalizedStep = {
108
- _tag: "Step",
109
- name,
110
- run: stepOrFn
111
- };
112
- } else {
113
- normalizedStep = stepOrFn;
114
- if (!normalizedStep.name) stepCounter++;
115
- }
116
- const result = await executeStep(normalizedStep, ctx, job.name);
117
- if (result.status === "success" && result.outputs != null) outputsMap.set(normalizedStep.name, result.outputs);
118
- stepResults.push(result);
119
- if (result.status === "failure") {
120
- const durationMs = Date.now() - startTime;
121
- formatter.logJobFailure(job.name, durationMs, result.error);
122
- return {
123
- name: job.name,
124
- status: "failure",
125
- durationMs,
126
- steps: stepResults,
127
- ruleResults,
128
- error: result.error
129
- };
112
+ try {
113
+ let stepCounter = 0;
114
+ for (const stepOrFn of job.steps) {
115
+ let normalizedStep;
116
+ if (typeof stepOrFn === "function") {
117
+ stepCounter++;
118
+ const name = `step-${stepCounter}`;
119
+ refMap.set(stepOrFn, name);
120
+ normalizedStep = {
121
+ _tag: "Step",
122
+ name,
123
+ run: stepOrFn
124
+ };
125
+ } else {
126
+ normalizedStep = stepOrFn;
127
+ if (!normalizedStep.name) {
128
+ stepCounter++;
129
+ normalizedStep.name = `step-${stepCounter}`;
130
+ }
131
+ }
132
+ const result = await executeStep(normalizedStep, ctx, job.name);
133
+ if (result.status === "success" && result.outputs != null) outputsMap.set(normalizedStep.name, result.outputs);
134
+ stepResults.push(result);
135
+ if (result.status === "failure") {
136
+ const durationMs = Date.now() - startTime;
137
+ formatter.logJobFailure(job.name, durationMs, result.error);
138
+ return {
139
+ name: job.name,
140
+ status: "failure",
141
+ durationMs,
142
+ steps: stepResults,
143
+ ruleResults,
144
+ error: result.error
145
+ };
146
+ }
130
147
  }
148
+ const durationMs = Date.now() - startTime;
149
+ formatter.logJobComplete(job.name, durationMs);
150
+ return {
151
+ name: job.name,
152
+ status: "success",
153
+ durationMs,
154
+ steps: stepResults,
155
+ ruleResults
156
+ };
157
+ } finally {
158
+ await dispose().catch((err) => {
159
+ const message = err instanceof Error ? err.message : String(err);
160
+ logger.warn(`local job temp scope cleanup failed: ${message}`);
161
+ });
131
162
  }
132
- const durationMs = Date.now() - startTime;
133
- formatter.logJobComplete(job.name, durationMs);
134
- return {
135
- name: job.name,
136
- status: "success",
137
- durationMs,
138
- steps: stepResults,
139
- ruleResults
140
- };
141
163
  }
142
164
  /**
143
165
  * Execute a workflow with parallel job execution.
@@ -149,11 +171,27 @@ async function executeWorkflow(workflow, event, repoRoot, testSecrets, kiciDir)
149
171
  const sdkSetters = await resolveSdkSetters(kiciDir);
150
172
  let ruleResults;
151
173
  if (workflow.rules && workflow.rules.length > 0) {
152
- const ruleContext = createRuleContext(event, event.changedFiles);
174
+ const ruleContext = createRuleContext$1({
175
+ event,
176
+ changedFiles: event.changedFiles,
177
+ changedFilesStatus: event.changedFilesStatus ?? "fetched"
178
+ });
153
179
  const ruleEval = await evaluateRulesWithFormatting(workflow.rules, ruleContext, workflow.name);
154
180
  ruleResults = ruleEval.results;
155
181
  if (!ruleEval.allPassed) {
156
182
  const durationMs = Date.now() - startTime;
183
+ if (ruleEval.evaluationError) {
184
+ const error = /* @__PURE__ */ new Error(`rule '${ruleEval.evaluationError.label}' errored: ${ruleEval.evaluationError.message}`);
185
+ logger.info(pc.red(`\n✗ Workflow failed: ${error.message}\n`));
186
+ return {
187
+ name: workflow.name,
188
+ status: "failure",
189
+ durationMs,
190
+ jobs: [],
191
+ ruleResults,
192
+ error
193
+ };
194
+ }
157
195
  logger.info(pc.yellow(`\n⚠ Workflow skipped due to rule failure\n`));
158
196
  return {
159
197
  name: workflow.name,
@@ -1,3 +1,4 @@
1
+ import type { RuleResult } from '@kici-dev/sdk';
1
2
  type ColorFn = (text: string) => string;
2
3
  /**
3
4
  * Output formatter that prefixes messages with colored job labels.
@@ -38,9 +39,11 @@ declare class OutputFormatter {
38
39
  */
39
40
  logJobFailure(jobName: string, durationMs: number, error: Error): void;
40
41
  /**
41
- * Log rule evaluation.
42
+ * Log rule evaluation. A rule whose `check()` threw carries an `error`; it
43
+ * prints `✗ errored: <message>` so the author sees the crash reason instead of
44
+ * a bare `✗ failed`. A clean pass/false prints `✓ passed` / `✗ failed`.
42
45
  */
43
- logRuleResult(jobName: string, ruleLabel: string, passed: boolean): void;
46
+ logRuleResult(jobName: string, result: RuleResult): void;
44
47
  }
45
48
  export declare const formatter: OutputFormatter;
46
49
  export {};
@@ -77,12 +77,14 @@ var OutputFormatter = class {
77
77
  logger.error(color(`[${jobName}]`) + pc.red(` ✗ Failed: ${error.message}`) + pc.gray(` (${durationMs}ms)`));
78
78
  }
79
79
  /**
80
- * Log rule evaluation.
80
+ * Log rule evaluation. A rule whose `check()` threw carries an `error`; it
81
+ * prints `✗ errored: <message>` so the author sees the crash reason instead of
82
+ * a bare `✗ failed`. A clean pass/false prints `✓ passed` / `✗ failed`.
81
83
  */
82
- logRuleResult(jobName, ruleLabel, passed) {
84
+ logRuleResult(jobName, result) {
83
85
  const color = this.getJobColor(jobName);
84
- const status = passed ? pc.green("✓ passed") : pc.red("✗ failed");
85
- logger.info(color(`[${jobName}]`) + pc.gray(` rule "${ruleLabel}": `) + status);
86
+ const status = result.error ? pc.red(`✗ errored: ${result.error}`) : result.passed ? pc.green("✓ passed") : pc.red("✗ failed");
87
+ logger.info(color(`[${jobName}]`) + pc.gray(` rule "${result.label}": `) + status);
86
88
  }
87
89
  };
88
90
  const formatter = new OutputFormatter();
@@ -1,4 +1,4 @@
1
- import type { SimulatedEvent } from '@kici-dev/engine';
1
+ import type { SimulatedEvent, ChangedFilesStatus } from '@kici-dev/engine';
2
2
  export interface PayloadOptions {
3
3
  /** Path to custom fixture file */
4
4
  payload?: string;
@@ -12,6 +12,12 @@ export interface PayloadOptions {
12
12
  sha?: string;
13
13
  /** Simulate changed file paths for onChangedFiles trigger matching */
14
14
  files?: string[];
15
+ /**
16
+ * Simulate `changedFiles` availability for rule evaluation. Defaults to
17
+ * `'fetched'` (the author's file list is a genuine diff); set `'unavailable'`
18
+ * to test a rule's `ctx.changedFilesStatus` guard on a diff-less event.
19
+ */
20
+ changedFilesStatus?: ChangedFilesStatus;
15
21
  }
16
22
  /**
17
23
  * Build event payload from default fixture, custom file, and CLI overrides.