karajan-code 4.7.0 → 4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "4.7.0",
3
+ "version": "4.8.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",
@@ -65,7 +65,7 @@ function formatAiSlopBlock(slop) {
65
65
 
66
66
  function formatInjectionBlock(inj) {
67
67
  if (!inj.available) return ["### Prompt-injection scan", `- Status: not available — ${inj.reason || "scan failed"}`, ""];
68
- const lines = ["### Prompt-injection scan (specs / domain / onboarding)", `- Files scanned: ${inj.scanned ?? 0}`, `- Findings: ${inj.total ?? 0}`];
68
+ const lines = ["### Prompt-injection scan (agent-context surface: CLAUDE/AGENTS/GEMINI.md, templates, .rulesync, .karajan)", `- Files scanned: ${inj.scanned ?? 0}`, `- Findings: ${inj.total ?? 0}`];
69
69
  if ((inj.total ?? 0) > 0) {
70
70
  for (const [severity, items] of Object.entries(groupInjectionBySeverity(inj.findings || []))) {
71
71
  if (items.length === 0) continue;
@@ -17,6 +17,15 @@ const SCAN_TARGETS = [
17
17
  { rel: ".karajan/specs", kind: "dir" },
18
18
  { rel: ".karajan/onboarding", kind: "dir" },
19
19
  { rel: ".karajan/domain.md", kind: "file" },
20
+ // KJC-TSK-0695: the agent-context surface — whatever lands in the host
21
+ // agent's context every session is the real injection vector.
22
+ { rel: "CLAUDE.md", kind: "file" },
23
+ { rel: "AGENTS.md", kind: "file" },
24
+ { rel: "GEMINI.md", kind: "file" },
25
+ { rel: ".cursorrules", kind: "file" },
26
+ { rel: path.join(".github", "copilot-instructions.md"), kind: "file" },
27
+ { rel: "templates", kind: "dir" },
28
+ { rel: ".rulesync", kind: "dir" },
20
29
  ];
21
30
 
22
31
  export async function collectInjectionFindings(rootDir, logger = null) {
@@ -379,6 +379,7 @@ export function registerMeta(program, { pkgVersion }) {
379
379
  .option("--trend", "Append a sparkline of the harness score across the last 10 runs (uses .karajan/audit-history.db). KJC-TSK-0473.")
380
380
  .option("--report-file <path>", "Write the audit report to disk in addition to stdout. <path> may be a file (extension drives format: .md or .json) or a directory (creates audit-<ISO>.<md|json> inside). $KJ_AUDIT_REPORT_DIR env var is used as default directory if no --report-file is given.")
381
381
  .option("--deterministic-only", "Skip the LLM analysis entirely. Print/persist only the deterministic findings (basalCost, sonar, stack, growth-delta, webperf). Zero tokens spent. Compatible with --report-file and --json.")
382
+ .option("--security", "Focused deterministic security pass: prompt-injection scan over the agent-context surface (CLAUDE.md/AGENTS.md/templates/.rulesync/.karajan) + OSV + Semgrep + Sonar. Skips basal-cost, webperf, madge, knip, ai-slop, harness and the LLM — zero tokens. Designed for agents to self-invoke when a task touches sensitive surface (KJC-TSK-0695).")
382
383
  .option("-y, --yes", "Auto-confirm the 'Continue with LLM analysis?' prompt. Useful in scripts that want the full audit non-interactively. CI/non-TTY paths already auto-confirm without this flag.")
383
384
  .action(async (task, flags) => {
384
385
  await withConfig(pkgVersion, "audit", flags, async ({ config, logger }) => {
@@ -402,6 +403,7 @@ export function registerMeta(program, { pkgVersion }) {
402
403
  noAiSlop: flags.aiSlop === false,
403
404
  reportFile: flags.reportFile || null,
404
405
  deterministicOnly: Boolean(flags.deterministicOnly),
406
+ security: Boolean(flags.security),
405
407
  yes: Boolean(flags.yes),
406
408
  trend: Boolean(flags.trend),
407
409
  });
@@ -237,7 +237,10 @@ function describeInvocation({ task, dimensions, noSonar, noOsv, noSemgrep, noHar
237
237
  return parts.join(" ");
238
238
  }
239
239
 
240
- export async function auditCommand({ task, config, logger, dimensions, json, agentReadiness, path: pathArg, noSonar = false, noOsv = false, noSemgrep = false, noHarness = false, noAiSlop = false, reportFile = null, deterministicOnly = false, yes = false, trend = false, promptFn = null }) {
240
+ export async function auditCommand({ task, config, logger, dimensions, json, agentReadiness, path: pathArg, noSonar = false, noOsv = false, noSemgrep = false, noHarness = false, noAiSlop = false, reportFile = null, deterministicOnly = false, yes = false, trend = false, promptFn = null, security = false }) {
241
+ // KJC-TSK-0695: --security = the focused pass an agent self-invokes.
242
+ // Deterministic by definition (zero tokens) and skips non-security stages.
243
+ if (security) { deterministicOnly = true; noHarness = true; noAiSlop = true; }
241
244
  // --agent-readiness is a STANDALONE, deterministic, LLM-free audit
242
245
  // dimension. It scores any third-party repo for AI-agent readability
243
246
  // (llms.txt presence, page token budgets, robots allowlist, etc.).
@@ -281,6 +284,7 @@ export async function auditCommand({ task, config, logger, dimensions, json, age
281
284
  noOsv,
282
285
  noSemgrep,
283
286
  noAiSlop,
287
+ ...(security ? { securityOnly: true } : {}),
284
288
  };
285
289
  const deterministicCtx = await role.collectDeterministic(roleInput);
286
290
  const deterministicMd = formatDeterministicSummary(deterministicCtx);
@@ -77,8 +77,11 @@ const UNDEF_CHECK_FLAGS = [
77
77
  ];
78
78
 
79
79
  function applyRoleOverrides(out, flags) {
80
+ // A provider override is always a string (`--security codex`). A bare
81
+ // boolean means the flag belongs to the command itself (`kj audit
82
+ // --security`, KJC-TSK-0695) and must not become a provider.
80
83
  for (const [flag, role] of ROLE_PROVIDER_FLAGS) {
81
- if (flags[flag]) out.roles[role].provider = flags[flag];
84
+ if (typeof flags[flag] === "string" && flags[flag]) out.roles[role].provider = flags[flag];
82
85
  }
83
86
  // coder/reviewer also update top-level aliases
84
87
  if (flags.coder) out.coder = flags.coder;
@@ -79,6 +79,7 @@ const B = {
79
79
  [
80
80
  "These findings are never overridable — not even by Solomon arbitration.",
81
81
  "PII is never logged; new dependencies need a reason and a pinned version.",
82
+ "Sensitive surface (auth, user input, secrets, network, deps)? Self-invoke `kj audit --security` — deterministic, zero tokens: prompt-injection over the agent-context files + OSV + Semgrep + Sonar — and remediate the findings before requesting review.",
82
83
  ],
83
84
  "findings with severity, category and file:line — or an explicit 'no security surface touched'."),
84
85
  },
@@ -66,6 +66,8 @@ Invariants (the git gates enforce these — they are not suggestions):
66
66
  and it must be reviewed again. Disagree with a rejection? \`kj solomon\`.
67
67
  - Security findings are never overridable — not even by arbitration. You
68
68
  absorb the security role: \`kj brief security\` states what must be true.
69
+ Task touches auth, user input, secrets, network or deps? Run
70
+ \`kj audit --security\` (zero tokens) and remediate BEFORE the review.
69
71
  - A design decision the card's AC don't cover belongs to the USER: record
70
72
  it as a proposed ADR (\`kj adr add\`) and ask — never bury it in a PR
71
73
  bullet. An oversized-diff warning is not an opinion either: partition,
@@ -48,13 +48,17 @@ export class AuditRole extends AgentRole {
48
48
  * execute() would gather internally)
49
49
  */
50
50
  async collectDeterministic(input) {
51
+ // KJC-TSK-0695: securityOnly = the focused pass an agent self-invokes
52
+ // when a task touches sensitive surface. Only the security collectors
53
+ // run (sonar, osv, semgrep, injection) — zero tokens, fast.
54
+ const securityOnly = typeof input === "object" ? Boolean(input?.securityOnly) : false;
51
55
  const noSonar = typeof input === "object" ? Boolean(input?.noSonar) : false;
52
56
  const noOsv = typeof input === "object" ? Boolean(input?.noOsv) : false;
53
57
  const noSemgrep = typeof input === "object" ? Boolean(input?.noSemgrep) : false;
54
- const noMadge = typeof input === "object" ? Boolean(input?.noMadge) : false;
55
- const noKnip = typeof input === "object" ? Boolean(input?.noKnip) : false;
58
+ const noMadge = (typeof input === "object" ? Boolean(input?.noMadge) : false) || securityOnly;
59
+ const noKnip = (typeof input === "object" ? Boolean(input?.noKnip) : false) || securityOnly;
56
60
  const noInjectionScan = typeof input === "object" ? Boolean(input?.noInjectionScan) : false;
57
- const noAiSlop = typeof input === "object" ? Boolean(input?.noAiSlop) : false;
61
+ const noAiSlop = (typeof input === "object" ? Boolean(input?.noAiSlop) : false) || securityOnly;
58
62
  const projectDir = this.config?.projectDir || process.cwd();
59
63
  let basalCost = null;
60
64
  let growthDelta = null;
@@ -67,11 +71,13 @@ export class AuditRole extends AgentRole {
67
71
  let deadExports = null;
68
72
  let injectionFindings = null;
69
73
  let aiSlop = null;
70
- try {
71
- basalCost = await measureBasalCost(projectDir);
72
- const previous = await loadPreviousAudit(projectDir);
73
- growthDelta = computeGrowthDelta(basalCost, previous);
74
- } catch { /* basal cost is best-effort */ }
74
+ if (!securityOnly) {
75
+ try {
76
+ basalCost = await measureBasalCost(projectDir);
77
+ const previous = await loadPreviousAudit(projectDir);
78
+ growthDelta = computeGrowthDelta(basalCost, previous);
79
+ } catch { /* basal cost is best-effort */ }
80
+ }
75
81
  try {
76
82
  stack = await detectProjectStack(projectDir);
77
83
  } catch { /* stack detect is best-effort */ }
@@ -80,9 +86,11 @@ export class AuditRole extends AgentRole {
80
86
  sonarFindings = await collectSonarFindings(this.config, this.logger);
81
87
  } catch { /* sonar fetch is best-effort */ }
82
88
  }
83
- try {
84
- webperf = collectWebPerfInput(stack, this.config);
85
- } catch { /* webperf input is best-effort */ }
89
+ if (!securityOnly) {
90
+ try {
91
+ webperf = collectWebPerfInput(stack, this.config);
92
+ } catch { /* webperf input is best-effort */ }
93
+ }
86
94
  // OSV vulnerabilities — KJC-TSK-0365. Best-effort: missing
87
95
  // osv-scanner binary returns available:false and the audit
88
96
  // continues without the section.