karajan-code 2.7.4 → 2.8.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 (165) hide show
  1. package/README.md +1 -1
  2. package/package.json +24 -4
  3. package/scripts/diet-baseline.mjs +170 -0
  4. package/scripts/regen-arch-stats.sh +71 -0
  5. package/src/agents/availability.js +15 -9
  6. package/src/agents/claude-agent.js +143 -45
  7. package/src/agents/model-registry.js +1 -1
  8. package/src/audit/agent-readiness.js +240 -0
  9. package/src/bootstrap.js +26 -1
  10. package/src/brain/decision-tracker.js +3 -1
  11. package/src/brain/decisor.js +1 -1
  12. package/src/canvas/parse-md.js +238 -0
  13. package/src/canvas/schema.js +101 -0
  14. package/src/canvas/to-flat.js +85 -0
  15. package/src/canvas/validate.js +163 -0
  16. package/src/checks/binaries.js +1 -1
  17. package/src/checks/ports.js +85 -31
  18. package/src/checks/sonar.js +1 -1
  19. package/src/checks/system.js +1 -1
  20. package/src/cli/_shared.js +30 -0
  21. package/src/cli/register-meta.js +204 -0
  22. package/src/cli/register-pipeline.js +212 -0
  23. package/src/cli/register-plan.js +152 -0
  24. package/src/cli/register-roles-skills.js +38 -0
  25. package/src/cli/register-sonar.js +28 -0
  26. package/src/cli.js +83 -468
  27. package/src/commands/agents.js +18 -6
  28. package/src/commands/architect.js +29 -2
  29. package/src/commands/audit.js +27 -3
  30. package/src/commands/board.js +160 -30
  31. package/src/commands/clean.js +83 -0
  32. package/src/commands/code.js +7 -2
  33. package/src/commands/discover.js +7 -2
  34. package/src/commands/init.js +2 -2
  35. package/src/commands/plan/_shared.js +40 -0
  36. package/src/commands/plan/add-hu.js +21 -0
  37. package/src/commands/plan/delete.js +9 -0
  38. package/src/commands/plan/generate.js +284 -0
  39. package/src/commands/plan/index.js +14 -0
  40. package/src/commands/plan/list.js +21 -0
  41. package/src/commands/plan/ready.js +46 -0
  42. package/src/commands/plan/remove-hu.js +17 -0
  43. package/src/commands/plan/show.js +81 -0
  44. package/src/commands/plan/validate.js +20 -0
  45. package/src/commands/plan.js +14 -291
  46. package/src/commands/researcher.js +7 -2
  47. package/src/commands/resume.js +24 -1
  48. package/src/commands/review.js +7 -2
  49. package/src/commands/run.js +52 -1
  50. package/src/commands/status.js +1 -1
  51. package/src/commands/sync.js +75 -0
  52. package/src/commands/triage.js +7 -2
  53. package/src/commands/undo.js +1 -1
  54. package/src/config/defaults.js +213 -0
  55. package/src/config/loader.js +137 -0
  56. package/src/config/overrides.js +199 -0
  57. package/src/config/role-resolver.js +159 -0
  58. package/src/config/test-harness.js +79 -0
  59. package/src/config.js +30 -632
  60. package/src/git/automation.js +2 -2
  61. package/src/guards/output-guard.js +0 -2
  62. package/src/hu/acceptance-runner.js +52 -10
  63. package/src/hu/store.js +11 -0
  64. package/src/mcp/handlers/direct-handlers.js +2 -5
  65. package/src/mcp/handlers/management-handlers.js +48 -4
  66. package/src/mcp/handlers/run-handler.js +9 -8
  67. package/src/mcp/preflight.js +32 -13
  68. package/src/mcp/server-handlers.js +4 -3
  69. package/src/mcp/suggest-handler.js +3 -2
  70. package/src/mcp/tools.js +15 -0
  71. package/src/orchestrator/agent-fallback.js +1 -1
  72. package/src/orchestrator/ci-integration.js +10 -10
  73. package/src/orchestrator/config-init.js +25 -7
  74. package/src/orchestrator/direct-actions.js +6 -2
  75. package/src/orchestrator/drivers/error-recovery.js +287 -0
  76. package/src/orchestrator/drivers/init-context.js +222 -0
  77. package/src/orchestrator/drivers/iteration-loop.js +311 -0
  78. package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +48 -0
  79. package/src/orchestrator/drivers/iteration-phases/guards.js +84 -0
  80. package/src/orchestrator/drivers/iteration-phases/handle-approved.js +29 -0
  81. package/src/orchestrator/drivers/iteration-phases/quality-gates.js +76 -0
  82. package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +60 -0
  83. package/src/orchestrator/drivers/post-loop.js +370 -0
  84. package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +116 -0
  85. package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +47 -0
  86. package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +84 -0
  87. package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +268 -0
  88. package/src/orchestrator/drivers/pre-loop.js +434 -0
  89. package/src/orchestrator/drivers/run-hu-batch.js +367 -0
  90. package/src/orchestrator/flow-control.js +4 -3
  91. package/src/orchestrator/flow-runner.js +121 -2077
  92. package/src/orchestrator/fs-leak-detector.js +156 -0
  93. package/src/orchestrator/hu-sub-pipeline.js +197 -9
  94. package/src/orchestrator/integrations.js +88 -0
  95. package/src/orchestrator/post-loop-stages.js +35 -8
  96. package/src/orchestrator/preflight-checks.js +20 -7
  97. package/src/orchestrator/repair/repair-detector.js +117 -0
  98. package/src/orchestrator/repair/repair-runner.js +142 -0
  99. package/src/orchestrator/reviewer-fallback.js +1 -1
  100. package/src/orchestrator/solomon-escalation.js +3 -2
  101. package/src/orchestrator/solomon-rules.js +7 -3
  102. package/src/orchestrator/stages/architect-stage.js +12 -11
  103. package/src/orchestrator/stages/coder-stage.js +56 -7
  104. package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +127 -0
  105. package/src/orchestrator/stages/hu-reviewer-stage.js +6 -79
  106. package/src/orchestrator/stages/planner-stage.js +1 -1
  107. package/src/orchestrator/stages/research-stage.js +1 -1
  108. package/src/orchestrator/stages/reviewer-stage.js +7 -7
  109. package/src/orchestrator/stages/sonar-stage.js +94 -10
  110. package/src/orchestrator/stages/stage-classes.js +86 -0
  111. package/src/orchestrator/stages/stage-executor.js +1 -1
  112. package/src/orchestrator/stages/triage-stage.js +1 -1
  113. package/src/orchestrator/standby.js +3 -2
  114. package/src/orchestrator/verification-gate.js +20 -12
  115. package/src/plan/adr-generator.js +170 -0
  116. package/src/plan/adr-loader.js +60 -0
  117. package/src/plan/plan-executor.js +19 -1
  118. package/src/plan/plan-hu-ops.js +170 -1
  119. package/src/plan/plan-reviewer.js +199 -0
  120. package/src/plan/plan-schema.js +69 -1
  121. package/src/plan/plan-store.js +40 -3
  122. package/src/plan/spec-detector.js +61 -0
  123. package/src/plan/tests-synthesizer.js +116 -0
  124. package/src/planning-game/client.js +3 -14
  125. package/src/planning-game/decomposition.js +1 -1
  126. package/src/planning-game/pipeline-adapter.js +36 -5
  127. package/src/prompts/coder.js +172 -1
  128. package/src/prompts/hu-reviewer.js +0 -1
  129. package/src/prompts/planner.js +81 -4
  130. package/src/review/diff-generator.js +35 -22
  131. package/src/review/snapshot-diff.js +1 -1
  132. package/src/roles/coder-role.js +25 -3
  133. package/src/roles/planner-role.js +42 -1
  134. package/src/roles/repairer-role.js +181 -0
  135. package/src/roles/tester-role.js +70 -4
  136. package/src/session/mutators.js +304 -0
  137. package/src/session/runtime-overrides.js +59 -0
  138. package/src/{session-store.js → session/store.js} +2 -2
  139. package/src/skills/semantic-detector.js +10 -5
  140. package/src/skills/skill-detector.js +1 -1
  141. package/src/sonar/cloud-scanner.js +0 -1
  142. package/src/sonar/credentials.js +14 -6
  143. package/src/sonar/discovery.js +123 -0
  144. package/src/sonar/manager.js +18 -0
  145. package/src/sync/detect-drift.js +113 -0
  146. package/src/tracker-bootstrap.js +44 -0
  147. package/src/utils/agent-detect.js +15 -11
  148. package/src/utils/board-prompt-bridge.js +109 -0
  149. package/src/utils/cli-progress.js +161 -0
  150. package/src/utils/cwd-confirm.js +39 -0
  151. package/src/utils/derive-project-name-from-cwd.js +119 -0
  152. package/src/utils/garbage-collector.js +396 -0
  153. package/src/utils/git.js +16 -4
  154. package/src/utils/locale.js +1 -1
  155. package/src/utils/paths.js +21 -1
  156. package/src/utils/process.js +1 -1
  157. package/src/utils/prompt-project-name.js +35 -0
  158. package/src/utils/run-context.js +75 -0
  159. package/src/utils/run-log.js +12 -0
  160. package/src/utils/task-file.js +1 -1
  161. package/src/utils/telemetry.js +13 -1
  162. package/src/webperf/devtools-detect.js +1 -1
  163. package/templates/coder-rules.md +13 -0
  164. package/templates/roles/coder.md +34 -0
  165. package/templates/roles/repairer.md +94 -0
@@ -0,0 +1,161 @@
1
+ /**
2
+ * CLI progress reporter — prints live agent activity to the terminal while
3
+ * an agent (planner, coder, reviewer, auditor, …) is thinking.
4
+ *
5
+ * Context: commands like `kj plan --task-file spec.md` used to call
6
+ * `agent.runTask(...)` without an `onOutput` callback. The CLI then hung
7
+ * for 30 s – 3 min with zero feedback until a line like "Plan saved: …"
8
+ * finally appeared. Users assumed the command had deadlocked.
9
+ *
10
+ * This helper gives every CLI command the same live feedback the `kj run`
11
+ * orchestrator already emits: a header with role + start, a stream of
12
+ * tool calls / short text lines as the agent works, and a footer with
13
+ * outcome + elapsed time. It wraps `onOutput` so every command can pass
14
+ * `createCliProgressReporter({ role: "planner" }).onOutput` straight
15
+ * into the agent's runTask / reviewTask call.
16
+ *
17
+ * Output shape:
18
+ *
19
+ * [planner] starting...
20
+ * ▸ Read SPEC.md
21
+ * ▸ Grep "architecture"
22
+ * · Analyzing sections 1–5…
23
+ * ▸ Write plan.json
24
+ * [planner] done (42.8s)
25
+ *
26
+ * Design notes:
27
+ * - Writes to `process.stderr` by default (so stdout is reserved for
28
+ * structured output like `--json` mode).
29
+ * - `onOutput` is shaped as `{ stream, line, kind }` to match the
30
+ * contract Claude's `createStreamJsonFilter` emits — so passing
31
+ * this reporter's `onOutput` into a runTask{…, onOutput} call Just
32
+ * Works with Claude, Codex, Gemini, Aider and OpenCode (each one's
33
+ * agent wrapper already normalizes its CLI's stream format into
34
+ * that shape).
35
+ * - `kind` is optional; when absent the default bullet is used.
36
+ * - `quiet: true` turns the reporter into a no-op (useful when the
37
+ * command is running under `--json` and should not pollute stdout).
38
+ */
39
+
40
+ const ICONS = {
41
+ tool: "▸", // ▸ right-pointing small triangle
42
+ text: "·", // · middle dot
43
+ thinking: "…", // …
44
+ heartbeat: "⠿", // braille dots — distinct from real activity icons
45
+ };
46
+
47
+ /**
48
+ * Rotating idle messages so the user knows the process is alive without
49
+ * the same string repeating every minute. Picked at random each tick;
50
+ * the previous one is held back so two ticks in a row never duplicate.
51
+ */
52
+ const HEARTBEAT_LINES = [
53
+ "still working…",
54
+ "thinking through it…",
55
+ "wading through tokens…",
56
+ "asking the model nicely…",
57
+ "crunching the spec…",
58
+ "burning down the prompt…",
59
+ "kneading the context window…",
60
+ "still here, give me a sec…",
61
+ "the LLM is doing LLM things…",
62
+ "drafting, redrafting…",
63
+ ];
64
+
65
+ /**
66
+ * Default cadence for the idle heartbeat. ~45s is short enough to feel
67
+ * responsive on a quiet planner run (Claude can think for 1-2 min on a
68
+ * complex spec) without spamming every few seconds.
69
+ */
70
+ const DEFAULT_HEARTBEAT_MS = 45_000;
71
+
72
+ /**
73
+ * @typedef {Object} CliProgressReporter
74
+ * @property {(event: { stream?: string, line: string, kind?: string }) => void} onOutput
75
+ * @property {(outcome?: string) => void} finish
76
+ */
77
+
78
+ /**
79
+ * @param {Object} [opts]
80
+ * @param {string} [opts.role] - label shown in the header/footer ("planner", "auditor", …).
81
+ * Default: "agent".
82
+ * @param {NodeJS.WriteStream} [opts.stream] - output destination. Default: process.stderr.
83
+ * @param {boolean} [opts.quiet] - when true, nothing is printed (useful for --json mode).
84
+ * @param {number} [opts.heartbeatMs] - silent interval before printing an
85
+ * "alive" line. Defaults to 45 s. Set to 0 to disable the heartbeat
86
+ * (useful in tests).
87
+ * @returns {CliProgressReporter}
88
+ */
89
+ export function createCliProgressReporter(opts = {}) {
90
+ const {
91
+ role = "agent",
92
+ stream = process.stderr,
93
+ quiet = false,
94
+ heartbeatMs = DEFAULT_HEARTBEAT_MS,
95
+ } = opts;
96
+
97
+ if (quiet) {
98
+ return { onOutput: () => {}, finish: () => {} };
99
+ }
100
+
101
+ const startedAt = Date.now();
102
+ stream.write(`[${role}] starting...\n`);
103
+
104
+ // Idle heartbeat: a periodic timer that prints a varied "still alive"
105
+ // line whenever the agent goes quiet for longer than `heartbeatMs`.
106
+ // Reset on every real onOutput so heavy chatter doesn't trigger the
107
+ // duplicate. Always elapses to a different message than last time so
108
+ // two consecutive ticks never read the same.
109
+ let lastActivityAt = Date.now();
110
+ let lastHeartbeatIdx = -1;
111
+ let heartbeatTimer = null;
112
+
113
+ function pickHeartbeatLine() {
114
+ let idx;
115
+ do {
116
+ idx = Math.floor(Math.random() * HEARTBEAT_LINES.length);
117
+ } while (idx === lastHeartbeatIdx && HEARTBEAT_LINES.length > 1);
118
+ lastHeartbeatIdx = idx;
119
+ return HEARTBEAT_LINES[idx];
120
+ }
121
+
122
+ function tick() {
123
+ const idleFor = Date.now() - lastActivityAt;
124
+ if (idleFor < heartbeatMs) return; // real activity reset us
125
+ const elapsed = Math.round((Date.now() - startedAt) / 1000);
126
+ stream.write(` ${ICONS.heartbeat} ${pickHeartbeatLine()} (${elapsed}s)\n`);
127
+ lastActivityAt = Date.now(); // collapse next ticks
128
+ }
129
+
130
+ if (heartbeatMs > 0) {
131
+ heartbeatTimer = setInterval(tick, heartbeatMs);
132
+ if (typeof heartbeatTimer.unref === "function") heartbeatTimer.unref();
133
+ }
134
+
135
+ const onOutput = (event) => {
136
+ if (!event || typeof event !== "object") return;
137
+ const raw = typeof event.line === "string" ? event.line : "";
138
+ if (!raw) return;
139
+ // Strip trailing newline — we re-add our own.
140
+ const line = raw.replace(/\s+$/, "");
141
+ if (!line) return;
142
+ lastActivityAt = Date.now();
143
+ const icon = ICONS[event.kind] || ICONS.text;
144
+ stream.write(` ${icon} ${line}\n`);
145
+ };
146
+
147
+ /**
148
+ * @param {string} [outcome] default "done". Use "failed", "stopped", "timeout" etc.
149
+ */
150
+ const finish = (outcome = "done") => {
151
+ if (heartbeatTimer) {
152
+ clearInterval(heartbeatTimer);
153
+ heartbeatTimer = null;
154
+ }
155
+ const ms = Date.now() - startedAt;
156
+ const s = (ms / 1000).toFixed(1);
157
+ stream.write(`[${role}] ${outcome} (${s}s)\n`);
158
+ };
159
+
160
+ return { onOutput, finish };
161
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * PR-F: ask the user "Run on $(pwd)?" before launching `kj run` from
3
+ * a terminal. Same defensive UX as `git push --force-with-lease`
4
+ * surfacing the branch — the user wants to know what they're aiming
5
+ * at before the rocket fires.
6
+ *
7
+ * The prompt is plain readline (no extra deps) and accepts any of:
8
+ * y, yes, s, sí, ENTER → true
9
+ * n, no, ENTER+Esc → false
10
+ *
11
+ * Skipped automatically by the caller when stdin/stdout are not a
12
+ * TTY, when --yes is set, or when --json is set. So this module is
13
+ * never called in CI / non-interactive contexts.
14
+ */
15
+
16
+ import readline from "node:readline";
17
+
18
+ /**
19
+ * @param {string} projectDir absolute path of the cwd / projectDir
20
+ * @returns {Promise<boolean>} true to continue, false to abort
21
+ */
22
+ export async function confirmCwd(projectDir) {
23
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
24
+ // No emoji — keep terminal-safe even on basic Windows shells.
25
+ const question = `Karajan va a ejecutarse sobre:\n ${projectDir}\n\n¿Continuar? [Y/n] `;
26
+ return new Promise((resolve) => {
27
+ rl.question(question, (answer) => {
28
+ rl.close();
29
+ const trimmed = String(answer || "").trim().toLowerCase();
30
+ // Empty answer = ENTER = accept the default = yes.
31
+ if (trimmed === "" || trimmed === "y" || trimmed === "yes" || trimmed === "s" || trimmed === "si" || trimmed === "sí") {
32
+ resolve(true);
33
+ } else {
34
+ resolve(false);
35
+ }
36
+ });
37
+ rl.on("SIGINT", () => { rl.close(); resolve(false); });
38
+ });
39
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Derive a sensible default project name from the working directory,
3
+ * trying several signals in order:
4
+ *
5
+ * 1. `package.json` "name" field (most authoritative — that's what
6
+ * the user already chose for their npm/pnpm project).
7
+ * 2. Git remote URL basename (strip protocol, user, `.git` suffix —
8
+ * e.g. `git@github.com:foo/weather-dashboard.git` → `weather-dashboard`).
9
+ * 3. The cwd directory basename.
10
+ *
11
+ * Any of these is then prettified: kebab/snake → spaces, words
12
+ * capitalised. So `weather-dashboard` becomes `Weather Dashboard`.
13
+ *
14
+ * Returns null when no signal yields anything useful — caller can then
15
+ * fall back to its old "derive from task text" heuristic. This keeps
16
+ * the function pure / sync-friendly even though git/fs lookups are
17
+ * sync — they're cheap on a single-shot CLI startup.
18
+ *
19
+ * Why this matters: pre-fix, running `kj plan generate --task-file
20
+ * SPEC.md` inside `~/ws_demo/weather-dashboard` (an initialised git
21
+ * repo) suggested a project name made from the words of the SPEC's
22
+ * first sentence. That ignored both the directory the user was
23
+ * standing in and the repo they had just `git init`-ed. The right
24
+ * default is "the project I'm in", not "the topic of the document
25
+ * I just wrote".
26
+ */
27
+
28
+ import fs from "node:fs";
29
+ import path from "node:path";
30
+ import { execFileSync } from "node:child_process";
31
+
32
+ /**
33
+ * Turn `weather-dashboard` / `weather_dashboard` / `WeatherDashboard`
34
+ * into `Weather Dashboard`. Preserves single-token names as-is when
35
+ * already title-cased.
36
+ */
37
+ function prettifyName(raw) {
38
+ if (!raw || typeof raw !== "string") return null;
39
+ const trimmed = raw.trim();
40
+ if (!trimmed) return null;
41
+ // Insert space at camelCase boundaries, then split on -/_/whitespace.
42
+ const spaced = trimmed
43
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
44
+ .replace(/[-_]+/g, " ")
45
+ .replace(/\s+/g, " ")
46
+ .trim();
47
+ if (!spaced) return null;
48
+ return spaced
49
+ .split(" ")
50
+ .map((w) => (w.length ? w[0].toUpperCase() + w.slice(1) : w))
51
+ .join(" ");
52
+ }
53
+
54
+ function readPackageJsonName(cwd) {
55
+ try {
56
+ const pkgPath = path.join(cwd, "package.json");
57
+ if (!fs.existsSync(pkgPath)) return null;
58
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
59
+ if (typeof pkg.name === "string" && pkg.name.trim()) {
60
+ // Strip npm scope if present: @org/name → name
61
+ const unscoped = pkg.name.includes("/") ? pkg.name.split("/").pop() : pkg.name;
62
+ return unscoped;
63
+ }
64
+ return null;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ function readGitRemoteBasename(cwd) {
71
+ try {
72
+ // -C points git at the cwd without us cd'ing the process.
73
+ // Audit follow-up: was using execSync with template-string interpolation
74
+ // of `cwd`. Switched to execFileSync with arg arrays so no interpolation
75
+ // reaches /bin/sh.
76
+ const out = execFileSync("git", ["-C", String(cwd), "config", "--get", "remote.origin.url"], {
77
+ stdio: ["ignore", "pipe", "ignore"],
78
+ encoding: "utf8",
79
+ timeout: 1500,
80
+ }).trim();
81
+ if (!out) return null;
82
+ // Strip optional protocol + user, .git suffix, trailing slash.
83
+ // Examples:
84
+ // git@github.com:foo/weather-dashboard.git
85
+ // https://github.com/foo/weather-dashboard
86
+ // git@gitlab.com:team/weather-dashboard
87
+ const last = out.replace(/\.git\/?$/, "").split(/[/:]/).pop();
88
+ return last || null;
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+
94
+ function readCwdBasename(cwd) {
95
+ const base = path.basename(cwd || process.cwd());
96
+ if (!base || base === "/" || base === ".") return null;
97
+ return base;
98
+ }
99
+
100
+ /**
101
+ * Try every signal in priority order and return the first prettified
102
+ * non-empty result. Returns null when nothing useful is available.
103
+ *
104
+ * @param {string} [cwd] — defaults to process.cwd()
105
+ * @returns {string|null}
106
+ */
107
+ export function deriveProjectNameFromCwd(cwd = process.cwd()) {
108
+ const fromPkg = prettifyName(readPackageJsonName(cwd));
109
+ if (fromPkg) return fromPkg;
110
+ const fromGit = prettifyName(readGitRemoteBasename(cwd));
111
+ if (fromGit) return fromGit;
112
+ const fromCwd = prettifyName(readCwdBasename(cwd));
113
+ if (fromCwd) return fromCwd;
114
+ return null;
115
+ }
116
+
117
+ // Exposed for unit tests so they can drive the individual signals
118
+ // without real filesystem / git side-effects.
119
+ export const __test__ = { prettifyName, readPackageJsonName, readGitRemoteBasename, readCwdBasename };