karajan-code 4.1.3 → 4.1.4

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "4.1.3",
3
+ "version": "4.1.4",
4
4
  "description": "Local multi-agent coding orchestrator with TDD, SonarQube, and code review pipeline",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",
@@ -44,6 +44,12 @@ export function osvScannerSource(platform = process.platform, arch = process.arc
44
44
  */
45
45
  export function semgrepFallback(available = {}) {
46
46
  if (available.docker) return { via: "docker", command: "docker pull semgrep/semgrep" };
47
+ // KJC-BUG-0120 (issue #1256): on Debian/Ubuntu-like systems the system
48
+ // Python is externally managed (PEP 668) and often ships without pip at
49
+ // all — `python3 -m pip install --user` is guaranteed to fail there.
50
+ // pipx must come from the distro's own package manager.
51
+ if (available.apt) return { via: "apt", command: "sudo apt update && sudo apt install -y pipx && pipx install semgrep" };
52
+ if (available.dnf) return { via: "dnf", command: "sudo dnf install -y pipx && pipx install semgrep" };
47
53
  return { via: "pipx", command: "python3 -m pip install --user pipx && pipx install semgrep" };
48
54
  }
49
55
 
@@ -4,7 +4,6 @@ import { fileURLToPath } from "node:url";
4
4
  import { printBanner } from "../banner.js";
5
5
  import { ANSI } from "./formatters.js";
6
6
 
7
- // TODO: i18n display messages
8
7
  const DISPLAY_PKG_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../package.json");
9
8
  const DISPLAY_VERSION = JSON.parse(readFileSync(DISPLAY_PKG_PATH, "utf8")).version;
10
9
 
@@ -80,8 +80,13 @@ export async function getInstallHint(tool, available = null) {
80
80
  for (const { manager, command } of candidates) {
81
81
  if (avail[manager]) return { command, manager, manualUrl: MANUAL_URLS[tool] };
82
82
  }
83
- // Nothing matched — fall back to the first candidate's command as a
84
- // suggestion, so the user at least sees a working recipe.
83
+ // Nothing matched — suggest a recipe that actually works on this system.
84
+ // KJC-BUG-0120 (issue #1256): distro-aware fallbacks first (PEP 668 makes
85
+ // the generic pip bootstrap fail on Debian/Ubuntu); these are display-only
86
+ // (manager: null), never auto-run — sudo stays in the user's hands.
87
+ for (const { when, command } of DISTRO_FALLBACKS[tool] || []) {
88
+ if (avail[when]) return { command, manager: null, manualUrl: MANUAL_URLS[tool] };
89
+ }
85
90
  const first = candidates[0];
86
91
  return {
87
92
  command: first ? first.command : null,
@@ -90,6 +95,13 @@ export async function getInstallHint(tool, available = null) {
90
95
  };
91
96
  }
92
97
 
98
+ const DISTRO_FALLBACKS = {
99
+ semgrep: [
100
+ { when: "apt", command: "sudo apt update && sudo apt install -y pipx && pipx install semgrep" },
101
+ { when: "dnf", command: "sudo dnf install -y pipx && pipx install semgrep" },
102
+ ],
103
+ };
104
+
93
105
  const INSTALL_CANDIDATES = {
94
106
  semgrep: [
95
107
  { manager: "pipx", command: "pipx install semgrep" },
@@ -25,7 +25,9 @@ const OS_COMMANDS = {
25
25
  win32: ["winget install Git.Git"],
26
26
  },
27
27
  semgrep: {
28
- linux: ["python3 -m pip install --user semgrep # or: pipx install semgrep"],
28
+ // PEP 668 (#1256): system Python is externally managed on modern
29
+ // distros — pipx comes from the distro package manager, never from pip.
30
+ linux: ["sudo apt update && sudo apt install -y pipx && pipx install semgrep # Debian/Ubuntu", "sudo dnf install -y pipx && pipx install semgrep # Fedora/RHEL"],
29
31
  darwin: ["brew install semgrep"],
30
32
  win32: ["python -m pip install --user semgrep"],
31
33
  },
@@ -75,10 +77,11 @@ export function renderPendingBlock(pending, { platform = process.platform, retry
75
77
  for (const r of pending) {
76
78
  const why = r.error ? `install failed: ${r.error}` : r.reason || "no automatic route on this machine";
77
79
  lines.push(` ▸ ${r.tool} (${why})`);
78
- const commands = r.commands
79
- ?? (r.command ? [r.command] : null)
80
- ?? (r.suggested ? [r.suggested] : null)
81
- ?? (osCommandsFor(r.tool, platform).length > 0 ? osCommandsFor(r.tool, platform) : null)
80
+ const os = osCommandsFor(r.tool, platform);
81
+ const own = r.commands ?? (r.command ? [r.command] : null) ?? (r.suggested ? [r.suggested] : null);
82
+ // A FAILED install must not be re-suggested verbatim (#1256: the pip
83
+ // route that just broke) — the curated per-OS route goes first there.
84
+ const commands = (r.action === "failed" ? (os.length > 0 ? os : own) : (own ?? (os.length > 0 ? os : null)))
82
85
  ?? [`see ${r.manualUrl || "the tool's install docs"}`];
83
86
  for (const c of commands) lines.push(` ${c}`);
84
87
  lines.push("");