karajan-code 4.49.3 → 4.49.5
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.49.
|
|
3
|
+
"version": "4.49.5",
|
|
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",
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"postinstall": "node scripts/postinstall.js",
|
|
77
77
|
"setup": "node scripts/install.js",
|
|
78
78
|
"test": "vitest run",
|
|
79
|
+
"test:scenarios": "vitest run --config tests/scenarios/vitest.config.js --root .",
|
|
79
80
|
"test:watch": "vitest",
|
|
80
81
|
"test:coverage": "vitest run --coverage",
|
|
81
82
|
"lint": "eslint src/ packages/",
|
|
@@ -259,7 +259,12 @@ export function registerPipeline(program, { pkgVersion }) {
|
|
|
259
259
|
// ENV-B1 (KJC-TSK-0637): the gate mode records a verdict tied to
|
|
260
260
|
// the exact diff so the pre-commit hook can enforce cross-AI review.
|
|
261
261
|
if (flags.staged || flags.check || flags.range || flags.installGate || flags.prune) {
|
|
262
|
-
|
|
262
|
+
// KJC-BUG-0349: the gate's task is optional, but --task-file carries
|
|
263
|
+
// the evidence the reviewer must weigh; it was never read here.
|
|
264
|
+
const gateTask = task || flags.taskFile
|
|
265
|
+
? await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger })
|
|
266
|
+
: task;
|
|
267
|
+
await reviewGateCommand({ config, logger, flags: { ...flags, task: gateTask } });
|
|
263
268
|
return;
|
|
264
269
|
}
|
|
265
270
|
const resolvedTask = await resolveTaskInput({ task, taskFile: flags.taskFile, projectDir: config.projectDir, logger });
|
package/src/commands/rag.js
CHANGED
|
@@ -10,6 +10,7 @@ import { indexFile, indexProject, indexProjectDelta, sourceKind } from "../rag/i
|
|
|
10
10
|
import { indexableReason, ragExclude } from "../rag/indexable.js";
|
|
11
11
|
import { fileIndexState } from "../rag/coverage.js";
|
|
12
12
|
import { query } from "../rag/retriever.js";
|
|
13
|
+
import { pathNamedHits, withPathNamedHits } from "../rag/path-hits.js";
|
|
13
14
|
import { installPostMergeHook, maybeAutoUpdate } from "../rag/auto-update.js";
|
|
14
15
|
import { indexLibrary, LIBRARY_PROJECT } from "../rag/library.js";
|
|
15
16
|
import { loadGoldenQueries, runEval } from "../rag/eval.js";
|
|
@@ -166,7 +167,8 @@ export async function ragQueryCommand({ text, config, logger, flags = {} }) {
|
|
|
166
167
|
let where = flags.where || null;
|
|
167
168
|
if (library) where = flags.where ? `kind=library AND ${flags.where}` : "kind=library";
|
|
168
169
|
const rerankOpts = flags.rerank ? { model: flags.rerankModel } : null;
|
|
169
|
-
const
|
|
170
|
+
const ranked = await query(db, makeGovernedEmbedder(config), text, { topK, scope, project, mode, alpha, where, rerankOpts });
|
|
171
|
+
const hits = library ? ranked : withPathNamedHits(pathNamedHits(db, text, { project, projectDir: config?.projectDir || process.cwd() }), ranked, topK);
|
|
170
172
|
if (flags.json) {
|
|
171
173
|
process.stdout.write(`${JSON.stringify(hits)}\n`);
|
|
172
174
|
} else {
|
|
@@ -9,6 +9,7 @@ import { makeGovernedEmbedder } from "../../rag/governed-embedder.js";
|
|
|
9
9
|
import { indexProject } from "../../rag/indexer.js";
|
|
10
10
|
import { ragExclude } from "../../rag/indexable.js";
|
|
11
11
|
import { query } from "../../rag/retriever.js";
|
|
12
|
+
import { pathNamedHits, withPathNamedHits } from "../../rag/path-hits.js";
|
|
12
13
|
import { getKarajanHome } from "../../utils/paths.js";
|
|
13
14
|
import { resolveProjectDir, buildConfig, responseText, failPayload } from "../shared-helpers.js";
|
|
14
15
|
|
|
@@ -40,7 +41,9 @@ export async function handleRagQuery(args, server) {
|
|
|
40
41
|
const remedy = emptyIndexRemedy({ slug: project, legacyPath: join(getKarajanHome(), "rag.db"), dim });
|
|
41
42
|
return responseText({ hits: [], empty: true, topK, scope, project, remedy });
|
|
42
43
|
}
|
|
43
|
-
const
|
|
44
|
+
const ranked = await query(db, makeGovernedEmbedder(config), text, { topK, scope, project });
|
|
45
|
+
// KJC-BUG-0348: a file the question names comes first, as in the CLI.
|
|
46
|
+
const hits = withPathNamedHits(pathNamedHits(db, text, { project, projectDir }), ranked, topK);
|
|
44
47
|
return responseText({ hits, empty: false, topK, scope, project });
|
|
45
48
|
} finally { db.close(); }
|
|
46
49
|
} catch (err) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KJC-BUG-0348 (#2115): ranking alone decides what a query returns, and in a
|
|
3
|
+
* large index a freshly indexed file (an .astro page) never made the top hits,
|
|
4
|
+
* neither by its path nor by its text: the rag-first gate kept blocking it.
|
|
5
|
+
* A query that NAMES an indexed file returns that file first, deterministically.
|
|
6
|
+
*/
|
|
7
|
+
import { isAbsolute, relative } from "node:path";
|
|
8
|
+
|
|
9
|
+
// A path-like token: segments joined by "/" ending in a file extension.
|
|
10
|
+
const PATH_TOKEN = /[\w@.-]+(?:\/[\w@.-]+)*\.[A-Za-z0-9]+/g;
|
|
11
|
+
|
|
12
|
+
const parseMetadata = (raw) => {
|
|
13
|
+
try { return raw ? JSON.parse(raw) : {}; } catch { return {}; }
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** @returns {object[]} the first chunk of each indexed file of `project` the text names */
|
|
17
|
+
export function pathNamedHits(db, text, { project, projectDir }) {
|
|
18
|
+
const tokens = [...new Set(String(text || "").match(PATH_TOKEN) ?? [])];
|
|
19
|
+
if (!project || tokens.length === 0) return [];
|
|
20
|
+
const sources = db.prepare("SELECT DISTINCT source FROM chunks WHERE project_slug = ?").all(project).map((r) => r.source);
|
|
21
|
+
const named = sources.filter((source) => {
|
|
22
|
+
const rel = (isAbsolute(source) ? relative(projectDir, source) : source).split("\\").join("/");
|
|
23
|
+
return tokens.some((t) => rel === t || rel.endsWith(`/${t}`));
|
|
24
|
+
});
|
|
25
|
+
const first = db.prepare("SELECT id, source, kind, text, metadata, project_slug FROM chunks WHERE project_slug = ? AND source = ? ORDER BY id LIMIT 1");
|
|
26
|
+
return named.map((source) => {
|
|
27
|
+
const row = first.get(project, source);
|
|
28
|
+
return { ...row, metadata: parseMetadata(row.metadata), distance: 0, score: 0, matchedBy: "path" };
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The named files first, then the ranked hits, never the same chunk twice, at most topK. */
|
|
33
|
+
export function withPathNamedHits(named, ranked, topK) {
|
|
34
|
+
const ids = new Set(named.map((h) => h.id));
|
|
35
|
+
return [...named, ...ranked.filter((h) => !ids.has(h.id))].slice(0, topK);
|
|
36
|
+
}
|
package/src/sonar/scanner.js
CHANGED
|
@@ -8,7 +8,7 @@ import { waitForAnalysis } from "./api.js";
|
|
|
8
8
|
import { resolveSonarProjectKey } from "./project-key.js";
|
|
9
9
|
import {
|
|
10
10
|
resolveSonarHost,
|
|
11
|
-
|
|
11
|
+
resolveSonarTokenAsync,
|
|
12
12
|
resolveSonarCredentials,
|
|
13
13
|
} from "./config-resolver.js";
|
|
14
14
|
|
|
@@ -210,8 +210,9 @@ async function maybeRunCoverage(config) {
|
|
|
210
210
|
return checkLcovExists(lcovPath, blockOnFailure, run.stdout || "");
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
async function resolveSonarTokenWithFallback(config, apiHost) {
|
|
214
|
-
|
|
213
|
+
export async function resolveSonarTokenWithFallback(config, apiHost) {
|
|
214
|
+
// KJC-BUG-0350: the async resolver also reads the token the bootstrap persisted.
|
|
215
|
+
const explicitToken = await resolveSonarTokenAsync(config);
|
|
215
216
|
if (explicitToken) return explicitToken;
|
|
216
217
|
|
|
217
218
|
const { user: adminUser, passwords } = await resolveSonarCredentials(config);
|