karajan-code 1.16.0 → 1.18.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 (72) hide show
  1. package/package.json +1 -1
  2. package/src/activity-log.js +13 -13
  3. package/src/agents/availability.js +2 -3
  4. package/src/agents/claude-agent.js +42 -21
  5. package/src/agents/model-registry.js +1 -1
  6. package/src/becaria/dispatch.js +1 -1
  7. package/src/becaria/repo.js +3 -3
  8. package/src/cli.js +5 -2
  9. package/src/commands/doctor.js +154 -108
  10. package/src/commands/init.js +101 -90
  11. package/src/commands/plan.js +1 -1
  12. package/src/commands/report.js +77 -71
  13. package/src/commands/roles.js +0 -1
  14. package/src/commands/run.js +2 -3
  15. package/src/config.js +174 -93
  16. package/src/git/automation.js +3 -4
  17. package/src/guards/intent-guard.js +123 -0
  18. package/src/guards/output-guard.js +158 -0
  19. package/src/guards/perf-guard.js +126 -0
  20. package/src/guards/policy-resolver.js +3 -3
  21. package/src/mcp/orphan-guard.js +1 -2
  22. package/src/mcp/progress.js +4 -3
  23. package/src/mcp/run-kj.js +1 -0
  24. package/src/mcp/server-handlers.js +242 -253
  25. package/src/mcp/server.js +4 -3
  26. package/src/mcp/tools.js +2 -0
  27. package/src/orchestrator/agent-fallback.js +1 -3
  28. package/src/orchestrator/iteration-stages.js +206 -170
  29. package/src/orchestrator/pre-loop-stages.js +200 -34
  30. package/src/orchestrator/solomon-rules.js +2 -2
  31. package/src/orchestrator.js +902 -746
  32. package/src/planning-game/adapter.js +23 -20
  33. package/src/planning-game/architect-adrs.js +45 -0
  34. package/src/planning-game/client.js +15 -1
  35. package/src/planning-game/decomposition.js +7 -5
  36. package/src/prompts/architect.js +88 -0
  37. package/src/prompts/discover.js +54 -53
  38. package/src/prompts/planner.js +53 -33
  39. package/src/prompts/triage.js +8 -16
  40. package/src/review/parser.js +18 -19
  41. package/src/review/profiles.js +2 -2
  42. package/src/review/schema.js +3 -3
  43. package/src/review/scope-filter.js +3 -4
  44. package/src/roles/architect-role.js +122 -0
  45. package/src/roles/commiter-role.js +2 -2
  46. package/src/roles/discover-role.js +59 -67
  47. package/src/roles/index.js +1 -0
  48. package/src/roles/planner-role.js +54 -38
  49. package/src/roles/refactorer-role.js +8 -7
  50. package/src/roles/researcher-role.js +6 -7
  51. package/src/roles/reviewer-role.js +4 -5
  52. package/src/roles/security-role.js +3 -4
  53. package/src/roles/solomon-role.js +6 -18
  54. package/src/roles/sonar-role.js +5 -1
  55. package/src/roles/tester-role.js +8 -5
  56. package/src/roles/triage-role.js +2 -2
  57. package/src/session-cleanup.js +29 -24
  58. package/src/session-store.js +1 -1
  59. package/src/sonar/api.js +1 -1
  60. package/src/sonar/manager.js +1 -1
  61. package/src/sonar/project-key.js +5 -5
  62. package/src/sonar/scanner.js +34 -65
  63. package/src/utils/display.js +312 -272
  64. package/src/utils/git.js +3 -3
  65. package/src/utils/logger.js +6 -1
  66. package/src/utils/model-selector.js +5 -5
  67. package/src/utils/process.js +80 -102
  68. package/src/utils/rate-limit-detector.js +13 -13
  69. package/src/utils/run-log.js +55 -52
  70. package/templates/kj.config.yml +33 -0
  71. package/templates/roles/architect.md +62 -0
  72. package/templates/roles/planner.md +1 -0
@@ -0,0 +1,126 @@
1
+ const FRONTEND_EXTENSIONS = new Set([".html", ".htm", ".css", ".jsx", ".tsx", ".astro", ".vue", ".svelte"]);
2
+
3
+ // Built-in perf anti-patterns (applied to added lines in frontend files)
4
+ const PERF_PATTERNS = [
5
+ { id: "img-no-dimensions", pattern: /<img\b(?![^>]*\bwidth\b)(?![^>]*\bheight\b)[^>]*>/i, severity: "warning", message: "Image without width/height attributes (causes CLS)" },
6
+ { id: "img-no-lazy", pattern: /<img\b(?![^>]*\bloading\s*=)(?![^>]*\bfetchpriority\s*=)[^>]*>/i, severity: "info", message: "Image without loading=\"lazy\" or fetchpriority (consider lazy loading)" },
7
+ { id: "script-no-defer", pattern: /<script\b(?![^>]*\b(?:defer|async)\b)(?![^>]*type\s*=\s*["']module["'])[^>]*src\s*=/i, severity: "warning", message: "External script without defer/async (render-blocking)" },
8
+ { id: "font-no-display", pattern: /@font-face\s*\{(?![^}]*font-display)/i, severity: "warning", message: "@font-face without font-display (causes FOIT)" },
9
+ { id: "css-import", pattern: /@import\s+(?:url\()?["'](?!.*\.module\.)/i, severity: "info", message: "CSS @import (causes sequential loading, prefer <link>)" },
10
+ { id: "inline-style-large", pattern: /style\s*=\s*["'][^"']{200,}["']/i, severity: "warning", message: "Large inline style (>200 chars, consider external CSS)" },
11
+ { id: "document-write", pattern: /document\.write\s*\(/, severity: "warning", message: "document.write() blocks parsing and degrades performance" },
12
+ ];
13
+
14
+ // Patterns for package.json changes (heavy dependencies added)
15
+ const HEAVY_DEPS = [
16
+ { id: "heavy-moment", pattern: /"moment"/, severity: "info", message: "moment.js added (consider dayjs or date-fns for smaller bundle)" },
17
+ { id: "heavy-lodash", pattern: /"lodash"(?!\/)/, severity: "info", message: "Full lodash added (consider lodash-es or individual imports)" },
18
+ { id: "heavy-jquery", pattern: /"jquery"/, severity: "info", message: "jQuery added (consider native DOM APIs)" },
19
+ ];
20
+
21
+ function getExtension(filePath) {
22
+ if (!filePath) return "";
23
+ const dot = filePath.lastIndexOf(".");
24
+ return dot >= 0 ? filePath.slice(dot).toLowerCase() : "";
25
+ }
26
+
27
+ /**
28
+ * Check if any modified file in the diff is a frontend file
29
+ */
30
+ export function hasFrontendFiles(diff) {
31
+ if (!diff) return false;
32
+ for (const line of diff.split("\n")) {
33
+ if (line.startsWith("+++ b/")) {
34
+ const ext = getExtension(line.slice(6));
35
+ if (FRONTEND_EXTENSIONS.has(ext)) return true;
36
+ }
37
+ }
38
+ return false;
39
+ }
40
+
41
+ /**
42
+ * Extract added lines grouped by file from a unified diff
43
+ */
44
+ function extractAddedLinesByFile(diff) {
45
+ const results = [];
46
+ let currentFile = null;
47
+ let lineNum = 0;
48
+
49
+ for (const line of diff.split("\n")) {
50
+ if (line.startsWith("+++ b/")) {
51
+ currentFile = line.slice(6);
52
+ continue;
53
+ }
54
+ if (line.startsWith("@@ ")) {
55
+ const match = /@@ -\d+(?:,\d+)? \+(\d+)/.exec(line);
56
+ lineNum = match ? Number.parseInt(match[1], 10) - 1 : 0;
57
+ continue;
58
+ }
59
+ if (line.startsWith("+") && !line.startsWith("+++")) {
60
+ lineNum += 1;
61
+ results.push({ file: currentFile, line: lineNum, content: line.slice(1) });
62
+ } else if (!line.startsWith("-")) {
63
+ lineNum += 1;
64
+ }
65
+ }
66
+ return results;
67
+ }
68
+
69
+ /**
70
+ * Scan diff for frontend performance anti-patterns.
71
+ * Returns { pass: boolean, violations: [...], skipped: boolean }
72
+ */
73
+ export function scanPerfDiff(diff, config = {}) {
74
+ if (!diff || typeof diff !== "string") {
75
+ return { pass: true, violations: [], skipped: true };
76
+ }
77
+
78
+ if (!hasFrontendFiles(diff)) {
79
+ return { pass: true, violations: [], skipped: true };
80
+ }
81
+
82
+ const customPatterns = Array.isArray(config?.guards?.perf?.patterns)
83
+ ? config.guards.perf.patterns.map(p => ({
84
+ id: p.id || "custom-perf",
85
+ pattern: typeof p.pattern === "string" ? new RegExp(p.pattern, p.flags || "i") : p.pattern,
86
+ severity: p.severity || "warning",
87
+ message: p.message || "Custom perf pattern matched",
88
+ }))
89
+ : [];
90
+
91
+ const allPatterns = [...PERF_PATTERNS, ...customPatterns];
92
+ const addedLines = extractAddedLinesByFile(diff);
93
+ const violations = [];
94
+
95
+ for (const { file, line, content } of addedLines) {
96
+ const ext = getExtension(file);
97
+ const isFrontend = FRONTEND_EXTENSIONS.has(ext);
98
+ const isPackageJson = file?.endsWith("package.json");
99
+
100
+ if (isFrontend) {
101
+ for (const { id, pattern, severity, message } of allPatterns) {
102
+ if (pattern.test(content)) {
103
+ violations.push({ id, severity, file, line, message, matchedContent: content.trim().slice(0, 200) });
104
+ }
105
+ }
106
+ }
107
+
108
+ if (isPackageJson) {
109
+ for (const { id, pattern, severity, message } of HEAVY_DEPS) {
110
+ if (pattern.test(content)) {
111
+ violations.push({ id, severity, file, line, message, matchedContent: content.trim().slice(0, 200) });
112
+ }
113
+ }
114
+ }
115
+ }
116
+
117
+ // perf-guard is advisory by default — only blocks on critical (none built-in are critical)
118
+ const blockOnWarning = Boolean(config?.guards?.perf?.block_on_warning);
119
+ const hasCritical = violations.some(v => v.severity === "critical");
120
+ const hasWarning = violations.some(v => v.severity === "warning");
121
+ const pass = !hasCritical && !(blockOnWarning && hasWarning);
122
+
123
+ return { pass, violations, skipped: false };
124
+ }
125
+
126
+ export { PERF_PATTERNS, HEAVY_DEPS, FRONTEND_EXTENSIONS };
@@ -1,4 +1,4 @@
1
- export const VALID_TASK_TYPES = ["sw", "infra", "doc", "add-tests", "refactor"];
1
+ export const VALID_TASK_TYPES = new Set(["sw", "infra", "doc", "add-tests", "refactor"]);
2
2
 
3
3
  export const DEFAULT_POLICIES = {
4
4
  sw: { tdd: true, sonar: true, reviewer: true, testsRequired: true },
@@ -16,7 +16,7 @@ const FALLBACK_TYPE = "sw";
16
16
  * configOverrides optionally merges over defaults per taskType.
17
17
  */
18
18
  export function resolvePolicies(taskType, configOverrides) {
19
- const resolvedType = VALID_TASK_TYPES.includes(taskType) ? taskType : FALLBACK_TYPE;
19
+ const resolvedType = VALID_TASK_TYPES.has(taskType) ? taskType : FALLBACK_TYPE;
20
20
  const base = { ...DEFAULT_POLICIES[resolvedType] };
21
21
  const overrides = configOverrides?.[resolvedType];
22
22
  if (overrides && typeof overrides === "object") {
@@ -31,7 +31,7 @@ export function resolvePolicies(taskType, configOverrides) {
31
31
  * orchestrator to determine which pipeline stages to enable/disable.
32
32
  */
33
33
  export function applyPolicies({ taskType, policies } = {}) {
34
- const resolvedType = VALID_TASK_TYPES.includes(taskType) ? taskType : FALLBACK_TYPE;
34
+ const resolvedType = VALID_TASK_TYPES.has(taskType) ? taskType : FALLBACK_TYPE;
35
35
  const resolved = resolvePolicies(taskType, policies);
36
36
  return { taskType: resolvedType, ...resolved };
37
37
  }
@@ -1,5 +1,4 @@
1
- import { readFileSync } from "node:fs";
2
- import { watch } from "node:fs";
1
+ import { readFileSync, watch } from "node:fs";
3
2
 
4
3
  const DEFAULT_INTERVAL_MS = 5000;
5
4
 
@@ -57,7 +57,7 @@ export function buildPipelineTracker(config, emitter) {
57
57
  };
58
58
 
59
59
  emitter.on("progress", (event) => {
60
- const match = event.type?.match(/^(\w+):(start|end)$/);
60
+ const match = /^(\w+):(start|end)$/.exec(event.type ?? "");
61
61
  if (!match) return;
62
62
 
63
63
  const [, name, phase] = match;
@@ -127,9 +127,10 @@ export function buildProgressNotifier(extra) {
127
127
  if (idx < 0) return;
128
128
 
129
129
  const iteration = event.iteration || event.detail?.iteration;
130
+ const baseMessage = event.message || event.type;
130
131
  const message = iteration
131
- ? `[${event.iteration}] ${event.message || event.type}`
132
- : event.message || event.type;
132
+ ? `[${event.iteration}] ${baseMessage}`
133
+ : baseMessage;
133
134
 
134
135
  try {
135
136
  extra.sendNotification({
package/src/mcp/run-kj.js CHANGED
@@ -44,6 +44,7 @@ export async function runKjCommand({ command, commandArgs = [], options = {}, en
44
44
  normalizeBoolFlag(options.enableSecurity, "--enable-security", args);
45
45
  normalizeBoolFlag(options.enableTriage, "--enable-triage", args);
46
46
  normalizeBoolFlag(options.enableDiscover, "--enable-discover", args);
47
+ normalizeBoolFlag(options.enableArchitect, "--enable-architect", args);
47
48
  normalizeBoolFlag(options.enableSerena, "--enable-serena", args);
48
49
  normalizeBoolFlag(options.autoCommit, "--auto-commit", args);
49
50
  normalizeBoolFlag(options.autoPush, "--auto-push", args);