@danielblomma/cortex-mcp 2.2.5 → 2.4.1
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/CHANGELOG.md +201 -0
- package/README.md +73 -3
- package/bin/cortex.mjs +339 -242
- package/bin/daemon-control.mjs +162 -0
- package/package.json +7 -3
- package/scaffold/mcp/dist/.cortex-build-hash +1 -0
- package/scaffold/mcp/dist/cli/enterprise-setup.js +134 -0
- package/scaffold/mcp/dist/cli/govern.js +937 -0
- package/scaffold/mcp/dist/cli/query.js +409 -0
- package/scaffold/mcp/dist/cli/run.js +295 -0
- package/scaffold/mcp/dist/cli/stage.js +308 -0
- package/scaffold/mcp/dist/cli/telemetry-test.js +141 -0
- package/scaffold/mcp/dist/cli/ungoverned-detector.js +133 -0
- package/scaffold/mcp/dist/contextEntities.js +282 -0
- package/scaffold/mcp/dist/core/audit/query.js +72 -0
- package/scaffold/mcp/dist/core/audit/writer.js +28 -0
- package/scaffold/mcp/dist/core/config.js +235 -0
- package/scaffold/mcp/dist/core/enterprise-host-identity.js +193 -0
- package/scaffold/mcp/dist/core/enterprise-identity.js +18 -0
- package/scaffold/mcp/dist/core/enterprise-rotation.js +158 -0
- package/scaffold/mcp/dist/core/govern-paths.js +21 -0
- package/scaffold/mcp/dist/core/index.js +14 -0
- package/scaffold/mcp/dist/core/license.js +278 -0
- package/scaffold/mcp/dist/core/policy/enforce.js +66 -0
- package/scaffold/mcp/dist/core/policy/injection.js +172 -0
- package/scaffold/mcp/dist/core/policy/store.js +179 -0
- package/scaffold/mcp/dist/core/rbac/check.js +30 -0
- package/scaffold/mcp/dist/core/secure-endpoint.js +25 -0
- package/scaffold/mcp/dist/core/telemetry/collector.js +285 -0
- package/scaffold/mcp/dist/core/telemetry/state-dir.js +31 -0
- package/scaffold/mcp/dist/core/validators/builtins.js +632 -0
- package/scaffold/mcp/dist/core/validators/config.js +44 -0
- package/scaffold/mcp/dist/core/validators/engine.js +120 -0
- package/scaffold/mcp/dist/core/validators/evaluators/code_comments.js +236 -0
- package/scaffold/mcp/dist/core/validators/evaluators/regex.js +116 -0
- package/scaffold/mcp/dist/core/workflow/artifact-io.js +103 -0
- package/scaffold/mcp/dist/core/workflow/capabilities.js +88 -0
- package/scaffold/mcp/dist/core/workflow/default-workflows.js +102 -0
- package/scaffold/mcp/dist/core/workflow/enforcement.js +164 -0
- package/scaffold/mcp/dist/core/workflow/envelope.js +132 -0
- package/scaffold/mcp/dist/core/workflow/index.js +11 -0
- package/scaffold/mcp/dist/core/workflow/mcp-tools.js +175 -0
- package/scaffold/mcp/dist/core/workflow/resolution.js +69 -0
- package/scaffold/mcp/dist/core/workflow/run-lifecycle.js +123 -0
- package/scaffold/mcp/dist/core/workflow/schemas.js +141 -0
- package/scaffold/mcp/dist/core/workflow/synced-capability-registry.js +60 -0
- package/scaffold/mcp/dist/core/workflow/synced-registry.js +60 -0
- package/scaffold/mcp/dist/daemon/capability-sync-checker.js +242 -0
- package/scaffold/mcp/dist/daemon/client.js +130 -0
- package/scaffold/mcp/dist/daemon/egress-proxy.js +288 -0
- package/scaffold/mcp/dist/daemon/global-host-events.js +222 -0
- package/scaffold/mcp/dist/daemon/heartbeat-pusher.js +116 -0
- package/scaffold/mcp/dist/daemon/heartbeat-tracker.js +165 -0
- package/scaffold/mcp/dist/daemon/host-events-pusher.js +249 -0
- package/scaffold/mcp/dist/daemon/main.js +449 -0
- package/scaffold/mcp/dist/daemon/paths.js +36 -0
- package/scaffold/mcp/dist/daemon/project-service-registry.js +78 -0
- package/scaffold/mcp/dist/daemon/protocol.js +8 -0
- package/scaffold/mcp/dist/daemon/server.js +180 -0
- package/scaffold/mcp/dist/daemon/skill-sync-checker.js +557 -0
- package/scaffold/mcp/dist/daemon/sync-checker.js +165 -0
- package/scaffold/mcp/dist/daemon/ungoverned-scanner.js +136 -0
- package/scaffold/mcp/dist/daemon/workflow-sync-checker.js +249 -0
- package/scaffold/mcp/dist/defaults.js +6 -0
- package/scaffold/mcp/dist/embed.js +627 -0
- package/scaffold/mcp/dist/embedScheduler.js +479 -0
- package/scaffold/mcp/dist/embeddings.js +167 -0
- package/scaffold/mcp/dist/enterprise/audit/push.js +66 -0
- package/scaffold/mcp/dist/enterprise/index.js +327 -0
- package/scaffold/mcp/dist/enterprise/model/deploy.js +27 -0
- package/scaffold/mcp/dist/enterprise/policy/sync.js +129 -0
- package/scaffold/mcp/dist/enterprise/privacy/boundary.js +184 -0
- package/scaffold/mcp/dist/enterprise/reviews/changed-files.js +33 -0
- package/scaffold/mcp/dist/enterprise/reviews/pattern-context.js +202 -0
- package/scaffold/mcp/dist/enterprise/reviews/policy-selection.js +46 -0
- package/scaffold/mcp/dist/enterprise/reviews/push.js +102 -0
- package/scaffold/mcp/dist/enterprise/reviews/trust-state.js +186 -0
- package/scaffold/mcp/dist/enterprise/telemetry/sync.js +57 -0
- package/scaffold/mcp/dist/enterprise/tools/enterprise.js +826 -0
- package/scaffold/mcp/dist/enterprise/tools/harness.js +40 -0
- package/scaffold/mcp/dist/enterprise/tools/walk.js +73 -0
- package/scaffold/mcp/dist/enterprise/violations/push.js +77 -0
- package/scaffold/mcp/dist/enterprise/workflow/push.js +44 -0
- package/scaffold/mcp/dist/enterprise/workflow/state.js +329 -0
- package/scaffold/mcp/dist/frontmatter.js +33 -0
- package/scaffold/mcp/dist/graph.js +769 -0
- package/scaffold/mcp/dist/graphCsv.js +55 -0
- package/scaffold/mcp/dist/graphMetrics.js +8 -0
- package/scaffold/mcp/dist/hooks/permission-request.js +89 -0
- package/scaffold/mcp/dist/hooks/post-tool-use.js +105 -0
- package/scaffold/mcp/dist/hooks/pre-compact.js +29 -0
- package/scaffold/mcp/dist/hooks/pre-tool-use.js +78 -0
- package/scaffold/mcp/dist/hooks/session-end.js +43 -0
- package/scaffold/mcp/dist/hooks/session-start.js +41 -0
- package/scaffold/mcp/dist/hooks/shared.js +194 -0
- package/scaffold/mcp/dist/hooks/stop.js +28 -0
- package/scaffold/mcp/dist/hooks/user-prompt-submit.js +33 -0
- package/scaffold/mcp/dist/impactPresentation.js +137 -0
- package/scaffold/mcp/dist/impactRanking.js +191 -0
- package/scaffold/mcp/dist/impactResponse.js +30 -0
- package/scaffold/mcp/dist/impactResults.js +105 -0
- package/scaffold/mcp/dist/impactSeed.js +20 -0
- package/scaffold/mcp/dist/impactTraversal.js +64 -0
- package/scaffold/mcp/dist/jsonl.js +77 -0
- package/scaffold/mcp/dist/loadGraph.js +759 -0
- package/scaffold/mcp/dist/lruCache.js +38 -0
- package/scaffold/mcp/dist/paths.js +97 -0
- package/scaffold/mcp/dist/patternEvidence.js +272 -0
- package/scaffold/mcp/dist/plugin.js +81 -0
- package/scaffold/mcp/dist/presets.js +78 -0
- package/scaffold/mcp/dist/relatedResponse.js +18 -0
- package/scaffold/mcp/dist/relatedTraversal.js +78 -0
- package/scaffold/mcp/dist/rules.js +23 -0
- package/scaffold/mcp/dist/search.js +212 -0
- package/scaffold/mcp/dist/searchCore.js +457 -0
- package/scaffold/mcp/dist/searchResults.js +230 -0
- package/scaffold/mcp/dist/server.js +317 -0
- package/scaffold/mcp/dist/types.js +1 -0
- package/scaffold/mcp/package-lock.json +336 -212
- package/scaffold/mcp/package.json +15 -6
- package/scaffold/mcp/src/cli/enterprise-setup.ts +82 -8
- package/scaffold/mcp/src/cli/govern.ts +137 -52
- package/scaffold/mcp/src/cli/query.ts +36 -2
- package/scaffold/mcp/src/cli/run.ts +53 -19
- package/scaffold/mcp/src/cli/stage.ts +8 -3
- package/scaffold/mcp/src/core/config.ts +9 -2
- package/scaffold/mcp/src/core/enterprise-host-identity.ts +259 -0
- package/scaffold/mcp/src/core/enterprise-identity.ts +20 -0
- package/scaffold/mcp/src/core/enterprise-rotation.ts +185 -0
- package/scaffold/mcp/src/core/govern-paths.ts +30 -0
- package/scaffold/mcp/src/core/license.ts +186 -17
- package/scaffold/mcp/src/core/secure-endpoint.ts +23 -0
- package/scaffold/mcp/src/core/workflow/enforcement.ts +8 -1
- package/scaffold/mcp/src/core/workflow/mcp-tools.ts +3 -0
- package/scaffold/mcp/src/core/workflow/resolution.ts +9 -1
- package/scaffold/mcp/src/core/workflow/synced-capability-registry.ts +15 -0
- package/scaffold/mcp/src/core/workflow/synced-registry.ts +15 -0
- package/scaffold/mcp/src/daemon/capability-sync-checker.ts +38 -2
- package/scaffold/mcp/src/daemon/egress-proxy.ts +14 -8
- package/scaffold/mcp/src/daemon/global-host-events.ts +288 -0
- package/scaffold/mcp/src/daemon/heartbeat-pusher.ts +4 -0
- package/scaffold/mcp/src/daemon/heartbeat-tracker.ts +2 -0
- package/scaffold/mcp/src/daemon/host-events-pusher.ts +5 -0
- package/scaffold/mcp/src/daemon/main.ts +99 -25
- package/scaffold/mcp/src/daemon/project-service-registry.ts +107 -0
- package/scaffold/mcp/src/daemon/skill-sync-checker.ts +368 -31
- package/scaffold/mcp/src/daemon/sync-checker.ts +4 -0
- package/scaffold/mcp/src/daemon/ungoverned-scanner.ts +57 -25
- package/scaffold/mcp/src/daemon/workflow-sync-checker.ts +41 -2
- package/scaffold/mcp/src/embed.ts +211 -10
- package/scaffold/mcp/src/enterprise/audit/push.ts +8 -0
- package/scaffold/mcp/src/enterprise/index.ts +1 -1
- package/scaffold/mcp/src/enterprise/policy/sync.ts +12 -0
- 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/reviews/push.ts +9 -0
- package/scaffold/mcp/src/enterprise/reviews/trust-state.ts +3 -1
- package/scaffold/mcp/src/enterprise/telemetry/sync.ts +9 -0
- package/scaffold/mcp/src/enterprise/tools/enterprise.ts +49 -36
- package/scaffold/mcp/src/enterprise/violations/push.ts +9 -0
- package/scaffold/mcp/src/enterprise/workflow/push.ts +7 -0
- package/scaffold/mcp/src/paths.ts +3 -5
- package/scaffold/mcp/src/patternEvidence.ts +347 -0
- package/scaffold/mcp/src/plugin.ts +20 -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/copilot-shim.test.mjs +19 -1
- package/scaffold/mcp/tests/egress-proxy.test.mjs +37 -0
- package/scaffold/mcp/tests/embed-entities.test.mjs +109 -1
- package/scaffold/mcp/tests/enterprise-identity-sync.test.mjs +401 -0
- package/scaffold/mcp/tests/enterprise-pattern-context.test.mjs +322 -0
- package/scaffold/mcp/tests/enterprise-setup.test.mjs +189 -0
- package/scaffold/mcp/tests/global-host-events.test.mjs +81 -0
- package/scaffold/mcp/tests/govern-install.test.mjs +95 -2
- package/scaffold/mcp/tests/govern-repair.test.mjs +20 -3
- package/scaffold/mcp/tests/heartbeat-tracker.test.mjs +26 -0
- package/scaffold/mcp/tests/license.test.mjs +367 -0
- package/scaffold/mcp/tests/paths.test.mjs +11 -3
- package/scaffold/mcp/tests/pattern-evidence.test.mjs +321 -0
- package/scaffold/mcp/tests/project-service-registry.test.mjs +172 -0
- package/scaffold/mcp/tests/query-cli.test.mjs +73 -1
- package/scaffold/mcp/tests/review-trust-contract.test.mjs +18 -0
- package/scaffold/mcp/tests/search-graph-score.test.mjs +167 -0
- package/scaffold/mcp/tests/secure-enterprise-endpoint.test.mjs +46 -0
- package/scaffold/mcp/tests/skill-sync-checker.test.mjs +446 -2
- package/scaffold/mcp/tests/ungoverned-scanner.test.mjs +104 -22
- package/scaffold/mcp/tests/workflow-cli.test.mjs +14 -1
- package/scaffold/mcp/tests/workflow-synced-capabilities.test.mjs +37 -0
- package/scaffold/mcp/tests/workflow-synced-registry.test.mjs +40 -1
- 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
|
@@ -6,6 +6,11 @@ import {
|
|
|
6
6
|
import { hostname } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { loadEnterpriseConfig } from "../core/config.js";
|
|
9
|
+
import { matchesEnterpriseHostIdentity } from "../core/enterprise-host-identity.js";
|
|
10
|
+
import {
|
|
11
|
+
enterpriseCredentialId,
|
|
12
|
+
isAllowedLicenseEndpoint,
|
|
13
|
+
} from "../core/license.js";
|
|
9
14
|
import { workflowDefinitionSchema, type WorkflowDefinition } from "../core/workflow/schemas.js";
|
|
10
15
|
import { writeHostAuditEvent } from "./ungoverned-scanner.js";
|
|
11
16
|
import { daemonDir } from "./paths.js";
|
|
@@ -55,6 +60,7 @@ type LocalWorkflowRecord = {
|
|
|
55
60
|
};
|
|
56
61
|
|
|
57
62
|
type LocalWorkflowsState = {
|
|
63
|
+
credential_id?: string;
|
|
58
64
|
workflows: Record<string, LocalWorkflowRecord>;
|
|
59
65
|
last_synced_at?: string;
|
|
60
66
|
};
|
|
@@ -79,6 +85,10 @@ export function readSyncedWorkflowsState(): LocalWorkflowsState {
|
|
|
79
85
|
try {
|
|
80
86
|
const parsed = JSON.parse(readFileSync(path, "utf8")) as LocalWorkflowsState;
|
|
81
87
|
return {
|
|
88
|
+
credential_id:
|
|
89
|
+
typeof parsed.credential_id === "string"
|
|
90
|
+
? parsed.credential_id
|
|
91
|
+
: undefined,
|
|
82
92
|
workflows: parsed.workflows ?? {},
|
|
83
93
|
last_synced_at: parsed.last_synced_at,
|
|
84
94
|
};
|
|
@@ -149,6 +159,28 @@ export async function runWorkflowSyncOnce(
|
|
|
149
159
|
await writeAudit(cwd, outcome);
|
|
150
160
|
return outcome;
|
|
151
161
|
}
|
|
162
|
+
if (!isAllowedLicenseEndpoint(baseUrl)) {
|
|
163
|
+
const outcome: WorkflowSyncOutcome = {
|
|
164
|
+
kind: "failed",
|
|
165
|
+
error: "insecure or invalid enterprise endpoint",
|
|
166
|
+
};
|
|
167
|
+
await writeAudit(cwd, outcome);
|
|
168
|
+
return outcome;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const credentialId = enterpriseCredentialId(baseUrl, apiKey);
|
|
172
|
+
if (!matchesEnterpriseHostIdentity(credentialId)) {
|
|
173
|
+
const outcome: WorkflowSyncOutcome = {
|
|
174
|
+
kind: "failed",
|
|
175
|
+
error:
|
|
176
|
+
"enterprise identity conflict: this user profile is already enrolled to another endpoint or API key",
|
|
177
|
+
};
|
|
178
|
+
await writeAudit(cwd, outcome);
|
|
179
|
+
return outcome;
|
|
180
|
+
}
|
|
181
|
+
const state = readSyncedWorkflowsState();
|
|
182
|
+
const identityChanged = state.credential_id !== credentialId;
|
|
183
|
+
state.credential_id = credentialId;
|
|
152
184
|
|
|
153
185
|
let manifest: ManifestEntry[];
|
|
154
186
|
try {
|
|
@@ -162,7 +194,6 @@ export async function runWorkflowSyncOnce(
|
|
|
162
194
|
return outcome;
|
|
163
195
|
}
|
|
164
196
|
|
|
165
|
-
const state = readSyncedWorkflowsState();
|
|
166
197
|
const remoteByName = new Map(manifest.map((e) => [e.workflow_id, e]));
|
|
167
198
|
|
|
168
199
|
const added: string[] = [];
|
|
@@ -174,7 +205,11 @@ export async function runWorkflowSyncOnce(
|
|
|
174
205
|
const isNew = !local;
|
|
175
206
|
const isChanged =
|
|
176
207
|
Boolean(local) &&
|
|
177
|
-
(
|
|
208
|
+
(
|
|
209
|
+
identityChanged ||
|
|
210
|
+
local.updated_at !== entry.updated_at ||
|
|
211
|
+
local.version !== entry.version
|
|
212
|
+
);
|
|
178
213
|
if (!isNew && !isChanged) continue;
|
|
179
214
|
|
|
180
215
|
let fetched: FetchedWorkflow;
|
|
@@ -224,6 +259,10 @@ export async function runWorkflowSyncOnce(
|
|
|
224
259
|
|
|
225
260
|
const totalChanged = added.length + changed.length + removed.length;
|
|
226
261
|
if (totalChanged === 0) {
|
|
262
|
+
if (identityChanged) {
|
|
263
|
+
state.last_synced_at = new Date().toISOString();
|
|
264
|
+
writeSyncedWorkflowsState(state);
|
|
265
|
+
}
|
|
227
266
|
const outcome: WorkflowSyncOutcome = {
|
|
228
267
|
kind: "unchanged",
|
|
229
268
|
count: manifest.length,
|
|
@@ -31,6 +31,31 @@ const MODEL_CACHE_DIR = PATHS.embeddingsModelCache;
|
|
|
31
31
|
const EMBEDDINGS_DIR = path.dirname(EMBEDDINGS_PATH);
|
|
32
32
|
|
|
33
33
|
export const DEFAULT_MODEL_ID = "jinaai/jina-embeddings-v2-base-code";
|
|
34
|
+
export const COMPACT_FILE_TEXT_STRATEGY = "compact_files_v1";
|
|
35
|
+
export const COMPACT_FILE_TEXT_THRESHOLD_CHARS = 32768;
|
|
36
|
+
export const COMPACT_FILE_TEXT_TARGET_CHARS = 16000;
|
|
37
|
+
|
|
38
|
+
const COMPACT_FILE_SIGNAL_BUDGET_CHARS = 4096;
|
|
39
|
+
const COMPACT_FILE_SIGNAL_MAX_LINE_CHARS = 512;
|
|
40
|
+
const COMPACT_FILE_MIN_HEAD_CHARS = 4096;
|
|
41
|
+
const COMPACT_FILE_MIN_TAIL_CHARS = 2048;
|
|
42
|
+
|
|
43
|
+
export type EmbedTextProfile = "full" | "compact-files";
|
|
44
|
+
|
|
45
|
+
type FileEmbeddingTextResult = {
|
|
46
|
+
text: string;
|
|
47
|
+
profile: EmbedTextProfile;
|
|
48
|
+
compacted: boolean;
|
|
49
|
+
original_chars: number;
|
|
50
|
+
text_chars: number;
|
|
51
|
+
omitted_chars: number;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type ParseFileEntitiesOptions = {
|
|
55
|
+
textProfile?: EmbedTextProfile;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
type SignatureEntityType = "File" | "Rule" | "ADR" | "Module" | "Project" | "Chunk";
|
|
34
59
|
|
|
35
60
|
export function resolveModelId(): string {
|
|
36
61
|
return (process.env.CORTEX_EMBED_MODEL ?? DEFAULT_MODEL_ID).trim() || DEFAULT_MODEL_ID;
|
|
@@ -48,6 +73,11 @@ type FileEntity = {
|
|
|
48
73
|
updated_at: string;
|
|
49
74
|
text: string;
|
|
50
75
|
signature: string;
|
|
76
|
+
text_profile: EmbedTextProfile;
|
|
77
|
+
text_compacted: boolean;
|
|
78
|
+
text_original_chars: number;
|
|
79
|
+
text_chars: number;
|
|
80
|
+
text_omitted_chars: number;
|
|
51
81
|
};
|
|
52
82
|
|
|
53
83
|
type RuleEntity = {
|
|
@@ -155,6 +185,129 @@ function normalizeText(value: string): string {
|
|
|
155
185
|
return value.replace(/\s+/g, " ").trim();
|
|
156
186
|
}
|
|
157
187
|
|
|
188
|
+
export function resolveEmbedTextProfile(raw = process.env.CORTEX_EMBED_TEXT_PROFILE): EmbedTextProfile {
|
|
189
|
+
const value = (raw ?? "").trim().toLowerCase();
|
|
190
|
+
if (!value || value === "full") {
|
|
191
|
+
return "full";
|
|
192
|
+
}
|
|
193
|
+
if (value === "compact-files") {
|
|
194
|
+
return "compact-files";
|
|
195
|
+
}
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Unsupported CORTEX_EMBED_TEXT_PROFILE=${JSON.stringify(raw)}; expected "full" or "compact-files"`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function isSignalLine(line: string): boolean {
|
|
202
|
+
const trimmed = line.trim();
|
|
203
|
+
if (!trimmed) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
return (
|
|
207
|
+
/^(import|export)\b/.test(trimmed) ||
|
|
208
|
+
/^(abstract\s+|async\s+|public\s+|private\s+|protected\s+|static\s+|readonly\s+|override\s+)*(class|interface|type|enum|function)\b/.test(trimmed) ||
|
|
209
|
+
/^(const|let|var)\s+[$A-Z_a-z][$\w]*\s*=/.test(trimmed) ||
|
|
210
|
+
/^(describe|it|test)\s*\(/.test(trimmed) ||
|
|
211
|
+
/^(@[A-Z_a-z][$\w]*|#[#\s])/.test(trimmed) ||
|
|
212
|
+
/^```[A-Za-z0-9_-]+/.test(trimmed) ||
|
|
213
|
+
/\b(route|router|endpoint|controller|handler|middleware|permission|auth|token|secret|security|todo|fixme)\b/i.test(trimmed)
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function collectSignalLines(content: string, budgetChars: number): string {
|
|
218
|
+
const lines: string[] = [];
|
|
219
|
+
const seen = new Set<string>();
|
|
220
|
+
let used = 0;
|
|
221
|
+
|
|
222
|
+
for (const line of content.split(/\r?\n/)) {
|
|
223
|
+
if (!isSignalLine(line)) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const normalized = line.trimEnd();
|
|
227
|
+
const signalLine = normalized.length > COMPACT_FILE_SIGNAL_MAX_LINE_CHARS
|
|
228
|
+
? `${normalized.slice(0, COMPACT_FILE_SIGNAL_MAX_LINE_CHARS)} [cortex ${COMPACT_FILE_TEXT_STRATEGY} signal_line_truncated_chars=${normalized.length - COMPACT_FILE_SIGNAL_MAX_LINE_CHARS}]`
|
|
229
|
+
: normalized;
|
|
230
|
+
if (seen.has(signalLine)) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const next = used + signalLine.length + 1;
|
|
234
|
+
if (next > budgetChars) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
lines.push(signalLine);
|
|
238
|
+
seen.add(signalLine);
|
|
239
|
+
used = next;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return lines.join("\n");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function buildFileEmbeddingText(
|
|
246
|
+
filePath: string,
|
|
247
|
+
excerpt: string,
|
|
248
|
+
content: string,
|
|
249
|
+
profile: EmbedTextProfile = "full"
|
|
250
|
+
): FileEmbeddingTextResult {
|
|
251
|
+
const fullText = `${filePath}\n${excerpt}\n${content}`;
|
|
252
|
+
if (profile === "full" || fullText.length <= COMPACT_FILE_TEXT_THRESHOLD_CHARS) {
|
|
253
|
+
return {
|
|
254
|
+
text: fullText,
|
|
255
|
+
profile,
|
|
256
|
+
compacted: false,
|
|
257
|
+
original_chars: fullText.length,
|
|
258
|
+
text_chars: fullText.length,
|
|
259
|
+
omitted_chars: 0
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const signalText = collectSignalLines(content, COMPACT_FILE_SIGNAL_BUDGET_CHARS);
|
|
264
|
+
const markerPrefix = `[cortex ${COMPACT_FILE_TEXT_STRATEGY} omitted_chars=`;
|
|
265
|
+
const staticChars =
|
|
266
|
+
filePath.length +
|
|
267
|
+
excerpt.length +
|
|
268
|
+
signalText.length +
|
|
269
|
+
markerPrefix.length +
|
|
270
|
+
64;
|
|
271
|
+
const available = Math.max(
|
|
272
|
+
COMPACT_FILE_MIN_HEAD_CHARS + COMPACT_FILE_MIN_TAIL_CHARS,
|
|
273
|
+
COMPACT_FILE_TEXT_TARGET_CHARS - staticChars
|
|
274
|
+
);
|
|
275
|
+
const headChars = Math.max(COMPACT_FILE_MIN_HEAD_CHARS, Math.floor(available * 0.62));
|
|
276
|
+
const tailChars = Math.max(COMPACT_FILE_MIN_TAIL_CHARS, available - headChars);
|
|
277
|
+
const head = content.slice(0, headChars);
|
|
278
|
+
const tail = content.slice(-tailChars);
|
|
279
|
+
const omittedChars = Math.max(0, content.length - head.length - tail.length);
|
|
280
|
+
const marker = `${markerPrefix}${omittedChars}]`;
|
|
281
|
+
const compactText = [
|
|
282
|
+
filePath,
|
|
283
|
+
excerpt,
|
|
284
|
+
head,
|
|
285
|
+
marker,
|
|
286
|
+
signalText ? `[cortex ${COMPACT_FILE_TEXT_STRATEGY} signal_lines]\n${signalText}` : "",
|
|
287
|
+
tail
|
|
288
|
+
].filter((part) => part.length > 0).join("\n");
|
|
289
|
+
|
|
290
|
+
if (compactText.length >= fullText.length) {
|
|
291
|
+
return {
|
|
292
|
+
text: fullText,
|
|
293
|
+
profile,
|
|
294
|
+
compacted: false,
|
|
295
|
+
original_chars: fullText.length,
|
|
296
|
+
text_chars: fullText.length,
|
|
297
|
+
omitted_chars: 0
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
text: compactText,
|
|
303
|
+
profile,
|
|
304
|
+
compacted: true,
|
|
305
|
+
original_chars: fullText.length,
|
|
306
|
+
text_chars: compactText.length,
|
|
307
|
+
omitted_chars: fullText.length - compactText.length
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
158
311
|
function ensureRequiredFiles(): void {
|
|
159
312
|
const required = [
|
|
160
313
|
path.join(CACHE_DIR, "documents.jsonl"),
|
|
@@ -169,7 +322,8 @@ function ensureRequiredFiles(): void {
|
|
|
169
322
|
}
|
|
170
323
|
}
|
|
171
324
|
|
|
172
|
-
export function parseFileEntities(raw: JsonObject[]): FileEntity[] {
|
|
325
|
+
export function parseFileEntities(raw: JsonObject[], options: ParseFileEntitiesOptions = {}): FileEntity[] {
|
|
326
|
+
const textProfile = options.textProfile ?? "full";
|
|
173
327
|
return raw
|
|
174
328
|
.map((item) => {
|
|
175
329
|
const id = asString(item.id);
|
|
@@ -182,7 +336,7 @@ export function parseFileEntities(raw: JsonObject[]): FileEntity[] {
|
|
|
182
336
|
const excerpt = asString(item.excerpt);
|
|
183
337
|
const updatedAt = asString(item.updated_at);
|
|
184
338
|
const checksum = asString(item.checksum, hashText(content));
|
|
185
|
-
const
|
|
339
|
+
const embeddingText = buildFileEmbeddingText(filePath, excerpt, content, textProfile);
|
|
186
340
|
|
|
187
341
|
return {
|
|
188
342
|
id,
|
|
@@ -194,8 +348,13 @@ export function parseFileEntities(raw: JsonObject[]): FileEntity[] {
|
|
|
194
348
|
source_of_truth: asBoolean(item.source_of_truth, false),
|
|
195
349
|
trust_level: asNumber(item.trust_level, 50),
|
|
196
350
|
updated_at: updatedAt,
|
|
197
|
-
text,
|
|
198
|
-
signature: hashText(`file|${checksum}|${updatedAt}|${hashText(text)}`)
|
|
351
|
+
text: embeddingText.text,
|
|
352
|
+
signature: hashText(`file|${checksum}|${updatedAt}|${hashText(embeddingText.text)}`),
|
|
353
|
+
text_profile: embeddingText.profile,
|
|
354
|
+
text_compacted: embeddingText.compacted,
|
|
355
|
+
text_original_chars: embeddingText.original_chars,
|
|
356
|
+
text_chars: embeddingText.text_chars,
|
|
357
|
+
text_omitted_chars: embeddingText.omitted_chars
|
|
199
358
|
};
|
|
200
359
|
})
|
|
201
360
|
.filter((value): value is FileEntity => value !== null);
|
|
@@ -408,8 +567,24 @@ function roundVector(values: number[]): number[] {
|
|
|
408
567
|
return values.map((value) => Number(value.toFixed(6)));
|
|
409
568
|
}
|
|
410
569
|
|
|
411
|
-
function resolveSignatureProfile(
|
|
412
|
-
|
|
570
|
+
export function resolveSignatureProfile(
|
|
571
|
+
maxTokenCap: number | null,
|
|
572
|
+
textProfile: EmbedTextProfile = "full",
|
|
573
|
+
entityType?: SignatureEntityType
|
|
574
|
+
): string {
|
|
575
|
+
const parts: string[] = [];
|
|
576
|
+
if (maxTokenCap) {
|
|
577
|
+
parts.push(`max_tokens=${maxTokenCap}`);
|
|
578
|
+
}
|
|
579
|
+
if (textProfile === "compact-files" && (entityType === undefined || entityType === "File")) {
|
|
580
|
+
parts.push(
|
|
581
|
+
"text_profile=compact-files",
|
|
582
|
+
COMPACT_FILE_TEXT_STRATEGY,
|
|
583
|
+
`threshold_chars=${COMPACT_FILE_TEXT_THRESHOLD_CHARS}`,
|
|
584
|
+
`target_chars=${COMPACT_FILE_TEXT_TARGET_CHARS}`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
return parts.length ? `embed|${parts.join("|")}` : "";
|
|
413
588
|
}
|
|
414
589
|
|
|
415
590
|
function embeddingSignature(entitySignature: string, profile: string): string {
|
|
@@ -434,8 +609,19 @@ async function main(): Promise<void> {
|
|
|
434
609
|
fs.mkdirSync(MODEL_CACHE_DIR, { recursive: true });
|
|
435
610
|
|
|
436
611
|
const modelId = resolveModelId();
|
|
437
|
-
|
|
438
|
-
|
|
612
|
+
const textProfile = resolveEmbedTextProfile();
|
|
613
|
+
|
|
614
|
+
const documents = parseFileEntities(readJsonl(path.join(CACHE_DIR, "documents.jsonl")), { textProfile });
|
|
615
|
+
const textProfileStats = {
|
|
616
|
+
strategy: textProfile === "compact-files" ? COMPACT_FILE_TEXT_STRATEGY : null,
|
|
617
|
+
threshold_chars: textProfile === "compact-files" ? COMPACT_FILE_TEXT_THRESHOLD_CHARS : null,
|
|
618
|
+
target_chars: textProfile === "compact-files" ? COMPACT_FILE_TEXT_TARGET_CHARS : null,
|
|
619
|
+
file_entities: documents.length,
|
|
620
|
+
compacted_files: documents.filter((doc) => doc.text_compacted).length,
|
|
621
|
+
original_chars: documents.reduce((total, doc) => total + doc.text_original_chars, 0),
|
|
622
|
+
text_chars: documents.reduce((total, doc) => total + doc.text_chars, 0),
|
|
623
|
+
saved_chars: documents.reduce((total, doc) => total + doc.text_omitted_chars, 0)
|
|
624
|
+
};
|
|
439
625
|
const rules = parseRuleEntities(readJsonl(path.join(CACHE_DIR, "entities.rule.jsonl")));
|
|
440
626
|
const adrs = parseAdrEntities(readJsonl(path.join(CACHE_DIR, "entities.adr.jsonl")));
|
|
441
627
|
const modules = parseModuleEntities(readJsonl(path.join(CACHE_DIR, "entities.module.jsonl")));
|
|
@@ -483,7 +669,10 @@ async function main(): Promise<void> {
|
|
|
483
669
|
memoryBytes: memoryHeadroom,
|
|
484
670
|
sessions: previewPoolConfig.sessions
|
|
485
671
|
});
|
|
486
|
-
const
|
|
672
|
+
const defaultSignatureProfile = resolveSignatureProfile(tokenBudget.cap, "full");
|
|
673
|
+
const fileSignatureProfile = resolveSignatureProfile(tokenBudget.cap, textProfile, "File");
|
|
674
|
+
const signatureProfileForEntity = (entity: SearchEntity) =>
|
|
675
|
+
entity.type === "File" ? fileSignatureProfile : defaultSignatureProfile;
|
|
487
676
|
|
|
488
677
|
const existing = parseExistingEmbeddings(readJsonlRecords(EMBEDDINGS_PATH), modelId);
|
|
489
678
|
|
|
@@ -494,6 +683,7 @@ async function main(): Promise<void> {
|
|
|
494
683
|
let dimensions = 0;
|
|
495
684
|
|
|
496
685
|
entities.forEach((entity, index) => {
|
|
686
|
+
const signatureProfile = signatureProfileForEntity(entity);
|
|
497
687
|
const signature = embeddingSignature(entity.signature, signatureProfile);
|
|
498
688
|
const previous = existing.get(entity.id);
|
|
499
689
|
if (previous && previous.signature === signature && previous.vector.length > 0) {
|
|
@@ -628,6 +818,7 @@ async function main(): Promise<void> {
|
|
|
628
818
|
maxInFlightTokens,
|
|
629
819
|
onVector(index, rawVector) {
|
|
630
820
|
const entity = entities[index];
|
|
821
|
+
const signatureProfile = signatureProfileForEntity(entity);
|
|
631
822
|
const vector = roundVector(rawVector);
|
|
632
823
|
embedded += 1;
|
|
633
824
|
dimensions = dimensions || vector.length;
|
|
@@ -662,6 +853,13 @@ async function main(): Promise<void> {
|
|
|
662
853
|
mode,
|
|
663
854
|
model: modelId,
|
|
664
855
|
dimensions,
|
|
856
|
+
text_profile: textProfile,
|
|
857
|
+
signature_profile: fileSignatureProfile === defaultSignatureProfile ? defaultSignatureProfile : "per_entity",
|
|
858
|
+
signature_profiles: {
|
|
859
|
+
default: defaultSignatureProfile,
|
|
860
|
+
file: fileSignatureProfile
|
|
861
|
+
},
|
|
862
|
+
text_profile_stats: textProfileStats,
|
|
665
863
|
counts: {
|
|
666
864
|
entities: entities.length,
|
|
667
865
|
output: outputCount,
|
|
@@ -675,7 +873,10 @@ async function main(): Promise<void> {
|
|
|
675
873
|
fs.writeFileSync(EMBEDDINGS_MANIFEST_PATH, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
676
874
|
|
|
677
875
|
console.log(
|
|
678
|
-
`[embed] mode=${mode} model=${modelId} dim=${dimensions} pool=${poolConfig.sessions}x${poolConfig.threadsPerSession} batch<=${schedulerOptions.batchMaxItems} max_tokens<=${modelMaxTokensUsed || tokenBudget.cap || "model"} token_budget=${tokenBudget.mode} reason=${tokenBudget.reason}`
|
|
876
|
+
`[embed] mode=${mode} model=${modelId} dim=${dimensions} pool=${poolConfig.sessions}x${poolConfig.threadsPerSession} batch<=${schedulerOptions.batchMaxItems} max_tokens<=${modelMaxTokensUsed || tokenBudget.cap || "model"} token_budget=${tokenBudget.mode} reason=${tokenBudget.reason} text_profile=${textProfile}`
|
|
877
|
+
);
|
|
878
|
+
console.log(
|
|
879
|
+
`[embed] text_profile=${textProfile} compacted_files=${textProfileStats.compacted_files}/${textProfileStats.file_entities} saved_chars=${textProfileStats.saved_chars}`
|
|
679
880
|
);
|
|
680
881
|
console.log(
|
|
681
882
|
`[embed] entities=${entities.length} embedded=${embedded} reused=${reused} failed=${failed}`
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AuditEntry } from "../../core/audit/writer.js";
|
|
2
|
+
import { isAllowedEnterpriseEndpoint } from "../../core/secure-endpoint.js";
|
|
2
3
|
import { sanitizeAuditEntryForPush } from "../privacy/boundary.js";
|
|
3
4
|
|
|
4
5
|
export type AuditPushContext = {
|
|
@@ -40,6 +41,13 @@ export async function pushAuditEvents(
|
|
|
40
41
|
if (pending.length === 0) {
|
|
41
42
|
return { success: true, count: 0 };
|
|
42
43
|
}
|
|
44
|
+
if (!isAllowedEnterpriseEndpoint(baseUrl)) {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
count: 0,
|
|
48
|
+
error: "insecure or invalid enterprise endpoint",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
43
51
|
|
|
44
52
|
const auditUrl = `${baseUrl.replace(/\/$/, "")}/api/v1/audit/push`;
|
|
45
53
|
let pushedCount = 0;
|
|
@@ -21,7 +21,7 @@ import { pushWorkflowSnapshot, setWorkflowPushContext } from "./workflow/push.js
|
|
|
21
21
|
import { hasWorkflowState, loadWorkflowState } from "./workflow/state.js";
|
|
22
22
|
|
|
23
23
|
const require = createRequire(import.meta.url);
|
|
24
|
-
const pkg = require("
|
|
24
|
+
const pkg = require("../../package.json") as { version: string };
|
|
25
25
|
|
|
26
26
|
export const name = "cortex-enterprise";
|
|
27
27
|
export const version: string = pkg.version;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { OrgPolicy, PolicyStore } from "../../core/policy/store.js";
|
|
3
|
+
import { isAllowedEnterpriseEndpoint } from "../../core/secure-endpoint.js";
|
|
3
4
|
|
|
4
5
|
const CloudPolicySchema = z.object({
|
|
5
6
|
id: z.string().min(1).max(200),
|
|
@@ -60,6 +61,17 @@ export async function syncFromCloud(
|
|
|
60
61
|
lastSync = result;
|
|
61
62
|
return result;
|
|
62
63
|
}
|
|
64
|
+
if (!isAllowedEnterpriseEndpoint(endpoint)) {
|
|
65
|
+
const result: SyncResult = {
|
|
66
|
+
success: false,
|
|
67
|
+
synced: 0,
|
|
68
|
+
source: "cloud",
|
|
69
|
+
timestamp: new Date().toISOString(),
|
|
70
|
+
error: "insecure or invalid enterprise endpoint",
|
|
71
|
+
};
|
|
72
|
+
lastSync = result;
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
63
75
|
|
|
64
76
|
try {
|
|
65
77
|
const response = await fetch(endpoint, {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
function gitLines(projectRoot: string, args: string[]): string[] {
|
|
4
|
+
const output = execFileSync("git", args, {
|
|
5
|
+
cwd: projectRoot,
|
|
6
|
+
encoding: "utf8",
|
|
7
|
+
timeout: 5000,
|
|
8
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
9
|
+
});
|
|
10
|
+
return output.split("\n").map((value) => value.trim()).filter(Boolean);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function trackedChangedFiles(projectRoot: string): string[] {
|
|
14
|
+
try {
|
|
15
|
+
return gitLines(projectRoot, ["diff", "--name-only", "HEAD"]);
|
|
16
|
+
} catch {
|
|
17
|
+
// No HEAD yet (repo without commits): every tracked file is new.
|
|
18
|
+
return [
|
|
19
|
+
...gitLines(projectRoot, ["diff", "--name-only"]),
|
|
20
|
+
...gitLines(projectRoot, ["ls-files"]),
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolveChangedReviewFiles(projectRoot: string): string[] | null {
|
|
26
|
+
try {
|
|
27
|
+
gitLines(projectRoot, ["rev-parse", "--is-inside-work-tree"]);
|
|
28
|
+
const tracked = trackedChangedFiles(projectRoot);
|
|
29
|
+
const untracked = gitLines(projectRoot, ["ls-files", "--others", "--exclude-standard"]);
|
|
30
|
+
return [...new Set([...tracked, ...untracked])].sort();
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|