@kici-dev/compiler 0.7.0 → 0.9.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 (55) hide show
  1. package/dist/cli.js +5 -4
  2. package/dist/commands/docs.js +2 -2
  3. package/dist/commands/feedback.d.ts +23 -1
  4. package/dist/commands/feedback.js +86 -9
  5. package/dist/commands/index.d.ts +2 -2
  6. package/dist/commands/index.js +2 -2
  7. package/dist/commands/local.d.ts +13 -5
  8. package/dist/commands/local.js +19 -8
  9. package/dist/commands/report/identity.js +1 -1
  10. package/dist/commands/run-banner.d.ts +1 -1
  11. package/dist/commands/run-banner.js +1 -1
  12. package/dist/commands/run-routed.js +3 -0
  13. package/dist/commands/run.js +5 -2
  14. package/dist/commands/runs/logs.js +3 -2
  15. package/dist/commands/verify-attestation.js +1 -1
  16. package/dist/llm-context/llms-architecture.txt +9 -7
  17. package/dist/llm-context/llms-cli-remote.txt +27 -12
  18. package/dist/llm-context/llms-cli.txt +6 -4
  19. package/dist/llm-context/llms-features-execution.txt +9 -9
  20. package/dist/llm-context/llms-features.txt +452 -133
  21. package/dist/llm-context/llms-full.txt +561 -197
  22. package/dist/llm-context/llms-getting-started.txt +34 -17
  23. package/dist/llm-context/llms-providers.txt +2 -2
  24. package/dist/llm-context/llms-sdk-runtime.txt +16 -2
  25. package/dist/llm-context/llms-sdk.txt +7 -12
  26. package/dist/llm-context/llms.txt +7 -6
  27. package/dist/local-plane/orchestrator-process.d.ts +4 -5
  28. package/dist/local-plane/orchestrator-process.js +5 -1
  29. package/dist/local-plane/paths.d.ts +1 -0
  30. package/dist/local-plane/paths.js +1 -0
  31. package/dist/local-plane/plane-log.d.ts +27 -0
  32. package/dist/local-plane/plane-log.js +39 -0
  33. package/dist/local-plane/plane-manager.js +2 -2
  34. package/dist/local-plane/plane-trigger.d.ts +28 -0
  35. package/dist/local-plane/plane-trigger.js +57 -2
  36. package/dist/local-plane/postgres.js +9 -6
  37. package/dist/local-plane/run-follow.js +2 -1
  38. package/dist/remote/output/streaming.d.ts +12 -0
  39. package/dist/remote/output/streaming.js +20 -1
  40. package/dist/remote/platform-client.d.ts +2 -0
  41. package/dist/templates/agents-md.d.ts +1 -1
  42. package/dist/templates/agents-md.js +9 -7
  43. package/dist/templates/package-json.d.ts +9 -7
  44. package/dist/templates/package-json.js +11 -9
  45. package/dist/templates/workflows/hello-world.ts +1 -1
  46. package/dist/templates/workflows/pr-checks.ts +2 -2
  47. package/dist/test-runner/job-executor.js +3 -2
  48. package/dist/types.d.ts +12 -35
  49. package/dist/types.js +2 -12
  50. package/dist/types.test-d.d.ts +2 -0
  51. package/dist/types.test-d.js +63 -0
  52. package/dist/workflows/hello-world.ts +1 -1
  53. package/dist/workflows/pr-checks.ts +2 -2
  54. package/package.json +15 -15
  55. package/sbom.spdx.json +1303 -1483
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import { realpathSync } from "node:fs";
7
7
  import { Argument, Command } from "commander";
8
8
  import pc from "picocolors";
9
9
  //#region src/cli.ts
10
- const version = "0.7.0";
10
+ const version = "0.9.0";
11
11
  /**
12
12
  * Top-level commands that were removed, mapped to their current equivalent.
13
13
  * Consulted when the CLI hits an unknown command so the user gets a precise
@@ -312,7 +312,7 @@ Environment variables:
312
312
  ],
313
313
  [
314
314
  "logs",
315
- "Print the local dev plane orchestrator log path",
315
+ "Print the local dev plane log paths and rotation policy",
316
316
  "localLogsCommand"
317
317
  ],
318
318
  [
@@ -492,11 +492,12 @@ Environment variables:
492
492
  });
493
493
  process.exit(success ? 0 : 1);
494
494
  });
495
- program.command("feedback").description("Print how to report a discrepancy between what KiCI advertises and what it does. Files nothing.").option("--open", "Open the prefilled issue form in the default browser").option("--json", "Emit the reporting contract as JSON").action(async (options) => {
495
+ program.command("feedback").description("Print how to report a discrepancy between what KiCI advertises and what it does. Files nothing.").option("--open", "Open the prefilled issue form in the default browser").option("--json", "Emit the reporting contract as JSON").option("--draft <file>", "Build the prefilled issue-form URL from a JSON draft keyed by field id (with --open, open it)").action(async (options) => {
496
496
  const { feedbackCommand } = await import("./commands/index.js");
497
497
  const success = await feedbackCommand({
498
498
  open: options.open,
499
- json: options.json
499
+ json: options.json,
500
+ draft: options.draft
500
501
  });
501
502
  process.exit(success ? 0 : 1);
502
503
  });
@@ -3,11 +3,11 @@ import { fileURLToPath } from "node:url";
3
3
  import path from "node:path";
4
4
  import pc from "picocolors";
5
5
  import { readFile, readdir, writeFile } from "node:fs/promises";
6
- import { logger, toErrorMessage } from "@kici-dev/core";
6
+ import { docsUrl, logger, toErrorMessage } from "@kici-dev/core";
7
7
  import open from "open";
8
8
  //#region src/commands/docs.ts
9
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
- const DOCS_HOME_URL = "https://kici.dev/docs/";
10
+ const DOCS_HOME_URL = docsUrl("");
11
11
  function bundleFilenameForTopic(topic) {
12
12
  if (!topic) return "llms.txt";
13
13
  if (topic === "full") return "llms-full.txt";
@@ -5,6 +5,12 @@ export declare const FEEDBACK_TEMPLATE = "agent_report.yml";
5
5
  export declare const FEEDBACK_NEW_ISSUE_URL = "https://github.com/kici-dev/kici-public/issues/new?template=agent_report.yml";
6
6
  /** Suspected vulnerabilities go here instead, privately. */
7
7
  export declare const FEEDBACK_SECURITY_ADVISORY_URL = "https://github.com/kici-dev/kici-public/security/advisories/new";
8
+ /** How an agent turns its draft into the prefilled form a person reviews and files. */
9
+ export declare const FEEDBACK_DRAFT_COMMAND = "kici feedback --draft <file.json> --open";
10
+ /** GitHub's practical URL ceiling; a longer draft violates "minimal" anyway. */
11
+ export declare const FEEDBACK_URL_WARN_BYTES = 6000;
12
+ /** A draft: the issue title plus one string per contract field, keyed by field id. */
13
+ export type FeedbackDraft = Record<string, string>;
8
14
  export interface FeedbackField {
9
15
  id: string;
10
16
  label: string;
@@ -26,6 +32,10 @@ export interface FeedbackContract {
26
32
  requiredFields: FeedbackField[];
27
33
  prohibited: string[];
28
34
  privateReportCommand: string;
35
+ /** The command that turns a JSON draft into the prefilled issue-form URL. */
36
+ draftCommand: string;
37
+ /** The keys a draft must carry: `title` plus every required field's id. */
38
+ draftFields: string[];
29
39
  }
30
40
  /**
31
41
  * The single definition of what a reportable discrepancy is and what a report
@@ -34,11 +44,22 @@ export interface FeedbackContract {
34
44
  * thing — so the CLI and the doc cannot drift apart.
35
45
  */
36
46
  export declare const FEEDBACK_CONTRACT: FeedbackContract;
47
+ export declare function parseFeedbackDraft(raw: unknown): {
48
+ ok: true;
49
+ draft: FeedbackDraft;
50
+ } | {
51
+ ok: false;
52
+ problems: string[];
53
+ };
54
+ /** The form URL with every draft field prefilled — GitHub reads each query param by field id. */
55
+ export declare function draftIssueUrl(draft: FeedbackDraft): string;
37
56
  export interface FeedbackOptions {
38
57
  /** Open the prefilled issue form in the default browser. */
39
58
  open?: boolean;
40
59
  /** Emit the contract as JSON on stdout instead of prose. */
41
60
  json?: boolean;
61
+ /** Path to a JSON draft; print (and with --open, open) the prefilled issue-form URL. */
62
+ draft?: string;
42
63
  }
43
64
  /**
44
65
  * Print the contract for reporting a KiCI discrepancy: what qualifies, what a
@@ -47,7 +68,8 @@ export interface FeedbackOptions {
47
68
  *
48
69
  * The command reaches no network and files nothing. `--open` opens the
49
70
  * prefilled issue form; `--json` emits the same contract for an agent to
50
- * consume without parsing prose.
71
+ * consume without parsing prose; `--draft <file>` builds the issue-form URL
72
+ * with every field of a JSON draft prefilled.
51
73
  */
52
74
  export declare function feedbackCommand(options?: FeedbackOptions): Promise<boolean>;
53
75
  //# sourceMappingURL=feedback.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
2
  import pc from "picocolors";
3
- import { logger, toErrorMessage } from "@kici-dev/core";
3
+ import { readFile } from "node:fs/promises";
4
+ import { docsUrl, logger, toErrorMessage } from "@kici-dev/core";
4
5
  import open from "open";
5
6
  //#region src/commands/feedback.ts
6
7
  /** The public tracker. Reports about KiCI itself go here — never customer data. */
@@ -10,6 +11,10 @@ const FEEDBACK_TEMPLATE = "agent_report.yml";
10
11
  const FEEDBACK_NEW_ISSUE_URL = `${FEEDBACK_TRACKER_URL}/issues/new?template=${FEEDBACK_TEMPLATE}`;
11
12
  /** Suspected vulnerabilities go here instead, privately. */
12
13
  const FEEDBACK_SECURITY_ADVISORY_URL = `${FEEDBACK_TRACKER_URL}/security/advisories/new`;
14
+ /** How an agent turns its draft into the prefilled form a person reviews and files. */
15
+ const FEEDBACK_DRAFT_COMMAND = "kici feedback --draft <file.json> --open";
16
+ /** GitHub's practical URL ceiling; a longer draft violates "minimal" anyway. */
17
+ const FEEDBACK_URL_WARN_BYTES = 6e3;
13
18
  /**
14
19
  * The single definition of what a reportable discrepancy is and what a report
15
20
  * must carry. `kici feedback` prints it, `--json` emits it verbatim, and
@@ -21,7 +26,7 @@ const FEEDBACK_CONTRACT = {
21
26
  newIssueUrl: FEEDBACK_NEW_ISSUE_URL,
22
27
  template: FEEDBACK_TEMPLATE,
23
28
  securityAdvisoryUrl: FEEDBACK_SECURITY_ADVISORY_URL,
24
- guideUrl: "https://kici.dev/docs/user/reporting-discrepancies/",
29
+ guideUrl: docsUrl("user/reporting-discrepancies/"),
25
30
  searchCommand: "gh issue list --repo kici-dev/kici-public --search \"<terms>\" --state all",
26
31
  approval: {
27
32
  required: true,
@@ -59,8 +64,13 @@ const FEEDBACK_CONTRACT = {
59
64
  },
60
65
  {
61
66
  id: "version",
62
- label: "Version and environment",
63
- description: "Output of `kici --version`, plus Node version and OS."
67
+ label: "KiCI version",
68
+ description: "Output of `kici --version`."
69
+ },
70
+ {
71
+ id: "environment",
72
+ label: "Environment",
73
+ description: "Node version and OS."
64
74
  },
65
75
  {
66
76
  id: "justification",
@@ -74,8 +84,48 @@ const FEEDBACK_CONTRACT = {
74
84
  "No log excerpts you have not read line by line.",
75
85
  "Reproduce with a minimal synthetic workflow, never the real one you were working on."
76
86
  ],
77
- privateReportCommand: "kici report --run <run-id> --upload"
87
+ privateReportCommand: "kici report --run <run-id> --upload",
88
+ draftCommand: FEEDBACK_DRAFT_COMMAND,
89
+ draftFields: [
90
+ "title",
91
+ "advertised",
92
+ "observed",
93
+ "reproduction",
94
+ "version",
95
+ "environment",
96
+ "justification"
97
+ ]
78
98
  };
99
+ function parseFeedbackDraft(raw) {
100
+ const problems = [];
101
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {
102
+ ok: false,
103
+ problems: ["draft must be a JSON object keyed by field id"]
104
+ };
105
+ const input = raw;
106
+ const allowed = new Set(FEEDBACK_CONTRACT.draftFields);
107
+ for (const key of Object.keys(input)) if (!allowed.has(key)) problems.push(`unknown field: ${key}`);
108
+ const draft = {};
109
+ for (const id of FEEDBACK_CONTRACT.draftFields) {
110
+ const value = input[id];
111
+ if (value === void 0) problems.push(`missing required field: ${id}`);
112
+ else if (typeof value !== "string" || value.trim() === "") problems.push(`field must be a non-empty string: ${id}`);
113
+ else draft[id] = value;
114
+ }
115
+ return problems.length === 0 ? {
116
+ ok: true,
117
+ draft
118
+ } : {
119
+ ok: false,
120
+ problems
121
+ };
122
+ }
123
+ /** The form URL with every draft field prefilled — GitHub reads each query param by field id. */
124
+ function draftIssueUrl(draft) {
125
+ const url = new URL(FEEDBACK_NEW_ISSUE_URL);
126
+ for (const id of FEEDBACK_CONTRACT.draftFields) url.searchParams.set(id, draft[id] ?? "");
127
+ return url.toString();
128
+ }
79
129
  function printContract() {
80
130
  const c = FEEDBACK_CONTRACT;
81
131
  logger.info(pc.bold("Reporting a KiCI discrepancy"));
@@ -108,6 +158,7 @@ function printContract() {
108
158
  logger.info(` ${c.approval.rule}`);
109
159
  logger.info(` Form: ${c.newIssueUrl}`);
110
160
  logger.info(pc.gray(` Or: kici feedback --open`));
161
+ logger.info(pc.gray(` From a draft: ${c.draftCommand} (fields: kici feedback --json → draftFields)`));
111
162
  logger.info("");
112
163
  logger.info(pc.gray("Machine-readable: kici feedback --json"));
113
164
  }
@@ -118,25 +169,51 @@ function printContract() {
118
169
  *
119
170
  * The command reaches no network and files nothing. `--open` opens the
120
171
  * prefilled issue form; `--json` emits the same contract for an agent to
121
- * consume without parsing prose.
172
+ * consume without parsing prose; `--draft <file>` builds the issue-form URL
173
+ * with every field of a JSON draft prefilled.
122
174
  */
123
175
  async function feedbackCommand(options = {}) {
176
+ if (options.draft) return draftFromFile(options.draft, options.open === true);
124
177
  if (options.json) {
125
178
  process.stdout.write(`${JSON.stringify(FEEDBACK_CONTRACT, null, 2)}\n`);
126
179
  return true;
127
180
  }
128
181
  printContract();
129
182
  if (!options.open) return true;
183
+ return openOrExplain(FEEDBACK_NEW_ISSUE_URL);
184
+ }
185
+ /** Read a JSON draft, print its prefilled issue-form URL as the last stdout line, open it on request. */
186
+ async function draftFromFile(file, openIt) {
187
+ let raw;
188
+ try {
189
+ raw = JSON.parse(await readFile(file, "utf8"));
190
+ } catch (error) {
191
+ logger.error(pc.red(`Could not read the draft at ${file}: ${toErrorMessage(error)}`));
192
+ return false;
193
+ }
194
+ const parsed = parseFeedbackDraft(raw);
195
+ if (!parsed.ok) {
196
+ for (const problem of parsed.problems) logger.error(pc.red(problem));
197
+ logger.info(pc.gray(`Fields: ${FEEDBACK_CONTRACT.draftFields.join(", ")} — see kici feedback --json`));
198
+ return false;
199
+ }
200
+ const url = draftIssueUrl(parsed.draft);
201
+ if (url.length > 6e3) logger.warn(pc.yellow(`The prefilled URL is ${url.length} bytes; GitHub may truncate it. Shorten the reproduction.`));
202
+ process.stdout.write(`${url}\n`);
203
+ if (!openIt) return true;
204
+ return openOrExplain(url);
205
+ }
206
+ async function openOrExplain(url) {
130
207
  try {
131
- await open(FEEDBACK_NEW_ISSUE_URL);
208
+ await open(url);
132
209
  return true;
133
210
  } catch (error) {
134
211
  logger.error(pc.red(`Could not open a browser: ${toErrorMessage(error)}`));
135
- logger.info(pc.gray(`Open ${FEEDBACK_NEW_ISSUE_URL} manually.`));
212
+ logger.info(pc.gray(`Open ${url} manually.`));
136
213
  return false;
137
214
  }
138
215
  }
139
216
  //#endregion
140
- export { FEEDBACK_CONTRACT, FEEDBACK_NEW_ISSUE_URL, FEEDBACK_SECURITY_ADVISORY_URL, FEEDBACK_TEMPLATE, FEEDBACK_TRACKER_URL, feedbackCommand };
217
+ export { FEEDBACK_CONTRACT, FEEDBACK_DRAFT_COMMAND, FEEDBACK_NEW_ISSUE_URL, FEEDBACK_SECURITY_ADVISORY_URL, FEEDBACK_TEMPLATE, FEEDBACK_TRACKER_URL, FEEDBACK_URL_WARN_BYTES, draftIssueUrl, feedbackCommand, parseFeedbackDraft };
141
218
 
142
219
  //# sourceMappingURL=feedback.js.map
@@ -57,9 +57,9 @@ export type { WorkflowsListOptions } from './workflows.js';
57
57
  export { drainWorkerCommand } from './drain-worker.js';
58
58
  export type { DrainWorkerOptions } from './drain-worker.js';
59
59
  export { docsCommand, docsLlmCommand } from './docs.js';
60
- export { feedbackCommand, FEEDBACK_CONTRACT } from './feedback.js';
60
+ export { feedbackCommand, FEEDBACK_CONTRACT, parseFeedbackDraft, draftIssueUrl, } from './feedback.js';
61
61
  export type { DocsOptions, DocsLlmOptions } from './docs.js';
62
- export type { FeedbackOptions, FeedbackContract, FeedbackField } from './feedback.js';
62
+ export type { FeedbackOptions, FeedbackContract, FeedbackField, FeedbackDraft, } from './feedback.js';
63
63
  export { verifyAttestationCommand } from './verify-attestation.js';
64
64
  export type { VerifyAttestationOptions } from './verify-attestation.js';
65
65
  export { notificationsChannelsListCommand, notificationsChannelsAddCommand, notificationsChannelsRemoveCommand, notificationsSubscriptionsListCommand, notificationsSubscriptionsAddCommand, notificationsSubscriptionsRemoveCommand, notificationsRosterListCommand, notificationsRosterAddCommand, notificationsRosterRemoveCommand, } from './notifications.js';
@@ -6,7 +6,7 @@ import { docsCommand, docsLlmCommand } from "./docs.js";
6
6
  import { doctorCommand } from "./doctor.js";
7
7
  import { drainWorkerCommand } from "./drain-worker.js";
8
8
  import { endpointsCommand } from "./endpoints.js";
9
- import { FEEDBACK_CONTRACT, feedbackCommand } from "./feedback.js";
9
+ import { FEEDBACK_CONTRACT, draftIssueUrl, feedbackCommand, parseFeedbackDraft } from "./feedback.js";
10
10
  import { fixtureCommand } from "./fixture.js";
11
11
  import { hookInstallCommand } from "./hook.js";
12
12
  import { watchCommand } from "./watch.js";
@@ -35,4 +35,4 @@ import { rejectCommand } from "./reject.js";
35
35
  import { workflowsListCommand } from "./workflows.js";
36
36
  import { verifyAttestationCommand } from "./verify-attestation.js";
37
37
  import { notificationsChannelsAddCommand, notificationsChannelsListCommand, notificationsChannelsRemoveCommand, notificationsRosterAddCommand, notificationsRosterListCommand, notificationsRosterRemoveCommand, notificationsSubscriptionsAddCommand, notificationsSubscriptionsListCommand, notificationsSubscriptionsRemoveCommand } from "./notifications.js";
38
- export { FEEDBACK_CONTRACT, approveCommand, compileCommand, diagnosticsCommand, docsCommand, docsLlmCommand, doctorCommand, drainWorkerCommand, endpointsCommand, feedbackCommand, fixtureCommand, hookInstallCommand, initCommand, localAttachCommand, localDetachCommand, localDownCommand, localLogsCommand, localStatusCommand, localTrustRootCommand, localUpCommand, loginCommand, logoutCommand, notificationsChannelsAddCommand, notificationsChannelsListCommand, notificationsChannelsRemoveCommand, notificationsRosterAddCommand, notificationsRosterListCommand, notificationsRosterRemoveCommand, notificationsSubscriptionsAddCommand, notificationsSubscriptionsListCommand, notificationsSubscriptionsRemoveCommand, orchestratorsListCommand, orchestratorsUseCommand, orgCurrentCommand, orgListCommand, orgUseCommand, patCreateCommand, previewCommand, previewEvent, rejectCommand, reportCommand, reportListCommand, reportWithdrawCommand, runRemoteCommand, runRoutedCommand, runsArtifactsDownloadCommand, runsArtifactsListCommand, runsCancelCommand, runsListCommand, runsLogsCommand, runsRerunCommand, runsShowCommand, secretsListCommand, typesCommand, verifyAttestationCommand, watchCommand, workflowsListCommand };
38
+ export { FEEDBACK_CONTRACT, approveCommand, compileCommand, diagnosticsCommand, docsCommand, docsLlmCommand, doctorCommand, draftIssueUrl, drainWorkerCommand, endpointsCommand, feedbackCommand, fixtureCommand, hookInstallCommand, initCommand, localAttachCommand, localDetachCommand, localDownCommand, localLogsCommand, localStatusCommand, localTrustRootCommand, localUpCommand, loginCommand, logoutCommand, notificationsChannelsAddCommand, notificationsChannelsListCommand, notificationsChannelsRemoveCommand, notificationsRosterAddCommand, notificationsRosterListCommand, notificationsRosterRemoveCommand, notificationsSubscriptionsAddCommand, notificationsSubscriptionsListCommand, notificationsSubscriptionsRemoveCommand, orchestratorsListCommand, orchestratorsUseCommand, orgCurrentCommand, orgListCommand, orgUseCommand, parseFeedbackDraft, patCreateCommand, previewCommand, previewEvent, rejectCommand, reportCommand, reportListCommand, reportWithdrawCommand, runRemoteCommand, runRoutedCommand, runsArtifactsDownloadCommand, runsArtifactsListCommand, runsCancelCommand, runsListCommand, runsLogsCommand, runsRerunCommand, runsShowCommand, secretsListCommand, typesCommand, verifyAttestationCommand, watchCommand, workflowsListCommand };
@@ -5,7 +5,7 @@
5
5
  * orchestrator plus a local Postgres (embedded, with a podman fallback) that
6
6
  * `kici run --local` dispatches through. `up` boots or reuses the plane,
7
7
  * `status` reports it plus the control commands, `down` stops it, and `logs`
8
- * prints the orchestrator log path.
8
+ * prints the plane log paths and rotation policy.
9
9
  */
10
10
  import type { PlaneStatus, PlaneMode } from '../local-plane/plane-manager.js';
11
11
  import type { PlaneState } from '../local-plane/plane-liveness.js';
@@ -52,12 +52,20 @@ export declare function localStatusCommand(options?: {
52
52
  }): Promise<boolean>;
53
53
  /** Stop the local dev plane, reporting success only once the port is released. */
54
54
  export declare function localDownCommand(): Promise<boolean>;
55
- /** Print the local dev plane orchestrator log path. */
55
+ /**
56
+ * Print the local dev plane orchestrator log path, plus the rotation policy.
57
+ *
58
+ * The path is the only line this command writes to stdout — the policy note
59
+ * goes to stderr — so a caller reading stdout never has to strip prose from
60
+ * it. The CLI version banner still precedes it, as for every command, so
61
+ * `$(kici local logs)` is NOT a bare path: read the last stdout line.
62
+ */
56
63
  export declare function localLogsCommand(): Promise<boolean>;
57
64
  /**
58
- * Attach the local dev plane to the hosted Platform so `kici run --local` uses
59
- * real Platform-minted OIDC + attestation. Mints an org-scoped orchestrator key
60
- * with the logged-in PAT, then (re)boots the plane hybrid.
65
+ * Attach the local dev plane to the hosted Platform so `kici run --local` mints
66
+ * OIDC + attestation with the plane's own signing key under its own issuer, as
67
+ * a deployed orchestrator does. Mints an org-scoped orchestrator key with the
68
+ * logged-in PAT, then (re)boots the plane hybrid.
61
69
  */
62
70
  export declare function localAttachCommand(): Promise<boolean>;
63
71
  /** Detach the local dev plane from the Platform and reboot it offline (independent). */
@@ -1,5 +1,6 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
2
  import { loadGlobalConfig } from "../remote/config.js";
3
+ import "../local-plane/plane-log.js";
3
4
  import { attachPlane, detachPlane, planeDown, planeLogPath, planeStatus } from "../local-plane/plane-manager.js";
4
5
  import { resolvePlaneForRun } from "../local-plane/resolve-plane.js";
5
6
  import pc from "picocolors";
@@ -12,14 +13,14 @@ import { toErrorMessage } from "@kici-dev/core";
12
13
  * orchestrator plus a local Postgres (embedded, with a podman fallback) that
13
14
  * `kici run --local` dispatches through. `up` boots or reuses the plane,
14
15
  * `status` reports it plus the control commands, `down` stops it, and `logs`
15
- * prints the orchestrator log path.
16
+ * prints the plane log paths and rotation policy.
16
17
  */
17
18
  /** Print the control commands a user needs once the plane is running. */
18
19
  function printControlHints() {
19
20
  console.log("");
20
21
  console.log(pc.dim("Control commands:"));
21
22
  console.log(` ${pc.cyan("kici local status")} Show plane status`);
22
- console.log(` ${pc.cyan("kici local logs")} Print the orchestrator log path`);
23
+ console.log(` ${pc.cyan("kici local logs")} Print the plane log paths and rotation policy`);
23
24
  console.log(` ${pc.cyan("kici local down")} Stop the plane`);
24
25
  }
25
26
  /**
@@ -147,15 +148,25 @@ async function localDownCommand() {
147
148
  console.log(pc.green("✓") + " Local dev plane stopped.");
148
149
  return true;
149
150
  }
150
- /** Print the local dev plane orchestrator log path. */
151
+ /**
152
+ * Print the local dev plane orchestrator log path, plus the rotation policy.
153
+ *
154
+ * The path is the only line this command writes to stdout — the policy note
155
+ * goes to stderr — so a caller reading stdout never has to strip prose from
156
+ * it. The CLI version banner still precedes it, as for every command, so
157
+ * `$(kici local logs)` is NOT a bare path: read the last stdout line.
158
+ */
151
159
  async function localLogsCommand() {
152
- console.log(planeLogPath());
160
+ const logFile = planeLogPath();
161
+ console.log(logFile);
162
+ console.error(pc.dim(`Rotated to ${logFile}.1 when it reaches 50 MB, at the next plane start. When the plane runs embedded PostgreSQL, its log sits beside it at ${logFile}.pg and rotates the same way.`));
153
163
  return true;
154
164
  }
155
165
  /**
156
- * Attach the local dev plane to the hosted Platform so `kici run --local` uses
157
- * real Platform-minted OIDC + attestation. Mints an org-scoped orchestrator key
158
- * with the logged-in PAT, then (re)boots the plane hybrid.
166
+ * Attach the local dev plane to the hosted Platform so `kici run --local` mints
167
+ * OIDC + attestation with the plane's own signing key under its own issuer, as
168
+ * a deployed orchestrator does. Mints an org-scoped orchestrator key with the
169
+ * logged-in PAT, then (re)boots the plane hybrid.
159
170
  */
160
171
  async function localAttachCommand() {
161
172
  const config = await loadGlobalConfig();
@@ -180,7 +191,7 @@ async function localAttachCommand() {
180
191
  orgId
181
192
  });
182
193
  console.log(pc.green("✓") + ` Local dev plane attached (hybrid) at ${pc.bold(status.url ?? "")} ` + pc.dim(`(org: ${orgId})`));
183
- console.log(pc.dim("`kici run --local` now uses real Platform OIDC + attestation. Detach: ") + pc.cyan("kici local detach"));
194
+ console.log(pc.dim("`kici run --local` now mints OIDC + attestation with this plane's own key against the real Platform org. Detach: ") + pc.cyan("kici local detach"));
184
195
  return true;
185
196
  } catch (err) {
186
197
  console.error(pc.red(`Attach failed: ${toErrorMessage(err)}`));
@@ -19,7 +19,7 @@ import { PROTOCOL_VERSION } from "@kici-dev/engine";
19
19
  */
20
20
  function collectIdentity(probe) {
21
21
  const identity = {
22
- kiciCliVersion: "0.7.0",
22
+ kiciCliVersion: "0.9.0",
23
23
  nodeVersion: process.version,
24
24
  platform: process.platform,
25
25
  arch: process.arch,
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Three variants:
8
8
  * - `offline` — independent plane, local secrets, dev-signed identity.
9
- * - `attached` — hybrid plane, real Platform-minted OIDC + attestation.
9
+ * - `attached` — hybrid plane, OIDC + attestation signed by the plane's own key.
10
10
  * - `fallback` — wanted attached but the Platform was unreachable, so the run
11
11
  * fell back to offline; a LOUD first line makes the degradation obvious.
12
12
  */
@@ -5,7 +5,7 @@ function renderRunBanner(input) {
5
5
  const title = input.mode === "attached" ? "kici run --local (connected)" : "kici run --local";
6
6
  const rows = [];
7
7
  if (input.mode === "fallback") rows.push(["⚠", `Platform unreachable — fell back to OFFLINE${input.fallbackReason ? ` (${input.fallbackReason})` : ""}`]);
8
- if (input.mode === "attached") rows.push(["plane", "local dev orchestrator (hybrid, attached)"], ["agent", "this machine (bare-metal)"], ["secrets", `REAL scoped${input.orgId ? ` (org: ${input.orgId})` : ""}`], ["identity", "real Platform OIDC + attestation"], ["control", `kici local status | logs | detach (${input.planeUrl})`], ["force", "--offline (local plane) · --in-place (ambient)"]);
8
+ if (input.mode === "attached") rows.push(["plane", "local dev orchestrator (hybrid, attached)"], ["agent", "this machine (bare-metal)"], ["secrets", `REAL scoped${input.orgId ? ` (org: ${input.orgId})` : ""}`], ["identity", `plane-signed OIDC + attestation (iss=${input.planeUrl})`], ["control", `kici local status | logs | detach (${input.planeUrl})`], ["force", "--offline (local plane) · --in-place (ambient)"]);
9
9
  else rows.push(["plane", "local dev orchestrator (independent, offline)"], ["agent", "this machine (bare-metal)"], ["secrets", "LOCAL files (.kici/.secrets, .env.local, --env)"], ["identity", "DEV-SIGNED (iss=kici-local — NOT prod)"], ["control", `kici local status | logs | down (${input.planeUrl})`], ["force", "--connected (attach to Platform) · --in-place (ambient)"]);
10
10
  if (input.trusted) rows.push(["execution", "TRUSTED — host env passthrough (NOT sandboxed)"]);
11
11
  const labelWidth = Math.max(...rows.map(([label]) => label.length));
@@ -151,6 +151,9 @@ async function runRouted(options) {
151
151
  action: dispatch.action,
152
152
  clientPayload: dispatch.clientPayload
153
153
  }
154
+ }, {
155
+ repoBasePath: workdir.dir,
156
+ logPath: planeLogPath()
154
157
  });
155
158
  if (!quiet) logger.info(pc.green(`Run started: ${runId}`));
156
159
  const outcome = await followRun(plane.url, plane.adminToken, runId, {
@@ -9,6 +9,7 @@ import { buildEncryptedSecrets } from "../remote/secret-upload.js";
9
9
  import { createOverlayTarball, getSizeWarning, uploadTarball } from "../remote/uploader.js";
10
10
  import { formatJsonResult } from "../remote/output/json.js";
11
11
  import { formatJunitResult } from "../remote/output/junit.js";
12
+ import { unwrapStoredLogLine } from "../remote/output/streaming.js";
12
13
  import { formatErrorHighlight, formatMultiFixtureSummary, formatSummary } from "../remote/output/summary.js";
13
14
  import { compileFixtures, filterFixtures } from "../fixtures/compiler.js";
14
15
  import { describeEvent } from "../fixtures/describe-event.js";
@@ -500,7 +501,8 @@ async function pollRunToCompletion(ctx, runId, fixtureId, options) {
500
501
  throw err;
501
502
  }
502
503
  runSeen = true;
503
- for (const line of logs.lines) {
504
+ for (const raw of logs.lines) {
505
+ const line = unwrapStoredLogLine(raw);
504
506
  if (!options.quiet) process.stdout.write(line + "\n");
505
507
  tailLines.push(line);
506
508
  while (tailLines.length > MAX_TAIL) tailLines.shift();
@@ -585,7 +587,8 @@ function jobsFromStatus(status) {
585
587
  if (!status) return [];
586
588
  return status.jobs.map((j) => ({
587
589
  name: j.jobName,
588
- status: j.status
590
+ status: j.status,
591
+ ...typeof j.durationMs === "number" && { durationMs: j.durationMs }
589
592
  }));
590
593
  }
591
594
  /**
@@ -1,6 +1,7 @@
1
1
  import "../../rolldown-runtime-ClRpJifh.js";
2
2
  import { DashboardClient, DashboardClientError } from "../../remote/dashboard-client.js";
3
3
  import { colorStatus } from "../../remote/render.js";
4
+ import { unwrapStoredLogLine } from "../../remote/output/streaming.js";
4
5
  import pc from "picocolors";
5
6
  import { logger, toErrorMessage } from "@kici-dev/core";
6
7
  import { TERMINAL_RUN_STATES } from "@kici-dev/engine";
@@ -31,7 +32,7 @@ async function printAllLogs(client, runId, jobs, jobFilter) {
31
32
  for (const j of selectJobs(jobs, jobFilter)) for (const s of j.steps ?? []) {
32
33
  console.log(pc.bold(`\n=== ${j.jobName} › ${s.stepName} `) + colorStatus(s.status) + pc.bold(" ==="));
33
34
  const logs = await client.getStepLogs(runId, j.jobId, s.stepIndex);
34
- for (const line of logs.lines) console.log(line);
35
+ for (const line of logs.lines) console.log(unwrapStoredLogLine(line));
35
36
  }
36
37
  }
37
38
  async function collectAllLogs(client, runId, jobs, jobFilter) {
@@ -50,7 +51,7 @@ async function followLogs(client, runId, jobFilter) {
50
51
  const seen = printed[key] ?? 0;
51
52
  if (logs.lines.length > seen) {
52
53
  if (seen === 0) console.log(pc.bold(`\n=== ${j.jobName} › ${s.stepName} ===`));
53
- for (const line of logs.lines.slice(seen)) console.log(line);
54
+ for (const line of logs.lines.slice(seen)) console.log(unwrapStoredLogLine(line));
54
55
  printed[key] = logs.lines.length;
55
56
  }
56
57
  }
@@ -78,7 +78,7 @@ async function verifyAttestationCommand(artifact, options = {}) {
78
78
  if (c.source_origin === "run-remote") logger.info(pc.bold(pc.yellow(" SOURCE: kici run remote (local working-tree overlay — repository/ref/sha are caller-supplied, not a triggered VCS commit)")));
79
79
  else logger.info(pc.gray(` source: ${c.source_origin ?? "triggered"}`));
80
80
  const origin = result.attestationOrigin ?? "live";
81
- if (origin === "deferred") logger.info(pc.bold(pc.yellow(" ATTESTATION: deferred — the build facts were sealed at build time; the identity token was minted later (after a transient Platform outage), bound to the frozen statement by hash.")));
81
+ if (origin === "deferred") logger.info(pc.bold(pc.yellow(" ATTESTATION: deferred — the build facts were sealed at build time; the identity token was minted later (once the orchestrator's signing key was available), bound to the frozen statement by hash.")));
82
82
  else if (origin === "offline-backfill") logger.info(pc.bold(pc.yellow(" ATTESTATION: offline-backfill — the run was ingested while the Platform was down; its run/job rows were backfilled and the token minted later. The org id is the authoritative anchor; the temporal gap is disclosed.")));
83
83
  logger.info(pc.gray(` repository=${c.repository} ref=${c.ref} sha=${c.sha} provider=${c.provider ?? "unknown"} run=${c.kici_run_id} job=${c.kici_job_id}`));
84
84
  } else logger.error(`${pc.red("FAIL")} provenance NOT verified: ${result.failures.join(", ")}`);
@@ -411,7 +411,7 @@ Orchestrator Agent Sandbox (child pro
411
411
  | | |
412
412
  |-- job.dispatch (WS) ------------>| |
413
413
  | (jobConfig, sourceTarUrl, | |
414
- | sourceTarHash, depsUrl, |-- Create sandbox ------->|
414
+ | sourceTarDigest, depsUrl, |-- Create sandbox ------->|
415
415
  | depsHash) | (container/bare-metal/ |
416
416
  | | firecracker) |
417
417
  | | |-- Restore .kici/ source (tarball)
@@ -593,6 +593,8 @@ Dep cache misses alone do **not** trigger a build job. Deps are platform-specifi
593
593
 
594
594
  When multiple webhooks trigger simultaneously for the same repository state, the `BuildCoordinator` coalesces concurrent build requests using a combined key (`contentHash:lockfileHash`). Only one build job runs; all waiting dispatches share the result.
595
595
 
596
+ The coalescing map is per coordinator. The build job itself sits on the cluster-wide dispatch queue, so an agent connected to a sibling coordinator can claim it. That sibling persists the terminal `job.status` to the shared `execution_jobs` row (`precursor_result`), and the dispatching coordinator's `PendingPrecursorDbWatcher` reads the row back and settles the pending build through `settlePendingPrecursor()`. The local agent socket goes through the same function. See [runs that span coordinators](https://docs.kici.dev/operator/orchestrator/clustering/#runs-that-span-coordinators).
597
+
596
598
  ### Graceful degradation
597
599
 
598
600
  If cache storage is unavailable or a download fails:
@@ -667,7 +669,7 @@ Agent Orchestrator S3
667
669
  | | for deps integrity) |
668
670
  ```
669
671
 
670
- The two-phase metadata approach (`upload via PUT` then `initMeta via CopyObject`) works around the limitation that S3 pre-signed URLs cannot include custom metadata headers. For dependency tarballs, the agent also reports the SHA-256 content hash in `cache.upload.complete`; the orchestrator stores it as a companion `.hash` file alongside the tarball. When dispatching execution jobs, the orchestrator reads this hash and includes it as `depsHash` in `job.dispatch`, enabling agent-side integrity verification on download. Source tarballs do not use a companion `.hash` file the workflow `contentHash` carried in `sourceTarHash` is used to verify the extracted source against the lock file after extraction, which covers drift end-to-end.
672
+ The two-phase metadata approach (`upload via PUT` then `initMeta via CopyObject`) works around the limitation that S3 pre-signed URLs cannot include custom metadata headers. For dependency tarballs, the agent also reports the SHA-256 content hash in `cache.upload.complete`; the orchestrator stores it as a companion `.hash` file alongside the tarball. When dispatching execution jobs, the orchestrator reads this hash and includes it as `depsHash` in `job.dispatch`, enabling agent-side integrity verification on download. Source tarballs carry their own SHA-256 as `sourceTarDigest` in `job.dispatch`, verified before extraction; the workflow `contentHash` is then re-computed against the extracted source to verify it against the lock file, which covers drift end-to-end.
671
673
 
672
674
  ### URL delivery (downloads)
673
675
 
@@ -1166,7 +1168,7 @@ The compiler processes the workflow definition:
1166
1168
 
1167
1169
  When `kici run <event> --local` runs a workflow:
1168
1170
 
1169
- 1. **SDK module resolution:** The runner resolves `setStepOutputsMap` / `setJobOutputsMap` from the same `@kici-dev/sdk` module instance that the workflow uses (ensures the proxy reads from the same map)
1171
+ 1. **SDK module resolution:** The runner resolves `setStepOutputsMap` / `setJobOutputsMap` from the `@kici-dev/sdk/internal` subpath of the same SDK copy that the workflow uses (ensures the proxy reads from the same map)
1170
1172
  2. **Map injection:** Fresh `OutputsMap` and `StepRefMap` are created and injected via `setStepOutputsMap()` / `setStepRefMap()` before each job
1171
1173
  3. **Step execution:** Each step runs sequentially. If the step returns a value, it is stored in the `OutputsMap` keyed by step name
1172
1174
  4. **Bare function normalization:** Bare functions in the steps array are assigned counter names and registered in the `StepRefMap` (maps function reference to step name)
@@ -1301,7 +1303,7 @@ The Platform never processes, stores, or executes customer code, and never sees
1301
1303
 
1302
1304
  The orchestrator is the execution brain. It decides what to run and dispatches work to agents.
1303
1305
 
1304
- - **Trigger matching** -- Evaluates lock file triggers against webhook payloads to determine which jobs to run. Uses branch, path, and event matching via picomatch.
1306
+ - **Trigger matching** -- Evaluates lock file triggers against webhook payloads to determine which jobs to run. Uses glob-based branch, path, and event matching.
1305
1307
  - **Lock file caching** -- Fetches `kici.lock.json` via the configured source's fetcher (GitHub API, universal-git clone for generic webhook sources backed by a git URL, or the local filesystem for `file://` sources). An LRU cache wraps the per-provider fetcher, keyed by `{provider}:{repo}:{ref}` so cross-provider fallback resolutions stay isolated.
1306
1308
  - **Agent registry** -- Tracks connected agents with label-based routing for job dispatch.
1307
1309
  - **Job queue** -- PostgreSQL-backed FIFO queue for reliable dispatch.
@@ -1333,7 +1335,7 @@ The agent is the execution worker. It runs on customer infrastructure and has fu
1333
1335
  Shared business logic used by all three tiers. Single source of truth for cross-tier concerns. Has no internal `@kici-dev/*` dependencies -- only a handful of third-party libraries.
1334
1336
 
1335
1337
  - Protocol message schemas (Zod-based, direction-specific unions including dashboard REST-over-WS, browser live streaming, the test-relay control plane, log pull, run events, peer-to-peer, cluster join, and source registration)
1336
- - Provider interfaces (WebhookNormalizer, LockFileFetcher, ChangedFilesFetcher, FileContentsFetcher, CloneTokenProvider, RepoUrlBuilder, CheckStatusPoster), plus the deprecated `ContributorResolver` the pipeline no longer calls
1338
+ - Provider interfaces (WebhookNormalizer, LockFileFetcher, ChangedFilesFetcher, FileContentsFetcher, CloneTokenProvider, RepoUrlBuilder, CheckStatusPoster)
1337
1339
  - Git credential vocabulary (forge names plus the credential reference, grant, request, and result shapes the SDK declares and the orchestrator's broker resolves) and the agent→orchestrator relay protocol its credential helper calls. See [Git credentials](https://docs.kici.dev/user/patterns/git-credentials/)
1338
1340
  - Trigger matching engine (branch, path, event evaluation)
1339
1341
  - Content-requirement matcher (the declarative `requires` filter -- pure data describing a query over the bytes of one source file at the event's ref, interpreted by the orchestrator via the `FileContentsFetcher` so no author code runs there) and the shared text-match vocabulary (`contains` / `notContains` / `matches` / `notMatches`) it shares with the commit-message trigger filter
@@ -1383,7 +1385,7 @@ It also runs the **local dev plane** -- an on-demand, fully local execution stac
1383
1385
 
1384
1386
  ### `@kici-dev/core`
1385
1387
 
1386
- Light shared utilities with no server-side dependencies. It provides JSON-structured logging, error helpers, async-local-storage request context, and human-readable formatting (`formatBytes`/`formatDuration`/`formatUptime`). It also provides cryptographic helpers (`sha256`/`sha256File`/`deriveSharedSecret` plus symmetric encrypt/decrypt), retry-backoff computation, and the shared diagnostics-result contract. The rest of its surface ships as subpath entry points: the temp-directory allocator and its garbage collector, package-manager detection, CI-environment detection, and the idempotent-step runner (the check / confirm / apply primitive behind idempotent steps). One further subpath holds the `.kici/` source digest: the single content-hash definition the compiler writes into the lock file and the agent recomputes as its drift gate. Finally it supplies zx initialization (`initZx()`) and the TypeScript loader hook that transforms TypeScript on import. It is the dependency-light core that the SDK, compiler, and `kici` CLI consume directly so they stay free of heavier server-only dependencies. `@kici-dev/shared` re-exports it, so existing `@kici-dev/shared` import paths keep working.
1388
+ Light shared utilities with no server-side dependencies. It provides JSON-structured logging, error helpers, async-local-storage request context, and human-readable formatting (`formatBytes`/`formatDuration`/`formatUptime`). It also provides cryptographic helpers (`sha256`/`sha256File`/`deriveSharedSecret` plus symmetric encrypt/decrypt), retry-backoff computation, and the shared diagnostics-result contract. The rest of its surface ships as subpath entry points: the temp-directory allocator and its garbage collector, package-manager detection, CI-environment detection, and the idempotent-step runner (the check / confirm / apply primitive behind idempotent steps). One further subpath holds the `.kici/` source digest: the single content-hash definition the compiler writes into the lock file and the agent recomputes as its drift gate. It also owns the published docs host: `docsUrl()` builds every docs link a CLI prints or a template scaffolds from one `DOCS_SITE_URL` constant, so no other package spells the host. Finally it supplies zx initialization (`initZx()`) and the TypeScript loader hook that transforms TypeScript on import. It is the dependency-light core that the SDK, compiler, and `kici` CLI consume directly so they stay free of heavier server-only dependencies. `@kici-dev/shared` re-exports it, so existing `@kici-dev/shared` import paths keep working.
1387
1389
 
1388
1390
  > Source: `packages/core/src/`
1389
1391
 
@@ -1465,7 +1467,7 @@ KiCI uses three WebSocket layers for real-time communication.
1465
1467
 
1466
1468
  ### Platform ↔ Orchestrator
1467
1469
 
1468
- The orchestrator connects outbound to the Platform WebSocket endpoint. After authentication (API key validated via SHA-256 hash lookup), the connection is used for webhook relay, execution telemetry (events, status, logs), source registration, and peer discovery. The Platform can also relay `job.reroute` messages between orchestrators that cannot reach each other directly.
1470
+ The orchestrator connects outbound to the Platform WebSocket endpoint. After authentication (API key validated via SHA-256 hash lookup), the connection is used for webhook relay, execution telemetry (events, status, logs), source registration, and peer discovery. Peer discovery is matchmaking only: the Platform pushes a `peer.update` membership list to every orchestrator sharing a routing key, and the orchestrators then connect to each other directly. Inter-orchestrator traffic such as `job.reroute` never transits the Platform.
1469
1471
 
1470
1472
  ### Orchestrator ↔ Orchestrator (P2P)
1471
1473