kantban-cli 0.1.45 → 0.1.47

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseTimeout
3
- } from "./chunk-DAFLEMLK.js";
3
+ } from "./chunk-VDCHOIQI.js";
4
4
  import {
5
5
  normalizeEol,
6
6
  shellArgs
@@ -13,20 +13,21 @@ async function runGate(gate, options = {}) {
13
13
  const start = Date.now();
14
14
  return new Promise((resolve) => {
15
15
  const [shell, shellPrefix] = shellArgs();
16
+ let timedOut = false;
17
+ let timer;
16
18
  const child = execFile(shell, [...shellPrefix, gate.run], {
17
- timeout: timeoutMs,
18
19
  cwd: options.cwd,
19
20
  env: { ...process.env, ...options.env },
20
21
  maxBuffer: options.maxBuffer ?? 1024 * 1024
21
22
  // 1MB output cap
22
23
  }, (error, stdout, stderr) => {
24
+ if (timer) clearTimeout(timer);
23
25
  const duration_ms = Date.now() - start;
24
26
  const output = normalizeEol((stdout + stderr).trim());
25
- const timed_out = error?.killed === true;
26
27
  const buffer_exceeded = error?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
27
- if (error) {
28
+ if (error || timedOut) {
28
29
  let annotation = "";
29
- if (timed_out) annotation = `
30
+ if (timedOut) annotation = `
30
31
  [TIMED OUT after ${timeoutMs}ms]`;
31
32
  else if (buffer_exceeded) annotation = `
32
33
  [OUTPUT TRUNCATED \u2014 buffer limit exceeded]`;
@@ -37,8 +38,8 @@ async function runGate(gate, options = {}) {
37
38
  duration_ms,
38
39
  output: output + annotation,
39
40
  stderr: normalizeEol(stderr.trim()),
40
- exit_code: child.exitCode ?? (typeof error.code === "number" ? error.code : 1),
41
- timed_out: timed_out || buffer_exceeded
41
+ exit_code: child.exitCode ?? (typeof error?.code === "number" ? error.code : timedOut ? 124 : 1),
42
+ timed_out: timedOut || buffer_exceeded
42
43
  });
43
44
  return;
44
45
  }
@@ -53,6 +54,21 @@ async function runGate(gate, options = {}) {
53
54
  timed_out: false
54
55
  });
55
56
  });
57
+ timer = setTimeout(() => {
58
+ timedOut = true;
59
+ try {
60
+ child.kill("SIGKILL");
61
+ } catch {
62
+ }
63
+ try {
64
+ child.stdout?.destroy();
65
+ } catch {
66
+ }
67
+ try {
68
+ child.stderr?.destroy();
69
+ } catch {
70
+ }
71
+ }, timeoutMs);
56
72
  });
57
73
  }
58
74
  function formatGateErrors(results) {
@@ -104,4 +120,4 @@ export {
104
120
  formatGateErrors,
105
121
  runGates
106
122
  };
107
- //# sourceMappingURL=chunk-MKKHLFA5.js.map
123
+ //# sourceMappingURL=chunk-O47Q6CNM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/gate-runner.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { parseTimeout } from './gate-config.js';\nimport { shellArgs, normalizeEol } from './platform.js';\nimport type { GateDefinition, GateResult } from '@kantban/types';\n\nexport interface RunOptions {\n timeoutMs?: number;\n totalTimeoutMs?: number;\n cwd?: string;\n env?: Record<string, string>;\n maxBuffer?: number;\n}\n\nexport async function runGate(\n gate: GateDefinition,\n options: RunOptions = {},\n): Promise<GateResult> {\n const timeoutMs = gate.timeout ? parseTimeout(gate.timeout) : (options.timeoutMs ?? 60_000);\n const start = Date.now();\n\n return new Promise<GateResult>((resolve) => {\n // SECURITY: gate.run is an operator-authored shell command from pipeline.gates.yaml.\n // It is executed with full process privileges via `sh -c`. This is intentional —\n // gates are trusted operator code, not user input. If gate config sources are ever\n // extended to accept user-supplied values, sanitization must be added here.\n const [shell, shellPrefix] = shellArgs();\n\n // We deliberately do NOT pass execFile's `timeout` option. Node's built-in\n // timeout sends SIGTERM to the child but keeps the callback pending until\n // stdio pipes close. If the gate forks a grandchild that inherits\n // stdout/stderr (e.g. `npm run dev:pipeline &`, a stray browser, a vite\n // dev server), those pipes never close and the callback never fires. We\n // manage the timeout ourselves and force-destroy the pipes on expiration\n // so the promise always resolves — which is what keeps the gate-proxy\n // MCP message queue from deadlocking behind a hung gate.\n let timedOut = false;\n let timer: NodeJS.Timeout | undefined;\n\n const child = execFile(shell, [...shellPrefix, gate.run], {\n cwd: options.cwd,\n env: { ...process.env, ...options.env },\n maxBuffer: options.maxBuffer ?? 1024 * 1024, // 1MB output cap\n }, (error, stdout, stderr) => {\n if (timer) clearTimeout(timer);\n const duration_ms = Date.now() - start;\n const output = normalizeEol((stdout + stderr).trim());\n const buffer_exceeded = (error as NodeJS.ErrnoException)?.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER';\n\n if (error || timedOut) {\n let annotation = '';\n if (timedOut) annotation = `\\n[TIMED OUT after ${timeoutMs}ms]`;\n else if (buffer_exceeded) annotation = `\\n[OUTPUT TRUNCATED — buffer limit exceeded]`;\n\n resolve({\n name: gate.name,\n passed: false,\n required: gate.required ?? true,\n duration_ms,\n output: output + annotation,\n stderr: normalizeEol(stderr.trim()),\n exit_code:\n child.exitCode\n ?? (typeof error?.code === 'number' ? error.code : timedOut ? 124 : 1),\n timed_out: timedOut || buffer_exceeded,\n });\n return;\n }\n\n resolve({\n name: gate.name,\n passed: true,\n required: gate.required ?? true,\n duration_ms,\n output,\n stderr: normalizeEol(stderr.trim()),\n exit_code: 0,\n timed_out: false,\n });\n });\n\n timer = setTimeout(() => {\n timedOut = true;\n try { child.kill('SIGKILL'); } catch { /* already exited */ }\n try { child.stdout?.destroy(); } catch { /* already destroyed */ }\n try { child.stderr?.destroy(); } catch { /* already destroyed */ }\n }, timeoutMs);\n });\n}\n\n/** Format gate failures into a structured error string for agent/CLI display. */\nexport function formatGateErrors(results: GateResult[]): string {\n const failures = results.filter((r) => !r.passed);\n if (failures.length === 0) return 'All gates passed.';\n\n return failures\n .map((r) => {\n const lines = [`Gate \"${r.name}\" (${r.required ? 'required' : 'advisory'}): FAILED`];\n lines.push(` Exit code: ${r.exit_code}`);\n if (r.timed_out) lines.push(' Timed out: yes');\n if (r.output) {\n const stderr = normalizeEol(r.output).split('\\n').slice(-20).join('\\n');\n lines.push(` Output:\\n ${stderr.replace(/\\n/g, '\\n ')}`);\n }\n return lines.join('\\n');\n })\n .join('\\n\\n');\n}\n\nexport async function runGates(\n gates: GateDefinition[],\n options: RunOptions = {},\n): Promise<GateResult[]> {\n const results: GateResult[] = [];\n const totalStart = Date.now();\n\n for (const gate of gates) {\n // Check total timeout\n if (options.totalTimeoutMs) {\n const elapsed = Date.now() - totalStart;\n if (elapsed >= options.totalTimeoutMs) {\n // Remaining gates get timed-out results\n results.push({\n name: gate.name,\n passed: false,\n required: gate.required ?? true,\n duration_ms: 0,\n output: '[SKIPPED — total timeout exceeded]',\n stderr: '',\n exit_code: -1,\n timed_out: true,\n });\n continue;\n }\n // Reduce per-gate timeout by elapsed time\n const remainingMs = options.totalTimeoutMs - elapsed;\n const gateTimeout = gate.timeout ? parseTimeout(gate.timeout) : (options.timeoutMs ?? 60_000);\n const effectiveTimeout = Math.min(gateTimeout, remainingMs);\n results.push(await runGate(gate, { ...options, timeoutMs: effectiveTimeout }));\n } else {\n results.push(await runGate(gate, options));\n }\n }\n\n return results;\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VALID_BRANCH_RE
3
- } from "./chunk-DAFLEMLK.js";
3
+ } from "./chunk-VDCHOIQI.js";
4
4
  import {
5
5
  crossSpawnOptions,
6
6
  defaultPath,
@@ -1195,4 +1195,4 @@ export {
1195
1195
  cleanupGateProxyConfigs,
1196
1196
  ClaudeProvider
1197
1197
  };
1198
- //# sourceMappingURL=chunk-N4ZHMJD7.js.map
1198
+ //# sourceMappingURL=chunk-UKWQ6VUQ.js.map
@@ -3097,6 +3097,7 @@ var VerdictSchema = z51.object({
3097
3097
  // src/lib/gate-config.ts
3098
3098
  var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
3099
3099
  var VALID_BRANCH_RE = /^[a-zA-Z0-9][a-zA-Z0-9._\-/]*$/;
3100
+ var canonicalize = (s) => s.toLowerCase().replace(/[\s_-]+/g, "-");
3100
3101
  function assertNoPrototypePollutionKeys(value, depth = 0) {
3101
3102
  if (depth > 20 || value === null || typeof value !== "object") return;
3102
3103
  for (const key of Object.keys(value)) {
@@ -3112,8 +3113,8 @@ function parseGateConfig(yamlContent) {
3112
3113
  return GateConfigSchema.parse(raw);
3113
3114
  }
3114
3115
  function resolveGatesForColumn(config, columnName) {
3115
- const lowerName = columnName.toLowerCase();
3116
- const overrideKey = config.columns ? Object.keys(config.columns).find((k) => k.toLowerCase() === lowerName) : void 0;
3116
+ const lookupKey = canonicalize(columnName);
3117
+ const overrideKey = config.columns ? Object.keys(config.columns).find((k) => canonicalize(k) === lookupKey) : void 0;
3117
3118
  const override = overrideKey ? config.columns[overrideKey] : void 0;
3118
3119
  if (!override) {
3119
3120
  return [...config.default];
@@ -3128,6 +3129,11 @@ function resolveGatesForColumn(config, columnName) {
3128
3129
  const merged = config.default.filter((g) => !overrideNames.has(g.name));
3129
3130
  return [...merged, ...override.gates];
3130
3131
  }
3132
+ function findUnmatchedColumnKeys(config, boardColumnNames) {
3133
+ if (!config.columns) return [];
3134
+ const canonicalBoard = new Set(boardColumnNames.map(canonicalize));
3135
+ return Object.keys(config.columns).filter((k) => !canonicalBoard.has(canonicalize(k)));
3136
+ }
3131
3137
  var DEFAULT_TIMEOUT_MS = 6e4;
3132
3138
  function parseTimeout(timeout) {
3133
3139
  if (!timeout) return DEFAULT_TIMEOUT_MS;
@@ -3144,6 +3150,7 @@ export {
3144
3150
  VALID_BRANCH_RE,
3145
3151
  parseGateConfig,
3146
3152
  resolveGatesForColumn,
3153
+ findUnmatchedColumnKeys,
3147
3154
  parseTimeout
3148
3155
  };
3149
- //# sourceMappingURL=chunk-DAFLEMLK.js.map
3156
+ //# sourceMappingURL=chunk-VDCHOIQI.js.map