@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,387 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { execSync } from "node:child_process";
5
+ import {
6
+ CODE_FILE_EXTENSIONS,
7
+ CPP_IMPORT_RESOLUTION_EXTENSIONS,
8
+ IMPORT_RESOLUTION_EXTENSIONS,
9
+ IMPORT_RUNTIME_JS_EXTENSIONS,
10
+ IMPORT_RUNTIME_JS_RESOLUTION_EXTENSIONS,
11
+ LEGACY_DOTNET_METADATA_EXTENSIONS,
12
+ SKIP_DIRECTORIES,
13
+ STOP_WORDS,
14
+ SUPPORTED_TEXT_EXTENSIONS
15
+ } from "./constants.mjs";
16
+ import { REPO_ROOT } from "./runtime-paths.mjs";
17
+
18
+ export function ensureDirectory(directoryPath) {
19
+ fs.mkdirSync(directoryPath, { recursive: true });
20
+ }
21
+
22
+ export function isTextFile(relPath) {
23
+ const ext = path.extname(relPath).toLowerCase();
24
+ const base = path.basename(relPath).toLowerCase();
25
+ if (SUPPORTED_TEXT_EXTENSIONS.has(ext)) {
26
+ return true;
27
+ }
28
+
29
+ return base === "readme" || base.startsWith("readme.");
30
+ }
31
+
32
+ export function isBinaryBuffer(buffer) {
33
+ const scanLength = Math.min(buffer.length, 4000);
34
+ for (let index = 0; index < scanLength; index += 1) {
35
+ if (buffer[index] === 0) {
36
+ return true;
37
+ }
38
+ }
39
+
40
+ return false;
41
+ }
42
+
43
+ export function toPosixPath(value) {
44
+ return value.split(path.sep).join("/");
45
+ }
46
+
47
+ export function normalizeToken(value) {
48
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
49
+ }
50
+
51
+ export function tokenizeKeywords(value) {
52
+ return value
53
+ .toLowerCase()
54
+ .split(/[^a-z0-9]+/g)
55
+ .map((token) => token.trim())
56
+ .filter((token) => token.length >= 3 && !STOP_WORDS.has(token));
57
+ }
58
+
59
+ export function uniqueSorted(values) {
60
+ return [...new Set(values)].sort();
61
+ }
62
+
63
+ export function walkDirectory(directoryPath, files) {
64
+ const entries = fs.readdirSync(directoryPath, { withFileTypes: true });
65
+ for (const entry of entries) {
66
+ const absolutePath = path.join(directoryPath, entry.name);
67
+ if (entry.isDirectory() && shouldSkipDirectory(absolutePath, entry.name)) {
68
+ continue;
69
+ }
70
+
71
+ if (entry.isDirectory()) {
72
+ walkDirectory(absolutePath, files);
73
+ continue;
74
+ }
75
+
76
+ if (entry.isFile()) {
77
+ files.add(absolutePath);
78
+ }
79
+ }
80
+ }
81
+
82
+ export function shouldSkipDirectory(absolutePath, entryName) {
83
+ if (entryName === "bin" && path.resolve(path.dirname(absolutePath)) === REPO_ROOT) {
84
+ return false;
85
+ }
86
+ return SKIP_DIRECTORIES.has(entryName);
87
+ }
88
+
89
+ export function normalizeSourcePrefix(sourcePath) {
90
+ const source = toPosixPath(sourcePath).replace(/^\.\/+/, "").replace(/\/+$/, "");
91
+ return source === "." ? "" : source;
92
+ }
93
+
94
+ export function normalizeRelativePath(relPath) {
95
+ return toPosixPath(relPath).replace(/^\.\/+/, "").replace(/\/+$/, "");
96
+ }
97
+
98
+ export function hasSkippedDirectorySegment(relPath) {
99
+ const parts = normalizeRelativePath(relPath).split("/").filter(Boolean);
100
+ return parts.some((part, index) => {
101
+ if (part === "bin" && index === 0) {
102
+ return false;
103
+ }
104
+ return SKIP_DIRECTORIES.has(part);
105
+ });
106
+ }
107
+
108
+ export function hasSourcePrefix(relPath, sourcePaths) {
109
+ const normalizedRelPath = normalizeRelativePath(relPath);
110
+ if (!normalizedRelPath || hasSkippedDirectorySegment(normalizedRelPath)) {
111
+ return false;
112
+ }
113
+ return sourcePaths.some((sourcePath) => {
114
+ const source = normalizeSourcePrefix(sourcePath);
115
+ return source === "" || normalizedRelPath === source || normalizedRelPath.startsWith(`${source}/`);
116
+ });
117
+ }
118
+
119
+ export function pushImportResolutionCandidate(candidates, seenCandidates, candidatePath) {
120
+ if (!seenCandidates.has(candidatePath)) {
121
+ seenCandidates.add(candidatePath);
122
+ candidates.push(candidatePath);
123
+ }
124
+ }
125
+
126
+ export function isCppLikeFilePath(filePath) {
127
+ return [".c", ".h", ".cc", ".cpp", ".hh", ".hpp"].includes(path.posix.extname(filePath).toLowerCase());
128
+ }
129
+
130
+ export function resolveRelativeImportTargetId(filePath, importPath, indexedFileIds) {
131
+ const isCppLike = isCppLikeFilePath(filePath);
132
+ const isRelativeImport = importPath.startsWith(".");
133
+ const isLocalCppInclude =
134
+ isCppLike && !path.posix.isAbsolute(importPath) && !/^[A-Za-z]:[\\/]/.test(importPath);
135
+
136
+ if (!isRelativeImport && !isLocalCppInclude) {
137
+ return null;
138
+ }
139
+
140
+ const basePath = path.posix.normalize(path.posix.join(path.posix.dirname(filePath), importPath));
141
+ const candidates = [];
142
+ const seenCandidates = new Set();
143
+ pushImportResolutionCandidate(candidates, seenCandidates, basePath);
144
+
145
+ if (path.posix.extname(basePath) === "") {
146
+ const extensions = isCppLike ? CPP_IMPORT_RESOLUTION_EXTENSIONS : IMPORT_RESOLUTION_EXTENSIONS;
147
+ for (const extension of extensions) {
148
+ pushImportResolutionCandidate(candidates, seenCandidates, `${basePath}${extension}`);
149
+ }
150
+ if (!isCppLike) {
151
+ for (const extension of IMPORT_RESOLUTION_EXTENSIONS) {
152
+ pushImportResolutionCandidate(candidates, seenCandidates, path.posix.join(basePath, `index${extension}`));
153
+ }
154
+ }
155
+ } else if (IMPORT_RUNTIME_JS_EXTENSIONS.has(path.posix.extname(basePath))) {
156
+ const extension = path.posix.extname(basePath);
157
+ const stemPath = basePath.slice(0, -extension.length);
158
+ for (const candidateExtension of IMPORT_RUNTIME_JS_RESOLUTION_EXTENSIONS) {
159
+ pushImportResolutionCandidate(candidates, seenCandidates, `${stemPath}${candidateExtension}`);
160
+ }
161
+ }
162
+
163
+ for (const candidate of candidates) {
164
+ const targetFileId = `file:${candidate}`;
165
+ if (indexedFileIds.has(targetFileId)) {
166
+ return targetFileId;
167
+ }
168
+ }
169
+
170
+ return null;
171
+ }
172
+
173
+ export function getGitChanges() {
174
+ try {
175
+ const output = execSync("git status --porcelain", {
176
+ cwd: REPO_ROOT,
177
+ stdio: ["ignore", "pipe", "ignore"],
178
+ encoding: "utf8"
179
+ });
180
+
181
+ const changed = new Set();
182
+ const deleted = new Set();
183
+
184
+ for (const line of output.split(/\r?\n/)) {
185
+ if (!line) continue;
186
+ const status = line.slice(0, 2);
187
+ const payload = line.slice(3).trim();
188
+ if (!payload) continue;
189
+
190
+ if (payload.includes(" -> ")) {
191
+ const [fromPath, toPath] = payload.split(" -> ");
192
+ deleted.add(path.resolve(REPO_ROOT, fromPath));
193
+ changed.add(path.resolve(REPO_ROOT, toPath));
194
+ continue;
195
+ }
196
+
197
+ const absolutePath = path.resolve(REPO_ROOT, payload);
198
+ if (status.includes("D")) {
199
+ deleted.add(absolutePath);
200
+ } else {
201
+ changed.add(absolutePath);
202
+ }
203
+ }
204
+
205
+ return {
206
+ changed: [...changed],
207
+ deleted: [...deleted]
208
+ };
209
+ } catch {
210
+ return {
211
+ changed: [],
212
+ deleted: []
213
+ };
214
+ }
215
+ }
216
+
217
+ export function collectCandidateFiles(sourcePaths, mode) {
218
+ const candidates = new Set();
219
+ const deletedRelPaths = new Set();
220
+
221
+ if (mode === "changed") {
222
+ const gitChanges = getGitChanges();
223
+ if (gitChanges.changed.length > 0 || gitChanges.deleted.length > 0) {
224
+ for (const absolutePath of gitChanges.changed) {
225
+ if (!fs.existsSync(absolutePath)) {
226
+ continue;
227
+ }
228
+
229
+ const stats = fs.statSync(absolutePath);
230
+ if (stats.isFile()) {
231
+ const relPath = toPosixPath(path.relative(REPO_ROOT, absolutePath));
232
+ if (hasSourcePrefix(relPath, sourcePaths)) {
233
+ candidates.add(absolutePath);
234
+ }
235
+ continue;
236
+ }
237
+
238
+ if (stats.isDirectory()) {
239
+ const nestedFiles = new Set();
240
+ walkDirectory(absolutePath, nestedFiles);
241
+ for (const nestedPath of nestedFiles) {
242
+ const nestedRelPath = toPosixPath(path.relative(REPO_ROOT, nestedPath));
243
+ if (hasSourcePrefix(nestedRelPath, sourcePaths)) {
244
+ candidates.add(nestedPath);
245
+ }
246
+ }
247
+ }
248
+ }
249
+
250
+ for (const deletedPath of gitChanges.deleted) {
251
+ const relPath = toPosixPath(path.relative(REPO_ROOT, deletedPath));
252
+ if (hasSourcePrefix(relPath, sourcePaths)) {
253
+ deletedRelPaths.add(relPath);
254
+ }
255
+ }
256
+
257
+ return {
258
+ candidates,
259
+ incrementalMode: true,
260
+ deletedRelPaths: [...deletedRelPaths]
261
+ };
262
+ }
263
+ }
264
+
265
+ for (const sourcePath of sourcePaths) {
266
+ if (hasSkippedDirectorySegment(sourcePath)) {
267
+ continue;
268
+ }
269
+ const absoluteSourcePath = path.resolve(REPO_ROOT, sourcePath);
270
+ if (!fs.existsSync(absoluteSourcePath)) {
271
+ continue;
272
+ }
273
+
274
+ const stats = fs.statSync(absoluteSourcePath);
275
+ if (stats.isFile()) {
276
+ candidates.add(absoluteSourcePath);
277
+ continue;
278
+ }
279
+
280
+ if (stats.isDirectory()) {
281
+ walkDirectory(absoluteSourcePath, candidates);
282
+ }
283
+ }
284
+
285
+ return {
286
+ candidates,
287
+ incrementalMode: false,
288
+ deletedRelPaths: []
289
+ };
290
+ }
291
+
292
+ export function detectKind(relPath) {
293
+ const lower = relPath.toLowerCase();
294
+ const ext = path.extname(lower);
295
+ const isAdrPath =
296
+ /(^|\/)(adr|adrs|decisions)(\/|$)/.test(lower) ||
297
+ /(^|\/)adr[-_ ]?\d+/.test(path.basename(lower));
298
+
299
+ if (isAdrPath) {
300
+ return "ADR";
301
+ }
302
+
303
+ if (
304
+ lower.startsWith("docs/") ||
305
+ ext === ".md" ||
306
+ ext === ".mdx" ||
307
+ ext === ".txt" ||
308
+ ext === ".adoc" ||
309
+ ext === ".rst"
310
+ ) {
311
+ return "DOC";
312
+ }
313
+
314
+ if (LEGACY_DOTNET_METADATA_EXTENSIONS.has(ext) || !CODE_FILE_EXTENSIONS.has(ext)) {
315
+ return "DOC";
316
+ }
317
+
318
+ return "CODE";
319
+ }
320
+
321
+ export function trustLevelForKind(kind) {
322
+ if (kind === "ADR") return 95;
323
+ if (kind === "CODE") return 80;
324
+ return 70;
325
+ }
326
+
327
+ export function checksum(buffer) {
328
+ return crypto.createHash("sha256").update(buffer).digest("hex");
329
+ }
330
+
331
+ export function normalizeWhitespace(value) {
332
+ return value.replace(/\s+/g, " ").trim();
333
+ }
334
+
335
+ export function extractTitle(content, fallbackTitle) {
336
+ const lines = content.split(/\r?\n/);
337
+ for (const line of lines) {
338
+ const match = line.match(/^#\s+(.+)\s*$/);
339
+ if (match) return match[1].trim();
340
+ }
341
+
342
+ return fallbackTitle;
343
+ }
344
+
345
+ export function parseDecisionDate(content, fallbackDate) {
346
+ const datePatterns = [
347
+ /^\s*date:\s*["']?(\d{4}-\d{2}-\d{2})["']?\s*$/im,
348
+ /^\s*decision[_\s-]*date:\s*["']?(\d{4}-\d{2}-\d{2})["']?\s*$/im
349
+ ];
350
+
351
+ for (const pattern of datePatterns) {
352
+ const match = content.match(pattern);
353
+ if (match && !Number.isNaN(Date.parse(match[1]))) {
354
+ return match[1];
355
+ }
356
+ }
357
+
358
+ return fallbackDate.slice(0, 10);
359
+ }
360
+
361
+ export function adrTokens(adrRecord) {
362
+ const fileBase = path.basename(adrRecord.path).replace(path.extname(adrRecord.path), "");
363
+ const tokens = new Set([
364
+ normalizeToken(adrRecord.id),
365
+ normalizeToken(fileBase),
366
+ normalizeToken(adrRecord.title)
367
+ ]);
368
+
369
+ const numberMatch = fileBase.match(/(\d+)/);
370
+ if (numberMatch) {
371
+ tokens.add(normalizeToken(`adr-${numberMatch[1]}`));
372
+ tokens.add(normalizeToken(numberMatch[1]));
373
+ }
374
+
375
+ return [...tokens].filter(Boolean);
376
+ }
377
+
378
+ export function findSupersedesReferences(content) {
379
+ const refs = new Set();
380
+ const pattern = /(?:supersedes|ersätter)\s*[:\-]?\s*([A-Za-z0-9._/-]+)/gi;
381
+ let match;
382
+ while ((match = pattern.exec(content)) !== null) {
383
+ refs.add(match[1]);
384
+ }
385
+
386
+ return [...refs];
387
+ }
@@ -0,0 +1,131 @@
1
+ import path from "node:path";
2
+ import { readJsonlSafe } from "./io.mjs";
3
+ import { relationKey } from "./relations.mjs";
4
+ import { CACHE_DIR } from "./runtime-paths.mjs";
5
+
6
+ export function removeChunkStateForFile(fileId, chunkRecordMap, definesRelationMap, callsRelationMap, importsRelationMap, callsSqlRelationMap) {
7
+ const removedChunkIds = new Set();
8
+
9
+ for (const [chunkId, chunkRecord] of chunkRecordMap.entries()) {
10
+ if (chunkRecord.file_id === fileId) {
11
+ removedChunkIds.add(chunkId);
12
+ chunkRecordMap.delete(chunkId);
13
+ }
14
+ }
15
+
16
+ if (removedChunkIds.size === 0) {
17
+ return;
18
+ }
19
+
20
+ for (const [key, relation] of definesRelationMap.entries()) {
21
+ if (relation.from === fileId || removedChunkIds.has(relation.to)) {
22
+ definesRelationMap.delete(key);
23
+ }
24
+ }
25
+
26
+ for (const [key, relation] of callsRelationMap.entries()) {
27
+ if (removedChunkIds.has(relation.from) || removedChunkIds.has(relation.to)) {
28
+ callsRelationMap.delete(key);
29
+ }
30
+ }
31
+
32
+ for (const [key, relation] of importsRelationMap.entries()) {
33
+ if (removedChunkIds.has(relation.from)) {
34
+ importsRelationMap.delete(key);
35
+ }
36
+ }
37
+
38
+ for (const [key, relation] of callsSqlRelationMap.entries()) {
39
+ if (relation.from === fileId || removedChunkIds.has(relation.to)) {
40
+ callsSqlRelationMap.delete(key);
41
+ }
42
+ }
43
+ }
44
+
45
+ export function hydrateIncrementalChunkState(fileRecords) {
46
+ const fileIdSet = new Set(fileRecords.map((record) => record.id));
47
+ const chunkRecordMap = new Map();
48
+ const definesRelationMap = new Map();
49
+ const callsRelationMap = new Map();
50
+ const importsRelationMap = new Map();
51
+ const callsSqlRelationMap = new Map();
52
+
53
+ for (const record of readJsonlSafe(path.join(CACHE_DIR, "entities.chunk.jsonl"))) {
54
+ if (!record || typeof record !== "object") continue;
55
+ const chunkId = String(record.id ?? "");
56
+ const fileId = String(record.file_id ?? "");
57
+ if (!chunkId || !fileIdSet.has(fileId)) {
58
+ continue;
59
+ }
60
+ chunkRecordMap.set(chunkId, {
61
+ ...record,
62
+ id: chunkId,
63
+ file_id: fileId
64
+ });
65
+ }
66
+
67
+ const chunkIdSet = new Set(chunkRecordMap.keys());
68
+
69
+ for (const record of readJsonlSafe(path.join(CACHE_DIR, "relations.defines.jsonl"))) {
70
+ if (!record || typeof record !== "object") continue;
71
+ const from = String(record.from ?? "");
72
+ const to = String(record.to ?? "");
73
+ if (!fileIdSet.has(from) || !chunkIdSet.has(to)) {
74
+ continue;
75
+ }
76
+ definesRelationMap.set(relationKey(from, to), { from, to });
77
+ }
78
+
79
+ for (const record of readJsonlSafe(path.join(CACHE_DIR, "relations.calls.jsonl"))) {
80
+ if (!record || typeof record !== "object") continue;
81
+ const from = String(record.from ?? "");
82
+ const to = String(record.to ?? "");
83
+ const callType = String(record.call_type ?? "direct");
84
+ if (!chunkIdSet.has(from) || !chunkIdSet.has(to)) {
85
+ continue;
86
+ }
87
+ callsRelationMap.set(relationKey(from, to, callType), {
88
+ from,
89
+ to,
90
+ call_type: callType
91
+ });
92
+ }
93
+
94
+ for (const record of readJsonlSafe(path.join(CACHE_DIR, "relations.imports.jsonl"))) {
95
+ if (!record || typeof record !== "object") continue;
96
+ const from = String(record.from ?? "");
97
+ const to = String(record.to ?? "");
98
+ const importName = String(record.import_name ?? "");
99
+ if (!chunkIdSet.has(from) || !fileIdSet.has(to)) {
100
+ continue;
101
+ }
102
+ importsRelationMap.set(relationKey(from, to, importName), {
103
+ from,
104
+ to,
105
+ import_name: importName
106
+ });
107
+ }
108
+
109
+ for (const record of readJsonlSafe(path.join(CACHE_DIR, "relations.calls_sql.jsonl"))) {
110
+ if (!record || typeof record !== "object") continue;
111
+ const from = String(record.from ?? "");
112
+ const to = String(record.to ?? "");
113
+ const note = String(record.note ?? "");
114
+ if (!fileIdSet.has(from) || !chunkIdSet.has(to)) {
115
+ continue;
116
+ }
117
+ callsSqlRelationMap.set(relationKey(from, to, note), {
118
+ from,
119
+ to,
120
+ note
121
+ });
122
+ }
123
+
124
+ return {
125
+ chunkRecordMap,
126
+ definesRelationMap,
127
+ callsRelationMap,
128
+ importsRelationMap,
129
+ callsSqlRelationMap
130
+ };
131
+ }
@@ -0,0 +1,78 @@
1
+ import fs from "node:fs";
2
+
3
+ export function writeJsonl(filePath, records) {
4
+ const fd = fs.openSync(filePath, "w");
5
+ try {
6
+ for (const record of records) {
7
+ fs.writeSync(fd, `${JSON.stringify(record)}\n`, undefined, "utf8");
8
+ }
9
+ } finally {
10
+ fs.closeSync(fd);
11
+ }
12
+ }
13
+
14
+ export function stageJsonl(filePath, records) {
15
+ const stagedPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
16
+ writeJsonl(stagedPath, records);
17
+ return {
18
+ commit() {
19
+ fs.renameSync(stagedPath, filePath);
20
+ },
21
+ discard() {
22
+ fs.rmSync(stagedPath, { force: true });
23
+ }
24
+ };
25
+ }
26
+
27
+ export function countFileContentRecords(fileRecords) {
28
+ let count = 0;
29
+ for (const fileRecord of fileRecords) {
30
+ if (Object.prototype.hasOwnProperty.call(fileRecord, "content")) {
31
+ count += 1;
32
+ }
33
+ }
34
+ return count;
35
+ }
36
+
37
+ export function sanitizeTsvCell(value) {
38
+ if (value === null || value === undefined) return "";
39
+ return String(value).replace(/\t/g, " ").replace(/\r?\n/g, " ");
40
+ }
41
+
42
+ export function writeTsv(filePath, headers, rows) {
43
+ const fd = fs.openSync(filePath, "w");
44
+ try {
45
+ fs.writeSync(fd, `${headers.join("\t")}\n`, undefined, "utf8");
46
+ for (const row of rows) {
47
+ fs.writeSync(fd, `${row.map((value) => sanitizeTsvCell(value)).join("\t")}\n`, undefined, "utf8");
48
+ }
49
+ } finally {
50
+ fs.closeSync(fd);
51
+ }
52
+ }
53
+
54
+ export function* mapRows(records, project) {
55
+ for (const record of records) {
56
+ yield project(record);
57
+ }
58
+ }
59
+
60
+ export function readJsonlSafe(filePath) {
61
+ if (!fs.existsSync(filePath)) {
62
+ return [];
63
+ }
64
+
65
+ return fs
66
+ .readFileSync(filePath, "utf8")
67
+ .split(/\r?\n/)
68
+ .map((line) => line.trim())
69
+ .filter(Boolean)
70
+ .map((line) => {
71
+ try {
72
+ return JSON.parse(line);
73
+ } catch {
74
+ return null;
75
+ }
76
+ })
77
+ .filter((record) => record !== null);
78
+ }
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createIngestPipelineState,
4
+ runCacheWriteStage,
5
+ runDatabaseWriteStage,
6
+ runFileCacheStagingStage,
7
+ runManifestCompletionStage,
8
+ runMaterializationStage,
9
+ runParseStage,
10
+ runScanHydrationStage,
11
+ runTokenMatchingStage
12
+ } from "./pipeline-stages.mjs";
13
+ import {
14
+ buildChunkAliasIndexes,
15
+ buildSqlResourceReferenceMap,
16
+ extractSqlObjectReferencesFromContent,
17
+ generateConfigIncludeRelations,
18
+ generateConfigTransformKeyRelations,
19
+ generateMachineConfigRelations,
20
+ generateConfigTransformRelations,
21
+ generateNamedResourceRelations,
22
+ generateSectionHandlerRelations
23
+ } from "./relations.mjs";
24
+ import {
25
+ detectKind,
26
+ resolveRelativeImportTargetId
27
+ } from "./files.mjs";
28
+ import {
29
+ generateChunkDescription,
30
+ generateModuleSummary,
31
+ generateModules
32
+ } from "./chunks.mjs";
33
+ import { generateProjects } from "./projects.mjs";
34
+ import {
35
+ getChunkParserForExtension,
36
+ initializeParserComposition
37
+ } from "./parser-composition.mjs";
38
+ import {
39
+ parseFilesInWorkers,
40
+ resolveIngestWorkerCount
41
+ } from "./workers.mjs";
42
+
43
+ async function main() {
44
+ await initializeParserComposition();
45
+ const state = createIngestPipelineState();
46
+ runScanHydrationStage(state);
47
+ await runParseStage(state);
48
+ runMaterializationStage(state);
49
+ runFileCacheStagingStage(state);
50
+ runTokenMatchingStage(state);
51
+ runCacheWriteStage(state);
52
+ runDatabaseWriteStage(state);
53
+ runManifestCompletionStage(state);
54
+ }
55
+
56
+ export {
57
+ buildChunkAliasIndexes,
58
+ buildSqlResourceReferenceMap,
59
+ detectKind,
60
+ extractSqlObjectReferencesFromContent,
61
+ generateChunkDescription,
62
+ generateConfigIncludeRelations,
63
+ generateConfigTransformKeyRelations,
64
+ generateMachineConfigRelations,
65
+ generateConfigTransformRelations,
66
+ generateModuleSummary,
67
+ generateModules,
68
+ generateNamedResourceRelations,
69
+ generateProjects,
70
+ generateSectionHandlerRelations,
71
+ getChunkParserForExtension,
72
+ main,
73
+ parseFilesInWorkers,
74
+ resolveIngestWorkerCount,
75
+ resolveRelativeImportTargetId
76
+ };