karajan-code 2.8.0 → 2.9.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 +1 -1
- package/package.json +1 -1
- package/src/audit/basal-cost.js +92 -9
- package/src/audit/deterministic-summary.js +200 -0
- package/src/audit/osv-findings.js +162 -0
- package/src/audit/semgrep-findings.js +117 -0
- package/src/audit/sonar-findings.js +81 -0
- package/src/audit/webperf-input.js +90 -0
- package/src/canvas/schema.js +1 -1
- package/src/checks/binaries.js +2 -2
- package/src/checks/ci.js +2 -2
- package/src/checks/config-files.js +6 -6
- package/src/checks/dir-setup.js +4 -0
- package/src/checks/mcp-health.js +6 -0
- package/src/checks/ports.js +8 -0
- package/src/checks/rtk.js +1 -1
- package/src/checks/skills.js +5 -1
- package/src/checks/sonar.js +1 -1
- package/src/checks/system.js +2 -2
- package/src/checks/tokens.js +1 -1
- package/src/checks/types.js +0 -12
- package/src/cli/register-meta.js +15 -1
- package/src/commands/audit.js +239 -28
- package/src/commands/doctor.js +1 -1
- package/src/config/test-harness.js +0 -1
- package/src/orchestrator/ci-integration.js +1 -1
- package/src/orchestrator/direct-actions.js +1 -1
- package/src/orchestrator/drivers/error-recovery.js +2 -2
- package/src/orchestrator/drivers/iteration-loop.js +3 -13
- package/src/orchestrator/drivers/pre-loop.js +1 -4
- package/src/orchestrator/feedback-queue.js +0 -1
- package/src/orchestrator/integrations.js +0 -7
- package/src/orchestrator/role-output-compressor.js +0 -1
- package/src/orchestrator/session-journal.js +4 -41
- package/src/orchestrator/stages/stage-classes.js +0 -2
- package/src/plan/plan-store.js +0 -14
- package/src/planning-game/client.js +0 -14
- package/src/planning-game/pipeline-adapter.js +1 -1
- package/src/prompts/audit.js +206 -6
- package/src/prompts/rtk-snippet.js +1 -1
- package/src/roles/audit-role.js +125 -13
- package/src/sonar/api.js +1 -1
- package/src/utils/display/event-handlers.js +1 -1
- package/src/utils/display/header.js +1 -1
- package/src/utils/display/session-summary.js +3 -3
- package/src/utils/display/solomon.js +1 -1
- package/src/utils/role-assigner.js +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sonar findings collector for `kj audit` (KJC-TSK-0361).
|
|
3
|
+
*
|
|
4
|
+
* The audit pipeline already pays for SonarQube to exist (kj run + the
|
|
5
|
+
* SonarStage drive a full scan and gate the pipeline on its result). When
|
|
6
|
+
* the user runs `kj audit`, we don't want to pay for *another* full scan
|
|
7
|
+
* (~1-3 min) — we want to read whatever Sonar already knows. This module
|
|
8
|
+
* does exactly that: ping the configured Sonar host, and if it's
|
|
9
|
+
* reachable, fetch the current open-issue list + quality-gate status via
|
|
10
|
+
* the existing sonar/api.js helpers. Both results are best-effort: a
|
|
11
|
+
* sonar-down host or a never-scanned project quietly returns
|
|
12
|
+
* `{available: false, reason: "..."}` and the audit continues without
|
|
13
|
+
* the section.
|
|
14
|
+
*
|
|
15
|
+
* Why this matters: the LLM auditor was previously asked to find issues
|
|
16
|
+
* by reading source code, but Sonar already produces them with rule IDs
|
|
17
|
+
* (squid:S1234), severity, and line precision. Feeding those in lets the
|
|
18
|
+
* LLM cross-reference its own findings (high-confidence overlap) and
|
|
19
|
+
* focus its tokens on patterns Sonar can't see (architecture, naming,
|
|
20
|
+
* API design).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { isSonarReachable } from "../sonar/manager.js";
|
|
24
|
+
import { getOpenIssues, getQualityGateStatus } from "../sonar/api.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Collect deterministic Sonar findings for the audit prompt.
|
|
28
|
+
*
|
|
29
|
+
* @param {object} config - resolved Karajan config (must have config.sonarqube.host)
|
|
30
|
+
* @param {object} [logger] - optional logger for warn-level traces
|
|
31
|
+
* @returns {Promise<{available: boolean, reason?: string, issues?: object[], total?: number, qualityGate?: object}>}
|
|
32
|
+
*/
|
|
33
|
+
export async function collectSonarFindings(config, logger = null) {
|
|
34
|
+
const sonarConfig = config?.sonarqube || {};
|
|
35
|
+
const host = sonarConfig.host || "http://localhost:9000";
|
|
36
|
+
const healthcheckSeconds = sonarConfig.timeouts?.healthcheckSeconds ?? 5;
|
|
37
|
+
|
|
38
|
+
// Ping first — avoids surfacing a 30-second timeout into audit runtime
|
|
39
|
+
// when Docker is simply down or the user opted out of Sonar.
|
|
40
|
+
const reachable = await isSonarReachable(host, healthcheckSeconds).catch(() => false);
|
|
41
|
+
if (!reachable) {
|
|
42
|
+
if (logger?.warn) logger.warn(`sonar audit input skipped: host ${host} not reachable`);
|
|
43
|
+
return { available: false, reason: `sonar host ${host} not reachable` };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let issuesResult;
|
|
47
|
+
try {
|
|
48
|
+
issuesResult = await getOpenIssues(config);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (logger?.warn) logger.warn(`sonar audit input: getOpenIssues failed: ${err?.message || err}`);
|
|
51
|
+
return { available: false, reason: `getOpenIssues failed: ${err?.message || err}` };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let qualityGate = null;
|
|
55
|
+
try {
|
|
56
|
+
const qg = await getQualityGateStatus(config);
|
|
57
|
+
if (qg?.ok) qualityGate = { status: qg.status, conditions: qg.conditions };
|
|
58
|
+
} catch { /* gate is optional context */ }
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
available: true,
|
|
62
|
+
issues: issuesResult.issues || [],
|
|
63
|
+
total: issuesResult.total || 0,
|
|
64
|
+
qualityGate,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Group Sonar issues by severity, preserving Sonar's canonical ordering.
|
|
70
|
+
* Used by the prompt builder to render the findings section.
|
|
71
|
+
*/
|
|
72
|
+
export function groupIssuesBySeverity(issues) {
|
|
73
|
+
const order = ["BLOCKER", "CRITICAL", "MAJOR", "MINOR", "INFO"];
|
|
74
|
+
const groups = Object.fromEntries(order.map(s => [s, []]));
|
|
75
|
+
for (const issue of issues || []) {
|
|
76
|
+
const sev = issue.severity || "INFO";
|
|
77
|
+
if (groups[sev]) groups[sev].push(issue);
|
|
78
|
+
else groups.INFO.push(issue);
|
|
79
|
+
}
|
|
80
|
+
return groups;
|
|
81
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebPerf input collector for `kj audit` (KJC-TSK-0360).
|
|
3
|
+
*
|
|
4
|
+
* Two modes:
|
|
5
|
+
* - "static-hints": no live measurement available, but the project is
|
|
6
|
+
* frontend so the LLM should look for frontend-perf patterns in the
|
|
7
|
+
* source (bundle bloat, render-blocking, image opt, lazy loading).
|
|
8
|
+
* This is the common path because `kj audit` runs against a code
|
|
9
|
+
* tree, not a live preview server.
|
|
10
|
+
* - "cwv-result": a Core Web Vitals measurement was supplied (either
|
|
11
|
+
* via config.webperf.lastResult, or in the future via a `kj webperf`
|
|
12
|
+
* command that drives Chrome DevTools MCP). evaluateCwv from
|
|
13
|
+
* src/webperf/cwv-gate.js produces a structured verdict the prompt
|
|
14
|
+
* renders verbatim.
|
|
15
|
+
*
|
|
16
|
+
* The "live measurement" path is intentionally NOT wired here: the
|
|
17
|
+
* audit's LLM sub-agent is forbidden from MCP tool use (SUBAGENT_PREAMBLE
|
|
18
|
+
* in src/prompts/audit.js), and spawning Chrome / launching a preview
|
|
19
|
+
* server during a read-only audit is out of scope. When a future
|
|
20
|
+
* `kj webperf` command exists, it can write the result into
|
|
21
|
+
* `config.webperf.lastResult` and this collector will surface it.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { evaluateCwv, CWV_THRESHOLDS } from "../webperf/cwv-gate.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Decide what (if anything) to feed the audit prompt about web perf.
|
|
28
|
+
*
|
|
29
|
+
* @param {object|null} stack - detectProjectStack output (null when unknown)
|
|
30
|
+
* @param {object} [config] - resolved Karajan config (config.webperf may carry a stale CWV result)
|
|
31
|
+
* @returns {{available: boolean, mode?: 'static-hints'|'cwv-result', cwv?: object, reason?: string}}
|
|
32
|
+
*/
|
|
33
|
+
export function collectWebPerfInput(stack, config = {}) {
|
|
34
|
+
const webperfConfig = config?.webperf || {};
|
|
35
|
+
const lastResult = webperfConfig.lastResult || null;
|
|
36
|
+
|
|
37
|
+
if (lastResult && typeof lastResult === "object" && lastResult.metrics) {
|
|
38
|
+
const verdict = evaluateCwv(lastResult.metrics, lastResult.thresholds || webperfConfig.thresholds);
|
|
39
|
+
return {
|
|
40
|
+
available: true,
|
|
41
|
+
mode: "cwv-result",
|
|
42
|
+
url: lastResult.url || webperfConfig.url || null,
|
|
43
|
+
capturedAt: lastResult.capturedAt || null,
|
|
44
|
+
cwv: verdict,
|
|
45
|
+
thresholds: lastResult.thresholds || webperfConfig.thresholds || CWV_THRESHOLDS,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// No live measurement → static hints only when the project actually has
|
|
50
|
+
// a frontend layer to audit. Backend-only projects get nothing.
|
|
51
|
+
const isFrontendish = !stack || stack.isFrontend === true || stack.isFullstack === true;
|
|
52
|
+
if (!isFrontendish) {
|
|
53
|
+
return { available: false, reason: "project is backend-only — no frontend-perf hints to give" };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { available: true, mode: "static-hints" };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Render a CWV verdict (from collectWebPerfInput's cwv-result mode) as
|
|
61
|
+
* concise prompt-friendly markdown. Kept independent from the prompt
|
|
62
|
+
* builder so it stays unit-testable in isolation.
|
|
63
|
+
*/
|
|
64
|
+
export function formatCwvVerdict(input) {
|
|
65
|
+
if (!input || input.mode !== "cwv-result" || !input.cwv) return null;
|
|
66
|
+
const { cwv, url, capturedAt } = input;
|
|
67
|
+
const lines = [];
|
|
68
|
+
if (url) lines.push(`- Target URL: ${url}`);
|
|
69
|
+
if (capturedAt) lines.push(`- Captured: ${capturedAt}`);
|
|
70
|
+
lines.push(`- Verdict: ${cwv.pass ? "PASS" : "FAIL"}`);
|
|
71
|
+
if (Object.keys(cwv.scores || {}).length) {
|
|
72
|
+
lines.push("- Scores:");
|
|
73
|
+
for (const [metric, score] of Object.entries(cwv.scores)) {
|
|
74
|
+
lines.push(` - ${metric.toUpperCase()}: ${score.value} (${score.rating})`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (cwv.blocking?.length) {
|
|
78
|
+
lines.push("- Blocking metrics (above poor threshold):");
|
|
79
|
+
for (const b of cwv.blocking) {
|
|
80
|
+
lines.push(` - ${b.metric.toUpperCase()} ${b.value} > ${b.threshold}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (cwv.advisory?.length) {
|
|
84
|
+
lines.push("- Advisory metrics (between good and poor):");
|
|
85
|
+
for (const a of cwv.advisory) {
|
|
86
|
+
lines.push(` - ${a.metric.toUpperCase()} ${a.value} > good=${a.threshold}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return lines.join("\n");
|
|
90
|
+
}
|
package/src/canvas/schema.js
CHANGED
|
@@ -51,7 +51,7 @@ const Operation = v.looseObject({
|
|
|
51
51
|
acceptance: v.pipe(v.array(AcceptanceTest), v.minLength(1)),
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
const CanvasSchema = v.looseObject({
|
|
55
55
|
// Title is the project / feature name. The planner uses it as the
|
|
56
56
|
// plan name unless the user overrides on prompt.
|
|
57
57
|
title: NonEmptyString,
|
package/src/checks/binaries.js
CHANGED
|
@@ -60,7 +60,7 @@ function createCoreBinaryCheck(bin) {
|
|
|
60
60
|
/**
|
|
61
61
|
* Docker check. Manual strategy (platform-dependent install).
|
|
62
62
|
*/
|
|
63
|
-
|
|
63
|
+
function createDockerCheck() {
|
|
64
64
|
return {
|
|
65
65
|
name: "docker",
|
|
66
66
|
label: "Docker",
|
|
@@ -80,7 +80,7 @@ export function createDockerCheck() {
|
|
|
80
80
|
/**
|
|
81
81
|
* Serena MCP (optional). Prompt strategy: we know how to install it.
|
|
82
82
|
*/
|
|
83
|
-
|
|
83
|
+
function createSerenaCheck() {
|
|
84
84
|
return {
|
|
85
85
|
name: "serena",
|
|
86
86
|
label: "Serena MCP",
|
package/src/checks/ci.js
CHANGED
|
@@ -37,7 +37,7 @@ function createWorkflowCheck(workflow) {
|
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
function createGhCliCheck() {
|
|
41
41
|
return {
|
|
42
42
|
name: "ci:gh",
|
|
43
43
|
label: "CI: gh CLI",
|
|
@@ -55,7 +55,7 @@ export function createGhCliCheck() {
|
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
function createGhSecretsCheck() {
|
|
59
59
|
return {
|
|
60
60
|
name: "ci:secrets",
|
|
61
61
|
label: "CI: GitHub secrets",
|
|
@@ -20,7 +20,7 @@ import { STRATEGY } from "./types.js";
|
|
|
20
20
|
/**
|
|
21
21
|
* kj.config.yml presence check.
|
|
22
22
|
*/
|
|
23
|
-
|
|
23
|
+
function createConfigFileCheck() {
|
|
24
24
|
return {
|
|
25
25
|
name: "config",
|
|
26
26
|
label: "Config file",
|
|
@@ -42,7 +42,7 @@ export function createConfigFileCheck() {
|
|
|
42
42
|
/**
|
|
43
43
|
* Review rules (.md) informational check.
|
|
44
44
|
*/
|
|
45
|
-
|
|
45
|
+
function createReviewRulesCheck() {
|
|
46
46
|
return {
|
|
47
47
|
name: "review-rules",
|
|
48
48
|
label: "Reviewer rules (.md)",
|
|
@@ -63,7 +63,7 @@ export function createReviewRulesCheck() {
|
|
|
63
63
|
/**
|
|
64
64
|
* Coder rules (.md) informational check.
|
|
65
65
|
*/
|
|
66
|
-
|
|
66
|
+
function createCoderRulesCheck() {
|
|
67
67
|
return {
|
|
68
68
|
name: "coder-rules",
|
|
69
69
|
label: "Coder rules (.md)",
|
|
@@ -104,7 +104,7 @@ function findDuplicateTomlKeys(content) {
|
|
|
104
104
|
/**
|
|
105
105
|
* Claude config (~/.claude.json) validity.
|
|
106
106
|
*/
|
|
107
|
-
|
|
107
|
+
function createClaudeConfigCheck() {
|
|
108
108
|
return {
|
|
109
109
|
name: "agent-config:claude",
|
|
110
110
|
label: "Agent config: claude (~/.claude.json)",
|
|
@@ -135,7 +135,7 @@ export function createClaudeConfigCheck() {
|
|
|
135
135
|
/**
|
|
136
136
|
* Codex config (~/.codex/config.toml) validity.
|
|
137
137
|
*/
|
|
138
|
-
|
|
138
|
+
function createCodexConfigCheck() {
|
|
139
139
|
return {
|
|
140
140
|
name: "agent-config:codex",
|
|
141
141
|
label: "Agent config: codex (~/.codex/config.toml)",
|
|
@@ -173,7 +173,7 @@ export function createCodexConfigCheck() {
|
|
|
173
173
|
/**
|
|
174
174
|
* KJ config (~/.karajan/kj.config.yml) YAML validity (separate from existence).
|
|
175
175
|
*/
|
|
176
|
-
|
|
176
|
+
function createKjConfigYamlCheck() {
|
|
177
177
|
return {
|
|
178
178
|
name: "agent-config:karajan",
|
|
179
179
|
label: "Agent config: karajan (kj.config.yml)",
|
package/src/checks/dir-setup.js
CHANGED
|
@@ -15,6 +15,10 @@ const REQUIRED_DIRS = [
|
|
|
15
15
|
"logs",
|
|
16
16
|
];
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @internal Exported for dynamic import from tests/checks/dir-setup.test.js
|
|
20
|
+
* and tests/checks/auto-remediation-e2e.test.js. Knip false positive.
|
|
21
|
+
*/
|
|
18
22
|
export function createKarajanDirsCheck() {
|
|
19
23
|
return {
|
|
20
24
|
name: "karajan-dirs",
|
package/src/checks/mcp-health.js
CHANGED
|
@@ -86,6 +86,9 @@ async function pingMcpStdio(command, args) {
|
|
|
86
86
|
/**
|
|
87
87
|
* karajan-mcp health — stdio server bundled with this package.
|
|
88
88
|
* Strategy: auto (we can re-spawn it ourselves).
|
|
89
|
+
*
|
|
90
|
+
* @internal Exported for dynamic import from tests/checks/mcp-health.test.js.
|
|
91
|
+
* Knip false positive.
|
|
89
92
|
*/
|
|
90
93
|
export function createKarajanMcpCheck() {
|
|
91
94
|
return {
|
|
@@ -121,6 +124,9 @@ export function createKarajanMcpCheck() {
|
|
|
121
124
|
/**
|
|
122
125
|
* Serena MCP — external tool the user installs themselves. Warn only when
|
|
123
126
|
* enabled in config. Strategy: manual (we don't manage its lifecycle).
|
|
127
|
+
*
|
|
128
|
+
* @internal Exported for dynamic import from tests/checks/mcp-health.test.js.
|
|
129
|
+
* Knip false positive.
|
|
124
130
|
*/
|
|
125
131
|
export function createSerenaMcpCheck() {
|
|
126
132
|
return {
|
package/src/checks/ports.js
CHANGED
|
@@ -24,6 +24,10 @@ function extractPortFromHost(host, fallback) {
|
|
|
24
24
|
return m ? Number(m[1]) : fallback;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* @internal Exported for dynamic import from tests/checks/ports.test.js
|
|
29
|
+
* and tests/checks/auto-remediation-e2e.test.js. Knip false positive.
|
|
30
|
+
*/
|
|
27
31
|
export function createSonarPortCheck() {
|
|
28
32
|
return {
|
|
29
33
|
name: "port:sonar",
|
|
@@ -133,6 +137,10 @@ export function createSonarPortCheck() {
|
|
|
133
137
|
};
|
|
134
138
|
}
|
|
135
139
|
|
|
140
|
+
/**
|
|
141
|
+
* @internal Exported for dynamic import from tests/checks/ports.test.js
|
|
142
|
+
* and tests/checks/auto-remediation-e2e.test.js. Knip false positive.
|
|
143
|
+
*/
|
|
136
144
|
export function createHuBoardPortCheck() {
|
|
137
145
|
return {
|
|
138
146
|
name: "port:hu-board",
|
package/src/checks/rtk.js
CHANGED
|
@@ -8,7 +8,7 @@ import { getInstallCommand } from "../utils/os-detect.js";
|
|
|
8
8
|
import { withDocLink } from "../utils/doc-links.js";
|
|
9
9
|
import { STRATEGY } from "./types.js";
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
function createRtkCheck() {
|
|
12
12
|
return {
|
|
13
13
|
name: "rtk",
|
|
14
14
|
label: "RTK (Rust Token Killer)",
|
package/src/checks/skills.js
CHANGED
|
@@ -8,10 +8,14 @@ import { isOpenSkillsAvailable } from "../skills/openskills-client.js";
|
|
|
8
8
|
import { runCommand } from "../utils/process.js";
|
|
9
9
|
import { STRATEGY } from "./types.js";
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
function skillsEnabled(config) {
|
|
12
12
|
return config?.skills?.enabled !== false;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @internal Exported for dynamic import from tests/checks/skills.test.js
|
|
17
|
+
* and tests/checks/auto-remediation-e2e.test.js. Knip false positive.
|
|
18
|
+
*/
|
|
15
19
|
export function createOpenSkillsCheck() {
|
|
16
20
|
return {
|
|
17
21
|
name: "openskills",
|
package/src/checks/sonar.js
CHANGED
|
@@ -26,7 +26,7 @@ function sonarHost(config) {
|
|
|
26
26
|
* Informative check: Sonar enabled/disabled status.
|
|
27
27
|
* Produces an OK result with disabled description when Sonar is off.
|
|
28
28
|
*/
|
|
29
|
-
|
|
29
|
+
function createSonarStatusCheck() {
|
|
30
30
|
return {
|
|
31
31
|
name: "sonarqube",
|
|
32
32
|
label: "SonarQube",
|
package/src/checks/system.js
CHANGED
|
@@ -14,7 +14,7 @@ function getPackageVersion() {
|
|
|
14
14
|
return JSON.parse(readFileSync(pkgPath, "utf8")).version;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
function createKarajanVersionCheck() {
|
|
18
18
|
return {
|
|
19
19
|
name: "karajan",
|
|
20
20
|
label: "Karajan Code",
|
|
@@ -26,7 +26,7 @@ export function createKarajanVersionCheck() {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
function createGitRepoCheck() {
|
|
30
30
|
return {
|
|
31
31
|
name: "git",
|
|
32
32
|
label: "Git repository",
|
package/src/checks/tokens.js
CHANGED
package/src/checks/types.js
CHANGED
|
@@ -91,15 +91,3 @@ export const STRATEGY = Object.freeze({
|
|
|
91
91
|
NONE: "none",
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
export const SEVERITY = Object.freeze({
|
|
95
|
-
FAIL: "fail",
|
|
96
|
-
WARN: "warn",
|
|
97
|
-
INFO: "info",
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Whether the final status blocks the pipeline (for preflight).
|
|
102
|
-
*/
|
|
103
|
-
export function isBlocking(status) {
|
|
104
|
-
return status === STATUS.FAIL || status === STATUS.TIMEOUT;
|
|
105
|
-
}
|
package/src/cli/register-meta.js
CHANGED
|
@@ -90,10 +90,16 @@ export function registerMeta(program, { pkgVersion }) {
|
|
|
90
90
|
.description("Analyze codebase health (read-only)")
|
|
91
91
|
.argument("[task]", "Task description. If absent, defaults to a full-codebase analysis. Use --task-file to point at a .md.")
|
|
92
92
|
.option("--task-file <path>", "Read the task from a file (e.g. .md)")
|
|
93
|
-
.option("--dimensions <list>", "Comma-separated: security,quality,performance,architecture,testing", "all")
|
|
93
|
+
.option("--dimensions <list>", "Comma-separated: security,quality,performance,architecture,testing,accessibility (default: all; accessibility auto-skipped on detected backend-only projects)", "all")
|
|
94
94
|
.option("--json", "Output raw JSON")
|
|
95
95
|
.option("--agent-readiness", "Score the repo for AI-agent readability (llms.txt, SKILL.md coverage, page token budgets, robots allowlist, heading hierarchy). LLM-free; uses [path] or cwd as the audit target. See issue #542.")
|
|
96
96
|
.option("--path <dir>", "Path to audit (used with --agent-readiness; defaults to cwd)")
|
|
97
|
+
.option("--no-sonar", "Skip the SonarQube findings collector (faster, less context). Sonar findings are also skipped automatically when SonarQube is unreachable.")
|
|
98
|
+
.option("--no-osv", "Skip the OSV-Scanner vulnerability collector. Findings are also skipped automatically when osv-scanner is not installed.")
|
|
99
|
+
.option("--no-semgrep", "Skip the Semgrep SAST collector. Findings are also skipped automatically when semgrep is not installed (install via 'pipx install semgrep' or 'brew install semgrep').")
|
|
100
|
+
.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.")
|
|
101
|
+
.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.")
|
|
102
|
+
.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.")
|
|
97
103
|
.action(async (task, flags) => {
|
|
98
104
|
await withConfig(pkgVersion, "audit", flags, async ({ config, logger }) => {
|
|
99
105
|
let resolvedTask = task;
|
|
@@ -101,12 +107,20 @@ export function registerMeta(program, { pkgVersion }) {
|
|
|
101
107
|
const { readTaskFile } = await import("../utils/task-file.js");
|
|
102
108
|
resolvedTask = await readTaskFile(flags.taskFile, { projectDir: config.projectDir });
|
|
103
109
|
}
|
|
110
|
+
// commander resolves --no-sonar to flags.sonar=false (negation flag).
|
|
111
|
+
// We translate to the explicit `noSonar: true` shape AuditRole expects.
|
|
104
112
|
await auditCommand({
|
|
105
113
|
task: resolvedTask || "Analyze the full codebase",
|
|
106
114
|
config, logger,
|
|
107
115
|
dimensions: flags.dimensions, json: flags.json,
|
|
108
116
|
agentReadiness: Boolean(flags.agentReadiness),
|
|
109
117
|
path: flags.path,
|
|
118
|
+
noSonar: flags.sonar === false,
|
|
119
|
+
noOsv: flags.osv === false,
|
|
120
|
+
noSemgrep: flags.semgrep === false,
|
|
121
|
+
reportFile: flags.reportFile || null,
|
|
122
|
+
deterministicOnly: Boolean(flags.deterministicOnly),
|
|
123
|
+
yes: Boolean(flags.yes),
|
|
110
124
|
});
|
|
111
125
|
});
|
|
112
126
|
});
|