@kici-dev/compiler 0.1.23 → 0.1.24

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 (41) hide show
  1. package/dist/cli.js +15 -5
  2. package/dist/commands/index.d.ts +4 -2
  3. package/dist/commands/index.js +3 -2
  4. package/dist/commands/init.js +2 -2
  5. package/dist/commands/pat.d.ts +27 -0
  6. package/dist/commands/pat.js +76 -0
  7. package/dist/commands/preview.d.ts +88 -0
  8. package/dist/commands/{test.js → preview.js} +15 -14
  9. package/dist/commands/run.d.ts +11 -1
  10. package/dist/commands/run.js +34 -7
  11. package/dist/commands/verify-attestation.d.ts +4 -1
  12. package/dist/commands/verify-attestation.js +26 -10
  13. package/dist/generators/secrets-dts.js +2 -0
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.js +2 -2
  16. package/dist/llm-context/llms-architecture.txt +3 -3
  17. package/dist/llm-context/llms-cli.txt +149 -26
  18. package/dist/llm-context/llms-features.txt +107 -5
  19. package/dist/llm-context/llms-full.txt +390 -46
  20. package/dist/llm-context/llms-getting-started.txt +6 -6
  21. package/dist/llm-context/llms-sdk.txt +125 -6
  22. package/dist/llm-context/llms.txt +7 -5
  23. package/dist/local-executor/index.js +2 -1
  24. package/dist/local-executor/job-runner.js +3 -3
  25. package/dist/lockfile/generator.d.ts +10 -2
  26. package/dist/lockfile/generator.js +106 -52
  27. package/dist/remote/history.d.ts +1 -1
  28. package/dist/remote/history.js +1 -1
  29. package/dist/remote/local-repo-identity.d.ts +32 -0
  30. package/dist/remote/local-repo-identity.js +74 -0
  31. package/dist/remote/prod-defaults.d.ts +8 -0
  32. package/dist/remote/prod-defaults.js +9 -1
  33. package/dist/templates/agents-md.d.ts +1 -1
  34. package/dist/templates/agents-md.js +2 -2
  35. package/dist/templates/package-json.js +1 -1
  36. package/dist/test-runner/step-context.d.ts +1 -1
  37. package/dist/test-runner/step-context.js +2 -1
  38. package/dist/types.d.ts +33 -6
  39. package/dist/types.js +5 -1
  40. package/package.json +4 -7
  41. package/sbom.spdx.json +35 -35
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import { realpathSync } from "node:fs";
7
7
  import { Argument, Command, Option } from "commander";
8
8
  import pc from "picocolors";
9
9
  //#region src/cli.ts
10
- const version = "0.1.23";
10
+ const version = "0.1.24";
11
11
  /**
12
12
  * Build the kici Commander program with every command registered. Exported so
13
13
  * the surface registry can walk the real command tree without parsing argv (no
@@ -126,9 +126,9 @@ function buildProgram() {
126
126
  });
127
127
  process.exit(success ? 0 : 1);
128
128
  });
129
- program.command("test").argument("[event]", "Event type to preview (e.g., push, pr:open, schedule)").description("Preview which workflows match a trigger event (dry-run)").option("--branch <name>", "Override target branch for trigger matching (default: main)").option("--sha <hash>", "Override commit SHA").option("--workflow <name>", "Filter to specific workflow in display").option("--job <name>", "Filter to specific job in display").option("--debug", "Verbose internals", false).option("--kici-dir <path>", "Path to .kici directory", ".kici").option("--files <path>", "Simulate changed file path for trigger matching (repeatable)", (val, prev) => [...prev, val], []).option("--secret <key=value>", "Inject flat secret (repeatable)", (val, prev) => [...prev, val], []).option("--context <ctx.key=value>", "Inject context secret (repeatable)", (val, prev) => [...prev, val], []).action(async (event, options) => {
130
- const { testCommand } = await import("./commands/index.js");
131
- const success = await testCommand(event, options);
129
+ program.command("preview").argument("[event]", "Event type to preview (e.g., push, pr:open, schedule)").description("Preview which workflows match a trigger event (no execution)").option("--branch <name>", "Override target branch for trigger matching (default: main)").option("--sha <hash>", "Override commit SHA").option("--workflow <name>", "Filter to specific workflow in display").option("--job <name>", "Filter to specific job in display").option("--debug", "Verbose internals", false).option("--kici-dir <path>", "Path to .kici directory", ".kici").option("--files <path>", "Simulate changed file path for trigger matching (repeatable)", (val, prev) => [...prev, val], []).option("--secret <key=value>", "Inject flat secret (repeatable)", (val, prev) => [...prev, val], []).option("--context <ctx.key=value>", "Inject context secret (repeatable)", (val, prev) => [...prev, val], []).action(async (event, options) => {
130
+ const { previewCommand } = await import("./commands/index.js");
131
+ const success = await previewCommand(event, options);
132
132
  process.exit(success ? 0 : 1);
133
133
  });
134
134
  program.command("init").description("Initialize .kici/ directory with default workflows").option("--force", "Overwrite existing .kici/ directory", false).option("--skip-install", "Create files without installing dependencies", false).option("--package-manager <npm|pnpm|yarn>", "Force a package manager for the install step (default: auto-detect)").option("--mjs", "JavaScript-only mode (no TypeScript, no dependencies)", false).option("--no-agents-md", "Skip writing .kici/AGENTS.md (LLM authoring context)").option("--private-registry <url>", "Scaffold a workflow registries: entry pointing at <url>").option("--private-registry-scope <scope>", "Optional npm package scope (e.g. @my-org) for the private registry").option("--private-registry-secret <ref>", "Qualified secret reference (env:NAME) the private registry token comes from", "production:NPM_TOKEN").addOption(new Option("--use-verdaccio-local").default(false).hideHelp()).action(async (options) => {
@@ -199,6 +199,16 @@ Environment variables:
199
199
  const success = await secretsListCommand();
200
200
  process.exit(success ? 0 : 1);
201
201
  });
202
+ program.command("pat").description("Manage personal access tokens").command("create").description("Mint a personal access token (use --agent for a coding-agent token)").option("--name <name>", "Token name (defaults to the agent label)").option("--agent", "Mint an agent-kind PAT for the KiCI MCP server", false).option("--expires-in-days <n>", "Custom expiry in days", (v) => parseInt(v, 10)).action(async (options) => {
203
+ const { patCreateCommand } = await import("./commands/index.js");
204
+ const success = await patCreateCommand({
205
+ name: options.name,
206
+ agent: options.agent,
207
+ label: options.name,
208
+ expiresInDays: options.expiresInDays
209
+ });
210
+ process.exit(success ? 0 : 1);
211
+ });
202
212
  const runsCommand = program.command("runs").description("Inspect and manage execution runs");
203
213
  runsCommand.command("list").description("List execution runs (mirrors the dashboard Runs page)").option("--status <s>", "Filter by status").option("--workflow <w>", "Filter by workflow name").option("--branch <b>", "Filter by branch/ref").option("--repo <r>", "Filter by repository").option("--trigger <t>", "Filter by trigger type").option("--source <routingKey>", "Filter by source routing key").option("--since <ts>", "Only runs since (ISO-8601 or epoch ms)").option("--page <n>", "Page number", (v) => parseInt(v, 10)).option("--json", "Output raw JSON", false).action(async (options) => {
204
214
  const { runsListCommand } = await import("./commands/index.js");
@@ -295,7 +305,7 @@ Environment variables:
295
305
  const success = await drainWorkerCommand({ url: options.url });
296
306
  process.exit(success ? 0 : 1);
297
307
  });
298
- program.command("verify-attestation").argument("[artifact]", "Artifact path to digest-check against the attestation subject (optional)").description("Verify a KiCI provenance attestation bundle offline").option("--bundle <path>", "Path or URL to the attestation bundle JSON").option("--trust-root <url-or-file>", "Trusted issuer URL, or a self-contained { issuer, jwks } file").option("--audience <aud>", "Expected token audience").option("--json", "Output structured JSON result", false).action(async (artifact, options) => {
308
+ program.command("verify-attestation").argument("[artifact]", "Artifact path to digest-check against the attestation subject (optional)").description("Verify a KiCI provenance attestation bundle offline").option("--bundle <path>", "Path or URL to the attestation bundle JSON").option("--trust-root <url-or-file>", "Trusted issuer URL, or a self-contained { issuer, jwks } file (default: hosted KiCI platform)").option("--audience <aud>", "Expected token audience").option("--json", "Output structured JSON result", false).action(async (artifact, options) => {
299
309
  const { verifyAttestationCommand } = await import("./commands/index.js");
300
310
  const success = await verifyAttestationCommand(artifact, options);
301
311
  process.exit(success ? 0 : 1);
@@ -3,8 +3,8 @@ export type { CompileOptions } from './compile.js';
3
3
  export { watchCommand } from './watch.js';
4
4
  export { fixtureCommand } from './fixture.js';
5
5
  export type { FixtureOptions } from './fixture.js';
6
- export { testCommand, testDryRun } from './test.js';
7
- export type { TestOptions, RemoteRunOptions, RemoteRunResult } from './test.js';
6
+ export { previewCommand, previewEvent } from './preview.js';
7
+ export type { PreviewOptions, RemoteRunOptions, RemoteRunResult } from './preview.js';
8
8
  export { runLocalCommand, runRemoteCommand } from './run.js';
9
9
  export { initCommand } from './init.js';
10
10
  export type { InitOptions } from './init.js';
@@ -14,6 +14,8 @@ export { loginCommand } from './login.js';
14
14
  export type { LoginOptions } from './login.js';
15
15
  export { secretsListCommand } from './secrets-list.js';
16
16
  export type { SecretsListOptions } from './secrets-list.js';
17
+ export { patCreateCommand } from './pat.js';
18
+ export type { PatCreateOptions } from './pat.js';
17
19
  export { runsListCommand } from './runs/list.js';
18
20
  export type { RunsListOptions } from './runs/list.js';
19
21
  export { runsShowCommand } from './runs/show.js';
@@ -8,11 +8,12 @@ import { endpointsCommand } from "./endpoints.js";
8
8
  import { fixtureCommand } from "./fixture.js";
9
9
  import { hookInstallCommand } from "./hook.js";
10
10
  import { watchCommand } from "./watch.js";
11
- import { testCommand, testDryRun } from "./test.js";
11
+ import { previewCommand, previewEvent } from "./preview.js";
12
12
  import { runLocalCommand, runRemoteCommand } from "./run.js";
13
13
  import { initCommand } from "./init.js";
14
14
  import { loginCommand } from "./login.js";
15
15
  import { secretsListCommand } from "./secrets-list.js";
16
+ import { patCreateCommand } from "./pat.js";
16
17
  import { runsListCommand } from "./runs/list.js";
17
18
  import { runsShowCommand } from "./runs/show.js";
18
19
  import { runsLogsCommand } from "./runs/logs.js";
@@ -25,4 +26,4 @@ import { logoutCommand } from "./logout.js";
25
26
  import { rejectCommand } from "./reject.js";
26
27
  import { workflowsListCommand } from "./workflows.js";
27
28
  import { verifyAttestationCommand } from "./verify-attestation.js";
28
- export { approveCommand, compileCommand, diagnosticsCommand, docsCommand, docsLlmCommand, drainWorkerCommand, endpointsCommand, fixtureCommand, hookInstallCommand, initCommand, loginCommand, logoutCommand, orchestratorsListCommand, orchestratorsUseCommand, orgCurrentCommand, orgListCommand, orgUseCommand, rejectCommand, runLocalCommand, runRemoteCommand, runsCancelCommand, runsListCommand, runsLogsCommand, runsRerunCommand, runsShowCommand, secretsListCommand, testCommand, testDryRun, typesCommand, verifyAttestationCommand, watchCommand, workflowsListCommand };
29
+ export { approveCommand, compileCommand, diagnosticsCommand, docsCommand, docsLlmCommand, drainWorkerCommand, endpointsCommand, fixtureCommand, hookInstallCommand, initCommand, loginCommand, logoutCommand, orchestratorsListCommand, orchestratorsUseCommand, orgCurrentCommand, orgListCommand, orgUseCommand, patCreateCommand, previewCommand, previewEvent, rejectCommand, runLocalCommand, runRemoteCommand, runsCancelCommand, runsListCommand, runsLogsCommand, runsRerunCommand, runsShowCommand, secretsListCommand, typesCommand, verifyAttestationCommand, watchCommand, workflowsListCommand };
@@ -117,10 +117,10 @@ async function initCommand(options = {}) {
117
117
  logger.info(pc.gray(" 1. Edit workflows in .kici/workflows/"));
118
118
  if (options.mjs || options.skipInstall) {
119
119
  logger.info(pc.gray(" 2. Run your package manager install in .kici/ to generate a lockfile"));
120
- logger.info(pc.gray(" 3. Test locally: kici test push"));
120
+ logger.info(pc.gray(" 3. Preview matching: kici preview push"));
121
121
  logger.info(pc.gray(" 4. Commit .kici/ to your repository\n"));
122
122
  } else {
123
- logger.info(pc.gray(" 2. Test locally: kici test push"));
123
+ logger.info(pc.gray(" 2. Preview matching: kici preview push"));
124
124
  logger.info(pc.gray(" 3. Commit .kici/ to your repository\n"));
125
125
  }
126
126
  return true;
@@ -0,0 +1,27 @@
1
+ export interface PatCreateOptions {
2
+ /** Token name shown in the dashboard PAT list. Defaults to the agent label. */
3
+ name?: string;
4
+ /** Mint an agent-kind PAT (the only credential the developer MCP server accepts). */
5
+ agent?: boolean;
6
+ /**
7
+ * Agent label (required with `--agent`) — the human-set name recorded on every
8
+ * audit row the agent produces. Also used as the token name when `--name` is
9
+ * omitted.
10
+ */
11
+ label?: string;
12
+ /** Custom expiry in days (server default: 120). */
13
+ expiresInDays?: number;
14
+ /** Injected fetch for tests. Defaults to the global fetch. */
15
+ fetchImpl?: typeof fetch;
16
+ }
17
+ /**
18
+ * Mint a personal access token under the logged-in user's identity.
19
+ *
20
+ * `kici pat create --agent --name <label>` mints an agent-kind PAT: it inherits
21
+ * the user's permissions (provenance only, no authority change), carries its
22
+ * label into every audit row, and is the credential a coding agent points the
23
+ * KiCI developer MCP server at. The token is printed once — there is no way to
24
+ * retrieve it later.
25
+ */
26
+ export declare function patCreateCommand(options?: PatCreateOptions): Promise<boolean>;
27
+ //# sourceMappingURL=pat.d.ts.map
@@ -0,0 +1,76 @@
1
+ import "../chunk-BTugEXQM.js";
2
+ import { loadGlobalConfig } from "../remote/config.js";
3
+ import pc from "picocolors";
4
+ import { toErrorMessage } from "@kici-dev/core";
5
+ import { PatKind } from "@kici-dev/engine";
6
+ //#region src/commands/pat.ts
7
+ /**
8
+ * Mint a personal access token under the logged-in user's identity.
9
+ *
10
+ * `kici pat create --agent --name <label>` mints an agent-kind PAT: it inherits
11
+ * the user's permissions (provenance only, no authority change), carries its
12
+ * label into every audit row, and is the credential a coding agent points the
13
+ * KiCI developer MCP server at. The token is printed once — there is no way to
14
+ * retrieve it later.
15
+ */
16
+ async function patCreateCommand(options = {}) {
17
+ const doFetch = options.fetchImpl ?? fetch;
18
+ try {
19
+ const config = await loadGlobalConfig();
20
+ const token = config.pat ?? config.token;
21
+ const endpoint = config.platformEndpoint ?? config.endpoint;
22
+ if (!token || !endpoint) {
23
+ console.error(pc.red("Not logged in. Run `kici login` first."));
24
+ return false;
25
+ }
26
+ const kind = options.agent ? PatKind.enum.agent : PatKind.enum.user;
27
+ const label = options.label;
28
+ if (kind === PatKind.enum.agent && !label) {
29
+ console.error(pc.red("An agent PAT requires a label. Pass --name <label>."));
30
+ return false;
31
+ }
32
+ const name = options.name ?? label;
33
+ if (!name) {
34
+ console.error(pc.red("A token name is required. Pass --name <name>."));
35
+ return false;
36
+ }
37
+ const body = {
38
+ name,
39
+ kind
40
+ };
41
+ if (kind === PatKind.enum.agent) body.agentLabel = label;
42
+ if (options.expiresInDays !== void 0) body.expiresInDays = options.expiresInDays;
43
+ const res = await doFetch(`${endpoint.replace(/\/$/, "")}/api/v1/pats`, {
44
+ method: "POST",
45
+ headers: {
46
+ "Content-Type": "application/json",
47
+ Authorization: `Bearer ${token}`
48
+ },
49
+ body: JSON.stringify(body)
50
+ });
51
+ if (!res.ok) {
52
+ let detail;
53
+ try {
54
+ detail = (await res.json()).error;
55
+ } catch {}
56
+ console.error(pc.red(`Failed to create token (${res.status}): ${detail ?? "request failed"}`));
57
+ return false;
58
+ }
59
+ const created = await res.json();
60
+ console.log(pc.bold(kind === PatKind.enum.agent ? "\nAgent PAT created.\n" : "\nPAT created.\n"));
61
+ console.log(`${pc.gray("Name: ")}${created.name}`);
62
+ if (kind === PatKind.enum.agent) console.log(`${pc.gray("Agent: ")}${label}`);
63
+ console.log(`${pc.gray("Expires:")} ${created.expiresAt}`);
64
+ console.log(`\n${pc.gray("Token (shown once — save it now):")}`);
65
+ console.log(pc.cyan(created.token));
66
+ if (kind === PatKind.enum.agent) console.log(pc.gray("\nPoint your coding agent at the KiCI MCP server with this token as the Bearer credential."));
67
+ return true;
68
+ } catch (err) {
69
+ console.error(pc.red(`Failed to create token: ${toErrorMessage(err)}`));
70
+ return false;
71
+ }
72
+ }
73
+ //#endregion
74
+ export { patCreateCommand };
75
+
76
+ //# sourceMappingURL=pat.js.map
@@ -0,0 +1,88 @@
1
+ import { type PayloadOptions } from '../test-runner/payload-builder.js';
2
+ import { type CheckMode } from '@kici-dev/engine';
3
+ /** Options for the kici preview command (dry-run trigger preview) */
4
+ export interface PreviewOptions extends PayloadOptions {
5
+ /** Filter to specific workflow */
6
+ workflow?: string;
7
+ /** Filter to specific job */
8
+ job?: string;
9
+ /** Enable debug output */
10
+ debug?: boolean;
11
+ /** Path to .kici directory (defaults to .kici) */
12
+ kiciDir?: string;
13
+ /** Flat secret overrides: KEY=VALUE */
14
+ secret?: string[];
15
+ /** Context secret overrides: contextName.KEY=VALUE */
16
+ context?: string[];
17
+ }
18
+ /** Options for the kici run remote command */
19
+ export interface RemoteRunOptions extends PreviewOptions {
20
+ /** Run all available fixtures */
21
+ all?: boolean;
22
+ /** Interactively pick fixtures to run (multi-select checkbox). */
23
+ pick?: boolean;
24
+ /** Run matching fixtures concurrently */
25
+ parallel?: boolean;
26
+ /** Fire and forget (print runIds, don't stream) */
27
+ wait?: boolean;
28
+ /** Suppress output except final result */
29
+ quiet?: boolean;
30
+ /** Output structured JSON result */
31
+ json?: boolean;
32
+ /** Output JUnit XML result */
33
+ junit?: string;
34
+ /** Override routing key for this run */
35
+ routingKey?: string;
36
+ /** Show recent run history */
37
+ history?: boolean;
38
+ /** --env KEY=VALUE flag values, uploaded as per-run secrets. */
39
+ envFlags?: string[];
40
+ /** Target organization id (overrides config.activeOrgId). */
41
+ org?: string;
42
+ /** Target orchestrator cluster name (overrides the per-org default). */
43
+ orchestrator?: string;
44
+ /**
45
+ * Run mode resolved from --check / --fail-on-drift, threaded onto the dispatch
46
+ * payload so the orchestrator runs the agent step loop in the requested mode.
47
+ * Defaults to `apply`.
48
+ */
49
+ checkMode?: CheckMode;
50
+ /** `--target <selector>` values (repeatable), AND-combined into host narrowing. */
51
+ targets?: string[];
52
+ /** `--target-allow-empty`: a target that zeroes a runsOnAll job skips it instead of failing. */
53
+ targetAllowEmpty?: boolean;
54
+ /**
55
+ * `--approve-all` (alias `--yes`): auto-approve every approval gate this run
56
+ * holds on (run-scoped only — the run id this invocation dispatched). The
57
+ * operator must still be clause-eligible per hold; an ineligible hold blocks.
58
+ */
59
+ approveAll?: boolean;
60
+ /** `--input KEY=VALUE` values (repeatable): typed workflow-dispatch inputs. */
61
+ inputs?: string[];
62
+ }
63
+ /** Result of a single remote fixture run */
64
+ export interface RemoteRunResult {
65
+ fixtureId: string;
66
+ runId: string;
67
+ status: 'accepted' | 'rejected' | 'success' | 'failed' | 'cancelled' | 'error';
68
+ reason?: string;
69
+ observeUrl?: string;
70
+ durationMs?: number;
71
+ jobs?: Array<{
72
+ name: string;
73
+ status: string;
74
+ durationMs?: number;
75
+ }>;
76
+ }
77
+ /**
78
+ * Main preview command entry point.
79
+ *
80
+ * `kici preview <event>` is a dry-run trigger preview only — it executes nothing.
81
+ * If the argument looks like a fixture name (not a known event type), prints a migration message.
82
+ */
83
+ export declare function previewCommand(event: string | undefined, options: PreviewOptions): Promise<boolean>;
84
+ /**
85
+ * Local-only dry-run mode: compile workflows, match triggers, display what would execute.
86
+ */
87
+ export declare function previewEvent(event: string, options: PreviewOptions): Promise<boolean>;
88
+ //# sourceMappingURL=preview.d.ts.map
@@ -9,30 +9,31 @@ import { loadSecretsFile } from "../test-runner/secrets-file.js";
9
9
  import path from "node:path";
10
10
  import pc from "picocolors";
11
11
  import { readFile } from "node:fs/promises";
12
+ import { flattenStepInputs } from "@kici-dev/sdk";
12
13
  import { logger, toErrorMessage } from "@kici-dev/core";
13
14
  import { matchAllWorkflows } from "@kici-dev/engine";
14
15
  import { normalizeRunsOnToMatchers } from "@kici-dev/engine/labels/compile";
15
- //#region src/commands/test.ts
16
+ //#region src/commands/preview.ts
16
17
  /**
17
- * Main test command entry point.
18
+ * Main preview command entry point.
18
19
  *
19
- * `kici test <event>` is now dry-run trigger preview only.
20
+ * `kici preview <event>` is a dry-run trigger preview only — it executes nothing.
20
21
  * If the argument looks like a fixture name (not a known event type), prints a migration message.
21
22
  */
22
- async function testCommand(event, options) {
23
+ async function previewCommand(event, options) {
23
24
  if (options.debug) {
24
25
  process.env.KICI_DEBUG = "true";
25
26
  logger.info(pc.gray("Debug mode enabled"));
26
27
  }
27
28
  try {
28
29
  if (!event) {
29
- logger.info(pc.bold("\nUsage: kici test <event>\n"));
30
+ logger.info(pc.bold("\nUsage: kici preview <event>\n"));
30
31
  logger.info(pc.gray("Preview which workflows and jobs would run for a given event.\n"));
31
32
  logger.info(pc.gray("Examples:"));
32
- logger.info(pc.gray(" kici test push"));
33
- logger.info(pc.gray(" kici test pr:open"));
34
- logger.info(pc.gray(" kici test schedule"));
35
- logger.info(pc.gray(" kici test lifecycle:workflow_complete\n"));
33
+ logger.info(pc.gray(" kici preview push"));
34
+ logger.info(pc.gray(" kici preview pr:open"));
35
+ logger.info(pc.gray(" kici preview schedule"));
36
+ logger.info(pc.gray(" kici preview lifecycle:workflow_complete\n"));
36
37
  logger.info(pc.gray("For remote fixture execution, use: kici run remote [fixture] [options]"));
37
38
  logger.info(pc.gray("For local workflow execution, use: kici run local <event> [options]\n"));
38
39
  return true;
@@ -41,7 +42,7 @@ async function testCommand(event, options) {
41
42
  logger.info(pc.yellow(`\nFixture-based testing has moved to \`kici run remote\`.\nRun \`kici run remote ${event}\` instead.\n`));
42
43
  return false;
43
44
  }
44
- return await testDryRun(event, options);
45
+ return await previewEvent(event, options);
45
46
  } catch (error) {
46
47
  const message = toErrorMessage(error);
47
48
  logger.error(pc.red(`\nError: ${message}\n`));
@@ -64,7 +65,7 @@ function isKnownEventArg(arg) {
64
65
  /**
65
66
  * Local-only dry-run mode: compile workflows, match triggers, display what would execute.
66
67
  */
67
- async function testDryRun(event, options) {
68
+ async function previewEvent(event, options) {
68
69
  try {
69
70
  const kiciDir = resolveKiciDir(options.kiciDir);
70
71
  logger.info(pc.gray(`KiCI directory: ${kiciDir}`));
@@ -144,7 +145,7 @@ function workflowsToLockFormat(workflows) {
144
145
  if ("group" in n) return `__group:${n.group}`;
145
146
  return n.name;
146
147
  }) ?? [],
147
- steps: j.steps.map((s) => {
148
+ steps: flattenStepInputs(j.steps).map((s) => {
148
149
  if (typeof s === "function") return {
149
150
  name: "",
150
151
  hasOutputs: false
@@ -213,6 +214,6 @@ async function loadTestSecrets(kiciDir, secretFlags, contextFlags) {
213
214
  return secrets;
214
215
  }
215
216
  //#endregion
216
- export { testCommand, testDryRun };
217
+ export { previewCommand, previewEvent };
217
218
 
218
- //# sourceMappingURL=test.js.map
219
+ //# sourceMappingURL=preview.js.map
@@ -1,6 +1,6 @@
1
1
  import { type HostTargetSelector, type InputsDescriptorMap } from '@kici-dev/engine';
2
2
  import type { RunLocalOptions } from '../local-executor/types.js';
3
- import type { RemoteRunOptions } from './test.js';
3
+ import type { RemoteRunOptions } from './preview.js';
4
4
  /**
5
5
  * Compile `--target` selector strings into a {@link HostTargetSelector}. Each
6
6
  * string becomes one AND value (its own include set); repeated values
@@ -23,6 +23,16 @@ export declare function lookupDispatchInputsDescriptor(inlineLockFile: string |
23
23
  * authoritative and applies coercion + defaults exactly once.
24
24
  */
25
25
  export declare function buildDispatchInputs(pairs: string[], descriptor: InputsDescriptorMap | undefined): Record<string, string>;
26
+ /**
27
+ * Run `fn` with everything written to `process.stdout` redirected to
28
+ * `process.stderr`, then restore the original writer. Compiling a repo
29
+ * evaluates every user workflow / fixture module, and a `console.log` at a
30
+ * module's top level writes straight to stdout. On a machine-readable
31
+ * (`--json`) run that stdout MUST carry only the final JSON result, so the
32
+ * compile phases run inside this guard — workflow log output belongs on
33
+ * stderr regardless.
34
+ */
35
+ export declare function withStdoutOnStderr<T>(fn: () => Promise<T>): Promise<T>;
26
36
  /**
27
37
  * Run a workflow locally using the local executor.
28
38
  * Thin wrapper that delegates to executeLocal from local-executor.
@@ -3,6 +3,7 @@ import { resolveKiciDir } from "../execution/executor.js";
3
3
  import "../execution/index.js";
4
4
  import { loadGlobalConfig } from "../remote/config.js";
5
5
  import { RunHistory } from "../remote/history.js";
6
+ import { buildLocalRepoIdentity } from "../remote/local-repo-identity.js";
6
7
  import { AccessDeniedError, AmbiguousClusterError, AuthenticationError, ConnectionError, NoClusterError, PlatformRunClient } from "../remote/platform-client.js";
7
8
  import { buildEncryptedSecrets } from "../remote/secret-upload.js";
8
9
  import { createOverlayTarball, getSizeWarning, uploadTarball } from "../remote/uploader.js";
@@ -90,6 +91,24 @@ function buildDispatchInputs(pairs, descriptor) {
90
91
  /** Interval between status/log polls while a run is active. */
91
92
  const POLL_INTERVAL_MS = 750;
92
93
  /**
94
+ * Run `fn` with everything written to `process.stdout` redirected to
95
+ * `process.stderr`, then restore the original writer. Compiling a repo
96
+ * evaluates every user workflow / fixture module, and a `console.log` at a
97
+ * module's top level writes straight to stdout. On a machine-readable
98
+ * (`--json`) run that stdout MUST carry only the final JSON result, so the
99
+ * compile phases run inside this guard — workflow log output belongs on
100
+ * stderr regardless.
101
+ */
102
+ async function withStdoutOnStderr(fn) {
103
+ const realWrite = process.stdout.write.bind(process.stdout);
104
+ process.stdout.write = process.stderr.write.bind(process.stderr);
105
+ try {
106
+ return await fn();
107
+ } finally {
108
+ process.stdout.write = realWrite;
109
+ }
110
+ }
111
+ /**
93
112
  * Recompile `.kici/workflows` → `kici.lock.json` before a remote run, mirroring
94
113
  * `kici run local`. The orchestrator matches triggers and dispatches against the
95
114
  * inline lock, so a stale lock would route an edited or newly-added workflow
@@ -97,12 +116,14 @@ const POLL_INTERVAL_MS = 750;
97
116
  * abort before any upload or dispatch.
98
117
  */
99
118
  async function compileBeforeRemoteRun(options) {
100
- return compileCommand({
119
+ const pureStdout = Boolean(options.json || options.quiet);
120
+ const compile = () => compileCommand({
101
121
  kiciDir: options.kiciDir ?? ".kici",
102
122
  check: false,
103
123
  verbose: options.debug ?? false,
104
- quiet: Boolean(options.json || options.quiet)
124
+ quiet: pureStdout
105
125
  });
126
+ return pureStdout ? withStdoutOnStderr(compile) : compile();
106
127
  }
107
128
  /**
108
129
  * Run a workflow locally using the local executor.
@@ -148,7 +169,8 @@ async function runRemoteCommand(fixture, options) {
148
169
  console.log(history.formatTable(entries));
149
170
  return true;
150
171
  }
151
- const fixtures = await compileFixtures(path.join(kiciDir, "tests"));
172
+ const testsDir = path.join(kiciDir, "tests");
173
+ const fixtures = options.json ? await withStdoutOnStderr(() => compileFixtures(testsDir)) : await compileFixtures(testsDir);
152
174
  let selected;
153
175
  if (options.pick) {
154
176
  if (fixtures.length === 0) return listFixtures(fixtures);
@@ -355,11 +377,12 @@ async function runSingleFixture(fixture, ctx, options, config, history) {
355
377
  const uploaded = await initAndUpload(ctx, overlay, options);
356
378
  const event = buildEventFromFixture(opts);
357
379
  {
358
- const repoName = path.basename(overlay.repoRoot);
380
+ const identity = buildLocalRepoIdentity(overlay.repoRoot);
359
381
  const repo = event.payload.repository;
360
382
  event.payload.repository = {
361
383
  ...repo ?? {},
362
- full_name: `local/${repoName}`
384
+ full_name: identity.repoIdentifier,
385
+ provider: identity.provider
363
386
  };
364
387
  }
365
388
  if (!options.quiet) logger.info(pc.gray("Triggering test run..."));
@@ -589,7 +612,11 @@ async function runDirectWorkflow(workflowName, options) {
589
612
  try {
590
613
  const overlay = await prepareOverlay(options);
591
614
  const uploaded = await initAndUpload(ctx, overlay, options);
592
- const payload = { repository: { full_name: `local/${path.basename(overlay.repoRoot)}` } };
615
+ const identity = buildLocalRepoIdentity(overlay.repoRoot);
616
+ const payload = { repository: {
617
+ full_name: identity.repoIdentifier,
618
+ provider: identity.provider
619
+ } };
593
620
  const encrypted = await buildEncryptedSecrets(overlay.kiciDir, options.envFlags, options.context, uploaded.publicKey);
594
621
  const triggerResult = await ctx.client.trigger(ctx.orgId, ctx.target, {
595
622
  fixtureId,
@@ -644,6 +671,6 @@ function displayRemoteResults(results) {
644
671
  logger.info("");
645
672
  }
646
673
  //#endregion
647
- export { buildDispatchInputs, buildTargetSelector, lookupDispatchInputsDescriptor, runLocalCommand, runRemoteCommand };
674
+ export { buildDispatchInputs, buildTargetSelector, lookupDispatchInputsDescriptor, runLocalCommand, runRemoteCommand, withStdoutOnStderr };
648
675
 
649
676
  //# sourceMappingURL=run.js.map
@@ -1,7 +1,10 @@
1
1
  export interface VerifyAttestationOptions {
2
2
  /** Path or `http(s)` URL to the attestation bundle JSON. Required. */
3
3
  bundle?: string;
4
- /** Trusted issuer URL (online discovery) or a self-contained `{ issuer, jwks }` file. Required. */
4
+ /**
5
+ * Trusted issuer URL (online discovery) or a self-contained `{ issuer, jwks }`
6
+ * file. Optional — defaults to the hosted KiCI Platform's provenance issuer.
7
+ */
5
8
  trustRoot?: string;
6
9
  /** Expected token audience (defaults to the KiCI provenance audience). */
7
10
  audience?: string;
@@ -1,5 +1,6 @@
1
1
  import "../chunk-BTugEXQM.js";
2
2
  import { resolveTrustRoot } from "../provenance-trust-root.js";
3
+ import "../remote/prod-defaults.js";
3
4
  import pc from "picocolors";
4
5
  import { readFile } from "node:fs/promises";
5
6
  import { logger, sha256File, toErrorMessage } from "@kici-dev/core";
@@ -7,13 +8,18 @@ import { KICI_PROVENANCE_AUDIENCE } from "@kici-dev/engine/provenance/bundle";
7
8
  import { verifyKiciBundle } from "@kici-dev/engine/provenance/verify";
8
9
  //#region src/commands/verify-attestation.ts
9
10
  /**
10
- * `kici verify-attestation [artifact] --bundle <path|url> --trust-root <url|file>`
11
+ * `kici verify-attestation [artifact] --bundle <path|url> [--trust-root <url|file>]`
11
12
  *
12
13
  * Offline verification of a KiCI-signed provenance bundle: read the bundle,
13
- * resolve the trusted issuer + JWKS out-of-band (`--trust-root`), optionally
14
- * digest the artifact, and hand everything to the shared browser-safe
15
- * `verifyKiciBundle` core in `@kici-dev/engine`. The engine owns all crypto;
16
- * this command is the thin Node wrapper (fs / fetch / artifact digest / output).
14
+ * resolve the trusted issuer + JWKS out-of-band, optionally digest the artifact,
15
+ * and hand everything to the shared browser-safe `verifyKiciBundle` core in
16
+ * `@kici-dev/engine`. The engine owns all crypto; this command is the thin Node
17
+ * wrapper (fs / fetch / artifact digest / output).
18
+ *
19
+ * `--trust-root` is optional: when omitted it defaults to the hosted KiCI
20
+ * Platform's provenance issuer, so the common case (verifying a bundle attested
21
+ * on the hosted platform) needs no flag. Pass `--trust-root` to verify against a
22
+ * different environment (e.g. staging) or an offline `{ issuer, jwks }` file.
17
23
  *
18
24
  * Returns a boolean (verified) so `cli.ts` can map it to an exit code (0/1).
19
25
  */
@@ -23,12 +29,22 @@ async function verifyAttestationCommand(artifact, options = {}) {
23
29
  logger.error(pc.red("Error: --bundle <path|url> is required"));
24
30
  return false;
25
31
  }
26
- if (!options.trustRoot) {
27
- logger.error(pc.red("Error: --trust-root <url|file> is required"));
28
- return false;
29
- }
32
+ const trustRoot = options.trustRoot ?? "https://api.kici.dev";
33
+ const usingDefault = !options.trustRoot;
34
+ if (usingDefault) logger.info(pc.gray(`Using default trust root ${trustRoot} (pass --trust-root to override)`));
30
35
  const bundle = JSON.parse(await readBundle(options.bundle));
31
- const { issuer, jwks } = await resolveTrustRoot(options.trustRoot);
36
+ let resolved;
37
+ try {
38
+ resolved = await resolveTrustRoot(trustRoot);
39
+ } catch (error) {
40
+ const msg = toErrorMessage(error);
41
+ if (usingDefault && /\b503\b/.test(msg)) {
42
+ logger.error(pc.red(`Error: build provenance is not enabled on the hosted KiCI platform yet (${trustRoot} returned 503). Pass --trust-root to verify against another environment (e.g. staging) or an offline { issuer, jwks } file.`));
43
+ return false;
44
+ }
45
+ throw error;
46
+ }
47
+ const { issuer, jwks } = resolved;
32
48
  const expectedDigest = artifact ? {
33
49
  alg: "sha256",
34
50
  hex: await sha256File(artifact)
@@ -59,6 +59,8 @@ function generateSecretsDts(options) {
59
59
  lines.push(" }");
60
60
  lines.push("}");
61
61
  lines.push("");
62
+ lines.push("export {};");
63
+ lines.push("");
62
64
  return lines.join("\n");
63
65
  }
64
66
  //#endregion
package/dist/index.d.ts CHANGED
@@ -3,8 +3,8 @@ export type { ExecutionResult } from './execution/index.js';
3
3
  export { validateConfig } from './validation/index.js';
4
4
  export type { ValidationResult } from './validation/index.js';
5
5
  export { generateLockFile, serializeLockFile } from './lockfile/index.js';
6
- export { SCHEMA_VERSION, isLockStaticJob } from './types.js';
7
- export type { LockFile, LockWorkflow, LockJob, LockDynamicJobFn, LockJobOrFactory, LockTrigger, LockPrTrigger, LockPushTrigger, LockMatrix, LockRule, LockStep, LockApproval, LockSource, LockBranchPattern, } from './types.js';
6
+ export { SCHEMA_VERSION, isLockStaticJob, isLockParallelStep } from './types.js';
7
+ export type { LockFile, LockWorkflow, LockJob, LockDynamicJobFn, LockJobOrFactory, LockTrigger, LockPrTrigger, LockPushTrigger, LockMatrix, LockRule, LockStep, LockParallelStep, LockStepEntry, LockApproval, LockSource, LockBranchPattern, } from './types.js';
8
8
  export { formatError, compilerError, isCompilerError } from './errors/index.js';
9
9
  export type { SourceLocation, CompilerError } from './errors/index.js';
10
10
  export { CapabilityGapError, formatCapabilityGapError } from './errors/index.js';
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { executeConfig } from "./execution/executor.js";
6
6
  import "./execution/index.js";
7
7
  import { validateConfig } from "./validation/validator.js";
8
8
  import "./validation/index.js";
9
- import { SCHEMA_VERSION, isLockStaticJob } from "./types.js";
9
+ import { SCHEMA_VERSION, isLockParallelStep, isLockStaticJob } from "./types.js";
10
10
  import { generateLockFile, serializeLockFile } from "./lockfile/generator.js";
11
11
  import "./lockfile/index.js";
12
- export { CapabilityGapError, SCHEMA_VERSION, compilerError, executeConfig, formatCapabilityGapError, formatError, generateLockFile, isCompilerError, isLockStaticJob, serializeLockFile, validateConfig };
12
+ export { CapabilityGapError, SCHEMA_VERSION, compilerError, executeConfig, formatCapabilityGapError, formatError, generateLockFile, isCompilerError, isLockParallelStep, isLockStaticJob, serializeLockFile, validateConfig };
@@ -376,7 +376,7 @@ Source: https://docs.kici.dev/architecture/data-flows/
376
376
 
377
377
  This document describes the key data flows through the KiCI architecture: webhook delivery, job execution, developer-initiated remote runs, dependency caching, re-run and cancel, trace ID propagation, internal event routing, and generic webhook ingestion.
378
378
 
379
- > **Lock file schema version:** The lock file uses schema version 21, which adds the `CheckMode` / `CheckStepOutcome` enums for check-mode step execution on top of v20's `LabelMatcher` (exact/regex) selectors for `runsOn`/`runsOnAll`/`excludeLabels`, v19's `maxParallel`/`failFast` fan-out concurrency, v18's `runsOnAll` host fan-out predicate and `onUnreachable` policy, v17's typed init presets (`mise` / `{ mise }`) and `auto` detection, v16's normalized approval config, v15's per-job init config, v14's declarative cache specs, v11's `LockInlineValue` for pure function inline evaluation, v10's simplified negative patterns (! prefix in repos/paths arrays), v9's global workflow repos matching, and v8's runsOn polymorphic type support.
379
+ > **Lock file schema version:** The lock file uses schema version 29. The orchestrator rejects any fetched lock whose `schemaVersion` does not exactly match the engine version it was built against, so a stale lock must be recompiled with `kici compile` and pushed again after any SDK upgrade that bumps the schema.
380
380
 
381
381
  ## Webhook delivery flow
382
382
 
@@ -582,7 +582,7 @@ Dep cache misses alone do **not** trigger a build job. Deps are platform-specifi
582
582
 
583
583
  ### Cross-source / no-contentHash workflows
584
584
 
585
- - **Lock files without `contentHash`** (schema v1) skip the source cache entirely; agents compile from source. Regenerate lock files with `kici compile` to enable caching. The current lock file schema version is 21.
585
+ - **Lock files without `contentHash`** (schema v1) skip the source cache entirely; agents compile from source. Regenerate lock files with `kici compile` to enable caching. The current lock file schema version is 29.
586
586
  - **Cross-source / global-workflow dispatch** (a workflow registered against source A fired by a webhook on source B) bypasses both caches. The registration's lock file entry still carries `contentHash`, but the cross-source path always clone-and-installs — the eval temp dir doesn't ship `@kici-dev/sdk`. The execution agent still verifies `contentHash` against the cloned source for drift detection.
587
587
 
588
588
  ### Build deduplication
@@ -1150,7 +1150,7 @@ The compiler processes the workflow definition:
1150
1150
 
1151
1151
  ### Execution time (local test runner)
1152
1152
 
1153
- When `kici test` runs a workflow:
1153
+ When `kici run local` runs a workflow:
1154
1154
 
1155
1155
  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)
1156
1156
  2. **Map injection:** Fresh `OutputsMap` and `StepRefMap` are created and injected via `setStepOutputsMap()` / `setStepRefMap()` before each job