@kici-dev/compiler 0.1.27 → 0.3.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 (153) 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.js +120 -11
  4. package/dist/commands/compile.js +28 -4
  5. package/dist/commands/diagnostics.js +14 -2
  6. package/dist/commands/doctor.d.ts +64 -0
  7. package/dist/commands/doctor.js +413 -0
  8. package/dist/commands/endpoints.js +6 -0
  9. package/dist/commands/fixture.js +13 -0
  10. package/dist/commands/hook.js +3 -2
  11. package/dist/commands/index.d.ts +8 -0
  12. package/dist/commands/index.js +5 -1
  13. package/dist/commands/init-host-os.d.ts +34 -0
  14. package/dist/commands/init-host-os.js +44 -0
  15. package/dist/commands/init.d.ts +4 -0
  16. package/dist/commands/init.js +159 -36
  17. package/dist/commands/local.d.ts +28 -2
  18. package/dist/commands/local.js +74 -5
  19. package/dist/commands/login.js +1 -1
  20. package/dist/commands/notifications.d.ts +61 -0
  21. package/dist/commands/notifications.js +274 -0
  22. package/dist/commands/preview.js +8 -1
  23. package/dist/commands/run-routed.js +10 -1
  24. package/dist/commands/run.js +41 -12
  25. package/dist/commands/runs/artifacts/download.d.ts +24 -0
  26. package/dist/commands/runs/artifacts/download.js +154 -0
  27. package/dist/commands/runs/artifacts/list.d.ts +5 -0
  28. package/dist/commands/runs/artifacts/list.js +47 -0
  29. package/dist/commands/runs/cancel.js +2 -2
  30. package/dist/commands/runs/list.js +8 -2
  31. package/dist/commands/verify-attestation.d.ts +3 -1
  32. package/dist/commands/verify-attestation.js +18 -7
  33. package/dist/errors/formatter.d.ts +14 -1
  34. package/dist/errors/formatter.js +13 -3
  35. package/dist/errors/index.d.ts +2 -1
  36. package/dist/errors/index.js +3 -2
  37. package/dist/errors/source-location.d.ts +20 -0
  38. package/dist/errors/source-location.js +45 -0
  39. package/dist/execution/executor.js +25 -5
  40. package/dist/fixtures/defaults/index.js +2 -1
  41. package/dist/llm-context/llms-architecture.txt +51 -22
  42. package/dist/llm-context/llms-cli.txt +2461 -1582
  43. package/dist/llm-context/llms-features.txt +178 -106
  44. package/dist/llm-context/llms-full.txt +6127 -4426
  45. package/dist/llm-context/llms-getting-started.txt +306 -42
  46. package/dist/llm-context/llms-patterns.txt +13 -7
  47. package/dist/llm-context/llms-sdk-runtime.txt +2013 -0
  48. package/dist/llm-context/llms-sdk.txt +591 -2152
  49. package/dist/llm-context/llms.txt +30 -16
  50. package/dist/local-plane/orchestrator-process.d.ts +12 -7
  51. package/dist/local-plane/orchestrator-process.js +28 -14
  52. package/dist/local-plane/os-mismatch-hint.d.ts +16 -0
  53. package/dist/local-plane/os-mismatch-hint.js +34 -0
  54. package/dist/local-plane/plane-liveness.d.ts +68 -0
  55. package/dist/local-plane/plane-liveness.js +145 -0
  56. package/dist/local-plane/plane-manager.d.ts +60 -18
  57. package/dist/local-plane/plane-manager.js +210 -70
  58. package/dist/local-plane/plane-trigger.d.ts +13 -4
  59. package/dist/local-plane/plane-trigger.js +27 -14
  60. package/dist/local-plane/platform-attach.js +1 -5
  61. package/dist/local-plane/port-holder.d.ts +78 -0
  62. package/dist/local-plane/port-holder.js +227 -0
  63. package/dist/local-plane/postgres.d.ts +15 -3
  64. package/dist/local-plane/postgres.js +34 -12
  65. package/dist/local-plane/run-follow.d.ts +20 -0
  66. package/dist/local-plane/run-follow.js +51 -4
  67. package/dist/local-plane/scaler-config.d.ts +28 -14
  68. package/dist/local-plane/scaler-config.js +59 -41
  69. package/dist/local-plane/source-provider.js +4 -10
  70. package/dist/lockfile/generator.d.ts +14 -1
  71. package/dist/lockfile/generator.js +99 -33
  72. package/dist/lockfile/index.d.ts +3 -1
  73. package/dist/lockfile/index.js +3 -2
  74. package/dist/lockfile/purity-diagnostics.d.ts +31 -0
  75. package/dist/lockfile/purity-diagnostics.js +52 -0
  76. package/dist/postinstall.js +2 -1
  77. package/dist/remote/artifact-extract.d.ts +8 -0
  78. package/dist/remote/artifact-extract.js +58 -0
  79. package/dist/remote/config.d.ts +21 -3
  80. package/dist/remote/config.js +24 -5
  81. package/dist/remote/dashboard-client.d.ts +39 -2
  82. package/dist/remote/dashboard-client.js +47 -4
  83. package/dist/remote/fs-case.d.ts +21 -0
  84. package/dist/remote/fs-case.js +45 -0
  85. package/dist/remote/history.d.ts +9 -3
  86. package/dist/remote/history.js +79 -10
  87. package/dist/remote/local-repo-identity.d.ts +1 -1
  88. package/dist/remote/local-repo-identity.js +2 -1
  89. package/dist/remote/notifications-client.d.ts +149 -0
  90. package/dist/remote/notifications-client.js +103 -0
  91. package/dist/remote/oauth.d.ts +13 -0
  92. package/dist/remote/oauth.js +31 -5
  93. package/dist/remote/output/summary.js +76 -14
  94. package/dist/remote/render.js +30 -9
  95. package/dist/remote/uploader.js +5 -4
  96. package/dist/templates/agents-md.d.ts +1 -1
  97. package/dist/templates/agents-md.js +6 -6
  98. package/dist/templates/index.d.ts +1 -1
  99. package/dist/templates/index.js +2 -2
  100. package/dist/templates/package-json.d.ts +15 -0
  101. package/dist/templates/package-json.js +26 -5
  102. package/dist/test-runner/dry-run.d.ts +2 -1
  103. package/dist/test-runner/dry-run.js +18 -1
  104. package/dist/test-runner/event-types.d.ts +2 -0
  105. package/dist/test-runner/event-types.js +3 -0
  106. package/dist/test-runner/job-executor.d.ts +1 -0
  107. package/dist/test-runner/job-executor.js +81 -43
  108. package/dist/test-runner/output-formatter.d.ts +5 -2
  109. package/dist/test-runner/output-formatter.js +6 -4
  110. package/dist/test-runner/payload-builder.d.ts +7 -1
  111. package/dist/test-runner/payload-builder.js +11 -2
  112. package/dist/test-runner/rule-evaluator.d.ts +7 -3
  113. package/dist/test-runner/rule-evaluator.js +13 -12
  114. package/dist/test-runner/step-context.d.ts +23 -5
  115. package/dist/test-runner/step-context.js +28 -118
  116. package/dist/types.d.ts +21 -2
  117. package/dist/types.js +4 -2
  118. package/dist/validation/index.d.ts +2 -0
  119. package/dist/validation/index.js +2 -1
  120. package/dist/validation/typecheck.d.ts +28 -0
  121. package/dist/validation/typecheck.js +110 -0
  122. package/dist/validation/validator.d.ts +9 -4
  123. package/dist/validation/validator.js +38 -44
  124. package/package.json +12 -15
  125. package/sbom.spdx.json +1322 -1964
  126. package/dist/assets/api-TJJVHrjC.json +0 -118
  127. package/dist/assets/descriptor-BTtjzN9L.json +0 -1382
  128. package/dist/assets/package-BpQF9kR8.json +0 -74
  129. package/dist/assets/package-Ceo2h27X.json +0 -89
  130. package/dist/assets/source_context-D0atuL28.json +0 -20
  131. package/dist/assets/type-BFqO8SCZ.json +0 -202
  132. package/dist/commands/cancel.d.ts +0 -22
  133. package/dist/commands/detect-package-manager.d.ts +0 -42
  134. package/dist/commands/held-run-resolve.d.ts +0 -50
  135. package/dist/commands/status.d.ts +0 -34
  136. package/dist/commands/test.d.ts +0 -88
  137. package/dist/errors/codes.d.ts +0 -25
  138. package/dist/local-executor/dag-scheduler.d.ts +0 -44
  139. package/dist/local-executor/index.d.ts +0 -23
  140. package/dist/local-executor/job-runner.d.ts +0 -47
  141. package/dist/local-executor/materializer.d.ts +0 -44
  142. package/dist/local-executor/output-streamer.d.ts +0 -31
  143. package/dist/local-executor/payload-generator.d.ts +0 -16
  144. package/dist/local-executor/picker.d.ts +0 -33
  145. package/dist/local-executor/runs-on-display.d.ts +0 -9
  146. package/dist/local-executor/secret-loader.d.ts +0 -18
  147. package/dist/local-executor/to-event-payload.d.ts +0 -16
  148. package/dist/local-executor/types.d.ts +0 -93
  149. package/dist/local-executor/workflow-lock.d.ts +0 -82
  150. package/dist/package-F7UXSDHW.json +0 -74
  151. package/dist/remote/client.d.ts +0 -210
  152. package/dist/remote/observer.d.ts +0 -81
  153. package/dist/test-runner/summary.d.ts +0 -7
@@ -8,19 +8,23 @@ The full markdown bundle of every page indexed here is available at https://docs
8
8
 
9
9
  Each bundle below is a self-contained markdown file for one authoring task. Fetch only the one your task needs instead of the full bundle:
10
10
 
11
- - [getting-started](https://docs.kici.dev/llms-getting-started.txt) (25 KB) — Install the SDK, write your first workflow, compile and test locally
12
- - [patterns](https://docs.kici.dev/llms-patterns.txt) (48 KB) — Copy-paste workflow recipes: triggers, conditionals, matrix, scheduling, integrations
13
- - [sdk](https://docs.kici.dev/llms-sdk.txt) (190 KB) — Authoring API: workflow/job/step factories, triggers, rules, matrix, runtime, caching
14
- - [cli](https://docs.kici.dev/llms-cli.txt) (135 KB) — Running the CLI: compile, test, run local/remote, auth, hooks, lock-file drift
15
- - [features](https://docs.kici.dev/llms-features.txt) (125 KB) — Workflow features: concurrency, contexts, secrets, approvals, provenance, events
11
+ - [getting-started](https://docs.kici.dev/llms-getting-started.txt) (51 KB) — Adopt KiCI: why it exists, how workflows execute, migrating from GitHub Actions, installing the SDK, and writing/compiling/testing your first workflow
12
+ - [patterns](https://docs.kici.dev/llms-patterns.txt) (49 KB) — Copy-paste workflow recipes: triggers, conditionals, matrix, scheduling, integrations
13
+ - [sdk](https://docs.kici.dev/llms-sdk.txt) (110 KB) — Core authoring API: workflow/job/step factories, triggers, rules, matrix, dynamic jobs, cross-job outputs
14
+ - [sdk-runtime](https://docs.kici.dev/llms-sdk-runtime.txt) (112 KB) — Runtime and advanced authoring: step runtime context, event payloads, host fan-out, idempotent steps, caching, artifacts, waiting
15
+ - [cli](https://docs.kici.dev/llms-cli.txt) (185 KB) — Running the CLI: compile, test, run local/remote, auth, hooks, lock-file drift
16
+ - [features](https://docs.kici.dev/llms-features.txt) (135 KB) — Workflow features: concurrency, contexts, secrets, approvals, provenance, events
16
17
  - [providers](https://docs.kici.dev/llms-providers.txt) (37 KB) — Connecting sources: GitHub App, universal-git (Forgejo/Gitea/GitLab), local file://
17
- - [architecture](https://docs.kici.dev/llms-architecture.txt) (87 KB) — How the runtime works: three-tier relay model, data flows, configuration
18
+ - [architecture](https://docs.kici.dev/llms-architecture.txt) (92 KB) — How the runtime works: three-tier relay model, data flows, configuration
18
19
 
19
20
  ## Getting started
20
21
 
21
- - [User guide](https://docs.kici.dev/user/): Writing and testing CI/CD workflows in TypeScript
22
+ - [User guide](https://docs.kici.dev/user/): Writing and testing CI/CD workflows in TypeScript, running on infrastructure you control
23
+ - [How your workflow code executes](https://docs.kici.dev/user/execution-model/): When and where your workflow TypeScript runs — compile time, orchestrator time, and agent time
22
24
  - [Getting started with workflows](https://docs.kici.dev/user/getting-started/): Install the SDK, write your first workflow, compile and test locally
25
+ - [Migrating from GitHub Actions](https://docs.kici.dev/user/migrating-from-github-actions/): Map GitHub Actions concepts to KiCI, translate a real workflow side-by-side, and see what has no equivalent yet
23
26
  - [5-minute quickstart](https://docs.kici.dev/user/quickstart/): Stand up a KiCI orchestrator + agent and run your first workflow
27
+ - [Why KiCI](https://docs.kici.dev/user/why-kici/): The case for running CI on infrastructure you control, with typed TypeScript workflows you test locally
24
28
 
25
29
  ## Workflow patterns
26
30
 
@@ -31,30 +35,40 @@ Each bundle below is a self-contained markdown file for one authoring task. Fetc
31
35
  - [Pattern reference](https://docs.kici.dev/user/patterns/reference/): Step context, examples repository, GitHub check run output — cross-cutting reference for all patterns
32
36
  - [Scheduling & event patterns](https://docs.kici.dev/user/patterns/scheduling-and-events/): Nightly cron, workflow-complete-triggered deploys, custom event chaining
33
37
 
34
- ## SDK reference
38
+ ## SDK reference: core
35
39
 
36
- - [Caching](https://docs.kici.dev/user/sdk/caching/): Cache files and directories across runs with declarative job/step cache or the imperative ctx.cache API
37
40
  - [SDK reference: core](https://docs.kici.dev/user/sdk/core/): Factory functions (workflow, job, step) and authoring patterns: needs, output chaining, dynamic groups
38
- - [Event payload reference](https://docs.kici.dev/user/sdk/event-payloads/): Generated schema of the normalized event envelope passed to rules and dynamic functions.
39
- - [SDK reference: idempotent](https://docs.kici.dev/user/sdk/idempotent/): Idempotent helpers for declarative check / apply patterns inside workflow steps
40
41
  - [SDK reference: parallel](https://docs.kici.dev/user/sdk/parallel/): Run independent steps concurrently within a single job, each as its own observable step
41
42
  - [SDK reference: rules, matrix, dynamic jobs](https://docs.kici.dev/user/sdk/rules-matrix-dynamic/): rule(), skip(), matrix builds (static + dynamic), and dynamicJob / dynamicGroup
43
+ - [SDK reference: triggers](https://docs.kici.dev/user/sdk/triggers/): GitHub event triggers, kiciEvent, workflowComplete, workflowsFailedBatch, jobComplete, genericWebhook, schedule, lifecycle
44
+ - [SDK reference: validation & events](https://docs.kici.dev/user/sdk/validation-events/): validateDag(), event definitions, and event emission
45
+ - [SDK reference](https://docs.kici.dev/user/sdk-reference/): Complete API reference for @kici-dev/sdk -- workflows, jobs, steps, triggers, rules, matrix, validation, runtime
46
+
47
+ ## SDK reference: runtime and advanced
48
+
49
+ - [Artifacts](https://docs.kici.dev/user/sdk/artifacts/): Share named, durable build outputs between jobs of a run and download them from the run page with ctx.artifacts
50
+ - [Caching](https://docs.kici.dev/user/sdk/caching/): Cache files and directories across runs with declarative job/step cache or the imperative ctx.cache API
51
+ - [Event payload reference](https://docs.kici.dev/user/sdk/event-payloads/): Generated schema of the normalized event envelope passed to rules and dynamic functions.
52
+ - [SDK reference: idempotent](https://docs.kici.dev/user/sdk/idempotent/): Idempotent helpers for declarative check / apply patterns inside workflow steps
42
53
  - [SDK reference: runsOnAll host fan-out](https://docs.kici.dev/user/sdk/runs-on-all/): Fan one job out to every matching connected host, one pinned execution per host
43
54
  - [SDK reference: runtime](https://docs.kici.dev/user/sdk/runtime/): Types index, StepContext, secrets, fixtures
44
- - [SDK reference: triggers](https://docs.kici.dev/user/sdk/triggers/): GitHub event triggers, kiciEvent, workflowComplete, jobComplete, genericWebhook, schedule, lifecycle
45
- - [SDK reference: validation & events](https://docs.kici.dev/user/sdk/validation-events/): validateDag(), event definitions, and event emission
55
+ - [SDK reference: temp directories](https://docs.kici.dev/user/sdk/temp-directories/): Allocate job-scoped scratch dirs and files with ctx.mktemp() / ctx.mktempFile()
46
56
  - [SDK reference: waitFor](https://docs.kici.dev/user/sdk/wait-for/): Polling helpers for steps that pause until an external condition becomes true
47
- - [SDK reference](https://docs.kici.dev/user/sdk-reference/): Complete API reference for @kici-dev/sdk -- workflows, jobs, steps, triggers, rules, matrix, validation, runtime
48
57
 
49
58
  ## CLI and authoring
50
59
 
51
60
  - [Drive KiCI from your coding agent](https://docs.kici.dev/user/ai-agents/): Point a coding agent at KiCI's MCP server and let it trigger, read, and re-run your CI under your own identity.
52
61
  - [CLI authentication](https://docs.kici.dev/user/cli-auth/): Authenticate the KiCI CLI with browser OAuth, device flow, or API key paste
53
- - [CLI reference](https://docs.kici.dev/user/cli-reference/): All CLI commands: compile, run (local/remote), orchestrators, preview, login, logout, org, diagnostics, runs (list/show/logs/rerun/cancel), secrets, types, fixture, init, hook, endpoints, workflows, docs, admin
62
+ - [CLI reference](https://docs.kici.dev/user/cli-reference/): All CLI commands: compile, preview, local, fixture, types, workflows, hook, docs, run, runs, approve, reject, login, logout, init, org, pat, secrets, admin, orchestrators, endpoints, notifications, verify-attestation, diagnostics, doctor
63
+ - [Common failures](https://docs.kici.dev/user/common-failures/): Symptom-to-fix reference for the failures workflow authors hit most — no jobs dispatched, lock-file drift, missing webhooks, and agents that won't connect
54
64
  - [Lifecycle hooks](https://docs.kici.dev/user/hooks/): SDK hook API for cancel, cleanup, success, failure, and step-level callbacks
55
65
  - [Lock file and workflow drift](https://docs.kici.dev/user/lock-file-and-drift/): Keep the lock file in sync with your workflow source and avoid drift
56
66
  - [Testing guide](https://docs.kici.dev/user/testing-guide/): Running remote test fixtures with kici run remote
57
67
  - [Workflow patterns](https://docs.kici.dev/user/workflow-patterns/): Common patterns: monorepo builds, conditional jobs, dynamic matrices, generic webhooks, scheduling
68
+ - [kici: account & org](https://docs.kici.dev/user/cli/account-and-org/): Login, org selection, personal access tokens, secrets, and admin
69
+ - [kici: authoring & local dev](https://docs.kici.dev/user/cli/authoring-and-local/): Compile, preview, local execution, fixtures, types, workflows, hooks, and docs
70
+ - [kici: notifications & diagnostics](https://docs.kici.dev/user/cli/notifications-and-diagnostics/): Notification channels, attestation verification, and diagnostics
71
+ - [kici: runs & approvals](https://docs.kici.dev/user/cli/runs-and-approvals/): Run push and inspection plus approval / rejection of held runs
58
72
 
59
73
  ## Workflow features
60
74
 
@@ -64,7 +78,7 @@ Each bundle below is a self-contained markdown file for one authoring task. Fetc
64
78
  - [Contexts](https://docs.kici.dev/user/contexts/): Configure deployment contexts with variables, secrets, and protection rules
65
79
  - [Dashboard](https://docs.kici.dev/user/dashboard/): Web UI for monitoring workflow runs, managing sources, secrets, and organization settings.
66
80
  - [Dynamic values](https://docs.kici.dev/user/dynamic-values/)
67
- - [Environment variables](https://docs.kici.dev/user/env-vars/): KICI_* environment variable reference for the CLI
81
+ - [Environment variables](https://docs.kici.dev/user/env-vars/): Environment variable reference for the CLI
68
82
  - [Event system](https://docs.kici.dev/user/events/): How KiCI's event model works -- event types, the registration model, event matching, and circuit breaker protection
69
83
  - [Global workflows](https://docs.kici.dev/user/global-workflows/): Cross-repo workflows that run on events from any repo in the same org
70
84
  - [Idempotent steps and check mode](https://docs.kici.dev/user/idempotent-steps/): Declare desired state with a step check facet, then run in apply or --check preview mode
@@ -52,14 +52,19 @@ export interface SpawnOrchestratorOptions {
52
52
  };
53
53
  }
54
54
  /**
55
- * Spawn the orchestrator's standalone entry in independent mode against the
56
- * local Postgres, detached, with stdout/stderr redirected to the plane log.
57
- * Boots with a bootstrap admin token (so the CLI can drive the admin API) and a
58
- * bare-metal scaler (so a dispatched job auto-spawns an ephemeral agent). Waits
59
- * for `GET /ready` to report warm, then returns the child pid + port.
55
+ * Spawn the orchestrator entry against the local Postgres, detached, with
56
+ * stdout/stderr redirected to the plane log, and return immediately. Boots with
57
+ * a bootstrap admin token (so the CLI can drive the admin API) and a bare-metal
58
+ * scaler (so a dispatched job auto-spawns an ephemeral agent).
59
+ *
60
+ * Returning before the process is ready is deliberate: it lets the caller record
61
+ * the pid on disk BEFORE the readiness wait, so a wait that fails or a CLI that
62
+ * dies mid-boot cannot leave a running orchestrator that nothing can identify.
60
63
  */
61
- export declare function spawnOrchestrator(databaseUrl: string, opts: SpawnOrchestratorOptions): Promise<{
64
+ export declare function spawnOrchestratorProcess(databaseUrl: string, opts: SpawnOrchestratorOptions): {
62
65
  pid: number;
63
66
  port: number;
64
- }>;
67
+ };
68
+ /** Poll `/ready` until the orchestrator serves, or throw once the attempts run out. */
69
+ export declare function awaitOrchestratorReady(port: number, attempts?: number, intervalMs?: number): Promise<void>;
65
70
  //# sourceMappingURL=orchestrator-process.d.ts.map
@@ -30,22 +30,32 @@ function resolveStandaloneEntry() {
30
30
  function resolveServerEntry() {
31
31
  return createRequire(import.meta.url).resolve("@kici-dev/orchestrator/server");
32
32
  }
33
+ /**
34
+ * Cap on one readiness probe. Generous enough for a server still warming up,
35
+ * bounded so a socket that accepts and then goes silent cannot stall the poll
36
+ * for the HTTP client's own multi-minute default — which would make
37
+ * `awaitOrchestratorReady`'s attempt budget meaningless.
38
+ */
39
+ const READY_PROBE_TIMEOUT_MS = 5e3;
33
40
  /** Single `/ready` probe against the local orchestrator. */
34
41
  async function orchestratorReady(port) {
35
42
  try {
36
- return (await fetch(`http://127.0.0.1:${port}/ready`)).status === 200;
43
+ return (await fetch(`http://127.0.0.1:${port}/ready`, { signal: AbortSignal.timeout(READY_PROBE_TIMEOUT_MS) })).status === 200;
37
44
  } catch {
38
45
  return false;
39
46
  }
40
47
  }
41
48
  /**
42
- * Spawn the orchestrator's standalone entry in independent mode against the
43
- * local Postgres, detached, with stdout/stderr redirected to the plane log.
44
- * Boots with a bootstrap admin token (so the CLI can drive the admin API) and a
45
- * bare-metal scaler (so a dispatched job auto-spawns an ephemeral agent). Waits
46
- * for `GET /ready` to report warm, then returns the child pid + port.
49
+ * Spawn the orchestrator entry against the local Postgres, detached, with
50
+ * stdout/stderr redirected to the plane log, and return immediately. Boots with
51
+ * a bootstrap admin token (so the CLI can drive the admin API) and a bare-metal
52
+ * scaler (so a dispatched job auto-spawns an ephemeral agent).
53
+ *
54
+ * Returning before the process is ready is deliberate: it lets the caller record
55
+ * the pid on disk BEFORE the readiness wait, so a wait that fails or a CLI that
56
+ * dies mid-boot cannot leave a running orchestrator that nothing can identify.
47
57
  */
48
- async function spawnOrchestrator(databaseUrl, opts) {
58
+ function spawnOrchestratorProcess(databaseUrl, opts) {
49
59
  const { orchestrator: port } = planePorts();
50
60
  const { logFile, root, cacheDir } = planePaths();
51
61
  fs.mkdirSync(root, { recursive: true });
@@ -100,16 +110,20 @@ async function spawnOrchestrator(databaseUrl, opts) {
100
110
  }
101
111
  });
102
112
  child.unref();
103
- for (let i = 0; i < 120; i++) {
104
- if (await orchestratorReady(port)) return {
105
- pid: child.pid,
106
- port
107
- };
108
- await new Promise((r) => setTimeout(r, 500));
113
+ return {
114
+ pid: child.pid,
115
+ port
116
+ };
117
+ }
118
+ /** Poll `/ready` until the orchestrator serves, or throw once the attempts run out. */
119
+ async function awaitOrchestratorReady(port, attempts = 120, intervalMs = 500) {
120
+ for (let i = 0; i < attempts; i++) {
121
+ if (await orchestratorReady(port)) return;
122
+ await new Promise((r) => setTimeout(r, intervalMs));
109
123
  }
110
124
  throw new Error("local orchestrator did not become ready");
111
125
  }
112
126
  //#endregion
113
- export { orchestratorReady, resolveAgentReachableHost, resolveServerEntry, resolveStandaloneEntry, spawnOrchestrator };
127
+ export { awaitOrchestratorReady, orchestratorReady, resolveAgentReachableHost, resolveServerEntry, resolveStandaloneEntry, spawnOrchestratorProcess };
114
128
 
115
129
  //# sourceMappingURL=orchestrator-process.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Detect when a routed local run cannot match the host OS.
3
+ *
4
+ * The local dev plane satisfies the host's own `kici:os:*` label (the scaler
5
+ * injects the host's derived OS labels into its bare-metal label sets). A job
6
+ * that requests a DIFFERENT OS than the host — e.g. a workflow authored on Linux
7
+ * run on a Mac — matches no local backend and dispatches nothing. This surfaces
8
+ * that as an honest hint instead of a silent "nothing happened".
9
+ */
10
+ /**
11
+ * One hint per job whose `runsOn` requires a `kici:os:*` label the host does not
12
+ * provide. Empty when every job either omits an OS selector or requests the
13
+ * host's own OS.
14
+ */
15
+ export declare function detectOsMismatchHints(lock: unknown, platform: string, arch: string): string[];
16
+ //# sourceMappingURL=os-mismatch-hint.d.ts.map
@@ -0,0 +1,34 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ import { deriveOsArchLabels } from "@kici-dev/engine";
3
+ //#region src/local-plane/os-mismatch-hint.ts
4
+ /**
5
+ * Detect when a routed local run cannot match the host OS.
6
+ *
7
+ * The local dev plane satisfies the host's own `kici:os:*` label (the scaler
8
+ * injects the host's derived OS labels into its bare-metal label sets). A job
9
+ * that requests a DIFFERENT OS than the host — e.g. a workflow authored on Linux
10
+ * run on a Mac — matches no local backend and dispatches nothing. This surfaces
11
+ * that as an honest hint instead of a silent "nothing happened".
12
+ */
13
+ const OS_LABEL_PREFIX = "kici:os:";
14
+ /**
15
+ * One hint per job whose `runsOn` requires a `kici:os:*` label the host does not
16
+ * provide. Empty when every job either omits an OS selector or requests the
17
+ * host's own OS.
18
+ */
19
+ function detectOsMismatchHints(lock, platform, arch) {
20
+ const hostOsLabels = new Set(deriveOsArchLabels(platform, arch).filter((l) => l.startsWith(OS_LABEL_PREFIX)));
21
+ const hostPrimary = [...hostOsLabels][0] ?? `${OS_LABEL_PREFIX}${platform}`;
22
+ const hints = [];
23
+ for (const workflow of lock.workflows ?? []) for (const job of workflow.jobs ?? []) {
24
+ const requestedOs = (job.runsOn ?? []).filter((m) => m.kind === "exact" && m.value?.startsWith(OS_LABEL_PREFIX)).map((m) => m.value);
25
+ if (requestedOs.length === 0) continue;
26
+ if (requestedOs.some((l) => hostOsLabels.has(l))) continue;
27
+ hints.push(`Job '${job.name ?? "job"}' wants ${requestedOs.join(", ")} but this host is ${platform} — nothing will dispatch locally. Retry with ${hostPrimary} or edit the job's runsOn in .kici/workflows/.`);
28
+ }
29
+ return hints;
30
+ }
31
+ //#endregion
32
+ export { detectOsMismatchHints };
33
+
34
+ //# sourceMappingURL=os-mismatch-hint.js.map
@@ -0,0 +1,68 @@
1
+ /** The stamp fields classification needs. Structural, to avoid an import cycle with plane-manager. */
2
+ export interface StampIdentity {
3
+ orchestratorPid: number;
4
+ port: number;
5
+ }
6
+ /** The `/health` fields that identify a KiCI orchestrator. */
7
+ export interface KiciHealth {
8
+ uptime: number;
9
+ version?: string;
10
+ buildCommit?: string;
11
+ }
12
+ export type PlaneState = 'stopped' | 'ready' | 'unready' | 'foreign-kici' | 'foreign-unknown';
13
+ export type PlaneClassification = {
14
+ kind: 'free';
15
+ } | {
16
+ kind: 'ours-ready';
17
+ pid: number;
18
+ } | {
19
+ kind: 'ours-unready';
20
+ pid: number;
21
+ checks: Record<string, boolean>;
22
+ } | {
23
+ kind: 'foreign-kici';
24
+ pid: number | null;
25
+ health: KiciHealth;
26
+ } | {
27
+ kind: 'foreign-unknown';
28
+ pid: number | null;
29
+ };
30
+ export interface PlaneProbes {
31
+ isPortFree(port: number): Promise<boolean>;
32
+ findPortHolderPid(port: number): Promise<number | null>;
33
+ /** The parsed `/health` body, or null when it did not answer in time. */
34
+ fetchHealth(port: number): Promise<unknown>;
35
+ fetchReady(port: number): Promise<{
36
+ ok: boolean;
37
+ checks: Record<string, boolean>;
38
+ }>;
39
+ isPidAlive(pid: number): boolean;
40
+ }
41
+ export interface ReclaimResult {
42
+ freed: boolean;
43
+ killedPid?: number;
44
+ error?: string;
45
+ }
46
+ /**
47
+ * Whether a `/health` body came from a KiCI orchestrator. The build-fingerprint
48
+ * fields are the discriminator: an `ok` status alone is far too common to
49
+ * justify signalling the process that produced it.
50
+ */
51
+ export declare function isKiciOrchestratorHealth(body: unknown): body is KiciHealth;
52
+ /** The state label a classification reports to the operator. */
53
+ export declare function planeStateOf(c: PlaneClassification): PlaneState;
54
+ /** The holder pid a classification carries, when it has one. */
55
+ export declare function classificationPid(c: PlaneClassification): number | null;
56
+ /**
57
+ * Decide what, if anything, is holding the plane port and whether it is ours.
58
+ *
59
+ * `stamp` is the on-disk record of the plane this config dir booted; a null
60
+ * stamp means nothing here booted the holder, so any live holder is foreign.
61
+ */
62
+ export declare function classifyPlane(port: number, stamp: StampIdentity | null, probes?: Partial<PlaneProbes>): Promise<PlaneClassification>;
63
+ /**
64
+ * Free the plane port, if it is ours or another KiCI plane orchestrator. A
65
+ * holder that does not identify as one is reported and left strictly alone.
66
+ */
67
+ export declare function reclaimPlanePort(port: number, c: PlaneClassification): Promise<ReclaimResult>;
68
+ //# sourceMappingURL=plane-liveness.d.ts.map
@@ -0,0 +1,145 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ import { findPortHolderPid, isPortFree, terminatePid, waitForPortFree } from "./port-holder.js";
3
+ //#region src/local-plane/plane-liveness.ts
4
+ /**
5
+ * One answer to "is a local dev plane running", shared by up, down, and status.
6
+ *
7
+ * Ownership is decided by pid identity — the process actually holding the port
8
+ * versus the pid the stamp names — not by whether a stamp file exists. That is
9
+ * what keeps the three commands consistent: a plane nobody stamped is foreign to
10
+ * all of them, rather than invisible to two and adoptable by the third.
11
+ *
12
+ * Every external effect arrives through `PlaneProbes`, so the policy here is
13
+ * exercised without a subprocess or a socket.
14
+ */
15
+ /**
16
+ * Cap on each loopback probe. The holder these probes exist to diagnose is
17
+ * frequently wedged, and a socket that accepts a connection but never answers
18
+ * would otherwise hang the CLI for the HTTP client's own multi-minute default —
19
+ * turning `kici local status` into the hang it is meant to explain. A holder
20
+ * that cannot answer loopback within this window is treated as not answering.
21
+ */
22
+ const PROBE_TIMEOUT_MS = 2e3;
23
+ const defaultProbes = {
24
+ isPortFree,
25
+ findPortHolderPid,
26
+ async fetchHealth(port) {
27
+ try {
28
+ const r = await fetch(`http://127.0.0.1:${port}/health`, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
29
+ if (!r.ok) return null;
30
+ return await r.json();
31
+ } catch {
32
+ return null;
33
+ }
34
+ },
35
+ async fetchReady(port) {
36
+ try {
37
+ const r = await fetch(`http://127.0.0.1:${port}/ready`, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
38
+ const body = await r.json().catch(() => ({}));
39
+ return {
40
+ ok: r.status === 200,
41
+ checks: body.checks ?? {}
42
+ };
43
+ } catch {
44
+ return {
45
+ ok: false,
46
+ checks: {}
47
+ };
48
+ }
49
+ },
50
+ isPidAlive(pid) {
51
+ try {
52
+ process.kill(pid, 0);
53
+ return true;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+ };
59
+ /**
60
+ * Whether a `/health` body came from a KiCI orchestrator. The build-fingerprint
61
+ * fields are the discriminator: an `ok` status alone is far too common to
62
+ * justify signalling the process that produced it.
63
+ */
64
+ function isKiciOrchestratorHealth(body) {
65
+ if (typeof body !== "object" || body === null) return false;
66
+ const b = body;
67
+ return b.status === "ok" && typeof b.uptime === "number" && "sdkBundleHash" in b && "engineBundleHash" in b;
68
+ }
69
+ /** The state label a classification reports to the operator. */
70
+ function planeStateOf(c) {
71
+ switch (c.kind) {
72
+ case "free": return "stopped";
73
+ case "ours-ready": return "ready";
74
+ case "ours-unready": return "unready";
75
+ case "foreign-kici": return "foreign-kici";
76
+ case "foreign-unknown": return "foreign-unknown";
77
+ }
78
+ }
79
+ /** The holder pid a classification carries, when it has one. */
80
+ function classificationPid(c) {
81
+ return "pid" in c ? c.pid : null;
82
+ }
83
+ /**
84
+ * Decide what, if anything, is holding the plane port and whether it is ours.
85
+ *
86
+ * `stamp` is the on-disk record of the plane this config dir booted; a null
87
+ * stamp means nothing here booted the holder, so any live holder is foreign.
88
+ */
89
+ async function classifyPlane(port, stamp, probes = {}) {
90
+ const p = {
91
+ ...defaultProbes,
92
+ ...probes
93
+ };
94
+ if (await p.isPortFree(port)) return { kind: "free" };
95
+ const holderPid = await p.findPortHolderPid(port);
96
+ if ((holderPid !== null ? stamp !== null && holderPid === stamp.orchestratorPid : stamp !== null && p.isPidAlive(stamp.orchestratorPid)) && stamp !== null) {
97
+ const ready = await p.fetchReady(port);
98
+ return ready.ok ? {
99
+ kind: "ours-ready",
100
+ pid: stamp.orchestratorPid
101
+ } : {
102
+ kind: "ours-unready",
103
+ pid: stamp.orchestratorPid,
104
+ checks: ready.checks
105
+ };
106
+ }
107
+ const health = await p.fetchHealth(port);
108
+ return isKiciOrchestratorHealth(health) ? {
109
+ kind: "foreign-kici",
110
+ pid: holderPid,
111
+ health
112
+ } : {
113
+ kind: "foreign-unknown",
114
+ pid: holderPid
115
+ };
116
+ }
117
+ /**
118
+ * Free the plane port, if it is ours or another KiCI plane orchestrator. A
119
+ * holder that does not identify as one is reported and left strictly alone.
120
+ */
121
+ async function reclaimPlanePort(port, c) {
122
+ if (c.kind === "free") return { freed: true };
123
+ if (c.kind === "foreign-unknown") return {
124
+ freed: false,
125
+ error: `port ${port} is held by a process that is not a KiCI plane orchestrator${c.pid === null ? "" : ` (pid ${c.pid})`} — refusing to stop it`
126
+ };
127
+ const pid = classificationPid(c);
128
+ if (pid === null) return {
129
+ freed: false,
130
+ error: `port ${port} is held by a KiCI plane orchestrator whose pid could not be determined`
131
+ };
132
+ await terminatePid(pid);
133
+ return await waitForPortFree(port) ? {
134
+ freed: true,
135
+ killedPid: pid
136
+ } : {
137
+ freed: false,
138
+ killedPid: pid,
139
+ error: `port ${port} is still held after stopping pid ${pid}`
140
+ };
141
+ }
142
+ //#endregion
143
+ export { classificationPid, classifyPlane, isKiciOrchestratorHealth, planeStateOf, reclaimPlanePort };
144
+
145
+ //# sourceMappingURL=plane-liveness.js.map
@@ -1,3 +1,15 @@
1
+ import { type PlaneState } from './plane-liveness.js';
2
+ /**
3
+ * The current CLI build's identity — semver plus git build commit — read at
4
+ * call time from the Rolldown-injected build constants (`scripts/build-ts.mjs`).
5
+ * Read on each call rather than captured in a module const so unit tests can
6
+ * inject the constants via `globalThis`. Falls back to `0.0.0` / `unknown` when
7
+ * running from source (unbuilt tree / vitest), where the defines are absent.
8
+ */
9
+ export declare function currentBuildIdentity(): {
10
+ version: string;
11
+ buildCommit: string;
12
+ };
1
13
  /** Whether the plane runs offline (independent) or attached to the Platform (hybrid). */
2
14
  export type PlaneMode = 'independent' | 'hybrid';
3
15
  /**
@@ -21,12 +33,19 @@ export interface PlaneStamp {
21
33
  port: number;
22
34
  pgKind: 'embedded' | 'podman';
23
35
  kiciVersion: string;
36
+ /** Git build commit of the CLI that booted this plane (see planeBuildIsStale). */
37
+ buildCommit: string;
24
38
  stampVersion: number;
25
39
  /** Offline (independent) vs attached (hybrid). Absent (legacy) reads as independent. */
26
40
  mode?: PlaneMode;
27
41
  }
28
42
  export interface PlaneStatus {
43
+ /** True only when the plane is serving — a live-but-unready plane is false. */
29
44
  running: boolean;
45
+ /** Liveness of the plane port: stopped, serving, live-but-not-serving, or held by someone else. */
46
+ state: PlaneState;
47
+ /** Failing `/ready` checks, when the plane is live but not serving. */
48
+ checks?: Record<string, boolean>;
30
49
  pid?: number;
31
50
  port?: number;
32
51
  pgKind?: 'embedded' | 'podman';
@@ -89,9 +108,29 @@ export declare function devIdentityPublicJwkFile(): string;
89
108
  * orchestrator via KICI_DEV_IDENTITY_KEY_FILE.
90
109
  */
91
110
  export declare function readOrCreateDevIdentity(): string;
111
+ /** Remove the plane's on-disk record (pidfile + stamp). */
112
+ export declare function clearStamp(): void;
113
+ /**
114
+ * Whether a running plane described by `existing` was booted from a different
115
+ * CLI build than the current one — a semver bump OR a git-commit change (the
116
+ * latter covers intermediate staging/E2E commits that share a semver). Returns
117
+ * false when there is no stamp, or when the current build has no concrete
118
+ * identity (`buildCommit === 'unknown'`, i.e. running from source / a test),
119
+ * so a source-context `planeUp` never reboots a healthy plane spuriously. An
120
+ * old stamp with no `buildCommit` field reads as `undefined` and therefore
121
+ * triggers a one-time reboot on the first upgrade past this feature.
122
+ */
123
+ export declare function planeBuildIsStale(existing: PlaneStamp | null): boolean;
92
124
  /** Absolute path of the plane orchestrator's log file. */
93
125
  export declare function planeLogPath(): string;
94
- /** Read the plane's current status by probing `/ready` against the stamped port. */
126
+ /**
127
+ * Read the plane's current status from whatever holds the plane port.
128
+ *
129
+ * `running` means "serving": a live orchestrator whose `/ready` fails is
130
+ * reported as `unready`, with the failing checks, rather than as stopped —
131
+ * inferring "not running" from an unready probe hides a process that still owns
132
+ * the port.
133
+ */
95
134
  export declare function planeStatus(): Promise<PlaneStatus>;
96
135
  /** Attach parameters for a hybrid plane boot (see attachPlane). */
97
136
  export interface PlaneUpAttach {
@@ -106,28 +145,31 @@ export interface PlaneUpOptions {
106
145
  attach?: PlaneUpAttach;
107
146
  }
108
147
  /**
109
- * Start (or reuse) the local dev plane. Idempotent: a healthy running plane
110
- * **whose stamped mode matches the requested mode** is returned as-is; a mode
111
- * change (independent↔hybrid) forces a teardown + reboot. Otherwise Postgres +
112
- * the orchestrator are booted and a fresh stamp + pidfile are written. When
148
+ * Start (or reuse) the local dev plane. Idempotent: a healthy running plane this
149
+ * config dir stamped, **whose stamped mode matches the requested mode**, is
150
+ * returned as-is. Otherwise the port is reclaimed if it is another KiCI plane,
151
+ * and Postgres + the orchestrator are booted with a fresh stamp + pidfile. When
113
152
  * `attach` is present the orchestrator boots hybrid against the Platform relay;
114
- * otherwise it boots independent with the dev-signed identity.
153
+ * otherwise it boots independent with the dev-signed identity. Throws when the
154
+ * port is held by a process that is not a KiCI plane orchestrator.
115
155
  */
116
156
  export declare function planeUp(opts?: PlaneUpOptions): Promise<PlaneStatus>;
157
+ /** Outcome of a teardown attempt. `stopped` is true only when the port is verified free. */
158
+ export interface PlaneDownResult {
159
+ stopped: boolean;
160
+ port: number;
161
+ holderPid?: number;
162
+ reason?: string;
163
+ }
117
164
  /**
118
- * Stop the local dev plane. Teardown is reconstructed from the stamp (the
119
- * start-time Postgres handle does not survive a separate CLI invocation):
120
- * SIGTERM the orchestrator, stop Postgres by backend kind, then clear the
121
- * pidfile + stamp.
122
- */
123
- export declare function planeDown(): Promise<void>;
124
- /**
125
- * Poll until process `pid` has exited (SIGTERM already sent), escalating to
126
- * SIGKILL if it outlives the grace period, so the caller can rely on the
127
- * listening socket being released once this resolves. `process.kill(pid, 0)`
128
- * throws `ESRCH` once the process is gone.
165
+ * Stop the local dev plane and confirm the port was released.
166
+ *
167
+ * Teardown is reconstructed from whatever holds the port, not only from the
168
+ * stamp: a plane this config dir never stamped still occupies the port and is
169
+ * still ours to stop. The pidfile and stamp are cleared only once the port is
170
+ * verified free, so a survivor never loses the pid that identifies it.
129
171
  */
130
- export declare function waitForProcessExit(pid: number, graceMs?: number): Promise<void>;
172
+ export declare function planeDown(): Promise<PlaneDownResult>;
131
173
  /**
132
174
  * Attach the local dev plane to the hosted Platform: mint an org-scoped
133
175
  * orchestrator key with the logged-in PAT, then (re)boot the plane hybrid