@kici-dev/compiler 0.5.0 → 0.6.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 (70) hide show
  1. package/dist/cli.js +37 -7
  2. package/dist/commands/approve.d.ts +12 -0
  3. package/dist/commands/approve.js +5 -2
  4. package/dist/commands/compile.js +4 -2
  5. package/dist/commands/doctor.js +2 -2
  6. package/dist/commands/endpoints.js +4 -6
  7. package/dist/commands/held-run-client.d.ts +21 -1
  8. package/dist/commands/held-run-client.js +34 -15
  9. package/dist/commands/hook.js +22 -20
  10. package/dist/commands/index.d.ts +2 -0
  11. package/dist/commands/index.js +2 -1
  12. package/dist/commands/init.d.ts +9 -2
  13. package/dist/commands/init.js +43 -16
  14. package/dist/commands/login.js +1 -1
  15. package/dist/commands/orchestrators.js +3 -2
  16. package/dist/commands/reject.d.ts +12 -0
  17. package/dist/commands/reject.js +5 -2
  18. package/dist/commands/report/collect.d.ts +82 -0
  19. package/dist/commands/report/collect.js +234 -0
  20. package/dist/commands/report/identity.d.ts +48 -0
  21. package/dist/commands/report/identity.js +49 -0
  22. package/dist/commands/report/index.d.ts +63 -0
  23. package/dist/commands/report/index.js +119 -0
  24. package/dist/commands/report/upload.d.ts +38 -0
  25. package/dist/commands/report/upload.js +64 -0
  26. package/dist/commands/run-hold-watch.js +2 -2
  27. package/dist/commands/run.js +6 -3
  28. package/dist/commands/runs/show.js +80 -1
  29. package/dist/commands/types.js +51 -8
  30. package/dist/execution/sdk-alias.js +4 -2
  31. package/dist/fixtures/compiler.js +2 -1
  32. package/dist/format.js +3 -3
  33. package/dist/generators/secrets-dts.d.ts +8 -2
  34. package/dist/generators/secrets-dts.js +3 -2
  35. package/dist/hooks/installer.js +2 -1
  36. package/dist/llm-context/llms-architecture.txt +35 -13
  37. package/dist/llm-context/llms-cli.txt +142 -28
  38. package/dist/llm-context/llms-features-execution.txt +2017 -0
  39. package/dist/llm-context/llms-features.txt +298 -1483
  40. package/dist/llm-context/llms-full.txt +3008 -1698
  41. package/dist/llm-context/llms-getting-started.txt +145 -12
  42. package/dist/llm-context/llms-patterns.txt +176 -1
  43. package/dist/llm-context/llms-providers.txt +11 -27
  44. package/dist/llm-context/llms-sdk-runtime.txt +25 -4
  45. package/dist/llm-context/llms-sdk.txt +31 -1
  46. package/dist/llm-context/llms.txt +22 -14
  47. package/dist/local-plane/paths.d.ts +15 -0
  48. package/dist/local-plane/paths.js +22 -1
  49. package/dist/local-plane/plane-manager.js +2 -2
  50. package/dist/local-plane/port-holder.js +1 -1
  51. package/dist/local-plane/postgres.d.ts +3 -16
  52. package/dist/local-plane/postgres.js +10 -15
  53. package/dist/lockfile/generator.d.ts +12 -0
  54. package/dist/lockfile/generator.js +47 -14
  55. package/dist/postinstall.js +2 -1
  56. package/dist/remote/config.d.ts +2 -15
  57. package/dist/remote/config.js +2 -16
  58. package/dist/remote/dashboard-client.d.ts +39 -0
  59. package/dist/remote/dashboard-client.js +41 -0
  60. package/dist/remote/oauth.js +7 -5
  61. package/dist/remote/uploader.js +2 -2
  62. package/dist/templates/package-json.js +1 -1
  63. package/dist/test-runner/dry-run.js +4 -2
  64. package/dist/test-runner/git-detector.js +2 -1
  65. package/dist/test-runner/job-executor.js +2 -1
  66. package/dist/test-runner/payload-builder.js +11 -17
  67. package/dist/types.d.ts +33 -3
  68. package/dist/validation/validator.js +23 -6
  69. package/package.json +16 -11
  70. package/sbom.spdx.json +953 -901
package/dist/cli.js CHANGED
@@ -4,10 +4,10 @@ import { shouldSuppressBanner } from "./cli-banner.js";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { resolve } from "node:path";
6
6
  import { realpathSync } from "node:fs";
7
- import { Argument, Command, Option } from "commander";
7
+ import { Argument, Command } from "commander";
8
8
  import pc from "picocolors";
9
9
  //#region src/cli.ts
10
- const version = "0.5.0";
10
+ const version = "0.6.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
@@ -139,7 +139,7 @@ function buildProgram() {
139
139
  const success = await previewCommand(event, options);
140
140
  process.exit(success ? 0 : 1);
141
141
  });
142
- 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("--workspace", "Integrate .kici/ into the detected pnpm/npm/yarn workspace so workflows can import sibling packages", false).option("--standalone", "Force a self-contained .kici/ even inside a workspace", 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) => {
142
+ 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("--workspace", "Integrate .kici/ into the detected pnpm/npm/yarn workspace so workflows can import sibling packages", false).option("--standalone", "Force a self-contained .kici/ even inside a workspace", 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").action(async (options) => {
143
143
  const { initCommand } = await import("./commands/index.js");
144
144
  const success = await initCommand({
145
145
  ...options,
@@ -355,7 +355,7 @@ Environment variables:
355
355
  const success = await runsListCommand(options);
356
356
  process.exit(success ? 0 : 1);
357
357
  });
358
- runsCommand.command("show").argument("<run-id>", "Run ID to inspect").description("Show a run summary with its jobs and steps").option("--json", "Output raw JSON", false).action(async (runId, options) => {
358
+ runsCommand.command("show").argument("<run-id>", "Run ID to inspect").description("Show a run summary with its jobs and steps, why a job did not run, and any approval hold").option("--json", "Output raw JSON", false).action(async (runId, options) => {
359
359
  const { runsShowCommand } = await import("./commands/index.js");
360
360
  const success = await runsShowCommand(runId, { json: options.json });
361
361
  process.exit(success ? 0 : 1);
@@ -396,19 +396,23 @@ Environment variables:
396
396
  });
397
397
  process.exit(success ? 0 : 1);
398
398
  });
399
- program.command("approve").argument("<run-id>", "Run ID whose approval gate to approve").description("Approve a held approval gate for a run").option("--job <name>", "Approve the hold for a specific job").option("--step <index>", "Approve a step-scoped hold (requires --job)").action(async (runId, options) => {
399
+ program.command("approve").argument("<run-id>", "Run ID whose approval gate to approve").description("Approve a held approval gate for a run").option("--job <name>", "Approve the hold for a specific job").option("--step <index>", "Approve a step-scoped hold (requires --job)").option("--hold-type <type>", "Approve the hold of this type (reviewer, timer, concurrency, security) — a job can carry two").option("--hold <id>", "Approve one hold by its id, as listed when nothing else separates them").action(async (runId, options) => {
400
400
  const { approveCommand } = await import("./commands/index.js");
401
401
  const success = await approveCommand(runId, {
402
402
  job: options.job,
403
- step: options.step
403
+ step: options.step,
404
+ hold: options.hold,
405
+ holdType: options.holdType
404
406
  });
405
407
  process.exit(success ? 0 : 1);
406
408
  });
407
- program.command("reject").argument("<run-id>", "Run ID whose approval gate to reject").description("Reject a held approval gate for a run").option("--job <name>", "Reject the hold for a specific job").option("--step <index>", "Reject a step-scoped hold (requires --job)").requiredOption("--reason <text>", "Reason for the rejection").action(async (runId, options) => {
409
+ program.command("reject").argument("<run-id>", "Run ID whose approval gate to reject").description("Reject a held approval gate for a run").option("--job <name>", "Reject the hold for a specific job").option("--step <index>", "Reject a step-scoped hold (requires --job)").option("--hold-type <type>", "Reject the hold of this type (reviewer, timer, concurrency, security) — a job can carry two").option("--hold <id>", "Reject one hold by its id, as listed when nothing else separates them").requiredOption("--reason <text>", "Reason for the rejection").action(async (runId, options) => {
408
410
  const { rejectCommand } = await import("./commands/index.js");
409
411
  const success = await rejectCommand(runId, {
410
412
  job: options.job,
411
413
  step: options.step,
414
+ hold: options.hold,
415
+ holdType: options.holdType,
412
416
  reason: options.reason
413
417
  });
414
418
  process.exit(success ? 0 : 1);
@@ -440,6 +444,32 @@ Environment variables:
440
444
  });
441
445
  process.exit(success ? 0 : 1);
442
446
  });
447
+ const reportCmd = program.command("report").description("Gather a redacted diagnostic bundle to share when reporting an issue");
448
+ reportCmd.enablePositionalOptions();
449
+ reportCmd.option("--run <id>", "Scope the bundle to a failing run").option("-o, --output <path>", "Where to write the bundle ZIP").option("--metadata <key=value>", "Attach metadata (repeatable)", (v, p) => [...p, v], []).option("--no-redact", "Do NOT redact secrets (prints a loud warning)").option("--upload", "Upload the bundle privately to KiCI and print a reference id").option("--message <text>", "Describe the problem (sent with --upload)").option("--email <address>", "Contact address for follow-up (sent with --upload)").option("--kici-dir <path>", "Path to the .kici directory", ".kici").action(async (options) => {
450
+ const { reportCommand } = await import("./commands/index.js");
451
+ const ok = await reportCommand({
452
+ output: options.output,
453
+ run: options.run,
454
+ metadata: options.metadata,
455
+ redact: options.redact !== false,
456
+ upload: options.upload,
457
+ message: options.message,
458
+ email: options.email,
459
+ kiciDir: options.kiciDir
460
+ });
461
+ process.exit(ok ? 0 : 1);
462
+ });
463
+ reportCmd.command("list").description("List the issue reports you have uploaded").option("--json", "Output raw JSON", false).action(async (options) => {
464
+ const { reportListCommand } = await import("./commands/index.js");
465
+ const ok = await reportListCommand({ json: options.json });
466
+ process.exit(ok ? 0 : 1);
467
+ });
468
+ reportCmd.command("withdraw").argument("<ref>", "Reference id of the report to withdraw").description("Withdraw an uploaded report and delete its bundle").action(async (ref) => {
469
+ const { reportWithdrawCommand } = await import("./commands/index.js");
470
+ const ok = await reportWithdrawCommand({ ref });
471
+ process.exit(ok ? 0 : 1);
472
+ });
443
473
  program.command("workflows").description("Manage workflow registrations").command("list").description("List permanently registered workflows").option("--json", "Output as JSON", false).option("--stale <duration>", "Filter stale registrations (e.g., 30d, 7d)").option("--trigger-type <type>", "Filter by trigger type").option("--repo <repo>", "Filter by repository").action(async (options) => {
444
474
  const { workflowsListCommand } = await import("./commands/index.js");
445
475
  const success = await workflowsListCommand({
@@ -11,6 +11,18 @@ export interface ApproveOptions {
11
11
  job?: string;
12
12
  /** Match a step-scoped hold by its step index. */
13
13
  step?: string;
14
+ /**
15
+ * Match one hold by its own id. The escape hatch for an ambiguity nothing
16
+ * else resolves; the error listing prints the ids when it needs to.
17
+ */
18
+ hold?: string;
19
+ /**
20
+ * Narrow to holds of one type (`reviewer` / `timer` / `concurrency` /
21
+ * `security`). A job carrying an SDK `requireApproval` AND a security-typed
22
+ * context gate has two pending holds under one job name, and this is what
23
+ * separates them.
24
+ */
25
+ holdType?: string;
14
26
  }
15
27
  /**
16
28
  * Approve a held approval gate.
@@ -22,9 +22,12 @@ async function approveCommand(runId, options = {}) {
22
22
  try {
23
23
  const ctx = await resolveHeldRunContext();
24
24
  if (!ctx) return false;
25
- const resolution = resolveHeldRunId(await listHeldRunsForRun(ctx, runId), {
25
+ const holds = await listHeldRunsForRun(ctx, runId);
26
+ const resolution = resolveHeldRunId(holds, {
26
27
  job: options.job,
27
- step: options.step
28
+ step: options.step,
29
+ holdId: options.hold,
30
+ holdType: options.holdType
28
31
  });
29
32
  if (!resolution.ok) {
30
33
  logger.error(pc.red(resolution.error));
@@ -60,7 +60,8 @@ async function compileCommand(options) {
60
60
  if (existsSync(path.join(absoluteKiciDir, "package.json"))) {
61
61
  const existingHash = await readExistingLockfileHash(lockPath);
62
62
  if (existingHash) {
63
- const currentHash = computeLockfileHash(detectGitRoot());
63
+ const gitRoot = detectGitRoot();
64
+ const currentHash = computeLockfileHash(gitRoot);
64
65
  if (currentHash && currentHash !== existingHash) {
65
66
  logger.info(pc.yellow("Dependencies changed") + " — reinstalling before compile...");
66
67
  await reinstallDeps(absoluteKiciDir);
@@ -75,7 +76,8 @@ async function compileCommand(options) {
75
76
  return false;
76
77
  }
77
78
  if (options.verbose) logger.debug(pc.dim("Validation passed"));
78
- const lockJson = serializeLockFile(generateLockFile(workflowsWithSource));
79
+ const lockFile = generateLockFile(workflowsWithSource);
80
+ const lockJson = serializeLockFile(lockFile);
79
81
  const windowWarning = schemaWindowWarning(BREAKING_FLOOR, SCHEMA_VERSION);
80
82
  if (windowWarning) logger.warn(pc.yellow(windowWarning));
81
83
  if (!options.check) {
@@ -20,7 +20,7 @@ import { promisify } from "node:util";
20
20
  * Platform API is required.
21
21
  */
22
22
  const execFileAsync = promisify(execFile);
23
- const SEVEN_DAYS_MS = 10080 * 60 * 1e3;
23
+ const SEVEN_DAYS_MS = 6048e5;
24
24
  /** Is a stored token + endpoint present (regardless of expiry)? */
25
25
  function hasCredentials(config) {
26
26
  return Boolean((config.pat ?? config.token) && (config.platformEndpoint ?? config.endpoint));
@@ -47,7 +47,7 @@ function checkLogin(config, now) {
47
47
  if (expiresAt - now < SEVEN_DAYS_MS) return {
48
48
  name,
49
49
  status: "warn",
50
- message: `Access token expires in ${Math.ceil((expiresAt - now) / (1440 * 60 * 1e3))} day(s).`,
50
+ message: `Access token expires in ${Math.ceil((expiresAt - now) / 864e5)} day(s).`,
51
51
  nextCommand: "kici login"
52
52
  };
53
53
  }
@@ -119,12 +119,10 @@ function categorizeTrigger(workflowName, trigger, groups) {
119
119
  eventName: `workflows_failed_batch${trigger.name ? `:${trigger.name}` : ""}`
120
120
  });
121
121
  break;
122
- case "job_complete":
123
- groups.kiciEvent.push({
124
- workflowName,
125
- eventName: `job_complete${trigger.workflow ? `:${trigger.workflow}` : ""}${trigger.job ? `.${trigger.job}` : ""}`
126
- });
127
- break;
122
+ case "job_complete": groups.kiciEvent.push({
123
+ workflowName,
124
+ eventName: `job_complete${trigger.workflow ? `:${trigger.workflow}` : ""}${trigger.job ? `.${trigger.job}` : ""}`
125
+ });
128
126
  }
129
127
  }
130
128
  /**
@@ -6,6 +6,19 @@
6
6
  * API.
7
7
  */
8
8
  import type { HeldRunSummary } from '@kici-dev/engine';
9
+ /**
10
+ * A failed held-runs request, carrying the HTTP status so a caller can tell a
11
+ * caller who will NEVER have the data (401 / 403 — no permission on the
12
+ * held-runs surface) from a transport or server failure that may succeed next
13
+ * time. `kici runs show` reads holds as optional detail, and reminding such a
14
+ * caller on every single invocation is noise, not information.
15
+ */
16
+ export declare class HeldRunRequestError extends Error {
17
+ readonly status: number;
18
+ constructor(message: string, status: number);
19
+ /** True when the caller lacks permission — retrying changes nothing. */
20
+ get isPermissionDenied(): boolean;
21
+ }
9
22
  /** Resolved auth context for a held-run command. */
10
23
  export interface HeldRunContext {
11
24
  endpoint: string;
@@ -15,8 +28,15 @@ export interface HeldRunContext {
15
28
  /**
16
29
  * Resolve the auth context, printing a clear error and returning null when the
17
30
  * CLI is not authenticated / no active org is set.
31
+ *
32
+ * `quiet` suppresses the error output for a caller whose own command does not
33
+ * depend on the held-run surface — `kici runs show` reads holds as extra
34
+ * detail on a run it can already display, so an unresolvable context must not
35
+ * make it print an authentication error for a command that worked.
18
36
  */
19
- export declare function resolveHeldRunContext(): Promise<HeldRunContext | null>;
37
+ export declare function resolveHeldRunContext(options?: {
38
+ quiet?: boolean;
39
+ }): Promise<HeldRunContext | null>;
20
40
  /** List the held runs for a single run id. */
21
41
  export declare function listHeldRunsForRun(ctx: HeldRunContext, runId: string): Promise<HeldRunSummary[]>;
22
42
  /**
@@ -11,25 +11,44 @@ import { logger } from "@kici-dev/core";
11
11
  * API.
12
12
  */
13
13
  /**
14
+ * A failed held-runs request, carrying the HTTP status so a caller can tell a
15
+ * caller who will NEVER have the data (401 / 403 — no permission on the
16
+ * held-runs surface) from a transport or server failure that may succeed next
17
+ * time. `kici runs show` reads holds as optional detail, and reminding such a
18
+ * caller on every single invocation is noise, not information.
19
+ */
20
+ var HeldRunRequestError = class extends Error {
21
+ status;
22
+ constructor(message, status) {
23
+ super(message);
24
+ this.status = status;
25
+ this.name = "HeldRunRequestError";
26
+ }
27
+ /** True when the caller lacks permission — retrying changes nothing. */
28
+ get isPermissionDenied() {
29
+ return this.status === 401 || this.status === 403;
30
+ }
31
+ };
32
+ /**
14
33
  * Resolve the auth context, printing a clear error and returning null when the
15
34
  * CLI is not authenticated / no active org is set.
35
+ *
36
+ * `quiet` suppresses the error output for a caller whose own command does not
37
+ * depend on the held-run surface — `kici runs show` reads holds as extra
38
+ * detail on a run it can already display, so an unresolvable context must not
39
+ * make it print an authentication error for a command that worked.
16
40
  */
17
- async function resolveHeldRunContext() {
41
+ async function resolveHeldRunContext(options = {}) {
18
42
  const config = await loadGlobalConfig();
19
- const token = config.pat ?? config.token;
20
- if (!token) {
21
- logger.error(pc.red("Not authenticated. Run `kici login` to get started."));
43
+ const fail = (message) => {
44
+ if (!options.quiet) logger.error(pc.red(message));
22
45
  return null;
23
- }
46
+ };
47
+ const token = config.pat ?? config.token;
48
+ if (!token) return fail("Not authenticated. Run `kici login` to get started.");
24
49
  const endpoint = config.platformEndpoint ?? config.endpoint;
25
- if (!endpoint) {
26
- logger.error(pc.red("No endpoint configured. Run `kici login` to configure."));
27
- return null;
28
- }
29
- if (!config.activeOrgId) {
30
- logger.error(pc.red("No active organization. Run `kici org use <name>` to set one."));
31
- return null;
32
- }
50
+ if (!endpoint) return fail("No endpoint configured. Run `kici login` to configure.");
51
+ if (!config.activeOrgId) return fail("No active organization. Run `kici org use <name>` to set one.");
33
52
  return {
34
53
  endpoint,
35
54
  token,
@@ -49,7 +68,7 @@ async function listHeldRunsForRun(ctx, runId) {
49
68
  method: "GET",
50
69
  headers: authHeaders(ctx.token)
51
70
  });
52
- if (!response.ok) throw new Error(await describeError(response));
71
+ if (!response.ok) throw new HeldRunRequestError(await describeError(response), response.status);
53
72
  return (await response.json()).heldRuns ?? [];
54
73
  }
55
74
  /**
@@ -99,6 +118,6 @@ async function describeError(response) {
99
118
  }
100
119
  }
101
120
  //#endregion
102
- export { listHeldRunsForRun, postApprove, postReject, resolveHeldRunContext };
121
+ export { HeldRunRequestError, listHeldRunsForRun, postApprove, postReject, resolveHeldRunContext };
103
122
 
104
123
  //# sourceMappingURL=held-run-client.js.map
@@ -31,27 +31,28 @@ async function hookInstallCommand(options = {}) {
31
31
  if (options.git) selectedTool = "git";
32
32
  else {
33
33
  const tools = await detectHookTools();
34
- if (tools.length === 0) if (process.stdout.isTTY && !isCiEnvironment()) {
35
- const choice = await select({
36
- message: "No pre-commit tool detected. How would you like to install the hook?",
37
- choices: [{
38
- name: "Raw git hook (.git/hooks/pre-commit)",
39
- value: "git"
40
- }, {
41
- name: "Skip installation",
42
- value: "skip"
43
- }]
44
- });
45
- if (choice === "skip") {
46
- logger.info(pc.yellow("Hook installation skipped."));
34
+ if (tools.length === 0) {
35
+ if (process.stdout.isTTY && !isCiEnvironment()) {
36
+ const choice = await select({
37
+ message: "No pre-commit tool detected. How would you like to install the hook?",
38
+ choices: [{
39
+ name: "Raw git hook (.git/hooks/pre-commit)",
40
+ value: "git"
41
+ }, {
42
+ name: "Skip installation",
43
+ value: "skip"
44
+ }]
45
+ });
46
+ if (choice === "skip") {
47
+ logger.info(pc.yellow("Hook installation skipped."));
48
+ return true;
49
+ }
50
+ selectedTool = choice;
51
+ } else {
52
+ logger.info(pc.yellow("No pre-commit tool detected. Use --git to install raw git hook."));
47
53
  return true;
48
54
  }
49
- selectedTool = choice;
50
- } else {
51
- logger.info(pc.yellow("No pre-commit tool detected. Use --git to install raw git hook."));
52
- return true;
53
- }
54
- else if (tools.length === 1) {
55
+ } else if (tools.length === 1) {
55
56
  selectedTool = tools[0].name;
56
57
  logger.info(pc.gray(`Detected ${selectedTool}, installing hook...`));
57
58
  } else if (process.stdout.isTTY && !isCiEnvironment()) selectedTool = await select({
@@ -87,7 +88,8 @@ async function hookInstallCommand(options = {}) {
87
88
  */
88
89
  async function detectVerdaccioNpmrc() {
89
90
  try {
90
- return (await readFile(path.resolve(".kici", ".npmrc"), "utf-8")).includes("verdaccio");
91
+ const npmrcPath = path.resolve(".kici", ".npmrc");
92
+ return (await readFile(npmrcPath, "utf-8")).includes("verdaccio");
91
93
  } catch {
92
94
  return false;
93
95
  }
@@ -40,6 +40,8 @@ export { diagnosticsCommand } from './diagnostics.js';
40
40
  export type { DiagnosticsOptions } from './diagnostics.js';
41
41
  export { doctorCommand } from './doctor.js';
42
42
  export type { DoctorOptions } from './doctor.js';
43
+ export { reportCommand, reportListCommand, reportWithdrawCommand } from './report/index.js';
44
+ export type { ReportOptions } from './report/index.js';
43
45
  export { orgListCommand, orgUseCommand, orgCurrentCommand } from './org.js';
44
46
  export { orchestratorsListCommand, orchestratorsUseCommand } from './orchestrators.js';
45
47
  export type { OrchestratorsOptions } from './orchestrators.js';
@@ -24,6 +24,7 @@ import { runsCancelCommand } from "./runs/cancel.js";
24
24
  import { runsArtifactsListCommand } from "./runs/artifacts/list.js";
25
25
  import { runsArtifactsDownloadCommand } from "./runs/artifacts/download.js";
26
26
  import { typesCommand } from "./types.js";
27
+ import { reportCommand, reportListCommand, reportWithdrawCommand } from "./report/index.js";
27
28
  import { orgCurrentCommand, orgListCommand, orgUseCommand } from "./org.js";
28
29
  import { orchestratorsListCommand, orchestratorsUseCommand } from "./orchestrators.js";
29
30
  import { localAttachCommand, localDetachCommand, localDownCommand, localLogsCommand, localStatusCommand, localUpCommand } from "./local.js";
@@ -33,4 +34,4 @@ import { rejectCommand } from "./reject.js";
33
34
  import { workflowsListCommand } from "./workflows.js";
34
35
  import { verifyAttestationCommand } from "./verify-attestation.js";
35
36
  import { notificationsChannelsAddCommand, notificationsChannelsListCommand, notificationsChannelsRemoveCommand, notificationsRosterAddCommand, notificationsRosterListCommand, notificationsRosterRemoveCommand, notificationsSubscriptionsAddCommand, notificationsSubscriptionsListCommand, notificationsSubscriptionsRemoveCommand } from "./notifications.js";
36
- export { approveCommand, compileCommand, diagnosticsCommand, docsCommand, docsLlmCommand, doctorCommand, drainWorkerCommand, endpointsCommand, 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, runRemoteCommand, runRoutedCommand, runsArtifactsDownloadCommand, runsArtifactsListCommand, runsCancelCommand, runsListCommand, runsLogsCommand, runsRerunCommand, runsShowCommand, secretsListCommand, typesCommand, verifyAttestationCommand, watchCommand, workflowsListCommand };
37
+ export { approveCommand, compileCommand, diagnosticsCommand, docsCommand, docsLlmCommand, doctorCommand, drainWorkerCommand, endpointsCommand, 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 };
@@ -19,8 +19,6 @@ export interface InitOptions {
19
19
  packageManager?: string;
20
20
  /** JavaScript-only mode (no TypeScript, no dependencies) */
21
21
  mjs?: boolean;
22
- /** Write .npmrc pointing @kici-dev scope to local Verdaccio */
23
- useVerdaccioLocal?: boolean;
24
22
  /** Private registry URL to scaffold a `registries:` entry for. */
25
23
  privateRegistry?: string;
26
24
  /** Optional npm scope (e.g. `@my-org`) the private registry serves. */
@@ -41,4 +39,13 @@ export interface InitOptions {
41
39
  * @returns true on success, false on error
42
40
  */
43
41
  export declare function initCommand(options?: InitOptions): Promise<boolean>;
42
+ /**
43
+ * Scaffold `.kici/.gitignore` so the generated `types/` declarations stay
44
+ * untracked. Never overwrites an existing file — the customer may have edited
45
+ * it. Deliberately ignores only `types/`, NOT `kici.lock.json` (the
46
+ * orchestrator fetches the lock from the repo, so it genuinely is source).
47
+ *
48
+ * @param kiciDir - The resolved `.kici` directory path.
49
+ */
50
+ export declare function writeKiciGitignore(kiciDir: string): Promise<void>;
44
51
  //# sourceMappingURL=init.d.ts.map
@@ -76,7 +76,8 @@ async function initCommand(options = {}) {
76
76
  logger.info(pc.gray("Writing .kiciignore"));
77
77
  await writeFile(kiciIgnorePath, kiciIgnoreTemplate, "utf-8");
78
78
  }
79
- const devMode = options.useVerdaccioLocal || await detectDevelopmentMode();
79
+ await writeKiciGitignore(kiciDir);
80
+ const devMode = await detectDevelopmentMode();
80
81
  const mode = await resolveScaffoldMode(options);
81
82
  const useVerdaccio = devMode;
82
83
  if (mode.kind === "integrate") {
@@ -204,7 +205,8 @@ async function detectDefaultBranch() {
204
205
  async function detectDevelopmentMode() {
205
206
  if (process.env.KICI_DEV === "true") return true;
206
207
  try {
207
- const content = await readFile(path.resolve(process.cwd(), "package.json"), "utf-8");
208
+ const rootPkgPath = path.resolve(process.cwd(), "package.json");
209
+ const content = await readFile(rootPkgPath, "utf-8");
208
210
  return JSON.parse(content).kici?.development === true;
209
211
  } catch {
210
212
  return false;
@@ -234,7 +236,8 @@ const VERDACCIO_NPMRC = "@kici-dev:registry=http://verdaccio.local:4873\n";
234
236
  async function writeTsConfigAndTypesDir(kiciDir) {
235
237
  logger.info(pc.gray("Writing .kici/tsconfig.json"));
236
238
  await writeFile(path.join(kiciDir, "tsconfig.json"), await generateTsConfig(), "utf-8");
237
- await mkdir(path.join(kiciDir, "types"), { recursive: true });
239
+ const typesDir = path.join(kiciDir, "types");
240
+ await mkdir(typesDir, { recursive: true });
238
241
  logger.info(pc.gray("Created .kici/types/ for generated type declarations"));
239
242
  }
240
243
  /** Run the given package manager's install in `dir`, with pnpm's build-gate flag. */
@@ -363,6 +366,28 @@ async function generateTsConfig() {
363
366
  baseConfig.compilerOptions.paths = typeScriptPaths;
364
367
  return JSON.stringify(baseConfig, null, 2) + "\n";
365
368
  }
369
+ /** The single entry `kici init` scaffolds into `.kici/.gitignore`. */
370
+ const KICI_GITIGNORE_TEMPLATE = `# Generated type declarations (\`kici types\` / authenticated \`kici compile\`).
371
+ # A snapshot of one org's secret keys — a local development aid, not source.
372
+ types/
373
+ `;
374
+ /**
375
+ * Scaffold `.kici/.gitignore` so the generated `types/` declarations stay
376
+ * untracked. Never overwrites an existing file — the customer may have edited
377
+ * it. Deliberately ignores only `types/`, NOT `kici.lock.json` (the
378
+ * orchestrator fetches the lock from the repo, so it genuinely is source).
379
+ *
380
+ * @param kiciDir - The resolved `.kici` directory path.
381
+ */
382
+ async function writeKiciGitignore(kiciDir) {
383
+ const gitignorePath = path.join(kiciDir, ".gitignore");
384
+ if (await checkExists(gitignorePath)) {
385
+ logger.info(pc.gray("Skipping .kici/.gitignore (already exists)"));
386
+ return;
387
+ }
388
+ logger.info(pc.gray("Writing .kici/.gitignore"));
389
+ await writeFile(gitignorePath, KICI_GITIGNORE_TEMPLATE, "utf-8");
390
+ }
366
391
  /**
367
392
  * Update .gitignore with .kici/ entries
368
393
  *
@@ -504,17 +529,18 @@ async function writeAgentsMd(kiciDir) {
504
529
  async function offerHookInstallation(useVerdaccio) {
505
530
  const tools = await detectHookTools();
506
531
  let selectedTool;
507
- if (tools.length === 0) if (!await confirm({
508
- message: "No pre-commit tool found. Install husky for git hooks?",
509
- default: false
510
- })) {
532
+ if (tools.length === 0) {
511
533
  if (!await confirm({
512
- message: "Add kici compile to .git/hooks/pre-commit instead?",
534
+ message: "No pre-commit tool found. Install husky for git hooks?",
513
535
  default: false
514
- })) return;
515
- selectedTool = "git";
516
- } else selectedTool = "husky";
517
- else if (tools.length === 1) {
536
+ })) {
537
+ if (!await confirm({
538
+ message: "Add kici compile to .git/hooks/pre-commit instead?",
539
+ default: false
540
+ })) return;
541
+ selectedTool = "git";
542
+ } else selectedTool = "husky";
543
+ } else if (tools.length === 1) {
518
544
  if (!await confirm({
519
545
  message: `Found ${tools[0].name}. Add kici compile hook?`,
520
546
  default: true
@@ -528,11 +554,12 @@ async function offerHookInstallation(useVerdaccio) {
528
554
  }))
529
555
  });
530
556
  const result = await installHook(selectedTool, { useVerdaccio });
531
- if (result.success) if (result.action === "skipped") logger.info(pc.yellow(`Hook already installed: ${result.message}`));
532
- else logger.info(pc.green(`Hook installed: ${result.message}`));
533
- else logger.warn(pc.yellow(`Warning: ${result.message}`));
557
+ if (result.success) {
558
+ if (result.action === "skipped") logger.info(pc.yellow(`Hook already installed: ${result.message}`));
559
+ else logger.info(pc.green(`Hook installed: ${result.message}`));
560
+ } else logger.warn(pc.yellow(`Warning: ${result.message}`));
534
561
  }
535
562
  //#endregion
536
- export { initCommand };
563
+ export { initCommand, writeKiciGitignore };
537
564
 
538
565
  //# sourceMappingURL=init.js.map
@@ -31,7 +31,7 @@ async function promptInput(prompt) {
31
31
  function checkPatExpiry(expiresAt) {
32
32
  const expiryDate = new Date(expiresAt);
33
33
  const now = /* @__PURE__ */ new Date();
34
- const daysUntilExpiry = (expiryDate.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24);
34
+ const daysUntilExpiry = (expiryDate.getTime() - now.getTime()) / 864e5;
35
35
  if (daysUntilExpiry <= 7 && daysUntilExpiry > 0) {
36
36
  console.log(pc.yellow(`\n Warning: your personal access token expires in ${Math.ceil(daysUntilExpiry)} day(s) (${expiryDate.toLocaleDateString()}).`));
37
37
  console.log(pc.yellow(" Run `kici login` again to refresh it."));
@@ -102,10 +102,11 @@ async function orchestratorsUseCommand(clusterName, options) {
102
102
  }
103
103
  return false;
104
104
  }
105
- await mergeGlobalConfig({ defaultClusters: {
105
+ const defaultClusters = {
106
106
  ...ctx.config.defaultClusters ?? {},
107
107
  [ctx.orgId]: clusterName
108
- } });
108
+ };
109
+ await mergeGlobalConfig({ defaultClusters });
109
110
  console.log(pc.green(`Default orchestrator for ${ctx.orgId} set to: ${clusterName}`));
110
111
  return true;
111
112
  }
@@ -11,6 +11,18 @@ export interface RejectOptions {
11
11
  job?: string;
12
12
  /** Match a step-scoped hold by its step index. */
13
13
  step?: string;
14
+ /**
15
+ * Match one hold by its own id. The escape hatch for an ambiguity nothing
16
+ * else resolves; the error listing prints the ids when it needs to.
17
+ */
18
+ hold?: string;
19
+ /**
20
+ * Narrow to holds of one type (`reviewer` / `timer` / `concurrency` /
21
+ * `security`). A job carrying an SDK `requireApproval` AND a security-typed
22
+ * context gate has two pending holds under one job name, and this is what
23
+ * separates them.
24
+ */
25
+ holdType?: string;
14
26
  /** Required rejection reason. */
15
27
  reason?: string;
16
28
  }
@@ -26,9 +26,12 @@ async function rejectCommand(runId, options = {}) {
26
26
  }
27
27
  const ctx = await resolveHeldRunContext();
28
28
  if (!ctx) return false;
29
- const resolution = resolveHeldRunId(await listHeldRunsForRun(ctx, runId), {
29
+ const holds = await listHeldRunsForRun(ctx, runId);
30
+ const resolution = resolveHeldRunId(holds, {
30
31
  job: options.job,
31
- step: options.step
32
+ step: options.step,
33
+ holdId: options.hold,
34
+ holdType: options.holdType
32
35
  });
33
36
  if (!resolution.ok) {
34
37
  logger.error(pc.red(resolution.error));