@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
@@ -0,0 +1,159 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ import { selectOverlayFiles } from "../remote/uploader.js";
3
+ import path from "node:path";
4
+ import fs from "node:fs/promises";
5
+ import { execFileSync, execSync } from "node:child_process";
6
+ import { makeTempDir } from "@kici-dev/core/tmp";
7
+ //#region src/local-plane/source-provider.ts
8
+ /**
9
+ * LocalSourceProvider — resolve the workdir a `kici run --local` dispatch
10
+ * registers as a `file://` source for the plane.
11
+ *
12
+ * The plane orchestrator's local provider fetches the lock from
13
+ * `<repoBasePath>/.kici/kici.lock.json` and the ephemeral agent clones
14
+ * `file://<repoBasePath>` at a committed sha. So the resolved workdir must be a
15
+ * git repo whose HEAD carries the workflow + lock the run should execute.
16
+ *
17
+ * Two profiles:
18
+ * - **default (isolated):** an isolated tmp clone at HEAD with the local overlay
19
+ * (dirty + untracked files) applied and committed onto a `kici-local` branch,
20
+ * so the clone-by-sha the agent performs sees uncommitted work without
21
+ * touching the developer's tree.
22
+ * - **`--in-place`:** the repo root directly (ambient state, the profile the CI
23
+ * deploy jobs use). Cleanup is a no-op.
24
+ */
25
+ /** The `kici-local` branch the isolated profile commits its overlay onto. */
26
+ const LOCAL_RUN_BRANCH = "kici-local";
27
+ /** Max number of overlay files copied concurrently. */
28
+ const COPY_BATCH_SIZE = 32;
29
+ /**
30
+ * Resolve the workdir for an offline routed run.
31
+ *
32
+ * @param opts.inPlace - Use the repo root directly instead of an isolated clone.
33
+ * @param opts.repoRoot - The developer's repo root.
34
+ */
35
+ async function resolveWorkdir(opts) {
36
+ requireGitRepo(opts.repoRoot);
37
+ return opts.inPlace ? resolveInPlace(opts.repoRoot) : resolveIsolated(opts.repoRoot);
38
+ }
39
+ /** In-place: the repo root, triggered at its current branch + HEAD. */
40
+ function resolveInPlace(repoRoot) {
41
+ const branch = gitOut(repoRoot, [
42
+ "rev-parse",
43
+ "--abbrev-ref",
44
+ "HEAD"
45
+ ]);
46
+ const sha = gitOut(repoRoot, ["rev-parse", "HEAD"]);
47
+ return {
48
+ dir: repoRoot,
49
+ ref: `refs/heads/${branch}`,
50
+ sha,
51
+ branch,
52
+ cleanup: async () => {}
53
+ };
54
+ }
55
+ /**
56
+ * Isolated: a tmp clone at HEAD with the local overlay applied and committed
57
+ * onto the `kici-local` branch, then commits so the agent's clone-by-sha
58
+ * carries the work.
59
+ */
60
+ async function resolveIsolated(repoRoot) {
61
+ const workdir = await makeTempDir("local-run", { persist: true });
62
+ const tmpDir = workdir.path;
63
+ const { sha, existingFiles, deletedFiles } = await selectOverlayFiles(repoRoot);
64
+ execSync(`git clone --no-hardlinks --quiet ${shellQuote(repoRoot)} ${shellQuote(tmpDir)}`, { stdio: "ignore" });
65
+ execSync(`git checkout --quiet ${sha}`, {
66
+ cwd: tmpDir,
67
+ stdio: "ignore"
68
+ });
69
+ await applyOverlay(repoRoot, tmpDir, existingFiles, deletedFiles);
70
+ execFileSync("git", [
71
+ "checkout",
72
+ "-B",
73
+ LOCAL_RUN_BRANCH
74
+ ], {
75
+ cwd: tmpDir,
76
+ stdio: "ignore"
77
+ });
78
+ execFileSync("git", ["add", "-A"], {
79
+ cwd: tmpDir,
80
+ stdio: "ignore"
81
+ });
82
+ if (gitOut(tmpDir, ["status", "--porcelain"])) execFileSync("git", [
83
+ "-c",
84
+ "user.email=local@kici.dev",
85
+ "-c",
86
+ "user.name=kici local",
87
+ "commit",
88
+ "--no-verify",
89
+ "--no-gpg-sign",
90
+ "-m",
91
+ "kici run --local overlay"
92
+ ], {
93
+ cwd: tmpDir,
94
+ stdio: "ignore"
95
+ });
96
+ const committed = gitOut(tmpDir, ["rev-parse", "HEAD"]);
97
+ return {
98
+ dir: tmpDir,
99
+ ref: `refs/heads/${LOCAL_RUN_BRANCH}`,
100
+ sha: committed,
101
+ branch: LOCAL_RUN_BRANCH,
102
+ cleanup: () => workdir.cleanup()
103
+ };
104
+ }
105
+ /** Copy overlay files onto the clone and remove local deletions. */
106
+ async function applyOverlay(repoRoot, tmpDir, existingFiles, deletedFiles) {
107
+ for (let i = 0; i < existingFiles.length; i += COPY_BATCH_SIZE) {
108
+ const batch = existingFiles.slice(i, i + COPY_BATCH_SIZE);
109
+ await Promise.all(batch.map((file) => copyOverlayFile(repoRoot, tmpDir, file)));
110
+ }
111
+ for (let i = 0; i < deletedFiles.length; i += COPY_BATCH_SIZE) {
112
+ const batch = deletedFiles.slice(i, i + COPY_BATCH_SIZE);
113
+ await Promise.all(batch.map((file) => fs.rm(path.join(tmpDir, file), { force: true })));
114
+ }
115
+ }
116
+ /**
117
+ * Copy a single overlay file, preserving its mode. Symlinks are recreated as
118
+ * links (not dereferenced) — the same shape the remote tarball preserves.
119
+ */
120
+ async function copyOverlayFile(repoRoot, tmpDir, file) {
121
+ const src = path.join(repoRoot, file);
122
+ const dest = path.join(tmpDir, file);
123
+ await fs.mkdir(path.dirname(dest), { recursive: true });
124
+ const srcStat = await fs.lstat(src);
125
+ if (srcStat.isSymbolicLink()) {
126
+ const target = await fs.readlink(src);
127
+ await fs.rm(dest, { force: true });
128
+ await fs.symlink(target, dest);
129
+ return;
130
+ }
131
+ await fs.copyFile(src, dest);
132
+ await fs.chmod(dest, srcStat.mode);
133
+ }
134
+ /** Throw an actionable error if `repoRoot` is not a git work tree. */
135
+ function requireGitRepo(repoRoot) {
136
+ try {
137
+ execSync("git rev-parse --is-inside-work-tree", {
138
+ cwd: repoRoot,
139
+ stdio: "ignore"
140
+ });
141
+ } catch {
142
+ throw new Error(`kici run --local needs a git repository, but "${repoRoot}" is not inside a git work tree. Initialize a repo (git init) before running.`);
143
+ }
144
+ }
145
+ /** Run a git command in `cwd` and return trimmed stdout. */
146
+ function gitOut(cwd, args) {
147
+ return execFileSync("git", args, {
148
+ cwd,
149
+ encoding: "utf8"
150
+ }).trim();
151
+ }
152
+ /** Minimal single-quote shell escaping for paths passed to git via execSync. */
153
+ function shellQuote(value) {
154
+ return `'${value.replace(/'/g, `'\\''`)}'`;
155
+ }
156
+ //#endregion
157
+ export { LOCAL_RUN_BRANCH, resolveWorkdir };
158
+
159
+ //# sourceMappingURL=source-provider.js.map
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Route a `kici run --local --trusted` run onto the plane's trusted label set
3
+ * by appending a routing label to every job's `runsOn` in the workdir lock the
4
+ * plane orchestrator reads.
5
+ *
6
+ * The orchestrator resolves each job's target agent from the lock's `runsOn`
7
+ * selectors (subset match + smallest-set-wins in the scaler label matcher).
8
+ * A default run leaves `runsOn` untouched, so a `runsOn: ['default']` job lands
9
+ * on the sandboxed `['default']` label set — byte-identical to a non-trusted
10
+ * run. `--trusted` appends the non-reserved `self-hosted` label, which only the
11
+ * trusted `['default','self-hosted']` label set carries, forcing every job onto
12
+ * the trusted profile.
13
+ *
14
+ * This is routing-by-label to a PRE-CONFIGURED trusted scaler label set — the
15
+ * `KICI_TRUSTED_ENV` value lives only in that label set's env (the plane's own
16
+ * config), never on a dispatch payload. The lock patch adds a label
17
+ * requirement, not the trusted flag itself.
18
+ */
19
+ /** Handle to restore the lock to its pre-patch bytes. */
20
+ export interface RunsOnLabelInjection {
21
+ /** Rewrite the original bytes captured before the patch. */
22
+ restore: () => void;
23
+ }
24
+ /**
25
+ * Append an exact-match `runsOn` selector for `label` to every job in the lock
26
+ * at `lockPath`, unless the job already requires it. Returns a `restore()` that
27
+ * rewrites the original file bytes (call it in a `finally` — for an in-place run
28
+ * it un-dirties the developer's tree; for an isolated run the throwaway clone is
29
+ * removed anyway).
30
+ */
31
+ export declare function injectRunsOnLabel(lockPath: string, label: string): RunsOnLabelInjection;
32
+ //# sourceMappingURL=trusted-routing.d.ts.map
@@ -0,0 +1,47 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ import fs from "node:fs";
3
+ //#region src/local-plane/trusted-routing.ts
4
+ /**
5
+ * Route a `kici run --local --trusted` run onto the plane's trusted label set
6
+ * by appending a routing label to every job's `runsOn` in the workdir lock the
7
+ * plane orchestrator reads.
8
+ *
9
+ * The orchestrator resolves each job's target agent from the lock's `runsOn`
10
+ * selectors (subset match + smallest-set-wins in the scaler label matcher).
11
+ * A default run leaves `runsOn` untouched, so a `runsOn: ['default']` job lands
12
+ * on the sandboxed `['default']` label set — byte-identical to a non-trusted
13
+ * run. `--trusted` appends the non-reserved `self-hosted` label, which only the
14
+ * trusted `['default','self-hosted']` label set carries, forcing every job onto
15
+ * the trusted profile.
16
+ *
17
+ * This is routing-by-label to a PRE-CONFIGURED trusted scaler label set — the
18
+ * `KICI_TRUSTED_ENV` value lives only in that label set's env (the plane's own
19
+ * config), never on a dispatch payload. The lock patch adds a label
20
+ * requirement, not the trusted flag itself.
21
+ */
22
+ /**
23
+ * Append an exact-match `runsOn` selector for `label` to every job in the lock
24
+ * at `lockPath`, unless the job already requires it. Returns a `restore()` that
25
+ * rewrites the original file bytes (call it in a `finally` — for an in-place run
26
+ * it un-dirties the developer's tree; for an isolated run the throwaway clone is
27
+ * removed anyway).
28
+ */
29
+ function injectRunsOnLabel(lockPath, label) {
30
+ const original = fs.readFileSync(lockPath, "utf-8");
31
+ const lock = JSON.parse(original);
32
+ for (const workflow of lock.workflows ?? []) for (const job of workflow.jobs ?? []) {
33
+ const runsOn = job.runsOn ??= [];
34
+ if (!runsOn.some((m) => m.kind === "exact" && m.value === label)) runsOn.push({
35
+ kind: "exact",
36
+ value: label
37
+ });
38
+ }
39
+ fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + "\n");
40
+ return { restore: () => {
41
+ fs.writeFileSync(lockPath, original);
42
+ } };
43
+ }
44
+ //#endregion
45
+ export { injectRunsOnLabel };
46
+
47
+ //# sourceMappingURL=trusted-routing.js.map
@@ -1,5 +1,18 @@
1
1
  import type { StepInput, TriggerConfig } from '@kici-dev/sdk';
2
2
  import { type LockFile, type LockTrigger, type LockStepEntry, type WorkflowWithSource } from '../types.js';
3
+ import { type SourceLocation } from '../errors/index.js';
4
+ /**
5
+ * Courtesy compatibility warning for `kici compile`.
6
+ *
7
+ * When the current schema version is itself a breaking version
8
+ * (`floor === version`), locks emitted now stamp `minReaderVersion = version`
9
+ * and cannot be read by orchestrators older than that version — return a
10
+ * one-line heads-up naming the required orchestrator schema. When the floor sits
11
+ * below the current version (`floor < version`) the emitted lock is additive
12
+ * over older readers down to the floor, so no warning is warranted (return
13
+ * null). The orchestrator remains the authoritative reject; this is informational.
14
+ */
15
+ export declare function schemaWindowWarning(floor: number, version: number): string | null;
3
16
  /**
4
17
  * Detect git repository root by running `git rev-parse --show-toplevel`.
5
18
  * Falls back to cwd if not in a git repo.
@@ -47,7 +60,7 @@ export declare function transformTriggers(triggers?: TriggerConfig[]): readonly
47
60
  * compiler's naming matches the agent's `extractAndNormalizeSteps` enumeration —
48
61
  * the flat-stepIndex invariant.
49
62
  */
50
- export declare function transformSteps(steps: readonly StepInput[], gitRoot: string): readonly LockStepEntry[];
63
+ export declare function transformSteps(steps: readonly StepInput[], gitRoot: string, jobLocation?: SourceLocation): readonly LockStepEntry[];
51
64
  /**
52
65
  * Serialize lock file to JSON string.
53
66
  *
@@ -1,5 +1,8 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
- import { SCHEMA_VERSION as SCHEMA_VERSION$1 } from "../types.js";
2
+ import { compilerError } from "../errors/formatter.js";
3
+ import { locationForJob, locationForWorkflow } from "../errors/source-location.js";
4
+ import "../errors/index.js";
5
+ import { BREAKING_FLOOR as BREAKING_FLOOR$1, SCHEMA_VERSION as SCHEMA_VERSION$1 } from "../types.js";
3
6
  import { computeContentHash } from "./hasher.js";
4
7
  import { resolveHashFiles } from "./hash-files.js";
5
8
  import { analyzePurity } from "./purity-analyzer.js";
@@ -13,6 +16,21 @@ import { normalizeRunsOnAllToMatchers, normalizeRunsOnToMatchers, runsOnPickFrom
13
16
  import { execSync } from "node:child_process";
14
17
  //#region src/lockfile/generator.ts
15
18
  /**
19
+ * Courtesy compatibility warning for `kici compile`.
20
+ *
21
+ * When the current schema version is itself a breaking version
22
+ * (`floor === version`), locks emitted now stamp `minReaderVersion = version`
23
+ * and cannot be read by orchestrators older than that version — return a
24
+ * one-line heads-up naming the required orchestrator schema. When the floor sits
25
+ * below the current version (`floor < version`) the emitted lock is additive
26
+ * over older readers down to the floor, so no warning is warranted (return
27
+ * null). The orchestrator remains the authoritative reject; this is informational.
28
+ */
29
+ function schemaWindowWarning(floor, version) {
30
+ if (floor < version) return null;
31
+ return `This lock uses schema v${version}, a breaking schema version — orchestrators older than v${version} cannot read it. Upgrade the orchestrator to schema v${version} or newer before it can dispatch from this lock.`;
32
+ }
33
+ /**
16
34
  * Detect git repository root by running `git rev-parse --show-toplevel`.
17
35
  * Falls back to cwd if not in a git repo.
18
36
  *
@@ -96,9 +114,11 @@ function generateLockFile(workflowsWithSource) {
96
114
  });
97
115
  return {
98
116
  schemaVersion: SCHEMA_VERSION$1,
117
+ minReaderVersion: BREAKING_FLOOR$1,
99
118
  source: topLevelSource,
100
119
  contentHash: sha256(JSON.stringify({
101
120
  schemaVersion: SCHEMA_VERSION$1,
121
+ minReaderVersion: BREAKING_FLOOR$1,
102
122
  source: topLevelSource,
103
123
  workflows
104
124
  })),
@@ -158,7 +178,7 @@ function transformWorkflow(workflow, sourceFile, exportRef, bundleSource, gitRoo
158
178
  ...workflow.concurrency.max !== void 0 && { max: workflow.concurrency.max }
159
179
  } },
160
180
  ...workflow.timeout !== void 0 && { timeout: workflow.timeout },
161
- ...workflow.approval !== void 0 && { approval: (assertNonStepApprovalScope(workflow.approval, "workflow"), toLockApproval(workflow.approval)) }
181
+ ...workflow.approval !== void 0 && { approval: (assertNonStepApprovalScope(workflow.approval, "workflow", locationForWorkflow(sourceFile)), toLockApproval(workflow.approval)) }
162
182
  };
163
183
  }
164
184
  /**
@@ -316,6 +336,14 @@ function toLockWorkflowComplete(t) {
316
336
  ...t.source !== void 0 && { source: t.source }
317
337
  };
318
338
  }
339
+ function toLockWorkflowsFailedBatch(t) {
340
+ return {
341
+ _type: "workflows_failed_batch",
342
+ accumulateFor: t.accumulateFor,
343
+ ...t.name !== void 0 && { name: t.name },
344
+ ...t.source !== void 0 && { source: t.source }
345
+ };
346
+ }
319
347
  function toLockJobComplete(t) {
320
348
  return {
321
349
  _type: "job_complete",
@@ -383,6 +411,7 @@ function transformOneTrigger(trigger) {
383
411
  case "WebhookTrigger": return [toLockWebhook(trigger)];
384
412
  case "KiciEventTrigger": return [toLockKiciEvent(trigger)];
385
413
  case "WorkflowCompleteTrigger": return [toLockWorkflowComplete(trigger)];
414
+ case "WorkflowsFailedBatchTrigger": return [toLockWorkflowsFailedBatch(trigger)];
386
415
  case "JobCompleteTrigger": return [toLockJobComplete(trigger)];
387
416
  case "GenericWebhookTrigger": return [toLockGenericWebhook(trigger)];
388
417
  case "ScheduleTrigger": return [toLockSchedule(trigger)];
@@ -478,30 +507,31 @@ function normalizeRunsOnForLock(runsOn, jobName) {
478
507
  * The overlap check compares exact matchers only — a glob/regex include and an
479
508
  * exact exclude (or vice versa) cannot be statically known to overlap.
480
509
  */
481
- function validateRunsOn(runsOn, jobName) {
510
+ function validateRunsOn(runsOn, jobName, location) {
482
511
  const { include, exclude } = normalizeRunsOnToMatchers(runsOn, `job '${jobName}' runsOn`);
483
512
  const includeExact = new Set(include.filter((m) => m.kind === "exact").map((m) => m.value));
484
513
  const overlap = exclude.filter((m) => m.kind === "exact").map((m) => m.value).filter((v) => includeExact.has(v));
485
- if (overlap.length > 0) throw new Error(`Job "${jobName}": labels and exclude overlap on [${overlap.join(", ")}]. A label cannot be both required and excluded.`);
514
+ if (overlap.length > 0) throw compilerError("E112", `Job "${jobName}": labels and exclude overlap on [${overlap.join(", ")}]. A label cannot be both required and excluded.`, {
515
+ location,
516
+ suggestion: "Remove the overlapping label(s) from either runsOn or the exclude set."
517
+ });
486
518
  }
487
519
  /**
488
- * Transform one environment reference (static name or function) into a lock
520
+ * Transform one context reference (static name or function) into a lock
489
521
  * `{ value, dynamic }` entry. A function element is analyzed for purity: a pure
490
522
  * function becomes an inline expression resolvable at two-phase eval; an impure
491
523
  * one carries only the `dynamic` flag (the agent runs an init job to resolve it).
492
524
  */
493
- function transformEnvironmentRef(ref, jobName) {
525
+ function transformContextRef(ref) {
494
526
  if (typeof ref === "function") {
495
527
  const fnSource = ref.toString();
496
- const purity = analyzePurity(fnSource);
497
- if (purity.pure) return {
528
+ if (analyzePurity(fnSource).pure) return {
498
529
  value: {
499
530
  _type: "inline",
500
531
  expression: fnSource
501
532
  },
502
533
  dynamic: true
503
534
  };
504
- console.warn(`[kici] Job "${jobName}": environment function is not pure (${purity.reason}). An init job will be required, adding ~5-10s delay.`);
505
535
  return {
506
536
  value: "",
507
537
  dynamic: true
@@ -516,15 +546,25 @@ function transformEnvironmentRef(ref, jobName) {
516
546
  * Transform a static job to lock file format.
517
547
  */
518
548
  function transformJob(job, configPath, index, gitRoot, uuidToName) {
519
- if (job.runsOn !== void 0 && job.runsOnAll !== void 0) throw new Error(`job '${job.name}': runsOn and runsOnAll are mutually exclusive`);
520
- if (job.runsOn === void 0 && job.runsOnAll === void 0) throw new Error(`job '${job.name}': one of runsOn or runsOnAll is required`);
549
+ const jobLocation = locationForJob(job, configPath);
550
+ if (job.runsOn !== void 0 && job.runsOnAll !== void 0) throw compilerError("E108", `job '${job.name}': runsOn and runsOnAll are mutually exclusive`, {
551
+ location: jobLocation,
552
+ suggestion: "Set exactly one of runsOn (single agent) or runsOnAll (fan-out to every matching agent)."
553
+ });
554
+ if (job.runsOn === void 0 && job.runsOnAll === void 0) throw compilerError("E109", `job '${job.name}': one of runsOn or runsOnAll is required`, {
555
+ location: jobLocation,
556
+ suggestion: "Add runsOn: \"kici:os:linux\" (or another agent label) to the job."
557
+ });
521
558
  if (job.onUnreachable !== void 0 && job.runsOnAll === void 0) console.warn(`[kici] job '${job.name}': onUnreachable is ignored without runsOnAll`);
522
- if (job.maxParallel !== void 0 && job.maxParallel < 1) throw new Error(`job '${job.name}': maxParallel must be >= 1`);
559
+ if (job.maxParallel !== void 0 && job.maxParallel < 1) throw compilerError("E110", `job '${job.name}': maxParallel must be >= 1`, {
560
+ location: jobLocation,
561
+ suggestion: "Set maxParallel to a positive integer, or remove it to run unbounded."
562
+ });
523
563
  if (!(job.matrix !== void 0 || job.runsOnAll !== void 0) && (job.maxParallel !== void 0 || job.failFast !== void 0)) console.warn(`[kici] job '${job.name}': maxParallel/failFast are ignored without matrix or runsOnAll (no fan-out to bound)`);
524
- if (job.runsOn !== void 0) validateRunsOn(job.runsOn, job.name);
525
- const environmentFields = {};
526
- const envRefs = job.environments ?? (job.environment !== void 0 ? [job.environment] : void 0);
527
- if (envRefs !== void 0 && envRefs.length > 0) environmentFields.environments = envRefs.map((ref) => transformEnvironmentRef(ref, job.name));
564
+ if (job.runsOn !== void 0) validateRunsOn(job.runsOn, job.name, jobLocation);
565
+ const contextFields = {};
566
+ const contextRefs = job.contexts ?? (job.context !== void 0 ? [job.context] : void 0);
567
+ if (contextRefs !== void 0 && contextRefs.length > 0) contextFields.contexts = contextRefs.map((ref) => transformContextRef(ref));
528
568
  const envFields = {};
529
569
  if (job.env !== void 0) {
530
570
  if (typeof job.env === "function") {
@@ -535,14 +575,16 @@ function transformJob(job, configPath, index, gitRoot, uuidToName) {
535
575
  _type: "inline",
536
576
  expression: fnSource
537
577
  };
538
- else console.warn(`[kici] Job "${job.name}": env function is not pure (${purity.reason}). An init job will be required, adding ~5-10s delay.`);
539
578
  } else if (typeof job.env === "object") envFields.env = { ...job.env };
540
579
  }
541
580
  if (job.resources !== void 0) try {
542
581
  validateResourceRequest(job.resources);
543
582
  } catch (err) {
544
583
  const reason = err instanceof Error ? err.message : String(err);
545
- throw new Error(`Job "${job.name}": invalid resources -- ${reason}`);
584
+ throw compilerError("E111", `Job "${job.name}": invalid resources -- ${reason}`, {
585
+ location: jobLocation,
586
+ suggestion: "Fix the resources request (valid CPU/memory strings, request <= limit)."
587
+ });
546
588
  }
547
589
  const concurrencyFields = {};
548
590
  if (job.concurrencyGroup !== void 0) {
@@ -554,7 +596,6 @@ function transformJob(job, configPath, index, gitRoot, uuidToName) {
554
596
  _type: "inline",
555
597
  expression: fnSource
556
598
  };
557
- else console.warn(`[kici] Job "${job.name}": concurrencyGroup function is not pure (${purity.reason}). An init job will be required, adding ~5-10s delay.`);
558
599
  } else if (typeof job.concurrencyGroup === "string") concurrencyFields.concurrencyGroup = job.concurrencyGroup;
559
600
  }
560
601
  return {
@@ -567,7 +608,7 @@ function transformJob(job, configPath, index, gitRoot, uuidToName) {
567
608
  ...job.maxParallel !== void 0 && { maxParallel: job.maxParallel },
568
609
  ...job.failFast !== void 0 && { failFast: job.failFast },
569
610
  ...resolveNeedsForLock(job.needs, uuidToName),
570
- steps: transformSteps(job.steps, gitRoot),
611
+ steps: transformSteps(job.steps, gitRoot, jobLocation),
571
612
  matrix: job.matrix ? transformMatrix(job.matrix, job.name, configPath) : void 0,
572
613
  include: job.include?.map((inc) => ({ ...inc })),
573
614
  exclude: job.exclude?.map((exc) => ({ ...exc })),
@@ -576,7 +617,8 @@ function transformJob(job, configPath, index, gitRoot, uuidToName) {
576
617
  ...job.checkout !== void 0 && { checkout: job.checkout },
577
618
  ...job.cache !== void 0 && { cache: normalizeCacheSpecs(job.cache) },
578
619
  ...job.container !== void 0 && { container: job.container },
579
- ...environmentFields,
620
+ ...job.sandbox !== void 0 && { sandbox: job.sandbox },
621
+ ...contextFields,
580
622
  ...envFields,
581
623
  ...concurrencyFields,
582
624
  ...job.onCancel !== void 0 && { hasOnCancel: true },
@@ -589,7 +631,7 @@ function transformJob(job, configPath, index, gitRoot, uuidToName) {
589
631
  ...job.timeout !== void 0 && { timeout: job.timeout },
590
632
  ...job.resources !== void 0 && { resources: job.resources },
591
633
  ...job.init !== void 0 && { init: job.init },
592
- ...job.approval !== void 0 && { approval: (assertNonStepApprovalScope(job.approval, "job"), toLockApproval(job.approval)) }
634
+ ...job.approval !== void 0 && { approval: (assertNonStepApprovalScope(job.approval, "job", jobLocation), toLockApproval(job.approval)) }
593
635
  };
594
636
  }
595
637
  /**
@@ -653,15 +695,21 @@ function toLockApproval(c) {
653
695
  * step-scope-only gate (it fires between a step's check and run), so it is a
654
696
  * compile error anywhere else.
655
697
  */
656
- function assertNonStepApprovalScope(c, scope) {
657
- if (normalizeApproval(c).when === "drift") throw new Error(`approval.when "drift" is only valid on steps (found at ${scope} scope)`);
698
+ function assertNonStepApprovalScope(c, scope, location) {
699
+ if (normalizeApproval(c).when === "drift") throw compilerError("E113", `approval.when "drift" is only valid on steps (found at ${scope} scope)`, {
700
+ location,
701
+ suggestion: "Move the drift-gated approval onto a step, or use when: \"always\" at job/workflow scope."
702
+ });
658
703
  }
659
704
  /**
660
705
  * Validate a step's approval config: `when: 'drift'` fires between the step's
661
706
  * check and run, so it requires a `check` facet. A compile error otherwise.
662
707
  */
663
- function assertStepApprovalCheckFacet(step) {
664
- if (step.approval !== void 0 && normalizeApproval(step.approval).when === "drift" && step.check === void 0) throw new Error(`step '${step.name || "(unnamed)"}': approval.when "drift" requires a check facet`);
708
+ function assertStepApprovalCheckFacet(step, jobLocation) {
709
+ if (step.approval !== void 0 && normalizeApproval(step.approval).when === "drift" && step.check === void 0) throw compilerError("E114", `step '${step.name || "(unnamed)"}': approval.when "drift" requires a check facet`, {
710
+ location: step._sourceLocation ?? jobLocation,
711
+ suggestion: "Add a check facet to the step, or use approval when: \"always\"."
712
+ });
665
713
  }
666
714
  /**
667
715
  * Transform a job's `steps` array into lock-file entries. Sequential steps and
@@ -670,22 +718,40 @@ function assertStepApprovalCheckFacet(step) {
670
718
  * compiler's naming matches the agent's `extractAndNormalizeSteps` enumeration —
671
719
  * the flat-stepIndex invariant.
672
720
  */
673
- function transformSteps(steps, gitRoot) {
721
+ function transformSteps(steps, gitRoot, jobLocation) {
722
+ const anchor = jobLocation ?? {
723
+ file: "",
724
+ line: 1,
725
+ column: 1
726
+ };
674
727
  const counter = { n: 0 };
675
728
  let groupOrdinal = 0;
676
729
  return steps.map((entry) => {
677
- if (isParallelGroup(entry)) return transformParallelGroup(entry, gitRoot, counter, groupOrdinal++);
678
- return transformSequentialStep(entry, gitRoot, counter);
730
+ if (isParallelGroup(entry)) return transformParallelGroup(entry, gitRoot, counter, groupOrdinal++, anchor);
731
+ return transformSequentialStep(entry, gitRoot, counter, anchor);
679
732
  });
680
733
  }
734
+ /** Best step-scoped location: the step's captured call-site, else the job anchor. */
735
+ function stepEntryLocation(entry, jobLocation) {
736
+ return entry._sourceLocation ?? jobLocation;
737
+ }
681
738
  /** Validate and transform a `ParallelGroup` into a `LockParallelStep`. */
682
- function transformParallelGroup(group, gitRoot, counter, groupOrdinal) {
683
- if (group.steps.length === 0) throw new Error("job step: empty parallel group not allowed");
739
+ function transformParallelGroup(group, gitRoot, counter, groupOrdinal, jobLocation) {
740
+ if (group.steps.length === 0) throw compilerError("E115", "job step: empty parallel group not allowed", {
741
+ location: jobLocation,
742
+ suggestion: "Add at least one step to the parallel group, or remove the group."
743
+ });
684
744
  const seen = /* @__PURE__ */ new Set();
685
745
  const children = group.steps.map((child) => {
686
- if (isParallelGroup(child)) throw new Error("job step: nested parallel groups are not supported");
687
- const lockChild = transformSequentialStep(child, gitRoot, counter);
688
- if (seen.has(lockChild.name)) throw new Error(`job step: duplicate step name '${lockChild.name}' in parallel group`);
746
+ if (isParallelGroup(child)) throw compilerError("E116", "job step: nested parallel groups are not supported", {
747
+ location: stepEntryLocation(child, jobLocation),
748
+ suggestion: "Flatten the nested group a parallel group may only contain steps."
749
+ });
750
+ const lockChild = transformSequentialStep(child, gitRoot, counter, jobLocation);
751
+ if (seen.has(lockChild.name)) throw compilerError("E117", `job step: duplicate step name '${lockChild.name}' in parallel group`, {
752
+ location: stepEntryLocation(child, jobLocation),
753
+ suggestion: "Give each step in a parallel group a unique name."
754
+ });
689
755
  seen.add(lockChild.name);
690
756
  return lockChild;
691
757
  });
@@ -699,7 +765,7 @@ function transformParallelGroup(group, gitRoot, counter, groupOrdinal) {
699
765
  };
700
766
  }
701
767
  /** Transform a single sequential step (or bare function) into a `LockStep`. */
702
- function transformSequentialStep(stepOrFn, gitRoot, counter) {
768
+ function transformSequentialStep(stepOrFn, gitRoot, counter, jobLocation) {
703
769
  if (typeof stepOrFn === "function") {
704
770
  counter.n++;
705
771
  return {
@@ -733,7 +799,7 @@ function transformSequentialStep(stepOrFn, gitRoot, counter) {
733
799
  ...step.cleanup !== void 0 && { hasCleanup: true },
734
800
  ...step.check !== void 0 && { hasCheck: true },
735
801
  ...step.whenInSync !== void 0 && { hasWhenInSync: true },
736
- ...step.approval !== void 0 && { approval: (assertStepApprovalCheckFacet(step), toLockApproval(step.approval)) }
802
+ ...step.approval !== void 0 && { approval: (assertStepApprovalCheckFacet(step, jobLocation), toLockApproval(step.approval)) }
737
803
  };
738
804
  }
739
805
  /**
@@ -812,6 +878,6 @@ function serializeLockFile(lockFile, pretty = true) {
812
878
  return JSON.stringify(lockFile, null, pretty ? 2 : void 0);
813
879
  }
814
880
  //#endregion
815
- export { computeLockfileHash, detectGitRoot, generateLockFile, serializeLockFile, transformSteps, transformTriggers };
881
+ export { computeLockfileHash, detectGitRoot, generateLockFile, schemaWindowWarning, serializeLockFile, transformSteps, transformTriggers };
816
882
 
817
883
  //# sourceMappingURL=generator.js.map
@@ -1,3 +1,5 @@
1
- export { generateLockFile, serializeLockFile, detectGitRoot, computeLockfileHash, } from './generator.js';
1
+ export { generateLockFile, serializeLockFile, detectGitRoot, computeLockfileHash, schemaWindowWarning, } from './generator.js';
2
2
  export { computeContentHash, COMPILE_SCHEMA_VERSION } from './hasher.js';
3
+ export { DynamicValueField, analyzeJobPurity, collectWorkflowPurityWarnings, } from './purity-diagnostics.js';
4
+ export type { JobPurityWarning } from './purity-diagnostics.js';
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
2
  import { COMPILE_SCHEMA_VERSION, computeContentHash } from "./hasher.js";
3
- import { computeLockfileHash, detectGitRoot, generateLockFile, serializeLockFile } from "./generator.js";
4
- export { COMPILE_SCHEMA_VERSION, computeContentHash, computeLockfileHash, detectGitRoot, generateLockFile, serializeLockFile };
3
+ import { computeLockfileHash, detectGitRoot, generateLockFile, schemaWindowWarning, serializeLockFile } from "./generator.js";
4
+ import { DynamicValueField, analyzeJobPurity, collectWorkflowPurityWarnings } from "./purity-diagnostics.js";
5
+ export { COMPILE_SCHEMA_VERSION, DynamicValueField, analyzeJobPurity, collectWorkflowPurityWarnings, computeContentHash, computeLockfileHash, detectGitRoot, generateLockFile, schemaWindowWarning, serializeLockFile };
@@ -0,0 +1,31 @@
1
+ import type { Job } from '@kici-dev/sdk';
2
+ import type { WorkflowWithSource } from '../types.js';
3
+ /** Which dynamic-value slot on a job carried the impure function. */
4
+ export declare enum DynamicValueField {
5
+ Context = "context",
6
+ Env = "env",
7
+ ConcurrencyGroup = "concurrencyGroup"
8
+ }
9
+ /**
10
+ * One impure dynamic-value function found on a static job. Each impurity forces
11
+ * the orchestrator to dispatch an agent-side `__init__` job (~5-10s) instead of
12
+ * inlining the value, so we surface it to the author at compile / preview time.
13
+ */
14
+ export interface JobPurityWarning {
15
+ workflowName: string;
16
+ sourceFile?: string;
17
+ jobName: string;
18
+ field: DynamicValueField;
19
+ reason: string;
20
+ }
21
+ /**
22
+ * Analyze a single static job's dynamic-value functions (context(s), env,
23
+ * concurrencyGroup) for impurity. Returns one warning per impure function.
24
+ */
25
+ export declare function analyzeJobPurity(job: Job, workflowName: string, sourceFile?: string): JobPurityWarning[];
26
+ /**
27
+ * Walk every workflow's static jobs and collect impurity warnings. Function-typed
28
+ * jobs (dynamic job generators) are skipped — their purity is analyzed at dispatch.
29
+ */
30
+ export declare function collectWorkflowPurityWarnings(workflows: WorkflowWithSource[]): JobPurityWarning[];
31
+ //# sourceMappingURL=purity-diagnostics.d.ts.map