karajan-code 4.31.1 → 4.33.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 (39) hide show
  1. package/package.json +1 -1
  2. package/src/agents/aider-agent.js +2 -12
  3. package/src/agents/base-agent.js +9 -20
  4. package/src/agents/claude-agent.js +2 -12
  5. package/src/agents/codex-agent.js +2 -12
  6. package/src/agents/dead-models.js +74 -0
  7. package/src/agents/gemini-agent.js +2 -12
  8. package/src/agents/model-errors.js +35 -0
  9. package/src/agents/opencode-agent.js +2 -12
  10. package/src/brain/agent-error-classifier.js +19 -1
  11. package/src/brain/role-fallback-chain.js +100 -0
  12. package/src/brain/with-brain-recovery.js +59 -3
  13. package/src/checks/action-pins.js +131 -0
  14. package/src/checks/project-checks.js +11 -0
  15. package/src/checks/repo-state.js +89 -0
  16. package/src/cli/advanced-commands.js +3 -0
  17. package/src/cli/register-meta.js +14 -0
  18. package/src/cli/register-pipeline.js +15 -1
  19. package/src/commands/bootstrap.js +131 -0
  20. package/src/commands/check.js +5 -0
  21. package/src/commands/code.js +57 -4
  22. package/src/commands/env.js +2 -1
  23. package/src/commands/go.js +9 -10
  24. package/src/commands/harden.js +3 -1
  25. package/src/commands/review-gate.js +28 -5
  26. package/src/environment/panel.js +52 -0
  27. package/src/environment/playbook.js +7 -6
  28. package/src/harden/config-templates.js +7 -1
  29. package/src/harden/guidelines-engine.js +7 -3
  30. package/src/harden/guidelines-templates.js +60 -10
  31. package/src/harden/hook-templates.js +4 -1
  32. package/src/privacy/diff-scope.js +58 -0
  33. package/src/privacy/scan.js +18 -0
  34. package/src/prompts/card-context.js +57 -0
  35. package/src/prompts/session-context.js +68 -0
  36. package/src/review/one-shot-review.js +5 -0
  37. package/src/review/ui-evidence.js +65 -0
  38. package/src/roles/agent-role.js +11 -2
  39. package/src/start/project-script.js +84 -0
@@ -16,6 +16,7 @@
16
16
  import { BaseRole } from "./base-role.js";
17
17
  import { createAgent as defaultCreateAgent } from "../agents/index.js";
18
18
  import { withBrainRecovery } from "../brain/with-brain-recovery.js";
19
+ import { buildRoleFallbackChain } from "../brain/role-fallback-chain.js";
19
20
 
20
21
  /**
21
22
  * Silence timeout (ms) for an agent subprocess, from
@@ -154,10 +155,18 @@ export class AgentRole extends BaseRole {
154
155
  // reviewer usa reviewTask) también funcionan: el wrapper sólo necesita
155
156
  // un objeto con la firma `runTask(args) → { ok, output, error, exitCode }`.
156
157
  const result = await withBrainRecovery({
157
- agent: { runTask: (args) => agent[this.agentMethod](args), provider },
158
+ agent: { runTask: (args) => agent[this.agentMethod](args), provider, model: this.config?.roles?.[this.name]?.model ?? null },
158
159
  taskArgs: runArgs,
159
160
  role: this.name,
160
161
  provider,
162
+ // KJC-TSK-0859: the chain the user declared under roles.<role>.fallback.
163
+ // The schema and the walker existed since KJC-TSK-0415; nobody built it,
164
+ // so every declared chain was dead config. This is the single point that
165
+ // covers every role inheriting from AgentRole.
166
+ fallback: buildRoleFallbackChain({
167
+ config: this.config, role: this.name, agentMethod: this.agentMethod,
168
+ createAgentFn: this._createAgent, logger: this.logger,
169
+ }),
161
170
  emitter: this.emitter,
162
171
  logger: this.logger,
163
172
  // KJC: sessionState lets withBrainRecovery persist a hibernating run
@@ -171,7 +180,7 @@ export class AgentRole extends BaseRole {
171
180
  const recoveryNote = result.recovery?.class ? ` [${result.recovery.class}]` : "";
172
181
  return {
173
182
  ok: false,
174
- result: { error: result.error || result.output || `${this.name} failed${recoveryNote}`, provider, recovery: result.recovery, action: result.action, standbyFile: result.standbyFile || null },
183
+ result: { error: result.error || result.output || `${this.name} failed${recoveryNote}`, provider, recovery: result.recovery, action: result.action, standbyFile: result.standbyFile || null, tried: result.tried || null },
175
184
  summary: `${this.name} failed${recoveryNote}: ${result.recovery?.message || result.error || "unknown error"}`,
176
185
  usage: result.usage
177
186
  };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * BOOT-C (KJC-TSK-0862) — the project's start script.
3
+ *
4
+ * From the Anthropic write-up on harnesses for long-running agents, and from
5
+ * the cold-start map: a session burns tokens working out how the project is
6
+ * launched, and worse, starts building on a tree that was already broken
7
+ * without knowing it. kj verifies the method; nobody verified that the
8
+ * application runs.
9
+ *
10
+ * kj declares the CONTRACT and checks it. The project writes the script,
11
+ * because kj cannot know how a project it has never seen is launched, and
12
+ * generating one from stack detection would produce the decorative artefact
13
+ * this codebase already rejects elsewhere (a config without its tool).
14
+ *
15
+ * It reports, it does not block: a badly written script must not stop work
16
+ * over something unrelated to the card. It earns the right to block when it
17
+ * proves reliable, the same path Sonar and the RAG took.
18
+ */
19
+
20
+ import { spawn } from "node:child_process";
21
+ import { existsSync } from "node:fs";
22
+ import { join } from "node:path";
23
+
24
+ export const DEFAULT_START_SCRIPT = ".karajan/start.sh";
25
+ export const DEFAULT_TIMEOUT_MS = 120_000;
26
+ export const MAX_OUTPUT_BYTES = 64 * 1024;
27
+
28
+ export const START_SCRIPT_CONTRACT = [
29
+ "El guion de arranque levanta lo que haya que levantar y corre un humo de referencia.",
30
+ "Sale con 0 si el proyecto arranca y el humo pasa; con otro código si no.",
31
+ "No modifica el árbol ni depende de credenciales: solo responde si esto funciona ahora mismo.",
32
+ ].join(" ");
33
+
34
+ /** Where the start script lives: the project's config decides, with a default. */
35
+ export function startScriptPath(projectDir, config = {}) {
36
+ return join(projectDir, config.start_script || DEFAULT_START_SCRIPT);
37
+ }
38
+
39
+ /**
40
+ * @returns {Promise<{status: "absent"|"ok"|"broken"|"timeout", exitCode?: number, output?: string, contract?: string, path: string}>}
41
+ */
42
+ export function runStartScript({ projectDir, config = {}, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
43
+ const script = startScriptPath(projectDir, config);
44
+ if (!existsSync(script)) {
45
+ return Promise.resolve({ status: "absent", path: script, contract: START_SCRIPT_CONTRACT });
46
+ }
47
+ return new Promise((resolve) => {
48
+ const ownGroup = process.platform !== "win32";
49
+ const child = spawn("sh", [script], { cwd: projectDir, detached: ownGroup });
50
+ let output = "";
51
+ let truncated = false;
52
+ let timedOut = false;
53
+ // Bounded on purpose (catch de codex): a verbose or runaway script would
54
+ // otherwise eat the CLI's memory for the whole timeout. The LAST bytes are
55
+ // the ones worth keeping — that is where a failure explains itself.
56
+ const collect = (buf) => {
57
+ output += String(buf);
58
+ if (output.length > MAX_OUTPUT_BYTES) {
59
+ output = output.slice(-MAX_OUTPUT_BYTES);
60
+ truncated = true;
61
+ }
62
+ };
63
+ child.stdout?.on("data", collect);
64
+ child.stderr?.on("data", collect);
65
+ // Kill the GROUP, not just the shell (catch de codex): this script's whole
66
+ // job is to START things, so the dev server it launched would keep its port
67
+ // long after we reported a timeout.
68
+ const killTree = () => {
69
+ try {
70
+ if (ownGroup && child.pid) process.kill(-child.pid, "SIGKILL");
71
+ else child.kill("SIGKILL");
72
+ } catch { /* ya no está */ }
73
+ };
74
+ // A script that hangs is not a verdict: it is a script that hangs.
75
+ const timer = setTimeout(() => { timedOut = true; killTree(); }, timeoutMs);
76
+ child.on("error", (err) => { clearTimeout(timer); resolve({ status: "broken", path: script, exitCode: null, output: `${output}${err.message}`, truncated }); });
77
+ // Resolve on CLOSE, never before: the answer comes once the tree is down.
78
+ child.on("close", (code) => {
79
+ clearTimeout(timer);
80
+ if (timedOut) resolve({ status: "timeout", path: script, output, truncated });
81
+ else resolve({ status: code === 0 ? "ok" : "broken", exitCode: code, path: script, output, truncated });
82
+ });
83
+ });
84
+ }