@danielblomma/cortex-mcp 2.4.0 → 2.4.2

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.
Files changed (214) hide show
  1. package/CHANGELOG.md +226 -0
  2. package/README.md +24 -3
  3. package/bin/cli/arguments.mjs +60 -0
  4. package/bin/cli/context-passthrough.mjs +129 -0
  5. package/bin/cli/daemon.mjs +157 -0
  6. package/bin/cli/enterprise.mjs +516 -0
  7. package/bin/cli/help.mjs +88 -0
  8. package/bin/cli/hooks.mjs +199 -0
  9. package/bin/cli/mcp-command.mjs +87 -0
  10. package/bin/cli/paths.mjs +14 -0
  11. package/bin/cli/process.mjs +49 -0
  12. package/bin/cli/project-commands.mjs +237 -0
  13. package/bin/cli/project-runtime.mjs +34 -0
  14. package/bin/cli/query-command.mjs +18 -0
  15. package/bin/cli/router.mjs +66 -0
  16. package/bin/cli/run-command.mjs +44 -0
  17. package/bin/cli/scaffold-ownership.mjs +936 -0
  18. package/bin/cli/scaffold.mjs +687 -0
  19. package/bin/cli/stage-command.mjs +9 -0
  20. package/bin/cli/telemetry-command.mjs +18 -0
  21. package/bin/cli/trusted-runtime.mjs +18 -0
  22. package/bin/cortex.mjs +12 -1607
  23. package/bin/daemon-control.mjs +162 -0
  24. package/mcp-registry-submission.json +1 -1
  25. package/package.json +8 -3
  26. package/scaffold/mcp/dist/.cortex-build-hash +1 -0
  27. package/scaffold/mcp/dist/cli/enterprise-setup.js +134 -0
  28. package/scaffold/mcp/dist/cli/govern.js +937 -0
  29. package/scaffold/mcp/dist/cli/query.js +409 -0
  30. package/scaffold/mcp/dist/cli/run.js +295 -0
  31. package/scaffold/mcp/dist/cli/stage.js +308 -0
  32. package/scaffold/mcp/dist/cli/telemetry-test.js +141 -0
  33. package/scaffold/mcp/dist/cli/ungoverned-detector.js +133 -0
  34. package/scaffold/mcp/dist/contextEntities.js +282 -0
  35. package/scaffold/mcp/dist/core/audit/query.js +72 -0
  36. package/scaffold/mcp/dist/core/audit/writer.js +28 -0
  37. package/scaffold/mcp/dist/core/config.js +235 -0
  38. package/scaffold/mcp/dist/core/enterprise-host-identity.js +193 -0
  39. package/scaffold/mcp/dist/core/enterprise-identity.js +18 -0
  40. package/scaffold/mcp/dist/core/enterprise-rotation.js +158 -0
  41. package/scaffold/mcp/dist/core/govern-paths.js +21 -0
  42. package/scaffold/mcp/dist/core/index.js +14 -0
  43. package/scaffold/mcp/dist/core/license.js +278 -0
  44. package/scaffold/mcp/dist/core/policy/enforce.js +66 -0
  45. package/scaffold/mcp/dist/core/policy/injection.js +172 -0
  46. package/scaffold/mcp/dist/core/policy/store.js +179 -0
  47. package/scaffold/mcp/dist/core/rbac/check.js +30 -0
  48. package/scaffold/mcp/dist/core/secure-endpoint.js +25 -0
  49. package/scaffold/mcp/dist/core/telemetry/collector.js +285 -0
  50. package/scaffold/mcp/dist/core/telemetry/state-dir.js +31 -0
  51. package/scaffold/mcp/dist/core/validators/builtins.js +632 -0
  52. package/scaffold/mcp/dist/core/validators/config.js +44 -0
  53. package/scaffold/mcp/dist/core/validators/engine.js +120 -0
  54. package/scaffold/mcp/dist/core/validators/evaluators/code_comments.js +236 -0
  55. package/scaffold/mcp/dist/core/validators/evaluators/regex.js +116 -0
  56. package/scaffold/mcp/dist/core/workflow/artifact-io.js +103 -0
  57. package/scaffold/mcp/dist/core/workflow/capabilities.js +88 -0
  58. package/scaffold/mcp/dist/core/workflow/default-workflows.js +102 -0
  59. package/scaffold/mcp/dist/core/workflow/enforcement.js +164 -0
  60. package/scaffold/mcp/dist/core/workflow/envelope.js +132 -0
  61. package/scaffold/mcp/dist/core/workflow/index.js +11 -0
  62. package/scaffold/mcp/dist/core/workflow/mcp-tools.js +175 -0
  63. package/scaffold/mcp/dist/core/workflow/resolution.js +69 -0
  64. package/scaffold/mcp/dist/core/workflow/run-lifecycle.js +123 -0
  65. package/scaffold/mcp/dist/core/workflow/schemas.js +141 -0
  66. package/scaffold/mcp/dist/core/workflow/synced-capability-registry.js +60 -0
  67. package/scaffold/mcp/dist/core/workflow/synced-registry.js +60 -0
  68. package/scaffold/mcp/dist/daemon/capability-sync-checker.js +242 -0
  69. package/scaffold/mcp/dist/daemon/client.js +130 -0
  70. package/scaffold/mcp/dist/daemon/egress-proxy.js +288 -0
  71. package/scaffold/mcp/dist/daemon/global-host-events.js +222 -0
  72. package/scaffold/mcp/dist/daemon/heartbeat-pusher.js +116 -0
  73. package/scaffold/mcp/dist/daemon/heartbeat-tracker.js +165 -0
  74. package/scaffold/mcp/dist/daemon/host-events-pusher.js +249 -0
  75. package/scaffold/mcp/dist/daemon/main.js +449 -0
  76. package/scaffold/mcp/dist/daemon/paths.js +36 -0
  77. package/scaffold/mcp/dist/daemon/project-service-registry.js +78 -0
  78. package/scaffold/mcp/dist/daemon/protocol.js +8 -0
  79. package/scaffold/mcp/dist/daemon/server.js +180 -0
  80. package/scaffold/mcp/dist/daemon/skill-sync-checker.js +557 -0
  81. package/scaffold/mcp/dist/daemon/sync-checker.js +165 -0
  82. package/scaffold/mcp/dist/daemon/ungoverned-scanner.js +136 -0
  83. package/scaffold/mcp/dist/daemon/workflow-sync-checker.js +249 -0
  84. package/scaffold/mcp/dist/defaults.js +6 -0
  85. package/scaffold/mcp/dist/embed.js +627 -0
  86. package/scaffold/mcp/dist/embedScheduler.js +479 -0
  87. package/scaffold/mcp/dist/embeddings.js +167 -0
  88. package/scaffold/mcp/dist/enterprise/audit/push.js +66 -0
  89. package/scaffold/mcp/dist/enterprise/index.js +327 -0
  90. package/scaffold/mcp/dist/enterprise/model/deploy.js +27 -0
  91. package/scaffold/mcp/dist/enterprise/policy/sync.js +129 -0
  92. package/scaffold/mcp/dist/enterprise/privacy/boundary.js +184 -0
  93. package/scaffold/mcp/dist/enterprise/reviews/changed-files.js +33 -0
  94. package/scaffold/mcp/dist/enterprise/reviews/pattern-context.js +202 -0
  95. package/scaffold/mcp/dist/enterprise/reviews/policy-selection.js +46 -0
  96. package/scaffold/mcp/dist/enterprise/reviews/push.js +102 -0
  97. package/scaffold/mcp/dist/enterprise/reviews/trust-state.js +186 -0
  98. package/scaffold/mcp/dist/enterprise/telemetry/sync.js +57 -0
  99. package/scaffold/mcp/dist/enterprise/tools/enterprise.js +826 -0
  100. package/scaffold/mcp/dist/enterprise/tools/harness.js +40 -0
  101. package/scaffold/mcp/dist/enterprise/tools/walk.js +73 -0
  102. package/scaffold/mcp/dist/enterprise/violations/push.js +77 -0
  103. package/scaffold/mcp/dist/enterprise/workflow/push.js +44 -0
  104. package/scaffold/mcp/dist/enterprise/workflow/state.js +329 -0
  105. package/scaffold/mcp/dist/frontmatter.js +33 -0
  106. package/scaffold/mcp/dist/graph.js +769 -0
  107. package/scaffold/mcp/dist/graphCsv.js +55 -0
  108. package/scaffold/mcp/dist/graphMetrics.js +8 -0
  109. package/scaffold/mcp/dist/hooks/permission-request.js +89 -0
  110. package/scaffold/mcp/dist/hooks/post-tool-use.js +105 -0
  111. package/scaffold/mcp/dist/hooks/pre-compact.js +29 -0
  112. package/scaffold/mcp/dist/hooks/pre-tool-use.js +78 -0
  113. package/scaffold/mcp/dist/hooks/session-end.js +43 -0
  114. package/scaffold/mcp/dist/hooks/session-start.js +41 -0
  115. package/scaffold/mcp/dist/hooks/shared.js +194 -0
  116. package/scaffold/mcp/dist/hooks/stop.js +28 -0
  117. package/scaffold/mcp/dist/hooks/user-prompt-submit.js +33 -0
  118. package/scaffold/mcp/dist/impactPresentation.js +137 -0
  119. package/scaffold/mcp/dist/impactRanking.js +191 -0
  120. package/scaffold/mcp/dist/impactResponse.js +30 -0
  121. package/scaffold/mcp/dist/impactResults.js +105 -0
  122. package/scaffold/mcp/dist/impactSeed.js +20 -0
  123. package/scaffold/mcp/dist/impactTraversal.js +64 -0
  124. package/scaffold/mcp/dist/jsonl.js +77 -0
  125. package/scaffold/mcp/dist/loadGraph.js +759 -0
  126. package/scaffold/mcp/dist/lruCache.js +38 -0
  127. package/scaffold/mcp/dist/paths.js +97 -0
  128. package/scaffold/mcp/dist/patternEvidence.js +272 -0
  129. package/scaffold/mcp/dist/plugin.js +81 -0
  130. package/scaffold/mcp/dist/presets.js +78 -0
  131. package/scaffold/mcp/dist/relatedResponse.js +18 -0
  132. package/scaffold/mcp/dist/relatedTraversal.js +78 -0
  133. package/scaffold/mcp/dist/rules.js +23 -0
  134. package/scaffold/mcp/dist/search.js +212 -0
  135. package/scaffold/mcp/dist/searchCore.js +457 -0
  136. package/scaffold/mcp/dist/searchResults.js +230 -0
  137. package/scaffold/mcp/dist/server.js +317 -0
  138. package/scaffold/mcp/dist/types.js +1 -0
  139. package/scaffold/mcp/package-lock.json +321 -200
  140. package/scaffold/mcp/package.json +15 -6
  141. package/scaffold/mcp/src/cli/enterprise-setup.ts +82 -8
  142. package/scaffold/mcp/src/cli/govern.ts +137 -52
  143. package/scaffold/mcp/src/cli/run.ts +53 -19
  144. package/scaffold/mcp/src/cli/stage.ts +8 -3
  145. package/scaffold/mcp/src/core/config.ts +9 -2
  146. package/scaffold/mcp/src/core/enterprise-host-identity.ts +259 -0
  147. package/scaffold/mcp/src/core/enterprise-identity.ts +20 -0
  148. package/scaffold/mcp/src/core/enterprise-rotation.ts +185 -0
  149. package/scaffold/mcp/src/core/govern-paths.ts +30 -0
  150. package/scaffold/mcp/src/core/license.ts +186 -17
  151. package/scaffold/mcp/src/core/secure-endpoint.ts +23 -0
  152. package/scaffold/mcp/src/core/workflow/enforcement.ts +8 -1
  153. package/scaffold/mcp/src/core/workflow/mcp-tools.ts +3 -0
  154. package/scaffold/mcp/src/core/workflow/resolution.ts +9 -1
  155. package/scaffold/mcp/src/core/workflow/synced-capability-registry.ts +15 -0
  156. package/scaffold/mcp/src/core/workflow/synced-registry.ts +15 -0
  157. package/scaffold/mcp/src/daemon/capability-sync-checker.ts +38 -2
  158. package/scaffold/mcp/src/daemon/egress-proxy.ts +14 -8
  159. package/scaffold/mcp/src/daemon/global-host-events.ts +288 -0
  160. package/scaffold/mcp/src/daemon/heartbeat-pusher.ts +4 -0
  161. package/scaffold/mcp/src/daemon/heartbeat-tracker.ts +2 -0
  162. package/scaffold/mcp/src/daemon/host-events-pusher.ts +5 -0
  163. package/scaffold/mcp/src/daemon/main.ts +99 -25
  164. package/scaffold/mcp/src/daemon/project-service-registry.ts +107 -0
  165. package/scaffold/mcp/src/daemon/skill-sync-checker.ts +368 -31
  166. package/scaffold/mcp/src/daemon/sync-checker.ts +4 -0
  167. package/scaffold/mcp/src/daemon/ungoverned-scanner.ts +57 -25
  168. package/scaffold/mcp/src/daemon/workflow-sync-checker.ts +41 -2
  169. package/scaffold/mcp/src/enterprise/audit/push.ts +8 -0
  170. package/scaffold/mcp/src/enterprise/policy/sync.ts +12 -0
  171. package/scaffold/mcp/src/enterprise/reviews/push.ts +9 -0
  172. package/scaffold/mcp/src/enterprise/reviews/trust-state.ts +3 -1
  173. package/scaffold/mcp/src/enterprise/telemetry/sync.ts +9 -0
  174. package/scaffold/mcp/src/enterprise/violations/push.ts +9 -0
  175. package/scaffold/mcp/src/enterprise/workflow/push.ts +7 -0
  176. package/scaffold/mcp/src/plugin.ts +20 -0
  177. package/scaffold/mcp/tests/copilot-shim.test.mjs +19 -1
  178. package/scaffold/mcp/tests/egress-proxy.test.mjs +37 -0
  179. package/scaffold/mcp/tests/enterprise-identity-sync.test.mjs +401 -0
  180. package/scaffold/mcp/tests/enterprise-setup.test.mjs +189 -0
  181. package/scaffold/mcp/tests/global-host-events.test.mjs +81 -0
  182. package/scaffold/mcp/tests/govern-install.test.mjs +95 -2
  183. package/scaffold/mcp/tests/govern-repair.test.mjs +20 -3
  184. package/scaffold/mcp/tests/heartbeat-tracker.test.mjs +26 -0
  185. package/scaffold/mcp/tests/license.test.mjs +367 -0
  186. package/scaffold/mcp/tests/project-service-registry.test.mjs +172 -0
  187. package/scaffold/mcp/tests/review-trust-contract.test.mjs +18 -0
  188. package/scaffold/mcp/tests/secure-enterprise-endpoint.test.mjs +46 -0
  189. package/scaffold/mcp/tests/skill-sync-checker.test.mjs +446 -2
  190. package/scaffold/mcp/tests/ungoverned-scanner.test.mjs +104 -22
  191. package/scaffold/mcp/tests/workflow-cli.test.mjs +14 -1
  192. package/scaffold/mcp/tests/workflow-synced-capabilities.test.mjs +37 -0
  193. package/scaffold/mcp/tests/workflow-synced-registry.test.mjs +40 -1
  194. package/scaffold/ownership/baseline-v2.4.1.json +22 -0
  195. package/scaffold/ownership/current.json +4 -0
  196. package/scaffold/ownership/v1.json +456 -0
  197. package/scaffold/scripts/ingest-parsers.mjs +1 -387
  198. package/scaffold/scripts/ingest-worker.mjs +4 -1
  199. package/scaffold/scripts/ingest.mjs +5 -3899
  200. package/scaffold/scripts/lib/ingest/arguments.mjs +78 -0
  201. package/scaffold/scripts/lib/ingest/chunks.mjs +212 -0
  202. package/scaffold/scripts/lib/ingest/config.mjs +120 -0
  203. package/scaffold/scripts/lib/ingest/constants.mjs +222 -0
  204. package/scaffold/scripts/lib/ingest/files.mjs +387 -0
  205. package/scaffold/scripts/lib/ingest/incremental-state.mjs +131 -0
  206. package/scaffold/scripts/lib/ingest/io.mjs +78 -0
  207. package/scaffold/scripts/lib/ingest/main.mjs +76 -0
  208. package/scaffold/scripts/lib/ingest/parser-composition.mjs +140 -0
  209. package/scaffold/scripts/lib/ingest/parser-registry.mjs +387 -0
  210. package/scaffold/scripts/lib/ingest/pipeline-stages.mjs +1625 -0
  211. package/scaffold/scripts/lib/ingest/projects.mjs +272 -0
  212. package/scaffold/scripts/lib/ingest/relations.mjs +860 -0
  213. package/scaffold/scripts/lib/ingest/runtime-paths.mjs +11 -0
  214. package/scaffold/scripts/lib/ingest/workers.mjs +264 -0
@@ -0,0 +1,78 @@
1
+ export function parseArgs(argv) {
2
+ const args = new Set(argv.slice(2));
3
+ if (args.has("--help") || args.has("-h")) {
4
+ printHelp();
5
+ process.exit(0);
6
+ }
7
+
8
+ return {
9
+ mode: args.has("--changed") ? "changed" : "full",
10
+ verbose: args.has("--verbose")
11
+ };
12
+ }
13
+ export function printHelp() {
14
+ console.log("Usage: ./scripts/ingest.sh [--changed] [--verbose]");
15
+ console.log("");
16
+ console.log("Options:");
17
+ console.log(" --changed Ingest only changed/untracked files when git is available.");
18
+ console.log(" --verbose Print skipped files and additional diagnostics.");
19
+ console.log(" -h, --help Show this help message.");
20
+ }
21
+
22
+ export function parsePositiveIntegerEnv(name, fallback) {
23
+ const rawValue = process.env[name];
24
+ if (!rawValue) {
25
+ return fallback;
26
+ }
27
+ const parsed = Number(rawValue);
28
+ if (!Number.isFinite(parsed) || parsed < 1) {
29
+ return fallback;
30
+ }
31
+ return Math.floor(parsed);
32
+ }
33
+
34
+ export function parseNonNegativeIntegerEnv(name, fallback) {
35
+ const rawValue = process.env[name];
36
+ if (!rawValue) {
37
+ return fallback;
38
+ }
39
+ const parsed = Number(rawValue);
40
+ if (!Number.isFinite(parsed) || parsed < 0) {
41
+ return fallback;
42
+ }
43
+ return Math.floor(parsed);
44
+ }
45
+
46
+ export function isTruthyEnv(value) {
47
+ if (typeof value !== "string") {
48
+ return false;
49
+ }
50
+ const normalized = value.trim().toLowerCase();
51
+ return normalized !== "" && normalized !== "0" && normalized !== "false" && normalized !== "no" && normalized !== "off";
52
+ }
53
+
54
+ export function createIngestMemoryTrace() {
55
+ const enabled = isTruthyEnv(process.env.CORTEX_INGEST_TRACE_MEMORY);
56
+
57
+ return {
58
+ enabled,
59
+ checkpoint(label, counts = {}) {
60
+ if (!enabled) {
61
+ return;
62
+ }
63
+
64
+ const memory = process.memoryUsage();
65
+ process.stderr.write(
66
+ `${JSON.stringify({
67
+ type: "cortex.ingest.memory",
68
+ label,
69
+ rss_bytes: memory.rss,
70
+ rss_mb: Number((memory.rss / 1024 / 1024).toFixed(2)),
71
+ heap_used_bytes: memory.heapUsed,
72
+ external_bytes: memory.external,
73
+ counts
74
+ })}\n`
75
+ );
76
+ }
77
+ };
78
+ }
@@ -0,0 +1,212 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { MAX_BODY_CHARS } from "./constants.mjs";
4
+ import { checksum, normalizeWhitespace } from "./files.mjs";
5
+ import { REPO_ROOT } from "./runtime-paths.mjs";
6
+
7
+ export function chunkIdFor(filePath, chunk) {
8
+ const startLine = Number.isFinite(chunk.startLine) ? chunk.startLine : 0;
9
+ const endLine = Number.isFinite(chunk.endLine) ? chunk.endLine : startLine;
10
+ return `chunk:${filePath}:${chunk.name}:${startLine}-${endLine}`;
11
+ }
12
+
13
+ export function generateChunkDescription(chunk) {
14
+ const parts = [chunk.kind];
15
+ if (chunk.exported) parts.push("exported");
16
+ if (chunk.async) parts.push("async");
17
+ parts.push(chunk.signature);
18
+
19
+ if (typeof chunk.description === "string" && chunk.description.trim().length > 10) {
20
+ parts.push(normalizeWhitespace(chunk.description).slice(0, 200));
21
+ }
22
+
23
+ // Extract leading JSDoc/comment from body
24
+ // Match leading JSDoc (/** */), block (/* */) and line (//) comments
25
+ const commentMatch = chunk.body.match(/^(?:\s*(?:\/\*[\s\S]*?\*\/|\/\/[^\n]*)[\s\n]*)+/);
26
+ if (commentMatch) {
27
+ const cleaned = commentMatch[0]
28
+ .replace(/\/\*\*|\*\/|\*|\/\//g, "")
29
+ .replace(/\s+/g, " ").trim()
30
+ .slice(0, 200);
31
+ if (cleaned.length > 10) parts.push(cleaned);
32
+ }
33
+
34
+ return parts.join(". ") + ".";
35
+ }
36
+
37
+ export function generateModuleSummary(dir, files, exportNames, repoRoot = REPO_ROOT) {
38
+ // Check for README.md in directory
39
+ const readmePath = path.join(repoRoot, dir, "README.md");
40
+ if (fs.existsSync(readmePath)) {
41
+ try {
42
+ const content = fs.readFileSync(readmePath, "utf8");
43
+ // Skip first heading line, take first 300 chars
44
+ const lines = content.split(/\r?\n/);
45
+ const startIdx = lines.findIndex(l => !l.startsWith("#") && l.trim().length > 0);
46
+ if (startIdx >= 0) {
47
+ const excerpt = lines.slice(startIdx).join(" ").trim().slice(0, 300);
48
+ if (excerpt.length > 20) return excerpt;
49
+ }
50
+ } catch {
51
+ // fall through to auto-generated summary
52
+ }
53
+ }
54
+
55
+ const name = path.basename(dir);
56
+ const codeFiles = files.filter(f => f.kind === "CODE");
57
+ const docFiles = files.filter(f => f.kind !== "CODE");
58
+
59
+ const parts = [`Module ${name}`];
60
+ parts.push(`Contains ${files.length} files (${codeFiles.length} code, ${docFiles.length} docs)`);
61
+
62
+ // Detect common file extension pattern
63
+ const exts = new Set(codeFiles.map(f => path.extname(f.path).toLowerCase()));
64
+ if (exts.size === 1) {
65
+ const ext = [...exts][0];
66
+ const extNames = {
67
+ ".ts": "TypeScript",
68
+ ".tsx": "TypeScript React",
69
+ ".mts": "TypeScript ESM",
70
+ ".cts": "TypeScript CommonJS",
71
+ ".js": "JavaScript",
72
+ ".jsx": "JavaScript React",
73
+ ".mjs": "JavaScript ESM",
74
+ ".cjs": "JavaScript CommonJS"
75
+ };
76
+ if (extNames[ext]) parts.push(`${extNames[ext]} source files`);
77
+ }
78
+
79
+ if (exportNames.length > 0) {
80
+ parts.push(`Key exports: ${exportNames.slice(0, 5).join(", ")}`);
81
+ }
82
+
83
+ return parts.join(". ") + ".";
84
+ }
85
+
86
+ export function generateModules(fileRecords, chunkRecords) {
87
+ const dirFiles = new Map();
88
+ const dirChunks = new Map();
89
+ const fileById = new Map(fileRecords.map(f => [f.id, f]));
90
+
91
+ for (const file of fileRecords) {
92
+ const dir = path.dirname(file.path);
93
+ if (!dirFiles.has(dir)) dirFiles.set(dir, []);
94
+ dirFiles.get(dir).push(file);
95
+ }
96
+
97
+ for (const chunk of chunkRecords) {
98
+ if (!chunk.exported || isWindowChunkId(chunk.id)) continue;
99
+ const file = fileById.get(chunk.file_id);
100
+ if (!file) continue;
101
+ const dir = path.dirname(file.path);
102
+ if (!dirChunks.has(dir)) dirChunks.set(dir, []);
103
+ dirChunks.get(dir).push(chunk);
104
+ }
105
+
106
+ const modules = [];
107
+ const containsRelations = [];
108
+ const containsModuleRelations = [];
109
+ const exportsRelations = [];
110
+
111
+ const MIN_MODULE_FILES = 2;
112
+
113
+ for (const [dir, files] of dirFiles) {
114
+ if (files.length < MIN_MODULE_FILES) continue;
115
+
116
+ const exports = dirChunks.get(dir) || [];
117
+ const exportNames = [...new Set(exports.slice(0, 20).map(c => c.name))];
118
+ const moduleId = `module:${dir}`;
119
+
120
+ modules.push({
121
+ id: moduleId,
122
+ path: dir,
123
+ name: path.basename(dir),
124
+ summary: generateModuleSummary(dir, files, exportNames),
125
+ file_count: files.length,
126
+ exported_symbols: exportNames.join(", "),
127
+ updated_at: files.reduce((latest, f) => f.updated_at > latest ? f.updated_at : latest, ""),
128
+ source_of_truth: false,
129
+ trust_level: 75,
130
+ status: "active"
131
+ });
132
+
133
+ // CONTAINS: Module -> File
134
+ for (const file of files) {
135
+ containsRelations.push({ from: moduleId, to: file.id });
136
+ }
137
+
138
+ // EXPORTS: Module -> Chunk
139
+ for (const chunk of exports) {
140
+ exportsRelations.push({ from: moduleId, to: chunk.id });
141
+ }
142
+ }
143
+
144
+ // CONTAINS_MODULE: parent Module -> child Module
145
+ const moduleDirs = new Set(modules.map(m => m.path));
146
+ for (const dir of moduleDirs) {
147
+ const parent = path.dirname(dir);
148
+ if (parent !== dir && moduleDirs.has(parent)) {
149
+ containsModuleRelations.push({
150
+ from: `module:${parent}`,
151
+ to: `module:${dir}`
152
+ });
153
+ }
154
+ }
155
+
156
+ return { modules, containsRelations, containsModuleRelations, exportsRelations };
157
+ }
158
+
159
+ export function isWindowChunkId(chunkId) {
160
+ return typeof chunkId === "string" && chunkId.includes(":window:");
161
+ }
162
+
163
+ export function splitChunkIntoWindows(chunkRecord, options) {
164
+ const { windowLines, overlapLines, splitMinLines, maxWindows, chunkBody } = options;
165
+ const sourceBody = typeof chunkBody === "string" ? chunkBody : chunkRecord.body;
166
+ const lines = sourceBody.split(/\r?\n/);
167
+ const totalLines = lines.length;
168
+ if (totalLines < splitMinLines || totalLines <= windowLines) {
169
+ return [];
170
+ }
171
+
172
+ const windows = [];
173
+ const safeOverlap = Math.max(0, Math.min(overlapLines, windowLines - 1));
174
+ let start = 0;
175
+ let windowIndex = 1;
176
+
177
+ while (start < totalLines && windows.length < maxWindows) {
178
+ const isLastAllowedWindow = windows.length + 1 >= maxWindows;
179
+ const end = isLastAllowedWindow ? totalLines : Math.min(totalLines, start + windowLines);
180
+ const windowStartLine = chunkRecord.start_line + start;
181
+ const windowEndLine = chunkRecord.start_line + Math.max(0, end - 1);
182
+ const windowBody = lines.slice(start, end).join("\n");
183
+ const persistedBody = isLastAllowedWindow ? windowBody : windowBody.slice(0, MAX_BODY_CHARS);
184
+ windows.push({
185
+ id: `${chunkRecord.id}:window:${windowIndex}:${windowStartLine}-${windowEndLine}`,
186
+ file_id: chunkRecord.file_id,
187
+ name: `${chunkRecord.name}#window${windowIndex}`,
188
+ kind: chunkRecord.kind,
189
+ signature: `${chunkRecord.signature} [window ${windowIndex}]`,
190
+ body: persistedBody,
191
+ description: chunkRecord.description || "",
192
+ start_line: windowStartLine,
193
+ end_line: windowEndLine,
194
+ language: chunkRecord.language,
195
+ exported: chunkRecord.exported || false,
196
+ checksum: checksum(Buffer.from(windowBody)),
197
+ updated_at: chunkRecord.updated_at,
198
+ trust_level: chunkRecord.trust_level,
199
+ status: chunkRecord.status,
200
+ source_of_truth: chunkRecord.source_of_truth
201
+ });
202
+
203
+ if (end >= totalLines) {
204
+ break;
205
+ }
206
+
207
+ start = end - safeOverlap;
208
+ windowIndex += 1;
209
+ }
210
+
211
+ return windows;
212
+ }
@@ -0,0 +1,120 @@
1
+ import { RULE_KEYWORD_LIMIT, STOP_WORDS } from "./constants.mjs";
2
+ import { tokenizeKeywords, uniqueSorted } from "./files.mjs";
3
+
4
+ export function parseSourcePaths(configText) {
5
+ const sourcePaths = [];
6
+ const lines = configText.split(/\r?\n/);
7
+ let inSourcePaths = false;
8
+
9
+ for (const line of lines) {
10
+ if (!inSourcePaths && /^source_paths:\s*$/.test(line.trim())) {
11
+ inSourcePaths = true;
12
+ continue;
13
+ }
14
+
15
+ if (!inSourcePaths) {
16
+ continue;
17
+ }
18
+
19
+ const entryMatch = line.match(/^\s*-\s*(.+?)\s*$/);
20
+ if (entryMatch) {
21
+ const unquoted = entryMatch[1].replace(/^['"]|['"]$/g, "");
22
+ sourcePaths.push(unquoted);
23
+ continue;
24
+ }
25
+
26
+ if (line.trim() !== "" && !/^\s/.test(line)) {
27
+ break;
28
+ }
29
+ }
30
+
31
+ return sourcePaths;
32
+ }
33
+
34
+ export function parseRules(rulesText) {
35
+ const lines = rulesText.split(/\r?\n/);
36
+ const rules = [];
37
+ let current = null;
38
+
39
+ const pushCurrent = () => {
40
+ if (!current || !current.id) {
41
+ return;
42
+ }
43
+ rules.push({
44
+ id: current.id,
45
+ description: current.description ?? "",
46
+ priority: Number.isFinite(current.priority) ? current.priority : 0,
47
+ enforce: current.enforce === true
48
+ });
49
+ };
50
+
51
+ for (const line of lines) {
52
+ const idMatch = line.match(/^\s*-\s*id:\s*(.+?)\s*$/);
53
+ if (idMatch) {
54
+ pushCurrent();
55
+ current = { id: idMatch[1].replace(/^['"]|['"]$/g, "") };
56
+ continue;
57
+ }
58
+
59
+ if (!current) {
60
+ continue;
61
+ }
62
+
63
+ const descriptionMatch = line.match(/^\s*description:\s*(.+?)\s*$/);
64
+ if (descriptionMatch) {
65
+ current.description = descriptionMatch[1].replace(/^['"]|['"]$/g, "");
66
+ continue;
67
+ }
68
+
69
+ const priorityMatch = line.match(/^\s*priority:\s*(\d+)\s*$/);
70
+ if (priorityMatch) {
71
+ current.priority = Number(priorityMatch[1]);
72
+ continue;
73
+ }
74
+
75
+ const enforceMatch = line.match(/^\s*enforce:\s*(true|false)\s*$/i);
76
+ if (enforceMatch) {
77
+ current.enforce = enforceMatch[1].toLowerCase() === "true";
78
+ }
79
+ }
80
+
81
+ pushCurrent();
82
+ return rules;
83
+ }
84
+
85
+ export function normalizeRuleTokens(ruleRecord) {
86
+ const idParts = ruleRecord.id.split(/[._-]+/g);
87
+ const descriptionTokens = tokenizeKeywords(ruleRecord.body);
88
+ const rawKeywords = [...idParts, ...descriptionTokens];
89
+ const normalized = rawKeywords
90
+ .map((token) => token.toLowerCase().replace(/[^a-z0-9]/g, ""))
91
+ .filter((token) => token.length >= 3 && !STOP_WORDS.has(token));
92
+
93
+ return uniqueSorted(normalized).slice(0, RULE_KEYWORD_LIMIT);
94
+ }
95
+
96
+ export function fileTokenSet(fileRecord) {
97
+ const tokenSource = `${fileRecord.path}\n${fileRecord.content.slice(0, 12000)}`;
98
+ return new Set(tokenizeKeywords(tokenSource));
99
+ }
100
+
101
+ export function collectRuleKeywordMatch(ruleKeywords, tokens, minimumMatches) {
102
+ const required = Math.min(minimumMatches, Math.max(1, ruleKeywords.length));
103
+ let count = 0;
104
+ const sample = [];
105
+
106
+ for (const keyword of ruleKeywords) {
107
+ if (!tokens.has(keyword)) {
108
+ continue;
109
+ }
110
+ count += 1;
111
+ if (sample.length < 5) {
112
+ sample.push(keyword);
113
+ }
114
+ if (count >= required && sample.length >= 5) {
115
+ break;
116
+ }
117
+ }
118
+
119
+ return { matched: count >= required, sample };
120
+ }
@@ -0,0 +1,222 @@
1
+ export const SUPPORTED_TEXT_EXTENSIONS = new Set([
2
+ ".md",
3
+ ".mdx",
4
+ ".txt",
5
+ ".adoc",
6
+ ".rst",
7
+ ".yaml",
8
+ ".yml",
9
+ ".json",
10
+ ".toml",
11
+ ".csv",
12
+ ".ts",
13
+ ".tsx",
14
+ ".mts",
15
+ ".cts",
16
+ ".js",
17
+ ".jsx",
18
+ ".mjs",
19
+ ".cjs",
20
+ ".py",
21
+ ".go",
22
+ ".java",
23
+ ".cs",
24
+ ".vb",
25
+ ".sln",
26
+ ".vbproj",
27
+ ".csproj",
28
+ ".fsproj",
29
+ ".props",
30
+ ".targets",
31
+ ".config",
32
+ ".resx",
33
+ ".settings",
34
+ ".rb",
35
+ ".rs",
36
+ ".php",
37
+ ".swift",
38
+ ".kt",
39
+ ".sql",
40
+ ".sh",
41
+ ".bash",
42
+ ".zsh",
43
+ ".ps1",
44
+ ".c",
45
+ ".h",
46
+ ".cpp",
47
+ ".hpp",
48
+ ".cc",
49
+ ".hh",
50
+ ".bas",
51
+ ".cls",
52
+ ".frm",
53
+ ".ctl"
54
+ ]);
55
+
56
+ export const LEGACY_DOTNET_METADATA_EXTENSIONS = new Set([
57
+ ".sln",
58
+ ".vbproj",
59
+ ".csproj",
60
+ ".fsproj",
61
+ ".props",
62
+ ".targets",
63
+ ".config",
64
+ ".resx",
65
+ ".settings"
66
+ ]);
67
+
68
+ export const PROJECT_DEFINITION_EXTENSIONS = new Set([".sln", ".vbproj", ".csproj", ".fsproj", ".vcxproj"]);
69
+ export const STRUCTURED_NON_CODE_CHUNK_EXTENSIONS = new Set([".config", ".resx", ".settings", ".md", ".mdx"]);
70
+
71
+ export const CODE_FILE_EXTENSIONS = new Set([
72
+ ".ts",
73
+ ".tsx",
74
+ ".mts",
75
+ ".cts",
76
+ ".js",
77
+ ".jsx",
78
+ ".mjs",
79
+ ".cjs",
80
+ ".py",
81
+ ".go",
82
+ ".java",
83
+ ".cs",
84
+ ".vb",
85
+ ".rb",
86
+ ".rs",
87
+ ".php",
88
+ ".swift",
89
+ ".kt",
90
+ ".sql",
91
+ ".sh",
92
+ ".bash",
93
+ ".zsh",
94
+ ".ps1",
95
+ ".c",
96
+ ".h",
97
+ ".cpp",
98
+ ".hpp",
99
+ ".cc",
100
+ ".hh",
101
+ ".bas",
102
+ ".cls",
103
+ ".frm",
104
+ ".ctl"
105
+ ]);
106
+
107
+ export const SQL_REFERENCE_SOURCE_EXTENSIONS = new Set([
108
+ ".vb",
109
+ ".cs",
110
+ ".config",
111
+ ".resx",
112
+ ".settings"
113
+ ]);
114
+ export const NAMED_RESOURCE_REFERENCE_SOURCE_EXTENSIONS = new Set([".vb", ".cs"]);
115
+
116
+ export const SQL_OBJECT_REFERENCE_PATTERNS = [
117
+ /\b(?:SqlCommand|OleDbCommand|OdbcCommand)\s*\(\s*"([^"\r\n]{2,200})"/gi,
118
+ /\bCommandText\s*=\s*"([^"\r\n]{2,500})"/gi,
119
+ /\bCommandType\s*=\s*(?:CommandType\.)?StoredProcedure[\s\S]{0,240}?"([^"\r\n]{2,200})"/gi,
120
+ /"([^"\r\n]{2,200})"[\s\S]{0,240}?\bCommandType\s*=\s*(?:CommandType\.)?StoredProcedure/gi
121
+ ];
122
+
123
+ export const SQL_STRING_REFERENCE_PATTERNS = [
124
+ /\bexec(?:ute)?\s+([#@]?[A-Za-z0-9_[\].]+)/gi,
125
+ /\bfrom\s+([#@]?[A-Za-z0-9_[\].]+)/gi,
126
+ /\bjoin\s+([#@]?[A-Za-z0-9_[\].]+)/gi,
127
+ /\bupdate\s+([#@]?[A-Za-z0-9_[\].]+)/gi,
128
+ /\binsert\s+into\s+([#@]?[A-Za-z0-9_[\].]+)/gi,
129
+ /\bdelete\s+from\s+([#@]?[A-Za-z0-9_[\].]+)/gi,
130
+ /\bmerge\s+into\s+([#@]?[A-Za-z0-9_[\].]+)/gi
131
+ ];
132
+
133
+ export const SQL_RESOURCE_KEY_PATTERNS = [
134
+ /\bMy\.Resources\.([A-Za-z_][A-Za-z0-9_]*)/g,
135
+ /\bResources\.([A-Za-z_][A-Za-z0-9_]*)/g,
136
+ /\bMy\.Settings\.([A-Za-z_][A-Za-z0-9_]*)/g,
137
+ /\b(?:[A-Za-z_][A-Za-z0-9_]*\.)?Settings\.Default\.([A-Za-z_][A-Za-z0-9_]*)/g,
138
+ /\bGetString\(\s*"([^"\r\n]+)"/g,
139
+ /\bGetObject\(\s*"([^"\r\n]+)"/g
140
+ ];
141
+ export const CONFIG_KEY_REFERENCE_PATTERNS = [
142
+ /\bConfigurationManager\.ConnectionStrings\s*\[\s*"([^"\r\n]+)"\s*\]/g,
143
+ /\bConfigurationManager\.ConnectionStrings\s*\(\s*"([^"\r\n]+)"\s*\)/g,
144
+ /\bConfigurationManager\.AppSettings\s*\[\s*"([^"\r\n]+)"\s*\]/g,
145
+ /\bConfigurationManager\.AppSettings\s*\(\s*"([^"\r\n]+)"\s*\)/g,
146
+ /\bGetConnectionString\(\s*"([^"\r\n]+)"\s*\)/g,
147
+ /\bGetAppSetting\(\s*"([^"\r\n]+)"\s*\)/g
148
+ ];
149
+
150
+ export const SKIP_DIRECTORIES = new Set([
151
+ ".git",
152
+ ".idea",
153
+ ".vscode",
154
+ "node_modules",
155
+ "bin",
156
+ "obj",
157
+ "dist",
158
+ "build",
159
+ "coverage",
160
+ ".next",
161
+ ".cache",
162
+ ".context"
163
+ ]);
164
+
165
+ export const MAX_FILE_BYTES = 1024 * 1024;
166
+ export const MAX_CONTENT_CHARS = 60000;
167
+ export const MAX_BODY_CHARS = 12000;
168
+ export const RULE_KEYWORD_LIMIT = 20;
169
+ export const DEFAULT_CHUNK_WINDOW_LINES = 80;
170
+ export const DEFAULT_CHUNK_OVERLAP_LINES = 16;
171
+ export const DEFAULT_CHUNK_SPLIT_MIN_LINES = 120;
172
+ export const DEFAULT_CHUNK_MAX_WINDOWS = 8;
173
+ export const IMPORT_RESOLUTION_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"];
174
+ export const IMPORT_RUNTIME_JS_EXTENSIONS = new Set([".js", ".jsx", ".mjs", ".cjs"]);
175
+ export const IMPORT_RUNTIME_JS_RESOLUTION_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
176
+ export const CPP_IMPORT_RESOLUTION_EXTENSIONS = [".h", ".hh", ".hpp", ".c", ".cc", ".cpp"];
177
+
178
+ export const STOP_WORDS = new Set([
179
+ "the",
180
+ "and",
181
+ "for",
182
+ "with",
183
+ "from",
184
+ "that",
185
+ "this",
186
+ "must",
187
+ "when",
188
+ "where",
189
+ "into",
190
+ "used",
191
+ "using",
192
+ "only",
193
+ "true",
194
+ "false",
195
+ "unless",
196
+ "should",
197
+ "global",
198
+ "active",
199
+ "rule",
200
+ "rules",
201
+ "data",
202
+ "file",
203
+ "files",
204
+ "code",
205
+ "docs",
206
+ "context",
207
+ "och",
208
+ "det",
209
+ "att",
210
+ "som",
211
+ "med",
212
+ "för",
213
+ "utan",
214
+ "eller",
215
+ "inte",
216
+ "ska",
217
+ "skall",
218
+ "måste",
219
+ "kan",
220
+ "vid",
221
+ "alla"
222
+ ]);