@danielblomma/cortex-mcp 2.2.4 → 2.4.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 +62 -7
- package/bin/cortex.mjs +31 -167
- package/package.json +2 -2
- package/scaffold/mcp/package-lock.json +24 -21
- package/scaffold/mcp/src/cli/query.ts +36 -2
- package/scaffold/mcp/src/embed.ts +234 -21
- package/scaffold/mcp/src/embedScheduler.ts +69 -1
- package/scaffold/mcp/src/enterprise/index.ts +1 -1
- package/scaffold/mcp/src/enterprise/reviews/changed-files.ts +34 -0
- package/scaffold/mcp/src/enterprise/reviews/pattern-context.ts +231 -0
- package/scaffold/mcp/src/enterprise/tools/enterprise.ts +49 -36
- package/scaffold/mcp/src/paths.ts +3 -5
- package/scaffold/mcp/src/patternEvidence.ts +347 -0
- package/scaffold/mcp/src/search.ts +35 -9
- package/scaffold/mcp/src/searchCore.ts +254 -12
- package/scaffold/mcp/src/searchResults.ts +96 -9
- package/scaffold/mcp/src/types.ts +7 -0
- package/scaffold/mcp/tests/changed-files.test.mjs +41 -0
- package/scaffold/mcp/tests/embed-entities.test.mjs +109 -1
- package/scaffold/mcp/tests/embed-scheduler.test.mjs +52 -0
- package/scaffold/mcp/tests/enterprise-pattern-context.test.mjs +322 -0
- package/scaffold/mcp/tests/paths.test.mjs +11 -3
- package/scaffold/mcp/tests/pattern-evidence.test.mjs +321 -0
- package/scaffold/mcp/tests/query-cli.test.mjs +73 -1
- package/scaffold/mcp/tests/search-graph-score.test.mjs +167 -0
- package/scaffold/scripts/dashboard.mjs +29 -18
- package/scaffold/scripts/doctor.sh +26 -8
- package/scaffold/scripts/ingest.mjs +37 -4
- package/scaffold/scripts/status.sh +13 -6
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { loadContextData } from "../../graph.js";
|
|
2
|
+
import { normalizeRepoPath, runLocalPatternEvidence } from "../../patternEvidence.js";
|
|
3
|
+
import { compareText } from "../../searchResults.js";
|
|
4
|
+
import type { ContextData, PatternEvidenceParams, ToolPayload } from "../../types.js";
|
|
5
|
+
|
|
6
|
+
type PatternEvidenceRunner = (input: PatternEvidenceParams) => Promise<ToolPayload>;
|
|
7
|
+
|
|
8
|
+
// Loads context data lazily on first use and shares it across all targets in
|
|
9
|
+
// one review, instead of re-reading the index from disk per target. Load
|
|
10
|
+
// failures surface per target, matching the per-file error handling below.
|
|
11
|
+
export function createLocalPatternRunner(
|
|
12
|
+
loadData: () => Promise<ContextData> = loadContextData,
|
|
13
|
+
): PatternEvidenceRunner {
|
|
14
|
+
let shared: Promise<ContextData> | undefined;
|
|
15
|
+
return async (params) => {
|
|
16
|
+
shared ??= loadData();
|
|
17
|
+
return runLocalPatternEvidence(params, { data: await shared });
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const PATTERN_REVIEW_QUESTION =
|
|
22
|
+
"Does this change follow the established pattern for this kind of problem in this repository, or does it introduce a second way to solve something that already has a local convention?";
|
|
23
|
+
|
|
24
|
+
const PATTERN_EVIDENCE_ORDER = ["same_file", "same_module", "same_feature_area", "repo_wide"] as const;
|
|
25
|
+
|
|
26
|
+
// Canonicalizes like the pattern evidence engine, then rejects paths that
|
|
27
|
+
// must never appear in review citations (absolute, drive-letter, or
|
|
28
|
+
// parent-escaping paths).
|
|
29
|
+
function normalizeReviewPath(value: string): string | null {
|
|
30
|
+
const normalized = normalizeRepoPath(value);
|
|
31
|
+
if (
|
|
32
|
+
!normalized ||
|
|
33
|
+
normalized.startsWith("/") ||
|
|
34
|
+
/^[A-Za-z]:\//.test(normalized) ||
|
|
35
|
+
normalized.split("/").includes("..")
|
|
36
|
+
) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return normalized;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function safeIdentifier(value: unknown): string | null {
|
|
43
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 1000) return null;
|
|
44
|
+
if (/(?:^|:)\/|[A-Za-z]:[\\/]|(?:^|[:/])\.\.(?:[:/]|$)/u.test(value)) return null;
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function emptyTiers(): ToolPayload[] {
|
|
49
|
+
return PATTERN_EVIDENCE_ORDER.map((name) => ({ name, evidence: [] }));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function sanitizeEvidenceTiers(value: unknown): { tiers: ToolPayload[]; dropped: number } {
|
|
53
|
+
const inputTiers = Array.isArray(value) ? value : [];
|
|
54
|
+
let dropped = 0;
|
|
55
|
+
const tiers = PATTERN_EVIDENCE_ORDER.map((name) => {
|
|
56
|
+
const source = inputTiers.find((candidate) =>
|
|
57
|
+
candidate && typeof candidate === "object" && (candidate as Record<string, unknown>).name === name
|
|
58
|
+
) as Record<string, unknown> | undefined;
|
|
59
|
+
const rawEvidence = Array.isArray(source?.evidence) ? source.evidence : [];
|
|
60
|
+
const evidence: ToolPayload[] = [];
|
|
61
|
+
for (const candidate of rawEvidence) {
|
|
62
|
+
if (!candidate || typeof candidate !== "object") {
|
|
63
|
+
dropped += 1;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const row = candidate as Record<string, unknown>;
|
|
67
|
+
const citationPath = typeof row.path === "string" ? normalizeReviewPath(row.path) : null;
|
|
68
|
+
const id = safeIdentifier(row.id);
|
|
69
|
+
if (!citationPath || !id) {
|
|
70
|
+
dropped += 1;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
evidence.push({
|
|
74
|
+
id,
|
|
75
|
+
entity_type: typeof row.entity_type === "string" ? row.entity_type.slice(0, 100) : "",
|
|
76
|
+
kind: typeof row.kind === "string" ? row.kind.slice(0, 100) : "",
|
|
77
|
+
title: typeof row.title === "string" ? row.title.slice(0, 500) : id,
|
|
78
|
+
path: citationPath,
|
|
79
|
+
start_line: Number.isInteger(row.start_line) ? row.start_line : null,
|
|
80
|
+
end_line: Number.isInteger(row.end_line) ? row.end_line : null,
|
|
81
|
+
excerpt: typeof row.excerpt === "string" ? row.excerpt.slice(0, 1000) : "",
|
|
82
|
+
score: typeof row.score === "number" && Number.isFinite(row.score) ? row.score : null,
|
|
83
|
+
matched_rules: Array.isArray(row.matched_rules)
|
|
84
|
+
? row.matched_rules.filter((item): item is string => typeof item === "string").slice(0, 50)
|
|
85
|
+
: [],
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return { name, evidence };
|
|
89
|
+
});
|
|
90
|
+
return { tiers, dropped };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function emptyTarget(file: string, status: "not_indexed" | "error", query?: string): ToolPayload {
|
|
94
|
+
return {
|
|
95
|
+
path: file,
|
|
96
|
+
status,
|
|
97
|
+
review_question: PATTERN_REVIEW_QUESTION,
|
|
98
|
+
query: query ?? null,
|
|
99
|
+
query_source: query ? "explicit" : null,
|
|
100
|
+
local_pattern_found: false,
|
|
101
|
+
fallback_used: false,
|
|
102
|
+
evidence_order: [...PATTERN_EVIDENCE_ORDER],
|
|
103
|
+
tiers: emptyTiers(),
|
|
104
|
+
warning: null,
|
|
105
|
+
message: status === "not_indexed"
|
|
106
|
+
? "Target is not present as a file-backed entity in the current Cortex index. Run cortex update before reviewing it."
|
|
107
|
+
: "Pattern evidence could not be produced for this target.",
|
|
108
|
+
citations_dropped: 0,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function disabledPatternReview(requested: number, limit: number, topK: number): ToolPayload {
|
|
113
|
+
return {
|
|
114
|
+
enabled: false,
|
|
115
|
+
non_blocking: true,
|
|
116
|
+
affects_policy_summary: false,
|
|
117
|
+
review_question: PATTERN_REVIEW_QUESTION,
|
|
118
|
+
limit,
|
|
119
|
+
top_k_per_tier: topK,
|
|
120
|
+
targets: [],
|
|
121
|
+
summary: {
|
|
122
|
+
requested,
|
|
123
|
+
eligible: 0,
|
|
124
|
+
analyzed: 0,
|
|
125
|
+
local_evidence: 0,
|
|
126
|
+
repo_fallback: 0,
|
|
127
|
+
no_evidence: 0,
|
|
128
|
+
not_indexed: 0,
|
|
129
|
+
errors: 0,
|
|
130
|
+
omitted: 0,
|
|
131
|
+
invalid_paths: 0,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function buildPatternReviewContext(input: {
|
|
137
|
+
files: string[];
|
|
138
|
+
enabled: boolean;
|
|
139
|
+
query?: string;
|
|
140
|
+
topK: number;
|
|
141
|
+
limit: number;
|
|
142
|
+
runner?: PatternEvidenceRunner;
|
|
143
|
+
}): Promise<ToolPayload> {
|
|
144
|
+
if (!input.enabled) {
|
|
145
|
+
return disabledPatternReview(input.files.length, input.limit, input.topK);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const normalized = input.files.map(normalizeReviewPath);
|
|
149
|
+
const invalidPaths = normalized.filter((value) => value === null).length;
|
|
150
|
+
const eligible = [...new Set(normalized.filter((value): value is string => value !== null))]
|
|
151
|
+
.sort(compareText);
|
|
152
|
+
const selected = eligible.slice(0, input.limit);
|
|
153
|
+
const runner = input.runner ?? createLocalPatternRunner();
|
|
154
|
+
const targets: ToolPayload[] = [];
|
|
155
|
+
const counts = {
|
|
156
|
+
local_evidence: 0,
|
|
157
|
+
repo_fallback: 0,
|
|
158
|
+
no_evidence: 0,
|
|
159
|
+
not_indexed: 0,
|
|
160
|
+
errors: 0,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
for (const file of selected) {
|
|
164
|
+
try {
|
|
165
|
+
const evidence = await runner({
|
|
166
|
+
target: file,
|
|
167
|
+
query: input.query,
|
|
168
|
+
top_k: input.topK,
|
|
169
|
+
include_deprecated: false,
|
|
170
|
+
});
|
|
171
|
+
const sanitized = sanitizeEvidenceTiers(evidence.tiers);
|
|
172
|
+
const localPatternFound = sanitized.tiers.slice(0, 3).some((tier) =>
|
|
173
|
+
Array.isArray(tier.evidence) && tier.evidence.length > 0
|
|
174
|
+
);
|
|
175
|
+
const repoEvidence = sanitized.tiers[3].evidence;
|
|
176
|
+
const fallbackUsed = !localPatternFound && Array.isArray(repoEvidence) && repoEvidence.length > 0;
|
|
177
|
+
const status = localPatternFound
|
|
178
|
+
? "local_evidence"
|
|
179
|
+
: fallbackUsed
|
|
180
|
+
? "repo_fallback"
|
|
181
|
+
: "no_evidence";
|
|
182
|
+
counts[status] += 1;
|
|
183
|
+
const warning = status === "local_evidence"
|
|
184
|
+
? typeof evidence.warning === "string" ? "Pattern evidence completed with local runtime warnings." : null
|
|
185
|
+
: status === "repo_fallback"
|
|
186
|
+
? "No applicable local pattern evidence was found; repository-wide fallback evidence is provided."
|
|
187
|
+
: "No applicable local pattern evidence was found.";
|
|
188
|
+
targets.push({
|
|
189
|
+
path: file,
|
|
190
|
+
status,
|
|
191
|
+
review_question: PATTERN_REVIEW_QUESTION,
|
|
192
|
+
query: typeof evidence.query === "string" ? evidence.query.slice(0, 1000) : input.query ?? null,
|
|
193
|
+
query_source: evidence.query_source === "explicit" || evidence.query_source === "derived_from_target"
|
|
194
|
+
? evidence.query_source
|
|
195
|
+
: input.query ? "explicit" : null,
|
|
196
|
+
local_pattern_found: localPatternFound,
|
|
197
|
+
fallback_used: fallbackUsed,
|
|
198
|
+
evidence_order: [...PATTERN_EVIDENCE_ORDER],
|
|
199
|
+
tiers: sanitized.tiers,
|
|
200
|
+
warning,
|
|
201
|
+
message: null,
|
|
202
|
+
citations_dropped: sanitized.dropped,
|
|
203
|
+
});
|
|
204
|
+
} catch (error) {
|
|
205
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
206
|
+
const notIndexed = /not found in indexed context|not file-backed/u.test(message);
|
|
207
|
+
const status = notIndexed ? "not_indexed" : "error";
|
|
208
|
+
counts[notIndexed ? "not_indexed" : "errors"] += 1;
|
|
209
|
+
targets.push(emptyTarget(file, status, input.query));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
enabled: true,
|
|
215
|
+
non_blocking: true,
|
|
216
|
+
affects_policy_summary: false,
|
|
217
|
+
review_question: PATTERN_REVIEW_QUESTION,
|
|
218
|
+
query: input.query ?? null,
|
|
219
|
+
limit: input.limit,
|
|
220
|
+
top_k_per_tier: input.topK,
|
|
221
|
+
targets,
|
|
222
|
+
summary: {
|
|
223
|
+
requested: input.files.length,
|
|
224
|
+
eligible: eligible.length,
|
|
225
|
+
analyzed: selected.length,
|
|
226
|
+
...counts,
|
|
227
|
+
omitted: Math.max(0, eligible.length - selected.length),
|
|
228
|
+
invalid_paths: invalidPaths,
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
|
@@ -4,6 +4,7 @@ import { readFileSync, statSync, writeFileSync } from "node:fs";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { walkProjectFiles } from "./walk.js";
|
|
7
|
+
import { resolveChangedReviewFiles } from "../reviews/changed-files.js";
|
|
7
8
|
import type { EnterpriseConfig } from "../../core/config.js";
|
|
8
9
|
import type { TelemetryCollector } from "../../core/telemetry/collector.js";
|
|
9
10
|
import type { AuditWriter } from "../../core/audit/writer.js";
|
|
@@ -16,6 +17,7 @@ import { queueViolation } from "../violations/push.js";
|
|
|
16
17
|
import { getReviewPushContext, queueReviewResult } from "../reviews/push.js";
|
|
17
18
|
import { partitionReviewPolicies } from "../reviews/policy-selection.js";
|
|
18
19
|
import { recordQueuedReviewTrustState } from "../reviews/trust-state.js";
|
|
20
|
+
import { buildPatternReviewContext } from "../reviews/pattern-context.js";
|
|
19
21
|
import { pushWorkflowSnapshot } from "../workflow/push.js";
|
|
20
22
|
import { OUTBOUND_DATA_BOUNDARY } from "../privacy/boundary.js";
|
|
21
23
|
import {
|
|
@@ -41,6 +43,25 @@ type ToolPayload = Record<string, unknown>;
|
|
|
41
43
|
|
|
42
44
|
const VALID_ROLES = new Set<Role>(["admin", "developer", "readonly"]);
|
|
43
45
|
|
|
46
|
+
export function buildContextReviewAuditInput(input: {
|
|
47
|
+
scope: "all" | "changed";
|
|
48
|
+
include_passed: boolean;
|
|
49
|
+
include_pattern_evidence: boolean;
|
|
50
|
+
pattern_query?: string;
|
|
51
|
+
pattern_top_k: number;
|
|
52
|
+
pattern_limit: number;
|
|
53
|
+
}): Record<string, unknown> {
|
|
54
|
+
return {
|
|
55
|
+
scope: input.scope,
|
|
56
|
+
include_passed: input.include_passed,
|
|
57
|
+
include_pattern_evidence: input.include_pattern_evidence,
|
|
58
|
+
pattern_query_present: Boolean(input.pattern_query),
|
|
59
|
+
pattern_query_length: input.pattern_query?.length ?? 0,
|
|
60
|
+
pattern_top_k: input.pattern_top_k,
|
|
61
|
+
pattern_limit: input.pattern_limit,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
44
65
|
function snapshotReviewedFiles(
|
|
45
66
|
projectRoot: string,
|
|
46
67
|
changedFiles: string[] | undefined,
|
|
@@ -828,12 +849,20 @@ export function registerEnterpriseTools(
|
|
|
828
849
|
description:
|
|
829
850
|
"Run enterprise policy validators against the current project. " +
|
|
830
851
|
"Checks enforced policies (test coverage, file size, external API calls, code review) " +
|
|
831
|
-
"and returns pass/fail results
|
|
852
|
+
"and returns pass/fail results plus non-blocking repo-local pattern evidence.",
|
|
832
853
|
inputSchema: z.object({
|
|
833
854
|
scope: z.enum(["all", "changed"]).default("changed")
|
|
834
855
|
.describe("'changed' validates only git-modified files; 'all' validates everything"),
|
|
835
856
|
include_passed: z.boolean().default(true)
|
|
836
857
|
.describe("Include passing validators in results"),
|
|
858
|
+
include_pattern_evidence: z.boolean().default(true)
|
|
859
|
+
.describe("Include bounded, local-only repo pattern context without changing pass/fail"),
|
|
860
|
+
pattern_query: z.string().trim().min(1).max(1000).optional()
|
|
861
|
+
.describe("Optional shared pattern query; omitted queries are derived per target"),
|
|
862
|
+
pattern_top_k: z.number().int().min(1).max(5).default(2)
|
|
863
|
+
.describe("Maximum evidence items returned per locality tier and target"),
|
|
864
|
+
pattern_limit: z.number().int().min(1).max(25).default(10)
|
|
865
|
+
.describe("Maximum review targets analyzed for pattern evidence"),
|
|
837
866
|
}),
|
|
838
867
|
},
|
|
839
868
|
async (input) => {
|
|
@@ -844,6 +873,10 @@ export function registerEnterpriseTools(
|
|
|
844
873
|
const parsed = z.object({
|
|
845
874
|
scope: z.enum(["all", "changed"]).default("changed"),
|
|
846
875
|
include_passed: z.boolean().default(true),
|
|
876
|
+
include_pattern_evidence: z.boolean().default(true),
|
|
877
|
+
pattern_query: z.string().trim().min(1).max(1000).optional(),
|
|
878
|
+
pattern_top_k: z.number().int().min(1).max(5).default(2),
|
|
879
|
+
pattern_limit: z.number().int().min(1).max(25).default(10),
|
|
847
880
|
}).parse(input ?? {});
|
|
848
881
|
|
|
849
882
|
const projectRoot = process.env.CORTEX_PROJECT_ROOT?.trim() || process.cwd();
|
|
@@ -859,40 +892,7 @@ export function registerEnterpriseTools(
|
|
|
859
892
|
// project review; no git dependency.
|
|
860
893
|
let changedFiles: string[] | undefined;
|
|
861
894
|
if (parsed.scope === "changed") {
|
|
862
|
-
|
|
863
|
-
// "valid repo, nothing changed" (→ empty list, reviewer sees
|
|
864
|
-
// nothing to scan) from "not a repo / git broken" (→ walk the
|
|
865
|
-
// project so scope=changed still returns meaningful results
|
|
866
|
-
// when run outside a git checkout).
|
|
867
|
-
let inGitRepo = false;
|
|
868
|
-
try {
|
|
869
|
-
const { execSync } = await import("node:child_process");
|
|
870
|
-
execSync("git rev-parse --is-inside-work-tree", {
|
|
871
|
-
cwd: projectRoot,
|
|
872
|
-
encoding: "utf8",
|
|
873
|
-
timeout: 3000,
|
|
874
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
875
|
-
});
|
|
876
|
-
inGitRepo = true;
|
|
877
|
-
} catch {
|
|
878
|
-
inGitRepo = false;
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
if (inGitRepo) {
|
|
882
|
-
try {
|
|
883
|
-
const { execSync } = await import("node:child_process");
|
|
884
|
-
const output = execSync("git diff --name-only HEAD 2>/dev/null || git diff --name-only", {
|
|
885
|
-
cwd: projectRoot,
|
|
886
|
-
encoding: "utf8",
|
|
887
|
-
timeout: 5000,
|
|
888
|
-
});
|
|
889
|
-
changedFiles = output.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
890
|
-
} catch {
|
|
891
|
-
changedFiles = [];
|
|
892
|
-
}
|
|
893
|
-
} else {
|
|
894
|
-
changedFiles = walkProjectFiles(projectRoot);
|
|
895
|
-
}
|
|
895
|
+
changedFiles = resolveChangedReviewFiles(projectRoot) ?? walkProjectFiles(projectRoot);
|
|
896
896
|
} else {
|
|
897
897
|
changedFiles = walkProjectFiles(projectRoot);
|
|
898
898
|
}
|
|
@@ -914,6 +914,14 @@ export function registerEnterpriseTools(
|
|
|
914
914
|
projectRoot,
|
|
915
915
|
changedFiles,
|
|
916
916
|
}, config.validators);
|
|
917
|
+
const patternReview = await buildPatternReviewContext({
|
|
918
|
+
files: changedFiles ?? [],
|
|
919
|
+
enabled: parsed.include_pattern_evidence,
|
|
920
|
+
query: parsed.pattern_query,
|
|
921
|
+
topK: parsed.pattern_top_k,
|
|
922
|
+
limit: parsed.pattern_limit,
|
|
923
|
+
});
|
|
924
|
+
const patternSummary = patternReview.summary as Record<string, unknown>;
|
|
917
925
|
|
|
918
926
|
// Filter out passed if requested
|
|
919
927
|
const results = parsed.include_passed
|
|
@@ -944,7 +952,7 @@ export function registerEnterpriseTools(
|
|
|
944
952
|
recordToolActivity({
|
|
945
953
|
timestamp: now,
|
|
946
954
|
tool: "context.review",
|
|
947
|
-
input: parsed
|
|
955
|
+
input: buildContextReviewAuditInput(parsed),
|
|
948
956
|
result_count: output.results.length,
|
|
949
957
|
entities_returned: output.results.map((r) => r.policy_id),
|
|
950
958
|
rules_applied: output.results.filter((r) => !r.pass).map((r) => r.policy_id),
|
|
@@ -958,6 +966,10 @@ export function registerEnterpriseTools(
|
|
|
958
966
|
failed: output.summary.failed,
|
|
959
967
|
warnings: output.results.filter((r) => !r.pass && r.severity === "warning").length,
|
|
960
968
|
skipped: skippedPolicies.length,
|
|
969
|
+
pattern_analyzed: patternSummary.analyzed ?? 0,
|
|
970
|
+
pattern_local_evidence: patternSummary.local_evidence ?? 0,
|
|
971
|
+
pattern_repo_fallback: patternSummary.repo_fallback ?? 0,
|
|
972
|
+
pattern_omitted: patternSummary.omitted ?? 0,
|
|
961
973
|
},
|
|
962
974
|
});
|
|
963
975
|
|
|
@@ -1018,6 +1030,7 @@ export function registerEnterpriseTools(
|
|
|
1018
1030
|
workflow_source: trustState.workflow.source,
|
|
1019
1031
|
delivery: trustState.delivery,
|
|
1020
1032
|
trust_warnings: trustState.trust_warnings,
|
|
1033
|
+
pattern_review: patternReview,
|
|
1021
1034
|
});
|
|
1022
1035
|
},
|
|
1023
1036
|
);
|
|
@@ -98,12 +98,10 @@ export const PATHS = {
|
|
|
98
98
|
transformsConfigRelations: path.join(CACHE_DIR, "relations.transforms_config.jsonl")
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
-
//
|
|
102
|
-
// hubs, leaf code is not), so it gets low weight. Tuned together with the
|
|
103
|
-
// midrank-percentile graph_score in searchResults.ts.
|
|
101
|
+
// Tuned together with the midrank-percentile graph_score in searchResults.ts.
|
|
104
102
|
export const DEFAULT_RANKING: RankingWeights = {
|
|
105
|
-
semantic: 0.
|
|
106
|
-
graph: 0.
|
|
103
|
+
semantic: 0.4,
|
|
104
|
+
graph: 0.25,
|
|
107
105
|
trust: 0.2,
|
|
108
106
|
recency: 0.15
|
|
109
107
|
};
|