karajan-code 4.11.0 → 4.12.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.
package/README.md CHANGED
@@ -24,12 +24,15 @@
24
24
 
25
25
  Your AI agent (Claude Code, Codex, Gemini CLI, Cursor…) writes the code. **Karajan governs how it happens**: it installs a method your agent follows on every task, and enforces it with git gates that make a false green structurally impossible.
26
26
 
27
- - **RAG before assuming** — `kj rag query` answers what your codebase does; no agent guesses. Works out of the box: local Ollama, or the built-in ONNX embedder when nothing can be installed; cloud embedders require an explicit sensitivity declaration and PII-redact every chunk.
27
+ - **RAG before assuming** — `kj rag query` answers what your codebase does; no agent guesses. The install wires it as a native MCP tool (`kj_rag_query`) so querying the index is the agent's cheapest path. Works out of the box: local Ollama, or the built-in ONNX embedder when nothing can be installed; cloud embedders require an explicit sensitivity declaration and PII-redact every chunk. A distilled engineering canon rides along: `kj rag query --library` serves pattern cards (when it applies, when it does NOT, the canonical citation) so plans name a greenfield alternative instead of following the legacy line by inertia.
28
28
  - **Card first, on YOUR board** — every piece of work is tracked before it starts: kj's HU Board (`kj hu add|move|list`), the Planning Game, or the board the project already uses (Linear, Trello, Jira, GitHub Issues) via your agent's own MCP/tools. Declared, verified at install, never optional — Karajan does not run without a board. ADRs live in git (`kj adr add|list`).
29
29
  - **Tests prove behavior** — the failing test exists first; the suite is never left red.
30
30
  - **Deterministic first, then cross-AI review** — `kj review --staged` runs SonarQube on the changed files before any AI opinion (BLOCKER/CRITICAL reject on the spot), then binds a verdict from a *different* AI to the sha256 of the exact diff — stamped with the workspace it ran from. Without an approved verdict, **the commit does not enter** (pre-commit gate).
31
31
  - **A third AI arbitrates disputes** — `kj solomon` rules when brain and reviewer disagree. Security findings are never overridable — not even by arbitration.
32
32
  - **Branch first, lanes for parallel work** — the base branch only moves through atomic PRs; `kj worktree start|list|done` gives each concurrent task its own isolated lane.
33
+ - **Least privilege for agents** — spawned agent subprocesses receive an env allowlist (their own CLI's auth, never your cloud keys or registry tokens), and `kj check` inventories every MCP the project can reach, flagging what appeared since the last check. Sensitive-surface tasks self-invoke `kj audit --security` — a zero-token pass (prompt-injection over the agent-context files + OSV + Semgrep + Sonar) — and remediate before review.
34
+ - **Nothing personal ships** — every outbound boundary audits before it leaves the machine: the pre-commit rejects a staged diff carrying your denylisted personal data, hardcoded platform tokens (`ghp_`, `sk-`, `AKIA`…) block outright, `verify-pack`-style tarball scans guard the publish, and `kj privacy scan <dir>` audits any build output. Your denylist lives in `~/.karajan/privacy.yml` — the install asks and writes it for you.
35
+ - **Installing IS activating** — `kj env install` performs the enforcement itself (git hooks, verdict gate, tool gate) instead of trusting the agent to run setup steps, and ends by printing the method into the very conversation that installed it. A commit outside the method is rejected, not narrated.
33
36
 
34
37
  This repo runs under its own environment: every commit to karajan-code carries a cross-AI verdict.
35
38
 
package/docs/README.es.md CHANGED
@@ -16,12 +16,15 @@
16
16
 
17
17
  Tu agente de IA (Claude Code, Codex, Gemini CLI, Cursor…) escribe el código. **Karajan gobierna cómo ocurre**: instala un método que tu agente sigue en cada tarea y lo hace cumplir con gates de git que hacen el falso verde estructuralmente imposible.
18
18
 
19
- - **RAG antes de suponer** — `kj rag query` responde qué hace tu código; ningún agente adivina. Funciona de serie: Ollama local, o el embedder ONNX integrado cuando no se puede instalar nada; los embedders cloud exigen declarar la sensibilidad y redactan PII de cada chunk.
19
+ - **RAG antes de suponer** — `kj rag query` responde qué hace tu código; ningún agente adivina. La instalación lo cablea como herramienta MCP nativa (`kj_rag_query`), de modo que consultar el índice sea el camino más barato del agente. Funciona de serie: Ollama local, o el embedder ONNX integrado cuando no se puede instalar nada; los embedders cloud exigen declarar la sensibilidad y redactan PII de cada chunk. Y viaja con un canon de ingeniería destilado: `kj rag query --library` sirve fichas de patrón (cuándo aplica, cuándo NO, la cita canónica) para que los planes nombren una alternativa greenfield en vez de seguir la línea del legacy por inercia.
20
20
  - **Card primero, en TU board** — todo trabajo se registra antes de empezar: el HU Board de kj (`kj hu add|move|list`), el Planning Game, o el board que el proyecto ya use (Linear, Trello, Jira, GitHub Issues) vía los MCP/tools de tu agente. Declarado, verificado en la instalación, jamás opcional — Karajan no funciona sin board. Los ADRs viven en git (`kj adr add|list`).
21
21
  - **Los tests prueban el comportamiento** — el test que falla existe primero; la suite nunca se queda en rojo.
22
22
  - **Determinista primero, luego revisión IA-cruzada** — `kj review --staged` pasa SonarQube sobre los ficheros cambiados antes de cualquier opinión de IA (BLOCKER/CRITICAL rechazan en el acto), y después liga el veredicto de una IA *distinta* al sha256 del diff exacto — estampado con el workspace desde el que corrió. Sin veredicto aprobado, **el commit no entra** (gate pre-commit).
23
23
  - **Una tercera IA arbitra las disputas** — `kj solomon` decide cuando brain y reviewer discrepan. Los hallazgos de seguridad no los anula nadie — ni siquiera el arbitraje.
24
24
  - **Rama primero, carriles para el paralelo** — la rama base solo se mueve por PRs atómicas; `kj worktree start|list|done` da a cada tarea concurrente su carril aislado.
25
+ - **Mínimo privilegio para agentes** — los subprocesos de agente reciben un allowlist de entorno (la auth de su propio CLI, jamás tus claves cloud ni tokens de registro), y `kj check` inventaría cada MCP alcanzable del proyecto marcando lo aparecido desde el último check. Las tareas con superficie sensible se auto-invocan `kj audit --security` — pasada de cero tokens (prompt-injection sobre los ficheros de contexto del agente + OSV + Semgrep + Sonar) — y remedian antes del review.
26
+ - **Nada personal se publica** — cada boundary de salida se audita antes de dejar la máquina: el pre-commit rechaza un diff con tus datos vetados, los tokens de plataforma hardcodeados (`ghp_`, `sk-`, `AKIA`…) bloquean directamente, el scan del tarball guarda el publish, y `kj privacy scan <dir>` audita cualquier build. Tu denylist vive en `~/.karajan/privacy.yml` — la instalación pregunta y la escribe por ti.
27
+ - **Instalar ES activar** — `kj env install` ejecuta él mismo el enforcement (hooks de git, gate de veredicto, tool gate) en vez de confiar en que el agente corra pasos de setup, y termina imprimiendo el método en la propia conversación que instaló. Un commit fuera del método se rechaza, no se narra.
25
28
 
26
29
  Este repo corre bajo su propio entorno: cada commit de karajan-code lleva un veredicto de IA cruzada.
27
30
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "4.11.0",
3
+ "version": "4.12.0",
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",
@@ -0,0 +1,109 @@
1
+ /**
2
+ * release-check (KJC-TSK-0712) — the release checklist made verifiable:
3
+ * a note loses salience; a check fails RED with the exact list. Generics
4
+ * fit any karajan project; each project extends via `release_check.items`
5
+ * (file_contains with {version}, or command with exit-0 semantics).
6
+ */
7
+
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { isAbsolute, join } from "node:path";
10
+ import { runCommand } from "../utils/process.js";
11
+ import { loadPrivacyList, scanPaths } from "../privacy/scan.js";
12
+
13
+ const semverCmp = (a, b) => {
14
+ const pa = a.split(".").map(Number), pb = b.split(".").map(Number);
15
+ for (let i = 0; i < 3; i++) if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0);
16
+ return 0;
17
+ };
18
+
19
+ async function genericChecks(projectDir) {
20
+ const checks = [];
21
+ const pkgPath = join(projectDir, "package.json");
22
+ if (!existsSync(pkgPath)) {
23
+ checks.push({ name: "manifest", ok: true, detail: "no package.json — generic version checks skipped (declared items still run)" });
24
+ return { checks, version: null, pkg: null };
25
+ }
26
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
27
+ const version = pkg.version;
28
+ checks.push({ name: "manifest", ok: Boolean(version), detail: version ? `version ${version}` : "package.json has no version" });
29
+
30
+ const clPath = join(projectDir, "CHANGELOG.md");
31
+ if (!existsSync(clPath)) {
32
+ checks.push({ name: "changelog-current", ok: false, detail: "no CHANGELOG.md — the release story must exist before the release" });
33
+ } else {
34
+ const cl = readFileSync(clPath, "utf8");
35
+ const topVersioned = (cl.match(/^## \[(\d+\.\d+\.\d+)\]/m) || [])[1] || null;
36
+ // Content still sitting under [Unreleased] means unpromoted entries —
37
+ // the tarball would ship changes its release notes do not tell.
38
+ const afterUnreleased = cl.split(/^## \[Unreleased\]\s*$/m)[1] ?? "";
39
+ const unreleasedBody = afterUnreleased.split(/^## \[/m)[0] ?? "";
40
+ const pending = /\S/.test(unreleasedBody);
41
+ const ok = topVersioned === version && !pending;
42
+ checks.push({
43
+ name: "changelog-current",
44
+ ok,
45
+ detail: ok
46
+ ? `top section is [${version}], Unreleased empty`
47
+ : pending
48
+ ? `[Unreleased] still carries content — promote it into [${version}] before releasing`
49
+ : `top CHANGELOG section is [${topVersioned ?? "none"}] but the manifest says ${version} — promote Unreleased before releasing`,
50
+ });
51
+ }
52
+
53
+ const tagsOut = await runCommand("git", ["-C", projectDir, "tag", "--list", "v[0-9]*"]);
54
+ const tags = (tagsOut.stdout || "").split("\n").map((t) => t.trim().replace(/^v/, "")).filter((t) => /^\d+\.\d+\.\d+$/.test(t));
55
+ const ahead = version ? tags.filter((t) => semverCmp(t, version) > 0) : [];
56
+ checks.push({
57
+ name: "tags",
58
+ ok: ahead.length === 0,
59
+ detail: ahead.length ? `tag(s) ahead of the manifest exist: v${ahead.join(", v")}` : (tags.includes(version) ? `v${version} already tagged` : `tag v${version} pending (created after publish)`),
60
+ });
61
+ return { checks, version, pkg };
62
+ }
63
+
64
+ async function packPrivacyCheck(projectDir, pkg) {
65
+ // Every non-private named package is publishable (exports/files-based
66
+ // packages carry no main/bin) — they all get the scan.
67
+ if (!pkg || pkg.private === true || !pkg.name || !pkg.version) return null;
68
+ try {
69
+ const out = await runCommand("npm", ["pack", "--dry-run", "--json", "--silent"], { cwd: projectDir });
70
+ const files = (JSON.parse(out.stdout || "[]")[0]?.files || []).map((f) => join(projectDir, f.path));
71
+ const blocks = scanPaths(files, { list: loadPrivacyList() }).filter((f) => f.severity === "block");
72
+ return { name: "pack-privacy", ok: blocks.length === 0, detail: blocks.length ? `${blocks.length} personal-data/secret hit(s) in the publishable files` : `${files.length} publishable file(s) clean` };
73
+ } catch (err) {
74
+ return { name: "pack-privacy", ok: false, detail: `npm pack --dry-run failed: ${err.message}` };
75
+ }
76
+ }
77
+
78
+ async function declaredItems(projectDir, config, version) {
79
+ const items = config?.release_check?.items;
80
+ if (!Array.isArray(items)) return [];
81
+ const checks = [];
82
+ for (const item of items) {
83
+ const name = item?.name || "unnamed item";
84
+ if (item?.file_contains?.path && item.file_contains.pattern != null) {
85
+ const p = isAbsolute(item.file_contains.path) ? item.file_contains.path : join(projectDir, item.file_contains.path);
86
+ const needle = String(item.file_contains.pattern).replaceAll("{version}", version ?? "");
87
+ const ok = existsSync(p) && readFileSync(p, "utf8").includes(needle);
88
+ checks.push({ name, ok, detail: ok ? `"${needle}" found in ${item.file_contains.path}` : `"${needle}" NOT found in ${item.file_contains.path}` });
89
+ } else if (item?.command) {
90
+ try {
91
+ const res = await runCommand("sh", ["-c", String(item.command).replaceAll("{version}", version ?? "")], { cwd: projectDir });
92
+ checks.push({ name, ok: res.exitCode === 0, detail: res.exitCode === 0 ? "command exited 0" : `command exited ${res.exitCode}` });
93
+ } catch (err) {
94
+ checks.push({ name, ok: false, detail: `command failed to run: ${err.message}` });
95
+ }
96
+ } else {
97
+ checks.push({ name, ok: false, detail: "unknown item shape — use file_contains {path, pattern} or command" });
98
+ }
99
+ }
100
+ return checks;
101
+ }
102
+
103
+ export async function runReleaseCheck({ projectDir = process.cwd(), config = {} } = {}) {
104
+ const { checks, version, pkg } = await genericChecks(projectDir);
105
+ const pack = await packPrivacyCheck(projectDir, pkg);
106
+ if (pack) checks.push(pack);
107
+ checks.push(...await declaredItems(projectDir, config, version));
108
+ return { ok: checks.every((c) => c.ok), version, checks };
109
+ }
@@ -30,7 +30,7 @@ export const ADVANCED_GROUPS = [
30
30
  { title: "Pipeline (piezas sueltas)", commands: ["autorun", "code", "review", "solomon", "agent", "scan"] },
31
31
  { title: "Análisis pre-run", commands: ["discover", "triage", "researcher", "architect", "onboard", "brief"] },
32
32
  { title: "Búsqueda / RAG", commands: ["rag", "qmd", "watch"] },
33
- { title: "Calidad / auditoría", commands: ["audit", "check", "mutate", "webperf", "sonar", "privacy"] },
33
+ { title: "Calidad / auditoría", commands: ["audit", "check", "mutate", "webperf", "sonar", "privacy", "release"] },
34
34
  { title: "Sesión / board", commands: ["resume", "report", "board", "hu", "adr", "worktree", "undo", "standby"] },
35
35
  { title: "Infra / setup", commands: ["install-tools", "ollama", "skills", "roles", "agents", "env"] },
36
36
  { title: "Mantenimiento", commands: ["clean", "sync", "telemetry", "report-issue"] },
@@ -20,6 +20,7 @@ import { mutateCommand } from "../commands/mutate.js";
20
20
  import { hardenCommand } from "../commands/harden.js";
21
21
  import { telemetryPreviewCommand, telemetryStatusCommand } from "../commands/telemetry.js";
22
22
  import { envInstallCommand, briefCommand } from "../commands/env.js";
23
+ import { runReleaseCheck } from "../checks/release-check.js";
23
24
  import { agentRunCommand } from "../commands/agent-run.js";
24
25
  import { reportIssueCommand } from "../commands/report-issue.js";
25
26
  import { huCommand } from "../commands/hu.js";
@@ -262,6 +263,23 @@ export function registerMeta(program, { pkgVersion }) {
262
263
  });
263
264
  });
264
265
 
266
+ // KJC-TSK-0712 — memory is the reminder, the check is the guarantee.
267
+ const release = program.command("release").description("Release ceremony helpers");
268
+ release.command("check")
269
+ .description("Verify the release checklist deterministically (manifest vs CHANGELOG vs tags, privacy scan of publishable files, plus the project's release_check.items); exit 1 lists exactly what is missing")
270
+ .option("--json", "Machine-readable result")
271
+ .action(async (flags) => {
272
+ await withConfig(pkgVersion, "release-check", flags, async ({ config }) => {
273
+ const res = await runReleaseCheck({ projectDir: config?.projectDir || process.cwd(), config });
274
+ if (flags.json) process.stdout.write(`${JSON.stringify(res)}\n`);
275
+ else {
276
+ for (const c of res.checks) console.log(` ${c.ok ? "✓" : "✗"} ${c.name}: ${c.detail}`);
277
+ console.log(res.ok ? `release check: ready to release ${res.version ?? ""}`.trim() : "release check: NOT ready — fix the red items first");
278
+ }
279
+ process.exitCode = res.ok ? 0 : 1;
280
+ });
281
+ });
282
+
265
283
  // KJC-TSK-0704 — the outbound privacy boundary: audit before anything ships.
266
284
  const privacy = program.command("privacy").description("Personal-data (PII) auditing of outbound boundaries: staged diffs, build outputs, docs trees");
267
285
  privacy.command("scan [paths...]")