truecourse 0.4.2 → 0.4.4

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/server.mjs CHANGED
@@ -17990,17 +17990,17 @@ var require_router = __commonJS({
17990
17990
  var toString = Object.prototype.toString;
17991
17991
  var proto = module.exports = function(options) {
17992
17992
  var opts = options || {};
17993
- function router9(req, res, next) {
17994
- router9.handle(req, res, next);
17995
- }
17996
- setPrototypeOf(router9, proto);
17997
- router9.params = {};
17998
- router9._params = [];
17999
- router9.caseSensitive = opts.caseSensitive;
18000
- router9.mergeParams = opts.mergeParams;
18001
- router9.strict = opts.strict;
18002
- router9.stack = [];
18003
- return router9;
17993
+ function router10(req, res, next) {
17994
+ router10.handle(req, res, next);
17995
+ }
17996
+ setPrototypeOf(router10, proto);
17997
+ router10.params = {};
17998
+ router10._params = [];
17999
+ router10.caseSensitive = opts.caseSensitive;
18000
+ router10.mergeParams = opts.mergeParams;
18001
+ router10.strict = opts.strict;
18002
+ router10.stack = [];
18003
+ return router10;
18004
18004
  };
18005
18005
  proto.param = function param(name, fn) {
18006
18006
  if (typeof name === "function") {
@@ -20592,7 +20592,7 @@ var require_application = __commonJS({
20592
20592
  "node_modules/.pnpm/express@4.22.1/node_modules/express/lib/application.js"(exports, module) {
20593
20593
  "use strict";
20594
20594
  var finalhandler = require_finalhandler();
20595
- var Router9 = require_router();
20595
+ var Router10 = require_router();
20596
20596
  var methods = require_methods();
20597
20597
  var middleware = require_init();
20598
20598
  var query = require_query();
@@ -20657,7 +20657,7 @@ var require_application = __commonJS({
20657
20657
  };
20658
20658
  app.lazyrouter = function lazyrouter() {
20659
20659
  if (!this._router) {
20660
- this._router = new Router9({
20660
+ this._router = new Router10({
20661
20661
  caseSensitive: this.enabled("case sensitive routing"),
20662
20662
  strict: this.enabled("strict routing")
20663
20663
  });
@@ -20666,17 +20666,17 @@ var require_application = __commonJS({
20666
20666
  }
20667
20667
  };
20668
20668
  app.handle = function handle(req, res, callback) {
20669
- var router9 = this._router;
20669
+ var router10 = this._router;
20670
20670
  var done = callback || finalhandler(req, res, {
20671
20671
  env: this.get("env"),
20672
20672
  onerror: logerror.bind(this)
20673
20673
  });
20674
- if (!router9) {
20674
+ if (!router10) {
20675
20675
  debug2("no routes defined on app");
20676
20676
  done();
20677
20677
  return;
20678
20678
  }
20679
- router9.handle(req, res, done);
20679
+ router10.handle(req, res, done);
20680
20680
  };
20681
20681
  app.use = function use(fn) {
20682
20682
  var offset = 0;
@@ -20696,15 +20696,15 @@ var require_application = __commonJS({
20696
20696
  throw new TypeError("app.use() requires a middleware function");
20697
20697
  }
20698
20698
  this.lazyrouter();
20699
- var router9 = this._router;
20699
+ var router10 = this._router;
20700
20700
  fns.forEach(function(fn2) {
20701
20701
  if (!fn2 || !fn2.handle || !fn2.set) {
20702
- return router9.use(path17, fn2);
20702
+ return router10.use(path17, fn2);
20703
20703
  }
20704
20704
  debug2(".use app under %s", path17);
20705
20705
  fn2.mountpath = path17;
20706
20706
  fn2.parent = this;
20707
- router9.use(path17, function mounted_app(req, res, next) {
20707
+ router10.use(path17, function mounted_app(req, res, next) {
20708
20708
  var orig = req.app;
20709
20709
  fn2.handle(req, res, function(err) {
20710
20710
  setPrototypeOf(req, orig.request);
@@ -22521,7 +22521,7 @@ var require_express = __commonJS({
22521
22521
  var mixin = require_merge_descriptors();
22522
22522
  var proto = require_application();
22523
22523
  var Route = require_route();
22524
- var Router9 = require_router();
22524
+ var Router10 = require_router();
22525
22525
  var req = require_request();
22526
22526
  var res = require_response();
22527
22527
  exports = module.exports = createApplication;
@@ -22544,7 +22544,7 @@ var require_express = __commonJS({
22544
22544
  exports.request = req;
22545
22545
  exports.response = res;
22546
22546
  exports.Route = Route;
22547
- exports.Router = Router9;
22547
+ exports.Router = Router10;
22548
22548
  exports.json = bodyParser.json;
22549
22549
  exports.query = require_query();
22550
22550
  exports.raw = bodyParser.raw;
@@ -40157,6 +40157,219 @@ var require_dist4 = __commonJS({
40157
40157
  }
40158
40158
  });
40159
40159
 
40160
+ // apps/server/src/lib/atomic-write.ts
40161
+ import fs6 from "node:fs";
40162
+ import path5 from "node:path";
40163
+ function atomicWriteJson(targetPath, data) {
40164
+ fs6.mkdirSync(path5.dirname(targetPath), { recursive: true });
40165
+ const tmp = `${targetPath}.tmp-${process.pid}-${Date.now()}`;
40166
+ fs6.writeFileSync(tmp, JSON.stringify(data, null, 2));
40167
+ fs6.renameSync(tmp, targetPath);
40168
+ }
40169
+ function lockPath(repoPath) {
40170
+ return path5.join(repoPath, ".truecourse", LOCK_FILENAME);
40171
+ }
40172
+ function acquireAnalyzeLock(repoPath) {
40173
+ const file = lockPath(repoPath);
40174
+ fs6.mkdirSync(path5.dirname(file), { recursive: true });
40175
+ try {
40176
+ const fd = fs6.openSync(file, "wx");
40177
+ fs6.writeSync(fd, `${process.pid}
40178
+ ${(/* @__PURE__ */ new Date()).toISOString()}
40179
+ `);
40180
+ fs6.closeSync(fd);
40181
+ } catch (err) {
40182
+ if (err.code === "EEXIST") {
40183
+ let owner = null;
40184
+ try {
40185
+ owner = parseInt(fs6.readFileSync(file, "utf-8").split("\n")[0], 10) || null;
40186
+ } catch {
40187
+ }
40188
+ throw new AnalyzeLockError(repoPath, owner);
40189
+ }
40190
+ throw err;
40191
+ }
40192
+ }
40193
+ function releaseAnalyzeLock(repoPath) {
40194
+ try {
40195
+ fs6.unlinkSync(lockPath(repoPath));
40196
+ } catch (err) {
40197
+ if (err.code !== "ENOENT") throw err;
40198
+ }
40199
+ }
40200
+ var LOCK_FILENAME, AnalyzeLockError;
40201
+ var init_atomic_write = __esm({
40202
+ "apps/server/src/lib/atomic-write.ts"() {
40203
+ "use strict";
40204
+ LOCK_FILENAME = ".analyze.lock";
40205
+ AnalyzeLockError = class extends Error {
40206
+ constructor(repoPath, ownerPid) {
40207
+ const who = ownerPid != null ? ` (held by pid ${ownerPid})` : "";
40208
+ super(
40209
+ `Another analyze is already running for ${repoPath}${who}. If you're sure no analyze is in progress, remove ${lockPath(repoPath)} and retry.`
40210
+ );
40211
+ this.name = "AnalyzeLockError";
40212
+ }
40213
+ };
40214
+ }
40215
+ });
40216
+
40217
+ // apps/server/src/lib/analysis-store.ts
40218
+ var analysis_store_exports = {};
40219
+ __export(analysis_store_exports, {
40220
+ analysisFilePath: () => analysisFilePath,
40221
+ appendHistory: () => appendHistory,
40222
+ buildAnalysisFilename: () => buildAnalysisFilename,
40223
+ clearLatestCache: () => clearLatestCache,
40224
+ deleteAnalysis: () => deleteAnalysis,
40225
+ deleteDiff: () => deleteDiff,
40226
+ deleteLatest: () => deleteLatest,
40227
+ diffPath: () => diffPath,
40228
+ findAnalysisFilename: () => findAnalysisFilename,
40229
+ historyPath: () => historyPath,
40230
+ latestPath: () => latestPath,
40231
+ listAnalyses: () => listAnalyses,
40232
+ readAnalysis: () => readAnalysis,
40233
+ readDiff: () => readDiff,
40234
+ readHistory: () => readHistory,
40235
+ readLatest: () => readLatest,
40236
+ removeFromHistory: () => removeFromHistory,
40237
+ writeAnalysis: () => writeAnalysis,
40238
+ writeDiff: () => writeDiff,
40239
+ writeLatest: () => writeLatest
40240
+ });
40241
+ import fs7 from "node:fs";
40242
+ import path6 from "node:path";
40243
+ function storeDir(repoPath) {
40244
+ return path6.join(repoPath, TRUECOURSE_DIR2);
40245
+ }
40246
+ function analysesDir(repoPath) {
40247
+ return path6.join(storeDir(repoPath), ANALYSES_DIR);
40248
+ }
40249
+ function analysisFilePath(repoPath, filename) {
40250
+ return path6.join(analysesDir(repoPath), filename);
40251
+ }
40252
+ function latestPath(repoPath) {
40253
+ return path6.join(storeDir(repoPath), LATEST_FILE);
40254
+ }
40255
+ function historyPath(repoPath) {
40256
+ return path6.join(storeDir(repoPath), HISTORY_FILE);
40257
+ }
40258
+ function diffPath(repoPath) {
40259
+ return path6.join(storeDir(repoPath), DIFF_FILE);
40260
+ }
40261
+ function buildAnalysisFilename(analysisId, createdAt) {
40262
+ const iso = createdAt.replace(/[:.]/g, "-").replace(/-\d{3}Z$/, "Z");
40263
+ const shortId = analysisId.replace(/-/g, "").slice(0, 8);
40264
+ return `${iso}_${shortId}.json`;
40265
+ }
40266
+ function readLatest(repoPath) {
40267
+ const file = latestPath(repoPath);
40268
+ let mtime;
40269
+ try {
40270
+ mtime = fs7.statSync(file).mtimeMs;
40271
+ } catch (err) {
40272
+ if (err.code === "ENOENT") {
40273
+ latestCache.delete(repoPath);
40274
+ return null;
40275
+ }
40276
+ throw err;
40277
+ }
40278
+ const cached = latestCache.get(repoPath);
40279
+ if (cached && cached.mtime === mtime) return cached.data;
40280
+ const data = JSON.parse(fs7.readFileSync(file, "utf-8"));
40281
+ latestCache.set(repoPath, { mtime, data });
40282
+ return data;
40283
+ }
40284
+ function writeLatest(repoPath, latest) {
40285
+ atomicWriteJson(latestPath(repoPath), latest);
40286
+ latestCache.delete(repoPath);
40287
+ }
40288
+ function deleteLatest(repoPath) {
40289
+ try {
40290
+ fs7.unlinkSync(latestPath(repoPath));
40291
+ } catch (err) {
40292
+ if (err.code !== "ENOENT") throw err;
40293
+ }
40294
+ latestCache.delete(repoPath);
40295
+ }
40296
+ function writeAnalysis(repoPath, snapshot) {
40297
+ const filename = buildAnalysisFilename(snapshot.id, snapshot.createdAt);
40298
+ atomicWriteJson(analysisFilePath(repoPath, filename), snapshot);
40299
+ return { filename, snapshot };
40300
+ }
40301
+ function readAnalysis(repoPath, filename) {
40302
+ const file = analysisFilePath(repoPath, filename);
40303
+ if (!fs7.existsSync(file)) return null;
40304
+ return JSON.parse(fs7.readFileSync(file, "utf-8"));
40305
+ }
40306
+ function listAnalyses(repoPath) {
40307
+ const dir = analysesDir(repoPath);
40308
+ if (!fs7.existsSync(dir)) return [];
40309
+ return fs7.readdirSync(dir).filter((name) => name.endsWith(".json")).sort();
40310
+ }
40311
+ function findAnalysisFilename(repoPath, analysisId) {
40312
+ for (const name of listAnalyses(repoPath).reverse()) {
40313
+ const snap = readAnalysis(repoPath, name);
40314
+ if (snap?.id === analysisId) return name;
40315
+ }
40316
+ return null;
40317
+ }
40318
+ function deleteAnalysis(repoPath, filename) {
40319
+ try {
40320
+ fs7.unlinkSync(analysisFilePath(repoPath, filename));
40321
+ } catch (err) {
40322
+ if (err.code !== "ENOENT") throw err;
40323
+ }
40324
+ }
40325
+ function readHistory(repoPath) {
40326
+ const file = historyPath(repoPath);
40327
+ if (!fs7.existsSync(file)) return { analyses: [] };
40328
+ return JSON.parse(fs7.readFileSync(file, "utf-8"));
40329
+ }
40330
+ function appendHistory(repoPath, entry) {
40331
+ const history = readHistory(repoPath);
40332
+ history.analyses.push(entry);
40333
+ atomicWriteJson(historyPath(repoPath), history);
40334
+ }
40335
+ function removeFromHistory(repoPath, analysisId) {
40336
+ const history = readHistory(repoPath);
40337
+ const next = history.analyses.filter((a) => a.id !== analysisId);
40338
+ if (next.length === history.analyses.length) return;
40339
+ atomicWriteJson(historyPath(repoPath), { analyses: next });
40340
+ }
40341
+ function readDiff(repoPath) {
40342
+ const file = diffPath(repoPath);
40343
+ if (!fs7.existsSync(file)) return null;
40344
+ return JSON.parse(fs7.readFileSync(file, "utf-8"));
40345
+ }
40346
+ function writeDiff(repoPath, diff) {
40347
+ atomicWriteJson(diffPath(repoPath), diff);
40348
+ }
40349
+ function deleteDiff(repoPath) {
40350
+ try {
40351
+ fs7.unlinkSync(diffPath(repoPath));
40352
+ } catch (err) {
40353
+ if (err.code !== "ENOENT") throw err;
40354
+ }
40355
+ }
40356
+ function clearLatestCache() {
40357
+ latestCache.clear();
40358
+ }
40359
+ var TRUECOURSE_DIR2, ANALYSES_DIR, LATEST_FILE, HISTORY_FILE, DIFF_FILE, latestCache;
40360
+ var init_analysis_store = __esm({
40361
+ "apps/server/src/lib/analysis-store.ts"() {
40362
+ "use strict";
40363
+ init_atomic_write();
40364
+ TRUECOURSE_DIR2 = ".truecourse";
40365
+ ANALYSES_DIR = "analyses";
40366
+ LATEST_FILE = "LATEST.json";
40367
+ HISTORY_FILE = "history.json";
40368
+ DIFF_FILE = "diff.json";
40369
+ latestCache = /* @__PURE__ */ new Map();
40370
+ }
40371
+ });
40372
+
40160
40373
  // node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
40161
40374
  var require_ignore = __commonJS({
40162
40375
  "node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js"(exports, module) {
@@ -45316,7 +45529,7 @@ var init_escape = __esm({
45316
45529
  });
45317
45530
 
45318
45531
  // node_modules/.pnpm/minimatch@10.2.4/node_modules/minimatch/dist/esm/index.js
45319
- var minimatch, starDotExtRE, starDotExtTest, starDotExtTestDot, starDotExtTestNocase, starDotExtTestNocaseDot, starDotStarRE, starDotStarTest, starDotStarTestDot, dotStarRE, dotStarTest, starRE, starTest, starTestDot, qmarksRE, qmarksTestNocase, qmarksTestNocaseDot, qmarksTestDot, qmarksTest, qmarksTestNoExt, qmarksTestNoExtDot, defaultPlatform, path5, sep, GLOBSTAR, qmark2, star2, twoStarDot, twoStarNoDot, filter, ext, defaults, braceExpand, makeRe, match, globMagic, regExpEscape2, Minimatch;
45532
+ var minimatch, starDotExtRE, starDotExtTest, starDotExtTestDot, starDotExtTestNocase, starDotExtTestNocaseDot, starDotStarRE, starDotStarTest, starDotStarTestDot, dotStarRE, dotStarTest, starRE, starTest, starTestDot, qmarksRE, qmarksTestNocase, qmarksTestNocaseDot, qmarksTestDot, qmarksTest, qmarksTestNoExt, qmarksTestNoExtDot, defaultPlatform, path7, sep, GLOBSTAR, qmark2, star2, twoStarDot, twoStarNoDot, filter, ext, defaults, braceExpand, makeRe, match, globMagic, regExpEscape2, Minimatch;
45320
45533
  var init_esm3 = __esm({
45321
45534
  "node_modules/.pnpm/minimatch@10.2.4/node_modules/minimatch/dist/esm/index.js"() {
45322
45535
  init_esm2();
@@ -45385,11 +45598,11 @@ var init_esm3 = __esm({
45385
45598
  return (f2) => f2.length === len && f2 !== "." && f2 !== "..";
45386
45599
  };
45387
45600
  defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
45388
- path5 = {
45601
+ path7 = {
45389
45602
  win32: { sep: "\\" },
45390
45603
  posix: { sep: "/" }
45391
45604
  };
45392
- sep = defaultPlatform === "win32" ? path5.win32.sep : path5.posix.sep;
45605
+ sep = defaultPlatform === "win32" ? path7.win32.sep : path7.posix.sep;
45393
45606
  minimatch.sep = sep;
45394
45607
  GLOBSTAR = Symbol("globstar **");
45395
45608
  minimatch.GLOBSTAR = GLOBSTAR;
@@ -47435,7 +47648,7 @@ var init_database_detector = __esm({
47435
47648
  });
47436
47649
 
47437
47650
  // packages/analyzer/dist/module-extractor.js
47438
- import path6 from "path";
47651
+ import path8 from "path";
47439
47652
  function extractModulesAndMethods(analyses, layerDetails, fileDependencies) {
47440
47653
  const modules = [];
47441
47654
  const methods = [];
@@ -47582,7 +47795,7 @@ function deriveModuleName(analysis) {
47582
47795
  return localExports[0].name;
47583
47796
  }
47584
47797
  const baseName = fileBaseName(analysis.filePath);
47585
- const strippedName = stripExtension(path6.basename(analysis.filePath));
47798
+ const strippedName = stripExtension(path8.basename(analysis.filePath));
47586
47799
  if (INDEX_NAMES.has(strippedName)) {
47587
47800
  return baseName;
47588
47801
  }
@@ -47599,10 +47812,10 @@ function deriveModuleName(analysis) {
47599
47812
  return baseName;
47600
47813
  }
47601
47814
  function deriveNextjsRouteName(filePath) {
47602
- const base = path6.basename(filePath).replace(/\.(ts|tsx|js|jsx)$/, "");
47815
+ const base = path8.basename(filePath).replace(/\.(ts|tsx|js|jsx)$/, "");
47603
47816
  if (base !== "route" && base !== "page")
47604
47817
  return null;
47605
- const parts = filePath.split(path6.sep);
47818
+ const parts = filePath.split(path8.sep);
47606
47819
  const appIdx = parts.lastIndexOf("app");
47607
47820
  if (appIdx === -1)
47608
47821
  return null;
@@ -47620,9 +47833,9 @@ function stripExtension(filename) {
47620
47833
  return filename;
47621
47834
  }
47622
47835
  function fileBaseName(filePath) {
47623
- const name = stripExtension(path6.basename(filePath));
47836
+ const name = stripExtension(path8.basename(filePath));
47624
47837
  if (INDEX_NAMES.has(name)) {
47625
- return path6.basename(path6.dirname(filePath));
47838
+ return path8.basename(path8.dirname(filePath));
47626
47839
  }
47627
47840
  return name;
47628
47841
  }
@@ -74385,10 +74598,10 @@ var init_secret_rules = __esm({
74385
74598
  });
74386
74599
 
74387
74600
  // packages/analyzer/dist/rules/security/secret-scanner.js
74388
- import path7 from "node:path";
74601
+ import path9 from "node:path";
74389
74602
  function isSensitiveFile(filePath) {
74390
- const basename2 = path7.basename(filePath);
74391
- const ext2 = path7.extname(filePath);
74603
+ const basename2 = path9.basename(filePath);
74604
+ const ext2 = path9.extname(filePath);
74392
74605
  if (basename2.startsWith(".env")) {
74393
74606
  const envVariant = basename2;
74394
74607
  if (SENSITIVE_FILE_EXTENSIONS.has(envVariant)) {
@@ -123988,211 +124201,6 @@ var init_dist2 = __esm({
123988
124201
  }
123989
124202
  });
123990
124203
 
123991
- // apps/server/src/lib/atomic-write.ts
123992
- import fs6 from "node:fs";
123993
- import path9 from "node:path";
123994
- function atomicWriteJson(targetPath, data) {
123995
- fs6.mkdirSync(path9.dirname(targetPath), { recursive: true });
123996
- const tmp = `${targetPath}.tmp-${process.pid}-${Date.now()}`;
123997
- fs6.writeFileSync(tmp, JSON.stringify(data, null, 2));
123998
- fs6.renameSync(tmp, targetPath);
123999
- }
124000
- function lockPath(repoPath) {
124001
- return path9.join(repoPath, ".truecourse", LOCK_FILENAME);
124002
- }
124003
- function acquireAnalyzeLock(repoPath) {
124004
- const file = lockPath(repoPath);
124005
- fs6.mkdirSync(path9.dirname(file), { recursive: true });
124006
- try {
124007
- const fd = fs6.openSync(file, "wx");
124008
- fs6.writeSync(fd, `${process.pid}
124009
- ${(/* @__PURE__ */ new Date()).toISOString()}
124010
- `);
124011
- fs6.closeSync(fd);
124012
- } catch (err) {
124013
- if (err.code === "EEXIST") {
124014
- let owner = null;
124015
- try {
124016
- owner = parseInt(fs6.readFileSync(file, "utf-8").split("\n")[0], 10) || null;
124017
- } catch {
124018
- }
124019
- throw new AnalyzeLockError(repoPath, owner);
124020
- }
124021
- throw err;
124022
- }
124023
- }
124024
- function releaseAnalyzeLock(repoPath) {
124025
- try {
124026
- fs6.unlinkSync(lockPath(repoPath));
124027
- } catch (err) {
124028
- if (err.code !== "ENOENT") throw err;
124029
- }
124030
- }
124031
- var LOCK_FILENAME, AnalyzeLockError;
124032
- var init_atomic_write = __esm({
124033
- "apps/server/src/lib/atomic-write.ts"() {
124034
- "use strict";
124035
- LOCK_FILENAME = ".analyze.lock";
124036
- AnalyzeLockError = class extends Error {
124037
- constructor(repoPath, ownerPid) {
124038
- const who = ownerPid != null ? ` (held by pid ${ownerPid})` : "";
124039
- super(
124040
- `Another analyze is already running for ${repoPath}${who}. If you're sure no analyze is in progress, remove ${lockPath(repoPath)} and retry.`
124041
- );
124042
- this.name = "AnalyzeLockError";
124043
- }
124044
- };
124045
- }
124046
- });
124047
-
124048
- // apps/server/src/lib/analysis-store.ts
124049
- var analysis_store_exports = {};
124050
- __export(analysis_store_exports, {
124051
- analysisFilePath: () => analysisFilePath,
124052
- appendHistory: () => appendHistory,
124053
- buildAnalysisFilename: () => buildAnalysisFilename,
124054
- clearLatestCache: () => clearLatestCache,
124055
- deleteAnalysis: () => deleteAnalysis,
124056
- deleteDiff: () => deleteDiff,
124057
- deleteLatest: () => deleteLatest,
124058
- diffPath: () => diffPath,
124059
- historyPath: () => historyPath,
124060
- latestPath: () => latestPath,
124061
- listAnalyses: () => listAnalyses,
124062
- readAnalysis: () => readAnalysis,
124063
- readDiff: () => readDiff,
124064
- readHistory: () => readHistory,
124065
- readLatest: () => readLatest,
124066
- removeFromHistory: () => removeFromHistory,
124067
- writeAnalysis: () => writeAnalysis,
124068
- writeDiff: () => writeDiff,
124069
- writeLatest: () => writeLatest
124070
- });
124071
- import fs7 from "node:fs";
124072
- import path10 from "node:path";
124073
- function storeDir(repoPath) {
124074
- return path10.join(repoPath, TRUECOURSE_DIR2);
124075
- }
124076
- function analysesDir(repoPath) {
124077
- return path10.join(storeDir(repoPath), ANALYSES_DIR);
124078
- }
124079
- function analysisFilePath(repoPath, filename) {
124080
- return path10.join(analysesDir(repoPath), filename);
124081
- }
124082
- function latestPath(repoPath) {
124083
- return path10.join(storeDir(repoPath), LATEST_FILE);
124084
- }
124085
- function historyPath(repoPath) {
124086
- return path10.join(storeDir(repoPath), HISTORY_FILE);
124087
- }
124088
- function diffPath(repoPath) {
124089
- return path10.join(storeDir(repoPath), DIFF_FILE);
124090
- }
124091
- function buildAnalysisFilename(analysisId, createdAt) {
124092
- const iso = createdAt.replace(/[:.]/g, "-").replace(/-\d{3}Z$/, "Z");
124093
- const shortId = analysisId.replace(/-/g, "").slice(0, 8);
124094
- return `${iso}_${shortId}.json`;
124095
- }
124096
- function readLatest(repoPath) {
124097
- const file = latestPath(repoPath);
124098
- let mtime;
124099
- try {
124100
- mtime = fs7.statSync(file).mtimeMs;
124101
- } catch (err) {
124102
- if (err.code === "ENOENT") {
124103
- latestCache.delete(repoPath);
124104
- return null;
124105
- }
124106
- throw err;
124107
- }
124108
- const cached = latestCache.get(repoPath);
124109
- if (cached && cached.mtime === mtime) return cached.data;
124110
- const data = JSON.parse(fs7.readFileSync(file, "utf-8"));
124111
- latestCache.set(repoPath, { mtime, data });
124112
- return data;
124113
- }
124114
- function writeLatest(repoPath, latest) {
124115
- atomicWriteJson(latestPath(repoPath), latest);
124116
- latestCache.delete(repoPath);
124117
- }
124118
- function deleteLatest(repoPath) {
124119
- try {
124120
- fs7.unlinkSync(latestPath(repoPath));
124121
- } catch (err) {
124122
- if (err.code !== "ENOENT") throw err;
124123
- }
124124
- latestCache.delete(repoPath);
124125
- }
124126
- function writeAnalysis(repoPath, snapshot) {
124127
- const filename = buildAnalysisFilename(snapshot.id, snapshot.createdAt);
124128
- atomicWriteJson(analysisFilePath(repoPath, filename), snapshot);
124129
- return { filename, snapshot };
124130
- }
124131
- function readAnalysis(repoPath, filename) {
124132
- const file = analysisFilePath(repoPath, filename);
124133
- if (!fs7.existsSync(file)) return null;
124134
- return JSON.parse(fs7.readFileSync(file, "utf-8"));
124135
- }
124136
- function listAnalyses(repoPath) {
124137
- const dir = analysesDir(repoPath);
124138
- if (!fs7.existsSync(dir)) return [];
124139
- return fs7.readdirSync(dir).filter((name) => name.endsWith(".json")).sort();
124140
- }
124141
- function deleteAnalysis(repoPath, filename) {
124142
- try {
124143
- fs7.unlinkSync(analysisFilePath(repoPath, filename));
124144
- } catch (err) {
124145
- if (err.code !== "ENOENT") throw err;
124146
- }
124147
- }
124148
- function readHistory(repoPath) {
124149
- const file = historyPath(repoPath);
124150
- if (!fs7.existsSync(file)) return { analyses: [] };
124151
- return JSON.parse(fs7.readFileSync(file, "utf-8"));
124152
- }
124153
- function appendHistory(repoPath, entry) {
124154
- const history = readHistory(repoPath);
124155
- history.analyses.push(entry);
124156
- atomicWriteJson(historyPath(repoPath), history);
124157
- }
124158
- function removeFromHistory(repoPath, analysisId) {
124159
- const history = readHistory(repoPath);
124160
- const next = history.analyses.filter((a) => a.id !== analysisId);
124161
- if (next.length === history.analyses.length) return;
124162
- atomicWriteJson(historyPath(repoPath), { analyses: next });
124163
- }
124164
- function readDiff(repoPath) {
124165
- const file = diffPath(repoPath);
124166
- if (!fs7.existsSync(file)) return null;
124167
- return JSON.parse(fs7.readFileSync(file, "utf-8"));
124168
- }
124169
- function writeDiff(repoPath, diff) {
124170
- atomicWriteJson(diffPath(repoPath), diff);
124171
- }
124172
- function deleteDiff(repoPath) {
124173
- try {
124174
- fs7.unlinkSync(diffPath(repoPath));
124175
- } catch (err) {
124176
- if (err.code !== "ENOENT") throw err;
124177
- }
124178
- }
124179
- function clearLatestCache() {
124180
- latestCache.clear();
124181
- }
124182
- var TRUECOURSE_DIR2, ANALYSES_DIR, LATEST_FILE, HISTORY_FILE, DIFF_FILE, latestCache;
124183
- var init_analysis_store = __esm({
124184
- "apps/server/src/lib/analysis-store.ts"() {
124185
- "use strict";
124186
- init_atomic_write();
124187
- TRUECOURSE_DIR2 = ".truecourse";
124188
- ANALYSES_DIR = "analyses";
124189
- LATEST_FILE = "LATEST.json";
124190
- HISTORY_FILE = "history.json";
124191
- DIFF_FILE = "diff.json";
124192
- latestCache = /* @__PURE__ */ new Map();
124193
- }
124194
- });
124195
-
124196
124204
  // apps/server/src/services/analysis-registry.ts
124197
124205
  function registerAnalysis(repoId, analysisId) {
124198
124206
  const existing = activeAnalyses2.get(repoId);
@@ -139790,7 +139798,7 @@ var require_dagre = __commonJS({
139790
139798
  });
139791
139799
 
139792
139800
  // apps/server/src/index.ts
139793
- var import_express9 = __toESM(require_express2(), 1);
139801
+ var import_express10 = __toESM(require_express2(), 1);
139794
139802
  var import_cors = __toESM(require_lib3(), 1);
139795
139803
  init_config();
139796
139804
  import fs12 from "node:fs";
@@ -140294,9 +140302,12 @@ var CreateRepoSchema = external_exports.object({
140294
140302
  path: external_exports.string().min(1)
140295
140303
  });
140296
140304
  var AnalyzeRepoSchema = external_exports.object({
140297
- branch: external_exports.string().optional(),
140298
- enabledCategories: external_exports.array(external_exports.string()).optional().default([]),
140299
- enableLlmRules: external_exports.boolean().optional().default(true),
140305
+ /** Which mode to run — full analyze (HEAD committed state) or diff (working tree
140306
+ * vs LATEST). Required; no silent default. */
140307
+ mode: external_exports.enum(["full", "diff"]),
140308
+ /** Skip git ops (branch detection, commit hash read, pre-parse stash). Useful
140309
+ * for non-git dirs or test environments. No per-repo-config equivalent —
140310
+ * only way to opt out for a single run. */
140300
140311
  skipGit: external_exports.boolean().optional().default(false)
140301
140312
  });
140302
140313
  var GenerateViolationsSchema = external_exports.object({
@@ -140426,6 +140437,40 @@ function emitAnalysisCanceled(repoId) {
140426
140437
  const io3 = getIO();
140427
140438
  io3.to(`repo:${repoId}`).emit("analysis:canceled", { repoId });
140428
140439
  }
140440
+ function createSocketLlmEstimateHandler(repoId) {
140441
+ return (estimate) => new Promise((resolve7) => {
140442
+ const io3 = getIO();
140443
+ const room = `repo:${repoId}`;
140444
+ io3.to(room).emit("analysis:llm-estimate", {
140445
+ repoId,
140446
+ estimate: {
140447
+ totalEstimatedTokens: estimate.totalEstimatedTokens,
140448
+ tiers: estimate.tiers,
140449
+ uniqueFileCount: estimate.uniqueFileCount,
140450
+ uniqueRuleCount: estimate.uniqueRuleCount
140451
+ }
140452
+ });
140453
+ const timeout = setTimeout(() => {
140454
+ cleanup();
140455
+ resolve7(true);
140456
+ }, 6e4);
140457
+ function onProceed(data) {
140458
+ if (data.repoId !== repoId) return;
140459
+ cleanup();
140460
+ io3.to(room).emit("analysis:llm-resolved", { repoId, proceed: data.proceed });
140461
+ resolve7(data.proceed);
140462
+ }
140463
+ function cleanup() {
140464
+ clearTimeout(timeout);
140465
+ for (const [, socket] of io3.sockets.sockets) {
140466
+ socket.removeListener("analysis:llm-proceed", onProceed);
140467
+ }
140468
+ }
140469
+ for (const [, socket] of io3.sockets.sockets) {
140470
+ socket.on("analysis:llm-proceed", onProceed);
140471
+ }
140472
+ });
140473
+ }
140429
140474
 
140430
140475
  // apps/server/src/socket/index.ts
140431
140476
  var io2 = null;
@@ -145331,9 +145376,9 @@ router.get("/:id/config", async (req, res, next) => {
145331
145376
  });
145332
145377
  var repos_default = router;
145333
145378
 
145334
- // apps/server/src/routes/analyze.ts
145379
+ // apps/server/src/routes/analyses.ts
145335
145380
  var import_express2 = __toESM(require_express2(), 1);
145336
- import path13 from "node:path";
145381
+ import path14 from "node:path";
145337
145382
 
145338
145383
  // apps/server/src/config/current-project.ts
145339
145384
  function resolveProjectForRequest(slug) {
@@ -145347,11 +145392,14 @@ function resolveProjectForRequest(slug) {
145347
145392
  }
145348
145393
 
145349
145394
  // apps/server/src/commands/analyze-in-process.ts
145395
+ init_analysis_store();
145396
+
145397
+ // apps/server/src/commands/analyze-core.ts
145350
145398
  init_logger();
145351
145399
  import { randomUUID as randomUUID6 } from "node:crypto";
145352
145400
 
145353
145401
  // apps/server/src/services/analyzer.service.ts
145354
- import path8 from "node:path";
145402
+ import path10 from "node:path";
145355
145403
  init_logger();
145356
145404
  init_dist2();
145357
145405
  function runDeterministicModuleChecks(result, enabledDeterministic) {
@@ -145419,7 +145467,7 @@ async function runAnalysis(repoPath, _branch, onProgress, options) {
145419
145467
  const statusResult = await git.status();
145420
145468
  hasChanges = !statusResult.isClean();
145421
145469
  const gitRoot = (await git.revparse(["--show-toplevel"])).trim();
145422
- isSubdirectory = path8.resolve(repoPath) !== path8.resolve(gitRoot);
145470
+ isSubdirectory = path10.resolve(repoPath) !== path10.resolve(gitRoot);
145423
145471
  }
145424
145472
  if (hasChanges && !options?.skipStash && !isSubdirectory && git) {
145425
145473
  onProgress({ step: "stash", percent: 2, detail: "Stashing pending changes to analyze committed state..." });
@@ -147808,7 +147856,7 @@ function processLlmCodeViolations(codeResult, validFilePaths, fileContents, allC
147808
147856
  }
147809
147857
  }
147810
147858
 
147811
- // apps/server/src/commands/analyze-in-process.ts
147859
+ // apps/server/src/commands/analyze-core.ts
147812
147860
  init_provider();
147813
147861
 
147814
147862
  // apps/server/src/services/usage.service.ts
@@ -147828,10 +147876,10 @@ function toUsageRecords(records) {
147828
147876
  }));
147829
147877
  }
147830
147878
 
147831
- // apps/server/src/commands/analyze-in-process.ts
147879
+ // apps/server/src/commands/analyze-core.ts
147832
147880
  init_analysis_store();
147833
147881
  init_atomic_write();
147834
- async function analyzeInProcess(project, options = {}) {
147882
+ async function analyzeCore(project, options) {
147835
147883
  try {
147836
147884
  acquireAnalyzeLock(project.path);
147837
147885
  } catch (err) {
@@ -147839,22 +147887,37 @@ async function analyzeInProcess(project, options = {}) {
147839
147887
  throw err;
147840
147888
  }
147841
147889
  try {
147842
- const start = Date.now();
147843
- const { skipGit, signal } = options;
147890
+ const { mode, signal } = options;
147891
+ const isDiff2 = mode === "diff";
147892
+ const skipGit = !isDiff2 && !!options.skipGit;
147844
147893
  const projectConfig = readProjectConfig(project.path);
147894
+ const latestBaseline = readLatest(project.path);
147895
+ if (isDiff2 && !latestBaseline) {
147896
+ throw new Error("Run a full analysis first before checking a diff.");
147897
+ }
147845
147898
  let branch = options.branch ?? null;
147846
147899
  let commitHash = options.commitHash ?? null;
147847
- if (!skipGit && (branch === null || commitHash === null)) {
147900
+ if (isDiff2) {
147901
+ branch = latestBaseline.analysis.branch ?? branch;
147902
+ if (commitHash === null) {
147903
+ try {
147904
+ const git = await getGit(project.path);
147905
+ commitHash = (await git.revparse(["HEAD"])).trim() || null;
147906
+ } catch {
147907
+ commitHash = null;
147908
+ }
147909
+ }
147910
+ } else if (!skipGit && (branch === null || commitHash === null)) {
147848
147911
  const git = await getGit(project.path);
147849
147912
  if (branch === null) branch = (await git.branch()).current || null;
147850
147913
  if (commitHash === null) commitHash = (await git.revparse(["HEAD"])).trim();
147851
147914
  }
147852
147915
  const analysisId = randomUUID6();
147853
147916
  const now = (/* @__PURE__ */ new Date()).toISOString();
147854
- const filename = buildAnalysisFilename(analysisId, now);
147917
+ const start = Date.now();
147855
147918
  const effectiveCategories = options.enabledCategoriesOverride?.length ? options.enabledCategoriesOverride : projectConfig.enabledCategories ?? void 0;
147856
147919
  const effectiveLlmRules = projectConfig.enableLlmRules ?? options.enableLlmRulesOverride ?? true;
147857
- options.tracker?.start("parse", "Starting analysis...");
147920
+ options.tracker?.start("parse", isDiff2 ? "Analyzing working tree..." : "Starting analysis...");
147858
147921
  const result = await runAnalysis(
147859
147922
  project.path,
147860
147923
  branch ?? void 0,
@@ -147862,68 +147925,86 @@ async function analyzeInProcess(project, options = {}) {
147862
147925
  options.tracker?.detail("parse", progress.detail ?? "Analyzing...");
147863
147926
  options.onProgress?.({ detail: progress.detail });
147864
147927
  },
147865
- { signal }
147928
+ { signal, skipStash: isDiff2 }
147866
147929
  );
147867
- if (signal?.aborted) throw new DOMException("Analysis cancelled", "AbortError");
147868
- const { graph, serviceIdMap, moduleIdMap, methodIdMap, dbIdMap } = buildGraph(result);
147869
- const previousLatest = readLatest(project.path);
147870
- const previousAnalysisId = previousLatest?.analysis.id ?? null;
147871
- const previousActiveViolations = previousLatest ? previousLatest.violations.filter(
147872
- (v) => branch == null || previousLatest.analysis.branch == null || previousLatest.analysis.branch === branch
147873
- ) : [];
147874
- try {
147875
- graph.flows = detectFlows(result);
147876
- } catch (flowError) {
147877
- log.error(`[Flows] Detection failed: ${flowError instanceof Error ? flowError.message : String(flowError)}`);
147878
- graph.flows = [];
147930
+ if (signal?.aborted) {
147931
+ throw new DOMException(isDiff2 ? "Diff cancelled" : "Analysis cancelled", "AbortError");
147879
147932
  }
147880
- touchProject(project.slug);
147933
+ const { graph, serviceIdMap, moduleIdMap, methodIdMap, dbIdMap } = buildGraph(result);
147934
+ let changedFiles = [];
147881
147935
  let changedFileSet;
147882
- if (previousLatest?.analysis.commitHash && !skipGit) {
147936
+ if (isDiff2) {
147883
147937
  try {
147884
147938
  const git = await getGit(project.path);
147885
- const diffOutput = await git.diff([previousLatest.analysis.commitHash, "HEAD", "--name-only"]);
147939
+ const statusResult = await git.status();
147940
+ for (const f2 of statusResult.not_added) changedFiles.push({ path: f2, status: "new" });
147941
+ for (const f2 of statusResult.created) changedFiles.push({ path: f2, status: "new" });
147942
+ for (const f2 of statusResult.modified) changedFiles.push({ path: f2, status: "modified" });
147943
+ for (const f2 of statusResult.staged) {
147944
+ if (!changedFiles.some((cf) => cf.path === f2)) {
147945
+ changedFiles.push({ path: f2, status: "modified" });
147946
+ }
147947
+ }
147948
+ for (const f2 of statusResult.deleted) changedFiles.push({ path: f2, status: "deleted" });
147949
+ } catch (err) {
147950
+ log.warn(`[Diff] git status failed: ${err instanceof Error ? err.message : String(err)}`);
147951
+ }
147952
+ } else if (latestBaseline?.analysis.commitHash && !skipGit) {
147953
+ try {
147954
+ const git = await getGit(project.path);
147955
+ const diffOutput = await git.diff([latestBaseline.analysis.commitHash, "HEAD", "--name-only"]);
147886
147956
  const files = diffOutput.trim().split("\n").filter(Boolean);
147887
147957
  if (files.length > 0) changedFileSet = new Set(files);
147888
147958
  } catch {
147889
147959
  }
147890
147960
  }
147961
+ if (!isDiff2) {
147962
+ try {
147963
+ graph.flows = detectFlows(result);
147964
+ } catch (flowError) {
147965
+ log.error(
147966
+ `[Flows] Detection failed: ${flowError instanceof Error ? flowError.message : String(flowError)}`
147967
+ );
147968
+ graph.flows = [];
147969
+ }
147970
+ touchProject(project.slug);
147971
+ }
147891
147972
  options.tracker?.done(
147892
147973
  "parse",
147893
147974
  `${result.services.length} services, ${result.fileAnalyses?.length ?? 0} files`
147894
147975
  );
147976
+ const previousActiveViolations = latestBaseline ? latestBaseline.violations.filter(
147977
+ (v) => branch == null || latestBaseline.analysis.branch == null || latestBaseline.analysis.branch === branch
147978
+ ) : [];
147979
+ const previousAnalysisId = latestBaseline?.analysis.id ?? null;
147895
147980
  const provider = options.provider ?? (effectiveLlmRules ? createLLMProvider() : void 0);
147896
147981
  if (provider) {
147897
147982
  provider.setAnalysisId(analysisId);
147898
147983
  provider.setRepoPath(project.path);
147899
147984
  if (signal) provider.setAbortSignal(signal);
147900
147985
  }
147901
- let pipelineResult;
147902
- try {
147903
- pipelineResult = await runViolationPipeline({
147904
- repoPath: project.path,
147905
- analysisId,
147906
- now,
147907
- result,
147908
- serviceIdMap,
147909
- moduleIdMap,
147910
- methodIdMap,
147911
- dbIdMap,
147912
- previousActiveViolations,
147913
- changedFileSet,
147914
- tracker: options.tracker,
147915
- enabledCategories: effectiveCategories,
147916
- enableLlmRules: effectiveLlmRules,
147917
- provider,
147918
- signal,
147919
- onLlmEstimate: options.onLlmEstimate ? async (estimate) => {
147920
- const proceed = await options.onLlmEstimate(estimate);
147921
- options.onLlmResolved?.(proceed);
147922
- return proceed;
147923
- } : void 0
147924
- });
147925
- } finally {
147926
- }
147986
+ const pipelineResult = await runViolationPipeline({
147987
+ repoPath: project.path,
147988
+ analysisId,
147989
+ now,
147990
+ result,
147991
+ serviceIdMap,
147992
+ moduleIdMap,
147993
+ methodIdMap,
147994
+ dbIdMap,
147995
+ previousActiveViolations,
147996
+ changedFileSet,
147997
+ tracker: options.tracker,
147998
+ enabledCategories: effectiveCategories,
147999
+ enableLlmRules: effectiveLlmRules,
148000
+ provider,
148001
+ signal,
148002
+ onLlmEstimate: options.onLlmEstimate ? async (estimate) => {
148003
+ const proceed = await options.onLlmEstimate(estimate);
148004
+ options.onLlmResolved?.(proceed);
148005
+ return proceed;
148006
+ } : void 0
148007
+ });
147927
148008
  if (pipelineResult.serviceDescriptions.length > 0) {
147928
148009
  for (const desc of pipelineResult.serviceDescriptions) {
147929
148010
  const svc = graph.services.find((s) => s.id === desc.id);
@@ -147934,56 +148015,100 @@ async function analyzeInProcess(project, options = {}) {
147934
148015
  enforceLocationInvariant(pipelineResult.added);
147935
148016
  enforceLocationInvariant(pipelineResult.unchanged);
147936
148017
  enforceLocationInvariant(pipelineResult.resolved);
147937
- const snapshot = {
147938
- id: analysisId,
147939
- createdAt: now,
148018
+ log.info(
148019
+ `[${isDiff2 ? "Diff" : "Analysis"}] core complete in ${Date.now() - start}ms \u2014 ${pipelineResult.added.length} added, ${pipelineResult.unchanged.length} unchanged, ${pipelineResult.resolvedRefs.length} resolved`
148020
+ );
148021
+ return {
148022
+ mode,
148023
+ analysisId,
148024
+ now,
147940
148025
  branch,
147941
148026
  commitHash,
147942
148027
  architecture: result.architecture,
147943
- status: "completed",
147944
148028
  metadata: result.metadata ?? null,
147945
148029
  graph,
147946
- violations: {
147947
- added: pipelineResult.added,
147948
- resolved: pipelineResult.resolvedRefs,
147949
- previousAnalysisId
147950
- },
147951
- usage
147952
- };
147953
- const latest = buildLatestSnapshot(snapshot, filename, pipelineResult.unchanged, pipelineResult.added);
147954
- const { bySeverity, total } = summarizeActiveViolations(latest.violations);
147955
- writeAnalysis(project.path, snapshot);
147956
- writeLatest(project.path, latest);
147957
- const historyEntry = buildHistoryEntry(snapshot, filename, pipelineResult);
147958
- appendHistory(project.path, historyEntry);
147959
- deleteDiff(project.path);
147960
- setLastAnalyzed(project.slug, now);
147961
- return {
147962
- analysisId,
147963
- filename,
147964
- serviceCount: result.services.length,
147965
- fileCount: result.fileAnalyses?.length ?? 0,
147966
- architecture: result.architecture,
147967
- durationMs: Date.now() - start,
147968
- violationsSummary: { total, bySeverity }
148030
+ changedFiles,
148031
+ pipelineResult,
148032
+ usage,
148033
+ latestBaseline,
148034
+ previousAnalysisId,
148035
+ analysisResult: result
147969
148036
  };
147970
148037
  } finally {
147971
148038
  releaseAnalyzeLock(project.path);
147972
148039
  }
147973
148040
  }
148041
+ function enforceLocationInvariant(violations) {
148042
+ for (const v of violations) {
148043
+ const hasFile = v.filePath != null;
148044
+ const hasRange = v.lineStart != null && v.lineEnd != null;
148045
+ if (hasFile === hasRange) continue;
148046
+ log.warn(
148047
+ `[Violations] ${v.ruleKey}: partial location (filePath=${v.filePath}, lineStart=${v.lineStart}, lineEnd=${v.lineEnd}) \u2014 dropping to uphold the location invariant`
148048
+ );
148049
+ v.filePath = null;
148050
+ v.lineStart = null;
148051
+ v.lineEnd = null;
148052
+ }
148053
+ }
148054
+
148055
+ // apps/server/src/commands/analyze-persist.ts
148056
+ init_logger();
148057
+ import path12 from "node:path";
148058
+ init_analysis_store();
148059
+ function persistFullAnalysis(project, core, startedAt) {
148060
+ const filename = buildAnalysisFilename(core.analysisId, core.now);
148061
+ const snapshot = {
148062
+ id: core.analysisId,
148063
+ createdAt: core.now,
148064
+ branch: core.branch,
148065
+ commitHash: core.commitHash,
148066
+ architecture: core.architecture,
148067
+ status: "completed",
148068
+ metadata: core.metadata,
148069
+ graph: core.graph,
148070
+ violations: {
148071
+ added: core.pipelineResult.added,
148072
+ resolved: core.pipelineResult.resolvedRefs,
148073
+ previousAnalysisId: core.previousAnalysisId
148074
+ },
148075
+ usage: core.usage
148076
+ };
148077
+ const latest = buildLatestSnapshot(
148078
+ snapshot,
148079
+ filename,
148080
+ core.pipelineResult.unchanged,
148081
+ core.pipelineResult.added
148082
+ );
148083
+ const { bySeverity, total } = summarizeActiveViolations(latest.violations);
148084
+ writeAnalysis(project.path, snapshot);
148085
+ writeLatest(project.path, latest);
148086
+ appendHistory(project.path, buildHistoryEntry(snapshot, filename, core.pipelineResult));
148087
+ deleteDiff(project.path);
148088
+ setLastAnalyzed(project.slug, core.now);
148089
+ return {
148090
+ analysisId: core.analysisId,
148091
+ filename,
148092
+ serviceCount: core.graph.services.length,
148093
+ fileCount: core.analysisResult.fileAnalyses?.length ?? 0,
148094
+ architecture: core.architecture,
148095
+ durationMs: Date.now() - startedAt,
148096
+ violationsSummary: { total, bySeverity }
148097
+ };
148098
+ }
148099
+ function persistDiffAnalysis(project, core) {
148100
+ if (!core.latestBaseline) {
148101
+ throw new Error("Diff persist requires a latestBaseline \u2014 analyzeCore should have enforced this.");
148102
+ }
148103
+ const diff = buildDiffSnapshot(project.path, core, core.latestBaseline);
148104
+ writeDiff(project.path, diff);
148105
+ log.info(
148106
+ `[Diff] Done \u2014 ${diff.summary.newCount} new, ${diff.summary.unchangedCount} unchanged, ${diff.summary.resolvedCount} resolved across ${diff.changedFiles.length} changed files`
148107
+ );
148108
+ return { diff, isStale: false };
148109
+ }
147974
148110
  function buildLatestSnapshot(snapshot, filename, unchanged, added) {
147975
- const { graph } = snapshot;
147976
- const serviceById = new Map(graph.services.map((s) => [s.id, s.name]));
147977
- const moduleById = new Map(graph.modules.map((m) => [m.id, m.name]));
147978
- const methodById = new Map(graph.methods.map((m) => [m.id, m.name]));
147979
- const databaseById = new Map(graph.databases.map((d) => [d.id, d.name]));
147980
- const denormalize = (v) => ({
147981
- ...v,
147982
- targetServiceName: v.targetServiceId ? serviceById.get(v.targetServiceId) ?? null : null,
147983
- targetModuleName: v.targetModuleId ? moduleById.get(v.targetModuleId) ?? null : null,
147984
- targetMethodName: v.targetMethodId ? methodById.get(v.targetMethodId) ?? null : null,
147985
- targetDatabaseName: v.targetDatabaseId ? databaseById.get(v.targetDatabaseId) ?? null : null
147986
- });
148111
+ const denormalize = makeDenormalizer(snapshot.graph);
147987
148112
  return {
147988
148113
  head: filename,
147989
148114
  analysis: {
@@ -147995,7 +148120,7 @@ function buildLatestSnapshot(snapshot, filename, unchanged, added) {
147995
148120
  metadata: snapshot.metadata,
147996
148121
  status: "completed"
147997
148122
  },
147998
- graph,
148123
+ graph: snapshot.graph,
147999
148124
  violations: [...added.map(denormalize), ...unchanged.map(denormalize)]
148000
148125
  };
148001
148126
  }
@@ -148059,27 +148184,99 @@ function buildHistoryEntry(snapshot, filename, pipeline) {
148059
148184
  }
148060
148185
  };
148061
148186
  }
148062
- function enforceLocationInvariant(violations) {
148063
- for (const v of violations) {
148064
- const hasFile = v.filePath != null;
148065
- const hasRange = v.lineStart != null && v.lineEnd != null;
148066
- if (hasFile === hasRange) continue;
148067
- log.warn(
148068
- `[Violations] ${v.ruleKey}: partial location (filePath=${v.filePath}, lineStart=${v.lineStart}, lineEnd=${v.lineEnd}) \u2014 dropping to uphold the location invariant`
148069
- );
148070
- v.filePath = null;
148071
- v.lineStart = null;
148072
- v.lineEnd = null;
148187
+ function buildDiffSnapshot(repoPath, core, baseline) {
148188
+ const { graph, changedFiles, pipelineResult } = core;
148189
+ const denormalize = makeDenormalizer(graph);
148190
+ const newViolations = pipelineResult.added.map(denormalize);
148191
+ const latestById = new Map(baseline.violations.map((v) => [v.id, v]));
148192
+ const resolvedViolations = pipelineResult.resolvedRefs.map((r) => latestById.get(r.id)).filter((v) => !!v);
148193
+ const changedAbs = new Set(changedFiles.map((c) => path12.resolve(repoPath, c.path)));
148194
+ const matchesChanged = (p) => !!p && (changedAbs.has(p) || changedAbs.has(path12.resolve(repoPath, p)));
148195
+ const affectedModules = graph.modules.filter((m) => matchesChanged(m.filePath));
148196
+ const affectedModuleIdSet = new Set(affectedModules.map((m) => m.id));
148197
+ const serviceNameById = new Map(graph.services.map((s) => [s.id, s.name]));
148198
+ const layerKeyById = new Map(
148199
+ graph.layers.map((l) => [l.id, `${l.serviceName}::${l.layer}`])
148200
+ );
148201
+ const affectedServices = /* @__PURE__ */ new Set();
148202
+ const affectedLayers = /* @__PURE__ */ new Set();
148203
+ const affectedModuleKeys = /* @__PURE__ */ new Set();
148204
+ for (const mod of affectedModules) {
148205
+ const svcName = serviceNameById.get(mod.serviceId);
148206
+ if (svcName) {
148207
+ affectedServices.add(svcName);
148208
+ affectedModuleKeys.add(`${svcName}::${mod.name}`);
148209
+ }
148210
+ const layerKey = layerKeyById.get(mod.layerId);
148211
+ if (layerKey) affectedLayers.add(layerKey);
148073
148212
  }
148213
+ const moduleNameById = new Map(graph.modules.map((m) => [m.id, m.name]));
148214
+ const affectedMethodKeys = [];
148215
+ for (const method of graph.methods) {
148216
+ if (!affectedModuleIdSet.has(method.moduleId)) continue;
148217
+ const modName = moduleNameById.get(method.moduleId);
148218
+ const mod = graph.modules.find((m) => m.id === method.moduleId);
148219
+ const svcName = mod ? serviceNameById.get(mod.serviceId) : void 0;
148220
+ if (svcName && modName) affectedMethodKeys.push(`${svcName}::${modName}::${method.name}`);
148221
+ }
148222
+ return {
148223
+ id: core.analysisId,
148224
+ baseAnalysisId: baseline.analysis.id,
148225
+ createdAt: core.now,
148226
+ branch: core.branch,
148227
+ commitHash: core.commitHash,
148228
+ graph,
148229
+ changedFiles,
148230
+ newViolations,
148231
+ resolvedViolations,
148232
+ affectedNodeIds: {
148233
+ services: [...affectedServices],
148234
+ layers: [...affectedLayers],
148235
+ modules: [...affectedModuleKeys],
148236
+ methods: affectedMethodKeys
148237
+ },
148238
+ summary: {
148239
+ newCount: newViolations.length,
148240
+ unchangedCount: pipelineResult.unchanged.length,
148241
+ resolvedCount: resolvedViolations.length
148242
+ },
148243
+ usage: core.usage
148244
+ };
148245
+ }
148246
+ function makeDenormalizer(graph) {
148247
+ const serviceById = new Map(graph.services.map((s) => [s.id, s.name]));
148248
+ const moduleById = new Map(graph.modules.map((m) => [m.id, m.name]));
148249
+ const methodById = new Map(graph.methods.map((m) => [m.id, m.name]));
148250
+ const databaseById = new Map(graph.databases.map((d) => [d.id, d.name]));
148251
+ return (v) => ({
148252
+ ...v,
148253
+ targetServiceName: v.targetServiceId ? serviceById.get(v.targetServiceId) ?? null : null,
148254
+ targetModuleName: v.targetModuleId ? moduleById.get(v.targetModuleId) ?? null : null,
148255
+ targetMethodName: v.targetMethodId ? methodById.get(v.targetMethodId) ?? null : null,
148256
+ targetDatabaseName: v.targetDatabaseId ? databaseById.get(v.targetDatabaseId) ?? null : null
148257
+ });
148258
+ }
148259
+
148260
+ // apps/server/src/commands/analyze-in-process.ts
148261
+ async function analyzeInProcess(project, options = {}) {
148262
+ const startedAt = Date.now();
148263
+ const core = await analyzeCore(project, { ...options, mode: "full" });
148264
+ return persistFullAnalysis(project, core, startedAt);
148265
+ }
148266
+
148267
+ // apps/server/src/commands/diff-in-process.ts
148268
+ async function diffInProcess(project, options = {}) {
148269
+ const core = await analyzeCore(project, { ...options, mode: "diff" });
148270
+ return persistDiffAnalysis(project, core);
148074
148271
  }
148075
148272
 
148076
- // apps/server/src/routes/analyze.ts
148273
+ // apps/server/src/routes/analyses.ts
148077
148274
  init_analysis_registry();
148078
148275
  init_provider();
148079
148276
 
148080
148277
  // apps/server/src/services/telemetry.service.ts
148081
148278
  import fs9 from "node:fs";
148082
- import path12 from "node:path";
148279
+ import path13 from "node:path";
148083
148280
  import os3 from "node:os";
148084
148281
  import crypto from "node:crypto";
148085
148282
 
@@ -151868,7 +152065,7 @@ var DEFAULT_CONFIG = {
151868
152065
  var POSTHOG_API_KEY = "phc_ys9Ykf49KmNqAC3fhq3jugTejc4BDqyKqRS8qRoYZYew";
151869
152066
  var TOOL_VERSION = "0.2.2";
151870
152067
  function getTelemetryConfigPath() {
151871
- return path12.join(os3.homedir(), ".truecourse", "telemetry.json");
152068
+ return path13.join(os3.homedir(), ".truecourse", "telemetry.json");
151872
152069
  }
151873
152070
  function readTelemetryConfig() {
151874
152071
  const configPath = getTelemetryConfigPath();
@@ -151892,7 +152089,7 @@ function readTelemetryConfig() {
151892
152089
  }
151893
152090
  function writeTelemetryConfig(partial) {
151894
152091
  const configPath = getTelemetryConfigPath();
151895
- const dir = path12.dirname(configPath);
152092
+ const dir = path13.dirname(configPath);
151896
152093
  fs9.mkdirSync(dir, { recursive: true });
151897
152094
  let current;
151898
152095
  try {
@@ -151960,96 +152157,109 @@ function trackEvent(event, properties) {
151960
152157
  }
151961
152158
  }
151962
152159
 
151963
- // apps/server/src/routes/analyze.ts
152160
+ // apps/server/src/services/violation-query.service.ts
152161
+ init_analysis_store();
152162
+ var SEVERITY_ORDER2 = {
152163
+ critical: 0,
152164
+ high: 1,
152165
+ medium: 2,
152166
+ low: 3,
152167
+ info: 4
152168
+ };
152169
+ function listViolations(repoPath, options = {}) {
152170
+ const latest = readLatest(repoPath);
152171
+ if (!latest) return { violations: [], total: 0 };
152172
+ if (options.analysisId && latest.analysis.id !== options.analysisId) {
152173
+ return { violations: [], total: 0 };
152174
+ }
152175
+ const statusMode = options.status ?? "active";
152176
+ let filtered;
152177
+ if (statusMode === "resolved") {
152178
+ filtered = latest.violations.filter((v) => v.status === "resolved");
152179
+ } else if (statusMode === "all") {
152180
+ filtered = latest.violations;
152181
+ } else {
152182
+ const active = ["new", "unchanged"];
152183
+ filtered = latest.violations.filter((v) => active.includes(v.status));
152184
+ }
152185
+ if (options.filePath) {
152186
+ const absPath = options.filePath.startsWith("/") ? options.filePath : `${repoPath}/${options.filePath}`;
152187
+ filtered = filtered.filter(
152188
+ (v) => v.type === "code" && (v.filePath === absPath || v.filePath === options.filePath)
152189
+ );
152190
+ }
152191
+ filtered.sort((a, b) => {
152192
+ const sa = SEVERITY_ORDER2[a.severity] ?? 5;
152193
+ const sb = SEVERITY_ORDER2[b.severity] ?? 5;
152194
+ if (sa !== sb) return sa - sb;
152195
+ return b.createdAt.localeCompare(a.createdAt);
152196
+ });
152197
+ const total = filtered.length;
152198
+ const limit = options.limit ?? 0;
152199
+ const offset = options.offset ?? 0;
152200
+ const paged = limit > 0 ? filtered.slice(offset, offset + limit) : filtered;
152201
+ return { violations: paged, total };
152202
+ }
152203
+ function getDiffResult(repoPath) {
152204
+ const diff = readDiff(repoPath);
152205
+ if (!diff) return null;
152206
+ const latest = readLatest(repoPath);
152207
+ const isStale = latest ? latest.analysis.id !== diff.baseAnalysisId : false;
152208
+ return { diff, isStale };
152209
+ }
152210
+
152211
+ // apps/server/src/routes/analyses.ts
152212
+ init_analysis_store();
151964
152213
  init_logger();
151965
152214
  var router2 = (0, import_express2.Router)();
151966
- router2.post("/:id/analyze", async (req, res, next) => {
152215
+ router2.post("/:id/analyses", async (req, res, next) => {
151967
152216
  try {
151968
152217
  const id = req.params.id;
151969
152218
  const parsed = AnalyzeRepoSchema.safeParse(req.body);
151970
152219
  if (!parsed.success) throw createAppError("Invalid request body", 400);
151971
- const { enabledCategories: globalEnabledCategories, enableLlmRules, skipGit } = parsed.data;
152220
+ const { mode, skipGit } = parsed.data;
151972
152221
  const repo = resolveProjectForRequest(id);
152222
+ if (mode === "diff" && !readLatest(repo.path)) {
152223
+ throw createAppError("Run a full analysis first before checking a diff.", 400);
152224
+ }
151973
152225
  const projectConfig = readProjectConfig(repo.path);
151974
- const effectiveCategories = globalEnabledCategories?.length ? globalEnabledCategories : projectConfig.enabledCategories ?? void 0;
151975
- const effectiveLlmRules = projectConfig.enableLlmRules ?? enableLlmRules;
152226
+ const effectiveCategories = projectConfig.enabledCategories ?? void 0;
152227
+ const effectiveLlmRules = projectConfig.enableLlmRules ?? true;
151976
152228
  const abortController = registerAnalysis(id, "pending");
151977
- res.status(202).json({
151978
- message: "Analysis started",
151979
- repoId: id
151980
- });
152229
+ res.status(202).json({ message: `${mode === "diff" ? "Diff check" : "Analysis"} started`, repoId: id, mode });
151981
152230
  const trackerSteps = buildAnalysisSteps(effectiveCategories, effectiveLlmRules);
151982
152231
  const tracker = createSocketTracker(id, trackerSteps);
151983
- const provider = effectiveLlmRules ? createLLMProvider() : void 0;
151984
- if (provider) {
151985
- provider.setRepoId(id);
151986
- provider.setRepoPath(repo.path);
151987
- provider.setAbortSignal(abortController.signal);
151988
- }
151989
152232
  pushLogger({
151990
- filePath: path13.join(repo.path, ".truecourse/logs/analyze.log"),
152233
+ filePath: path14.join(repo.path, ".truecourse/logs/analyze.log"),
151991
152234
  tee: process.env.TRUECOURSE_DEV === "1"
151992
152235
  });
151993
152236
  try {
151994
- const outcome = await analyzeInProcess(repo, {
151995
- skipGit,
151996
- enabledCategoriesOverride: effectiveCategories,
151997
- enableLlmRulesOverride: effectiveLlmRules,
151998
- tracker,
151999
- signal: abortController.signal,
152000
- provider,
152001
- onLlmEstimate: (estimate) => new Promise((resolve7) => {
152002
- const io3 = getIO();
152003
- const room = `repo:${id}`;
152004
- io3.to(room).emit("analysis:llm-estimate", {
152005
- repoId: id,
152006
- estimate: {
152007
- totalEstimatedTokens: estimate.totalEstimatedTokens,
152008
- tiers: estimate.tiers,
152009
- uniqueFileCount: estimate.uniqueFileCount,
152010
- uniqueRuleCount: estimate.uniqueRuleCount
152011
- }
152012
- });
152013
- const timeout = setTimeout(() => {
152014
- cleanup();
152015
- resolve7(true);
152016
- }, 6e4);
152017
- function onProceed(data) {
152018
- if (data.repoId !== id) return;
152019
- cleanup();
152020
- io3.to(room).emit("analysis:llm-resolved", { repoId: id, proceed: data.proceed });
152021
- resolve7(data.proceed);
152022
- }
152023
- function cleanup() {
152024
- clearTimeout(timeout);
152025
- for (const [, socket] of io3.sockets.sockets) {
152026
- socket.removeListener("analysis:llm-proceed", onProceed);
152027
- }
152028
- }
152029
- for (const [, socket] of io3.sockets.sockets) {
152030
- socket.on("analysis:llm-proceed", onProceed);
152031
- }
152032
- })
152033
- });
152034
- emitViolationsReady(id, outcome.analysisId);
152035
- emitAnalysisComplete(id, outcome.analysisId);
152036
- trackEvent("analyze", {
152037
- serviceCount: outcome.serviceCount,
152038
- fileCountRange: bucketFileCount(outcome.fileCount),
152039
- languages: [],
152040
- architecture: outcome.architecture,
152041
- durationRange: bucketDuration(outcome.durationMs)
152042
- });
152237
+ if (mode === "full") {
152238
+ await runFullAnalyze(id, repo, {
152239
+ skipGit,
152240
+ effectiveCategories,
152241
+ effectiveLlmRules,
152242
+ tracker,
152243
+ signal: abortController.signal
152244
+ });
152245
+ } else {
152246
+ await runDiffAnalyze(id, repo, {
152247
+ tracker,
152248
+ signal: abortController.signal
152249
+ });
152250
+ }
152043
152251
  } catch (error) {
152044
152252
  if (error instanceof DOMException && error.name === "AbortError") {
152045
- log.info(`[Analysis] Cancelled for repo ${id}`);
152253
+ log.info(`[${mode === "diff" ? "Diff" : "Analysis"}] Cancelled for repo ${id}`);
152046
152254
  emitAnalysisCanceled(id);
152047
152255
  } else {
152048
- log.error(`[Analysis] Failed for repo ${id}: ${error instanceof Error ? error.message : String(error)}`);
152256
+ log.error(
152257
+ `[${mode === "diff" ? "Diff" : "Analysis"}] Failed for repo ${id}: ${error instanceof Error ? error.message : String(error)}`
152258
+ );
152049
152259
  emitAnalysisProgress(id, {
152050
152260
  step: "error",
152051
152261
  percent: -1,
152052
- detail: error instanceof Error ? error.message : "Analysis failed"
152262
+ detail: error instanceof Error ? error.message : `${mode === "diff" ? "Diff check" : "Analysis"} failed`
152053
152263
  });
152054
152264
  }
152055
152265
  } finally {
@@ -152060,7 +152270,7 @@ router2.post("/:id/analyze", async (req, res, next) => {
152060
152270
  next(error);
152061
152271
  }
152062
152272
  });
152063
- router2.post("/:id/analyze/cancel", async (req, res, next) => {
152273
+ router2.post("/:id/analyses/cancel", async (req, res, next) => {
152064
152274
  try {
152065
152275
  const id = req.params.id;
152066
152276
  const canceled = cancelAnalysis(id);
@@ -152069,159 +152279,187 @@ router2.post("/:id/analyze/cancel", async (req, res, next) => {
152069
152279
  next(error);
152070
152280
  }
152071
152281
  });
152072
- var analyze_default = router2;
152073
-
152074
- // apps/server/src/routes/analysis.ts
152075
- var import_express3 = __toESM(require_express2(), 1);
152076
- import fs11 from "node:fs";
152077
- import path15 from "node:path";
152078
-
152079
- // apps/server/src/commands/diff-in-process.ts
152080
- import path14 from "node:path";
152081
- import { randomUUID as randomUUID7 } from "node:crypto";
152082
- init_logger();
152083
- init_analysis_store();
152084
- async function diffInProcess(project, options = {}) {
152085
- const latest = readLatest(project.path);
152086
- if (!latest) {
152087
- throw new Error("Run a full analysis first before checking a diff.");
152088
- }
152089
- const projectConfig = readProjectConfig(project.path);
152090
- const enabledCategories = options.enabledCategoriesOverride ?? projectConfig.enabledCategories ?? void 0;
152091
- const enableLlmRules = options.enableLlmRulesOverride ?? projectConfig.enableLlmRules ?? false;
152092
- const git = await getGit(project.path);
152093
- const statusResult = await git.status();
152094
- const changedFiles = [];
152095
- for (const f2 of statusResult.not_added) changedFiles.push({ path: f2, status: "new" });
152096
- for (const f2 of statusResult.created) changedFiles.push({ path: f2, status: "new" });
152097
- for (const f2 of statusResult.modified) changedFiles.push({ path: f2, status: "modified" });
152098
- for (const f2 of statusResult.staged) {
152099
- if (!changedFiles.some((cf) => cf.path === f2)) {
152100
- changedFiles.push({ path: f2, status: "modified" });
152101
- }
152102
- }
152103
- for (const f2 of statusResult.deleted) changedFiles.push({ path: f2, status: "deleted" });
152104
- const commitHash = (await git.revparse(["HEAD"])).trim() || null;
152105
- options.tracker?.start("parse", "Analyzing working tree...");
152106
- const result = await runAnalysis(
152107
- project.path,
152108
- latest.analysis.branch ?? void 0,
152109
- (progress) => {
152110
- options.tracker?.detail("parse", progress.detail ?? "Analyzing...");
152111
- options.onProgress?.({ detail: progress.detail });
152112
- },
152113
- { signal: options.signal, skipStash: true }
152114
- );
152115
- if (options.signal?.aborted) throw new DOMException("Diff cancelled", "AbortError");
152116
- const { graph, serviceIdMap, moduleIdMap, methodIdMap, dbIdMap } = buildGraph(result);
152117
- const analysisId = randomUUID7();
152118
- const now = (/* @__PURE__ */ new Date()).toISOString();
152119
- options.tracker?.done(
152120
- "parse",
152121
- `${result.services.length} services, ${result.fileAnalyses?.length ?? 0} files`
152122
- );
152123
- const pipelineResult = await runViolationPipeline({
152124
- repoPath: project.path,
152125
- analysisId,
152126
- now,
152127
- result,
152128
- serviceIdMap,
152129
- moduleIdMap,
152130
- methodIdMap,
152131
- dbIdMap,
152132
- previousActiveViolations: latest.violations,
152133
- enabledCategories,
152134
- enableLlmRules,
152135
- tracker: options.tracker,
152136
- signal: options.signal
152282
+ router2.get("/:id/analyses", async (req, res, next) => {
152283
+ try {
152284
+ const id = req.params.id;
152285
+ const repo = resolveProjectForRequest(id);
152286
+ const history = readHistory(repo.path);
152287
+ const entries = history.analyses.filter((e) => !e.metadata?.isDiffAnalysis).slice(-20).reverse();
152288
+ res.json(
152289
+ entries.map((e) => ({
152290
+ id: e.id,
152291
+ status: "completed",
152292
+ branch: e.branch,
152293
+ commitHash: e.commitHash,
152294
+ architecture: null,
152295
+ createdAt: e.createdAt,
152296
+ serviceCount: e.counts.services,
152297
+ violationsBySeverity: e.counts.violations.bySeverity,
152298
+ durationMs: e.usage.durationMs,
152299
+ totalTokens: e.usage.totalTokens,
152300
+ totalCost: e.usage.totalCostUsd,
152301
+ provider: e.usage.provider
152302
+ }))
152303
+ );
152304
+ } catch (error) {
152305
+ next(error);
152306
+ }
152307
+ });
152308
+ router2.get("/:id/analyses/diff", async (req, res, next) => {
152309
+ try {
152310
+ const id = req.params.id;
152311
+ const repo = resolveProjectForRequest(id);
152312
+ const result = getDiffResult(repo.path);
152313
+ if (!result) {
152314
+ res.json(null);
152315
+ return;
152316
+ }
152317
+ const { diff, isStale } = result;
152318
+ res.json({
152319
+ resolvedViolations: diff.resolvedViolations,
152320
+ newViolations: diff.newViolations,
152321
+ affectedNodeIds: diff.affectedNodeIds,
152322
+ summary: diff.summary,
152323
+ changedFiles: diff.changedFiles,
152324
+ isStale,
152325
+ diffAnalysisId: diff.id
152326
+ });
152327
+ } catch (error) {
152328
+ next(error);
152329
+ }
152330
+ });
152331
+ router2.get("/:id/analyses/:analysisId/usage", async (req, res, next) => {
152332
+ try {
152333
+ const id = req.params.id;
152334
+ const analysisId = req.params.analysisId;
152335
+ const repo = resolveProjectForRequest(id);
152336
+ const latest = readLatest(repo.path);
152337
+ if (latest?.analysis.id === analysisId) {
152338
+ const snap2 = readAnalysis(repo.path, latest.head);
152339
+ res.json(snap2?.usage ?? []);
152340
+ return;
152341
+ }
152342
+ const filename = findAnalysisFilename(repo.path, analysisId);
152343
+ if (!filename) {
152344
+ res.json([]);
152345
+ return;
152346
+ }
152347
+ const snap = readAnalysis(repo.path, filename);
152348
+ res.json(snap?.usage ?? []);
152349
+ } catch (error) {
152350
+ next(error);
152351
+ }
152352
+ });
152353
+ router2.delete("/:id/analyses/:analysisId", async (req, res, next) => {
152354
+ try {
152355
+ const id = req.params.id;
152356
+ const analysisId = req.params.analysisId;
152357
+ const repo = resolveProjectForRequest(id);
152358
+ const filename = findAnalysisFilename(repo.path, analysisId);
152359
+ if (!filename) throw createAppError("Analysis not found", 404);
152360
+ deleteAnalysis(repo.path, filename);
152361
+ removeFromHistory(repo.path, analysisId);
152362
+ const latest = readLatest(repo.path);
152363
+ if (latest?.head === filename) {
152364
+ rebuildLatestFromHistory(repo.path);
152365
+ deleteDiff(repo.path);
152366
+ }
152367
+ res.json({ ok: true });
152368
+ } catch (error) {
152369
+ next(error);
152370
+ }
152371
+ });
152372
+ async function runFullAnalyze(id, repo, opts) {
152373
+ const provider = opts.effectiveLlmRules ? createLLMProvider() : void 0;
152374
+ if (provider) {
152375
+ provider.setRepoId(id);
152376
+ provider.setRepoPath(repo.path);
152377
+ provider.setAbortSignal(opts.signal);
152378
+ }
152379
+ const outcome = await analyzeInProcess(repo, {
152380
+ skipGit: opts.skipGit,
152381
+ enabledCategoriesOverride: opts.effectiveCategories,
152382
+ enableLlmRulesOverride: opts.effectiveLlmRules,
152383
+ tracker: opts.tracker,
152384
+ signal: opts.signal,
152385
+ provider,
152386
+ onLlmEstimate: createSocketLlmEstimateHandler(id)
152137
152387
  });
152138
- const diff = buildDiffSnapshot({
152139
- latest,
152140
- graph,
152141
- analysisId,
152142
- now,
152143
- branch: latest.analysis.branch,
152144
- commitHash,
152145
- changedFiles,
152146
- pipelineResult,
152147
- repoPath: project.path
152388
+ emitViolationsReady(id, outcome.analysisId);
152389
+ emitAnalysisComplete(id, outcome.analysisId);
152390
+ trackEvent("analyze", {
152391
+ serviceCount: outcome.serviceCount,
152392
+ fileCountRange: bucketFileCount(outcome.fileCount),
152393
+ languages: [],
152394
+ architecture: outcome.architecture,
152395
+ durationRange: bucketDuration(outcome.durationMs)
152148
152396
  });
152149
- writeDiff(project.path, diff);
152150
- log.info(
152151
- `[Diff] Done \u2014 ${diff.summary.newCount} new, ${diff.summary.unchangedCount} unchanged, ${diff.summary.resolvedCount} resolved across ${diff.changedFiles.length} changed files`
152152
- );
152153
- return { diff, isStale: false };
152154
152397
  }
152155
- function buildDiffSnapshot(params) {
152156
- const { latest, graph, analysisId, branch, commitHash, changedFiles, pipelineResult, repoPath } = params;
152157
- const serviceById = new Map(graph.services.map((s) => [s.id, s.name]));
152158
- const moduleById = new Map(graph.modules.map((m) => [m.id, m.name]));
152159
- const methodById = new Map(graph.methods.map((m) => [m.id, m.name]));
152160
- const databaseById = new Map(graph.databases.map((d) => [d.id, d.name]));
152161
- const denormalize = (v) => ({
152162
- ...v,
152163
- targetServiceName: v.targetServiceId ? serviceById.get(v.targetServiceId) ?? null : null,
152164
- targetModuleName: v.targetModuleId ? moduleById.get(v.targetModuleId) ?? null : null,
152165
- targetMethodName: v.targetMethodId ? methodById.get(v.targetMethodId) ?? null : null,
152166
- targetDatabaseName: v.targetDatabaseId ? databaseById.get(v.targetDatabaseId) ?? null : null
152398
+ async function runDiffAnalyze(id, repo, opts) {
152399
+ const { diff } = await diffInProcess(repo, {
152400
+ tracker: opts.tracker,
152401
+ signal: opts.signal,
152402
+ onLlmEstimate: createSocketLlmEstimateHandler(id)
152167
152403
  });
152168
- const newViolations = pipelineResult.added.map(denormalize);
152169
- const latestById = new Map(latest.violations.map((v) => [v.id, v]));
152170
- const resolvedViolations = pipelineResult.resolvedRefs.map((r) => latestById.get(r.id)).filter((v) => !!v);
152171
- const changedAbs = new Set(
152172
- changedFiles.map((c) => path14.resolve(repoPath, c.path))
152173
- );
152174
- const matchesChanged = (p) => !!p && (changedAbs.has(p) || changedAbs.has(path14.resolve(repoPath, p)));
152175
- const affectedModules = graph.modules.filter((m) => matchesChanged(m.filePath));
152176
- const affectedModuleIdSet = new Set(affectedModules.map((m) => m.id));
152177
- const serviceNameById = new Map(graph.services.map((s) => [s.id, s.name]));
152178
- const layerKeyById = new Map(
152179
- graph.layers.map((l) => [l.id, `${l.serviceName}::${l.layer}`])
152180
- );
152181
- const affectedServices = /* @__PURE__ */ new Set();
152182
- const affectedLayers = /* @__PURE__ */ new Set();
152183
- const affectedModuleKeys = /* @__PURE__ */ new Set();
152184
- for (const mod of affectedModules) {
152185
- const svcName = serviceNameById.get(mod.serviceId);
152186
- if (svcName) {
152187
- affectedServices.add(svcName);
152188
- affectedModuleKeys.add(`${svcName}::${mod.name}`);
152189
- }
152190
- const layerKey = layerKeyById.get(mod.layerId);
152191
- if (layerKey) affectedLayers.add(layerKey);
152404
+ emitViolationsReady(id, diff.id);
152405
+ emitAnalysisComplete(id, diff.id);
152406
+ }
152407
+ function rebuildLatestFromHistory(repoPath) {
152408
+ const files = listAnalyses(repoPath);
152409
+ if (files.length === 0) {
152410
+ deleteLatest(repoPath);
152411
+ return;
152192
152412
  }
152193
- const moduleNameById = new Map(graph.modules.map((m) => [m.id, m.name]));
152194
- const affectedMethodKeys = [];
152195
- for (const method of graph.methods) {
152196
- if (!affectedModuleIdSet.has(method.moduleId)) continue;
152197
- const modName = moduleNameById.get(method.moduleId);
152198
- const mod = graph.modules.find((m) => m.id === method.moduleId);
152199
- const svcName = mod ? serviceNameById.get(mod.serviceId) : void 0;
152200
- if (svcName && modName) affectedMethodKeys.push(`${svcName}::${modName}::${method.name}`);
152413
+ const newest = files[files.length - 1];
152414
+ const snap = readAnalysis(repoPath, newest);
152415
+ if (!snap) {
152416
+ deleteLatest(repoPath);
152417
+ return;
152201
152418
  }
152202
- return {
152203
- id: analysisId,
152204
- baseAnalysisId: latest.analysis.id,
152205
- createdAt: params.now,
152206
- branch,
152207
- commitHash,
152208
- graph,
152209
- changedFiles,
152210
- newViolations,
152211
- resolvedViolations,
152212
- affectedNodeIds: {
152213
- services: [...affectedServices],
152214
- layers: [...affectedLayers],
152215
- modules: [...affectedModuleKeys],
152216
- methods: affectedMethodKeys
152419
+ const active = /* @__PURE__ */ new Map();
152420
+ for (const fname of files) {
152421
+ const s = readAnalysis(repoPath, fname);
152422
+ if (!s) continue;
152423
+ for (const r of s.violations.resolved) active.delete(r.id);
152424
+ for (const a of s.violations.added) active.set(a.id, s);
152425
+ }
152426
+ const serviceById = new Map(snap.graph.services.map((s) => [s.id, s.name]));
152427
+ const moduleById = new Map(snap.graph.modules.map((m) => [m.id, m.name]));
152428
+ const methodById = new Map(snap.graph.methods.map((m) => [m.id, m.name]));
152429
+ const databaseById = new Map(snap.graph.databases.map((d) => [d.id, d.name]));
152430
+ const latest = {
152431
+ head: newest,
152432
+ analysis: {
152433
+ id: snap.id,
152434
+ createdAt: snap.createdAt,
152435
+ branch: snap.branch,
152436
+ commitHash: snap.commitHash,
152437
+ architecture: snap.architecture,
152438
+ metadata: snap.metadata,
152439
+ status: "completed"
152217
152440
  },
152218
- summary: {
152219
- newCount: newViolations.length,
152220
- unchangedCount: pipelineResult.unchanged.length,
152221
- resolvedCount: resolvedViolations.length
152222
- }
152441
+ graph: snap.graph,
152442
+ violations: []
152223
152443
  };
152444
+ for (const snapshot of new Set(active.values())) {
152445
+ if (!snapshot) continue;
152446
+ for (const v of snapshot.violations.added) {
152447
+ if (!active.has(v.id)) continue;
152448
+ latest.violations.push({
152449
+ ...v,
152450
+ targetServiceName: v.targetServiceId ? serviceById.get(v.targetServiceId) ?? null : null,
152451
+ targetModuleName: v.targetModuleId ? moduleById.get(v.targetModuleId) ?? null : null,
152452
+ targetMethodName: v.targetMethodId ? methodById.get(v.targetMethodId) ?? null : null,
152453
+ targetDatabaseName: v.targetDatabaseId ? databaseById.get(v.targetDatabaseId) ?? null : null
152454
+ });
152455
+ }
152456
+ }
152457
+ writeLatest(repoPath, latest);
152224
152458
  }
152459
+ var analyses_default = router2;
152460
+
152461
+ // apps/server/src/routes/graph.ts
152462
+ var import_express3 = __toESM(require_express2(), 1);
152225
152463
 
152226
152464
  // apps/server/src/config/ui-state.ts
152227
152465
  import fs10 from "node:fs";
@@ -152949,196 +153187,139 @@ function markDependencyViolations(edges, detViolations) {
152949
153187
  }
152950
153188
  }
152951
153189
 
152952
- // apps/server/src/routes/analysis.ts
153190
+ // apps/server/src/routes/graph.ts
152953
153191
  init_analysis_store();
152954
153192
  var router3 = (0, import_express3.Router)();
152955
- router3.get(
152956
- "/:id/analyses",
152957
- async (req, res, next) => {
152958
- try {
152959
- const id = req.params.id;
152960
- const repo = resolveProjectForRequest(id);
152961
- const history = readHistory(repo.path);
152962
- const entries = history.analyses.filter((e) => !e.metadata?.isDiffAnalysis).slice(-20).reverse();
152963
- res.json(
152964
- entries.map((e) => ({
152965
- id: e.id,
152966
- status: "completed",
152967
- branch: e.branch,
152968
- commitHash: e.commitHash,
152969
- architecture: null,
152970
- createdAt: e.createdAt,
152971
- serviceCount: e.counts.services,
152972
- violationsBySeverity: e.counts.violations.bySeverity,
152973
- durationMs: e.usage.durationMs,
152974
- totalTokens: e.usage.totalTokens,
152975
- totalCost: e.usage.totalCostUsd,
152976
- provider: e.usage.provider
152977
- }))
152978
- );
152979
- } catch (error) {
152980
- next(error);
152981
- }
152982
- }
152983
- );
152984
- router3.get(
152985
- "/:id/analyses/:analysisId/usage",
152986
- async (req, res, next) => {
152987
- try {
152988
- const id = req.params.id;
152989
- const analysisId = req.params.analysisId;
152990
- const repo = resolveProjectForRequest(id);
152991
- const latest = readLatest(repo.path);
152992
- if (latest?.analysis.id === analysisId) {
152993
- const snap2 = readAnalysis(repo.path, latest.head);
152994
- res.json(snap2?.usage ?? []);
152995
- return;
152996
- }
152997
- const filename = await findAnalysisFilename(repo.path, analysisId);
152998
- if (!filename) {
152999
- res.json([]);
153000
- return;
153001
- }
153002
- const snap = readAnalysis(repo.path, filename);
153003
- res.json(snap?.usage ?? []);
153004
- } catch (error) {
153005
- next(error);
153006
- }
153007
- }
153008
- );
153009
- router3.get(
153010
- "/:id/graph",
153011
- async (req, res, next) => {
153012
- try {
153013
- const id = req.params.id;
153014
- const repo = resolveProjectForRequest(id);
153015
- const analysisIdParam = req.query.analysisId;
153016
- const level = req.query.level || "services";
153017
- let snapshot = readLatest(repo.path);
153018
- if (analysisIdParam && (!snapshot || snapshot.analysis.id !== analysisIdParam)) {
153019
- const diff = readDiff(repo.path);
153020
- if (diff && diff.id === analysisIdParam) {
153021
- snapshot = {
153022
- head: `diff-${diff.id}`,
153023
- analysis: {
153024
- id: diff.id,
153025
- createdAt: diff.createdAt,
153026
- branch: diff.branch,
153027
- commitHash: diff.commitHash,
153028
- architecture: snapshot?.analysis.architecture ?? "monolith",
153029
- metadata: null,
153030
- status: "completed"
153031
- },
153032
- graph: diff.graph,
153033
- violations: diff.newViolations
153034
- };
153035
- } else {
153036
- const filename = await findAnalysisFilename(repo.path, analysisIdParam);
153037
- if (!filename) {
153038
- res.json({ nodes: [], edges: [] });
153039
- return;
153040
- }
153041
- const snap = readAnalysis(repo.path, filename);
153042
- if (!snap) {
153043
- res.json({ nodes: [], edges: [] });
153044
- return;
153045
- }
153046
- snapshot = {
153047
- head: filename,
153048
- analysis: {
153049
- id: snap.id,
153050
- createdAt: snap.createdAt,
153051
- branch: snap.branch,
153052
- commitHash: snap.commitHash,
153053
- architecture: snap.architecture,
153054
- metadata: snap.metadata,
153055
- status: "completed"
153056
- },
153057
- graph: snap.graph,
153058
- violations: []
153059
- };
153193
+ router3.get("/:id/graph", async (req, res, next) => {
153194
+ try {
153195
+ const id = req.params.id;
153196
+ const repo = resolveProjectForRequest(id);
153197
+ const analysisIdParam = req.query.analysisId;
153198
+ const level = req.query.level || "services";
153199
+ let snapshot = readLatest(repo.path);
153200
+ if (analysisIdParam && (!snapshot || snapshot.analysis.id !== analysisIdParam)) {
153201
+ const diff = readDiff(repo.path);
153202
+ if (diff && diff.id === analysisIdParam) {
153203
+ snapshot = {
153204
+ head: `diff-${diff.id}`,
153205
+ analysis: {
153206
+ id: diff.id,
153207
+ createdAt: diff.createdAt,
153208
+ branch: diff.branch,
153209
+ commitHash: diff.commitHash,
153210
+ architecture: snapshot?.analysis.architecture ?? "monolith",
153211
+ metadata: null,
153212
+ status: "completed"
153213
+ },
153214
+ graph: diff.graph,
153215
+ violations: diff.newViolations
153216
+ };
153217
+ } else {
153218
+ const filename = findAnalysisFilename(repo.path, analysisIdParam);
153219
+ if (!filename) {
153220
+ res.json({ nodes: [], edges: [] });
153221
+ return;
153060
153222
  }
153223
+ const snap = readAnalysis(repo.path, filename);
153224
+ if (!snap) {
153225
+ res.json({ nodes: [], edges: [] });
153226
+ return;
153227
+ }
153228
+ snapshot = {
153229
+ head: filename,
153230
+ analysis: {
153231
+ id: snap.id,
153232
+ createdAt: snap.createdAt,
153233
+ branch: snap.branch,
153234
+ commitHash: snap.commitHash,
153235
+ architecture: snap.architecture,
153236
+ metadata: snap.metadata,
153237
+ status: "completed"
153238
+ },
153239
+ graph: snap.graph,
153240
+ violations: []
153241
+ };
153061
153242
  }
153062
- if (!snapshot) {
153063
- res.json({ nodes: [], edges: [] });
153064
- return;
153065
- }
153066
- const graphLevel = level.replace(/s$/, "");
153067
- const serviceIdToName = new Map(snapshot.graph.services.map((s) => [s.id, s.name]));
153068
- const moduleIdToName = new Map(snapshot.graph.modules.map((m) => [m.id, m.name]));
153069
- const dependencyViolations = snapshot.violations.filter((v) => !!(v.relatedServiceId || v.relatedModuleId)).map((v) => ({
153070
- id: v.id,
153071
- ruleKey: v.ruleKey,
153072
- category: v.type === "service" ? "service" : v.type === "function" ? "method" : "module",
153073
- title: v.title,
153074
- severity: v.severity,
153075
- serviceName: v.targetServiceId ? serviceIdToName.get(v.targetServiceId) ?? "" : "",
153076
- moduleName: v.targetModuleId ? moduleIdToName.get(v.targetModuleId) ?? null : null,
153077
- targetModuleId: v.targetModuleId ?? null,
153078
- relatedServiceId: v.relatedServiceId ?? null,
153079
- relatedModuleId: v.relatedModuleId ?? null,
153080
- isDependencyViolation: !!(v.relatedServiceId || v.relatedModuleId)
153081
- }));
153082
- const unifiedInput = {
153083
- services: snapshot.graph.services.map((s) => ({
153084
- ...s,
153085
- analysisId: snapshot.analysis.id,
153086
- createdAt: new Date(snapshot.analysis.createdAt)
153087
- })),
153088
- serviceDeps: snapshot.graph.serviceDependencies.map((d) => ({
153089
- ...d,
153090
- analysisId: snapshot.analysis.id
153091
- })),
153092
- layers: snapshot.graph.layers,
153093
- modules: snapshot.graph.modules.map((m) => ({
153094
- ...m,
153095
- analysisId: snapshot.analysis.id
153096
- })),
153097
- moduleDeps: snapshot.graph.moduleDeps.map((d) => ({
153098
- ...d,
153099
- analysisId: snapshot.analysis.id
153100
- })),
153101
- methods: snapshot.graph.methods.map((m) => ({
153102
- ...m,
153103
- analysisId: snapshot.analysis.id
153104
- })),
153105
- methodDeps: snapshot.graph.methodDeps.map((d) => ({
153106
- ...d,
153107
- analysisId: snapshot.analysis.id
153108
- })),
153109
- databases: snapshot.graph.databases.map((d) => ({
153110
- ...d,
153111
- analysisId: snapshot.analysis.id,
153112
- createdAt: new Date(snapshot.analysis.createdAt)
153113
- })),
153114
- dbConnections: snapshot.graph.databaseConnections.map((c) => ({
153115
- ...c,
153116
- analysisId: snapshot.analysis.id
153117
- })),
153118
- dependencyViolations
153119
- };
153120
- const graphData = buildUnifiedGraph(graphLevel, unifiedInput);
153121
- const keyMap = buildStableKeyMap({
153122
- services: snapshot.graph.services,
153123
- layers: snapshot.graph.layers,
153124
- modules: snapshot.graph.modules,
153125
- methods: snapshot.graph.methods
153126
- });
153127
- const savedStablePositions = getScopedPositions(repo.path, snapshot.analysis.branch, level);
153128
- const savedPositions = positionsToUuid(keyMap, savedStablePositions);
153129
- for (const node2 of graphData.nodes) {
153130
- const pos = savedPositions[node2.id];
153131
- if (pos) node2.position = pos;
153132
- }
153133
- const savedStableCollapsed = getScopedCollapsed(repo.path, snapshot.analysis.branch, level);
153134
- const collapsedIds = idsToUuid(keyMap, savedStableCollapsed);
153135
- res.set("Cache-Control", "no-store");
153136
- res.json({ ...graphData, collapsedIds });
153137
- } catch (error) {
153138
- next(error);
153139
153243
  }
153244
+ if (!snapshot) {
153245
+ res.json({ nodes: [], edges: [] });
153246
+ return;
153247
+ }
153248
+ const graphLevel = level.replace(/s$/, "");
153249
+ const serviceIdToName = new Map(snapshot.graph.services.map((s) => [s.id, s.name]));
153250
+ const moduleIdToName = new Map(snapshot.graph.modules.map((m) => [m.id, m.name]));
153251
+ const dependencyViolations = snapshot.violations.filter((v) => !!(v.relatedServiceId || v.relatedModuleId)).map((v) => ({
153252
+ id: v.id,
153253
+ ruleKey: v.ruleKey,
153254
+ category: v.type === "service" ? "service" : v.type === "function" ? "method" : "module",
153255
+ title: v.title,
153256
+ severity: v.severity,
153257
+ serviceName: v.targetServiceId ? serviceIdToName.get(v.targetServiceId) ?? "" : "",
153258
+ moduleName: v.targetModuleId ? moduleIdToName.get(v.targetModuleId) ?? null : null,
153259
+ targetModuleId: v.targetModuleId ?? null,
153260
+ relatedServiceId: v.relatedServiceId ?? null,
153261
+ relatedModuleId: v.relatedModuleId ?? null,
153262
+ isDependencyViolation: !!(v.relatedServiceId || v.relatedModuleId)
153263
+ }));
153264
+ const unifiedInput = {
153265
+ services: snapshot.graph.services.map((s) => ({
153266
+ ...s,
153267
+ analysisId: snapshot.analysis.id,
153268
+ createdAt: new Date(snapshot.analysis.createdAt)
153269
+ })),
153270
+ serviceDeps: snapshot.graph.serviceDependencies.map((d) => ({
153271
+ ...d,
153272
+ analysisId: snapshot.analysis.id
153273
+ })),
153274
+ layers: snapshot.graph.layers,
153275
+ modules: snapshot.graph.modules.map((m) => ({
153276
+ ...m,
153277
+ analysisId: snapshot.analysis.id
153278
+ })),
153279
+ moduleDeps: snapshot.graph.moduleDeps.map((d) => ({
153280
+ ...d,
153281
+ analysisId: snapshot.analysis.id
153282
+ })),
153283
+ methods: snapshot.graph.methods.map((m) => ({
153284
+ ...m,
153285
+ analysisId: snapshot.analysis.id
153286
+ })),
153287
+ methodDeps: snapshot.graph.methodDeps.map((d) => ({
153288
+ ...d,
153289
+ analysisId: snapshot.analysis.id
153290
+ })),
153291
+ databases: snapshot.graph.databases.map((d) => ({
153292
+ ...d,
153293
+ analysisId: snapshot.analysis.id,
153294
+ createdAt: new Date(snapshot.analysis.createdAt)
153295
+ })),
153296
+ dbConnections: snapshot.graph.databaseConnections.map((c) => ({
153297
+ ...c,
153298
+ analysisId: snapshot.analysis.id
153299
+ })),
153300
+ dependencyViolations
153301
+ };
153302
+ const graphData = buildUnifiedGraph(graphLevel, unifiedInput);
153303
+ const keyMap = buildStableKeyMap({
153304
+ services: snapshot.graph.services,
153305
+ layers: snapshot.graph.layers,
153306
+ modules: snapshot.graph.modules,
153307
+ methods: snapshot.graph.methods
153308
+ });
153309
+ const savedStablePositions = getScopedPositions(repo.path, snapshot.analysis.branch, level);
153310
+ const savedPositions = positionsToUuid(keyMap, savedStablePositions);
153311
+ for (const node2 of graphData.nodes) {
153312
+ const pos = savedPositions[node2.id];
153313
+ if (pos) node2.position = pos;
153314
+ }
153315
+ const savedStableCollapsed = getScopedCollapsed(repo.path, snapshot.analysis.branch, level);
153316
+ const collapsedIds = idsToUuid(keyMap, savedStableCollapsed);
153317
+ res.set("Cache-Control", "no-store");
153318
+ res.json({ ...graphData, collapsedIds });
153319
+ } catch (error) {
153320
+ next(error);
153140
153321
  }
153141
- );
153322
+ });
153142
153323
  function keyMapFromLatest(latest) {
153143
153324
  return buildStableKeyMap({
153144
153325
  services: latest.graph.services,
@@ -153147,391 +153328,205 @@ function keyMapFromLatest(latest) {
153147
153328
  methods: latest.graph.methods
153148
153329
  });
153149
153330
  }
153150
- router3.put(
153151
- "/:id/graph/positions",
153152
- async (req, res, next) => {
153153
- try {
153154
- const id = req.params.id;
153155
- const level = req.query.level || "services";
153156
- const { positions } = req.body;
153157
- if (!positions || typeof positions !== "object") {
153158
- throw createAppError("Invalid positions data", 400);
153159
- }
153160
- const repo = resolveProjectForRequest(id);
153161
- const latest = readLatest(repo.path);
153162
- if (!latest) throw createAppError("No analysis found", 404);
153163
- const keyMap = keyMapFromLatest(latest);
153164
- const stablePositions = positionsToStable(keyMap, positions);
153165
- setPositions(repo.path, latest.analysis.branch, level, stablePositions);
153166
- res.json({ ok: true });
153167
- } catch (error) {
153168
- next(error);
153169
- }
153170
- }
153171
- );
153172
- router3.delete(
153173
- "/:id/graph/positions",
153174
- async (req, res, next) => {
153175
- try {
153176
- const id = req.params.id;
153177
- const branch = req.query.branch;
153178
- const level = req.query.level || "services";
153179
- const repo = resolveProjectForRequest(id);
153180
- clearPositions(repo.path, branch ?? null, level);
153181
- res.json({ ok: true });
153182
- } catch (error) {
153183
- next(error);
153184
- }
153185
- }
153186
- );
153187
- router3.put(
153188
- "/:id/graph/collapsed",
153189
- async (req, res, next) => {
153190
- try {
153191
- const id = req.params.id;
153192
- const level = req.query.level || "modules";
153193
- const { collapsedIds: ids } = req.body;
153194
- if (!Array.isArray(ids)) throw createAppError("Invalid collapsedIds data", 400);
153195
- const repo = resolveProjectForRequest(id);
153196
- const latest = readLatest(repo.path);
153197
- if (!latest) throw createAppError("No analysis found", 404);
153198
- const keyMap = keyMapFromLatest(latest);
153199
- const stableIds = idsToStable(keyMap, ids);
153200
- setCollapsed(repo.path, latest.analysis.branch, level, stableIds);
153201
- res.json({ ok: true });
153202
- } catch (error) {
153203
- next(error);
153204
- }
153205
- }
153206
- );
153207
- router3.delete(
153208
- "/:id/analyses/:analysisId",
153209
- async (req, res, next) => {
153210
- try {
153211
- const id = req.params.id;
153212
- const analysisId = req.params.analysisId;
153213
- const repo = resolveProjectForRequest(id);
153214
- const filename = await findAnalysisFilename(repo.path, analysisId);
153215
- if (!filename) throw createAppError("Analysis not found", 404);
153216
- deleteAnalysis(repo.path, filename);
153217
- removeFromHistory(repo.path, analysisId);
153218
- const latest = readLatest(repo.path);
153219
- if (latest?.head === filename) {
153220
- await rebuildLatestFromHistory(repo.path);
153221
- deleteDiff(repo.path);
153222
- }
153223
- res.json({ ok: true });
153224
- } catch (error) {
153225
- next(error);
153331
+ router3.put("/:id/graph/positions", async (req, res, next) => {
153332
+ try {
153333
+ const id = req.params.id;
153334
+ const level = req.query.level || "services";
153335
+ const { positions } = req.body;
153336
+ if (!positions || typeof positions !== "object") {
153337
+ throw createAppError("Invalid positions data", 400);
153226
153338
  }
153339
+ const repo = resolveProjectForRequest(id);
153340
+ const latest = readLatest(repo.path);
153341
+ if (!latest) throw createAppError("No analysis found", 404);
153342
+ const keyMap = keyMapFromLatest(latest);
153343
+ const stablePositions = positionsToStable(keyMap, positions);
153344
+ setPositions(repo.path, latest.analysis.branch, level, stablePositions);
153345
+ res.json({ ok: true });
153346
+ } catch (error) {
153347
+ next(error);
153227
153348
  }
153228
- );
153229
- async function rebuildLatestFromHistory(repoPath) {
153230
- const files = listAnalyses(repoPath);
153231
- if (files.length === 0) {
153232
- deleteLatest(repoPath);
153233
- return;
153234
- }
153235
- const newest = files[files.length - 1];
153236
- const snap = readAnalysis(repoPath, newest);
153237
- if (!snap) {
153238
- deleteLatest(repoPath);
153239
- return;
153240
- }
153241
- const sorted2 = [...files];
153242
- const active = /* @__PURE__ */ new Map();
153243
- for (const fname of sorted2) {
153244
- const s = readAnalysis(repoPath, fname);
153245
- if (!s) continue;
153246
- for (const r of s.violations.resolved) active.delete(r.id);
153247
- for (const a of s.violations.added) active.set(a.id, s);
153349
+ });
153350
+ router3.delete("/:id/graph/positions", async (req, res, next) => {
153351
+ try {
153352
+ const id = req.params.id;
153353
+ const branch = req.query.branch;
153354
+ const level = req.query.level || "services";
153355
+ const repo = resolveProjectForRequest(id);
153356
+ clearPositions(repo.path, branch ?? null, level);
153357
+ res.json({ ok: true });
153358
+ } catch (error) {
153359
+ next(error);
153248
153360
  }
153249
- const serviceById = new Map(snap.graph.services.map((s) => [s.id, s.name]));
153250
- const moduleById = new Map(snap.graph.modules.map((m) => [m.id, m.name]));
153251
- const methodById = new Map(snap.graph.methods.map((m) => [m.id, m.name]));
153252
- const databaseById = new Map(snap.graph.databases.map((d) => [d.id, d.name]));
153253
- const latest = {
153254
- head: newest,
153255
- analysis: {
153256
- id: snap.id,
153257
- createdAt: snap.createdAt,
153258
- branch: snap.branch,
153259
- commitHash: snap.commitHash,
153260
- architecture: snap.architecture,
153261
- metadata: snap.metadata,
153262
- status: "completed"
153263
- },
153264
- graph: snap.graph,
153265
- violations: []
153266
- // Best-effort rebuild — populated from the snapshot that owns each active id.
153267
- };
153268
- for (const snapshot of new Set(active.values())) {
153269
- if (!snapshot) continue;
153270
- for (const v of snapshot.violations.added) {
153271
- if (!active.has(v.id)) continue;
153272
- latest.violations.push({
153273
- ...v,
153274
- targetServiceName: v.targetServiceId ? serviceById.get(v.targetServiceId) ?? null : null,
153275
- targetModuleName: v.targetModuleId ? moduleById.get(v.targetModuleId) ?? null : null,
153276
- targetMethodName: v.targetMethodId ? methodById.get(v.targetMethodId) ?? null : null,
153277
- targetDatabaseName: v.targetDatabaseId ? databaseById.get(v.targetDatabaseId) ?? null : null
153278
- });
153279
- }
153361
+ });
153362
+ router3.put("/:id/graph/collapsed", async (req, res, next) => {
153363
+ try {
153364
+ const id = req.params.id;
153365
+ const level = req.query.level || "modules";
153366
+ const { collapsedIds: ids } = req.body;
153367
+ if (!Array.isArray(ids)) throw createAppError("Invalid collapsedIds data", 400);
153368
+ const repo = resolveProjectForRequest(id);
153369
+ const latest = readLatest(repo.path);
153370
+ if (!latest) throw createAppError("No analysis found", 404);
153371
+ const keyMap = keyMapFromLatest(latest);
153372
+ const stableIds = idsToStable(keyMap, ids);
153373
+ setCollapsed(repo.path, latest.analysis.branch, level, stableIds);
153374
+ res.json({ ok: true });
153375
+ } catch (error) {
153376
+ next(error);
153280
153377
  }
153281
- writeLatest(repoPath, latest);
153282
- }
153283
- router3.get(
153284
- "/:id/files",
153285
- async (req, res, next) => {
153286
- try {
153287
- const id = req.params.id;
153288
- const ref = req.query.ref;
153289
- const repo = resolveProjectForRequest(id);
153290
- const git = await getGit(repo.path);
153291
- const result = await git.raw(["ls-files"]);
153292
- const files = result.split("\n").filter((f2) => f2.length > 0);
153293
- if (ref === "working-tree") {
153294
- const untrackedResult = await git.raw(["ls-files", "--others", "--exclude-standard"]);
153295
- const untrackedFiles = untrackedResult.split("\n").filter((f2) => f2.length > 0);
153296
- for (const f2 of untrackedFiles) {
153297
- if (!files.includes(f2)) files.push(f2);
153298
- }
153378
+ });
153379
+ var graph_default = router3;
153380
+
153381
+ // apps/server/src/routes/files.ts
153382
+ var import_express4 = __toESM(require_express2(), 1);
153383
+ import fs11 from "node:fs";
153384
+ import path15 from "node:path";
153385
+ init_analysis_store();
153386
+ var router4 = (0, import_express4.Router)();
153387
+ router4.get("/:id/files", async (req, res, next) => {
153388
+ try {
153389
+ const id = req.params.id;
153390
+ const ref = req.query.ref;
153391
+ const repo = resolveProjectForRequest(id);
153392
+ const git = await getGit(repo.path);
153393
+ const result = await git.raw(["ls-files"]);
153394
+ const files = result.split("\n").filter((f2) => f2.length > 0);
153395
+ if (ref === "working-tree") {
153396
+ const untrackedResult = await git.raw(["ls-files", "--others", "--exclude-standard"]);
153397
+ const untrackedFiles = untrackedResult.split("\n").filter((f2) => f2.length > 0);
153398
+ for (const f2 of untrackedFiles) {
153399
+ if (!files.includes(f2)) files.push(f2);
153299
153400
  }
153300
- res.json({ root: repo.path, files });
153301
- } catch (error) {
153302
- next(error);
153303
153401
  }
153402
+ res.json({ root: repo.path, files });
153403
+ } catch (error) {
153404
+ next(error);
153304
153405
  }
153305
- );
153306
- router3.get(
153307
- "/:id/changes",
153308
- async (req, res, next) => {
153309
- try {
153310
- const id = req.params.id;
153311
- const repo = resolveProjectForRequest(id);
153406
+ });
153407
+ router4.get("/:id/file-content", async (req, res, next) => {
153408
+ try {
153409
+ const id = req.params.id;
153410
+ const filePath = req.query.path;
153411
+ const ref = req.query.ref;
153412
+ if (!filePath) throw createAppError('Missing "path" query parameter', 400);
153413
+ const repo = resolveProjectForRequest(id);
153414
+ const resolved = path15.resolve(repo.path, filePath);
153415
+ if (!resolved.startsWith(path15.resolve(repo.path) + path15.sep) && resolved !== path15.resolve(repo.path)) {
153416
+ throw createAppError("Path traversal not allowed", 403);
153417
+ }
153418
+ let content;
153419
+ if (ref === "working-tree") {
153420
+ if (!fs11.existsSync(resolved)) throw createAppError("File not found", 404);
153421
+ const stat = fs11.statSync(resolved);
153422
+ if (!stat.isFile()) throw createAppError("Path is not a file", 400);
153423
+ content = fs11.readFileSync(resolved, "utf-8");
153424
+ } else {
153312
153425
  const git = await getGit(repo.path);
153313
- const statusResult = await git.status();
153314
- const changedFiles = [];
153315
- for (const f2 of statusResult.not_added) changedFiles.push({ path: f2, status: "new" });
153316
- for (const f2 of statusResult.created) changedFiles.push({ path: f2, status: "new" });
153317
- for (const f2 of statusResult.modified) changedFiles.push({ path: f2, status: "modified" });
153318
- for (const f2 of statusResult.staged) {
153319
- if (!changedFiles.some((cf) => cf.path === f2)) changedFiles.push({ path: f2, status: "modified" });
153320
- }
153321
- for (const f2 of statusResult.deleted) changedFiles.push({ path: f2, status: "deleted" });
153322
- const latest = readLatest(repo.path);
153323
- const affectedServices = [];
153324
- if (latest) {
153325
- for (const svc of latest.graph.services) {
153326
- const svcRoot = svc.rootPath;
153327
- const isAffected = changedFiles.some(
153328
- (cf) => cf.path.startsWith(svcRoot + "/") || cf.path === svcRoot
153329
- );
153330
- if (isAffected) affectedServices.push(svc.id);
153331
- }
153332
- }
153333
- res.json({ changedFiles, affectedServices });
153334
- } catch (error) {
153335
- next(error);
153336
- }
153337
- }
153338
- );
153339
- router3.post(
153340
- "/:id/diff-check",
153341
- async (req, res, next) => {
153342
- try {
153343
- const id = req.params.id;
153344
- const repo = resolveProjectForRequest(id);
153345
- const { diff } = await diffInProcess(repo);
153346
- res.json({
153347
- resolvedViolations: diff.resolvedViolations,
153348
- newViolations: diff.newViolations,
153349
- affectedNodeIds: diff.affectedNodeIds,
153350
- summary: diff.summary,
153351
- changedFiles: diff.changedFiles,
153352
- isStale: false,
153353
- diffAnalysisId: diff.id
153354
- });
153355
- } catch (error) {
153356
- if (error instanceof Error && error.message.includes("Run a full analysis first")) {
153357
- next(createAppError(error.message, 400));
153358
- return;
153359
- }
153360
- next(error);
153361
- }
153362
- }
153363
- );
153364
- router3.get(
153365
- "/:id/diff-check",
153366
- async (req, res, next) => {
153367
- try {
153368
- const id = req.params.id;
153369
- const repo = resolveProjectForRequest(id);
153370
- const diff = readDiff(repo.path);
153371
- if (!diff) {
153372
- res.json(null);
153373
- return;
153374
- }
153375
- const latest = readLatest(repo.path);
153376
- const isStale = latest ? latest.analysis.id !== diff.baseAnalysisId : false;
153377
- res.json({
153378
- resolvedViolations: diff.resolvedViolations,
153379
- newViolations: diff.newViolations,
153380
- affectedNodeIds: diff.affectedNodeIds,
153381
- summary: diff.summary,
153382
- changedFiles: diff.changedFiles,
153383
- isStale,
153384
- diffAnalysisId: diff.id
153385
- });
153386
- } catch (error) {
153387
- next(error);
153388
- }
153389
- }
153390
- );
153391
- router3.get(
153392
- "/:id/file-content",
153393
- async (req, res, next) => {
153394
- try {
153395
- const id = req.params.id;
153396
- const filePath = req.query.path;
153397
- const ref = req.query.ref;
153398
- if (!filePath) throw createAppError('Missing "path" query parameter', 400);
153399
- const repo = resolveProjectForRequest(id);
153400
- const resolved = path15.resolve(repo.path, filePath);
153401
- if (!resolved.startsWith(path15.resolve(repo.path) + path15.sep) && resolved !== path15.resolve(repo.path)) {
153402
- throw createAppError("Path traversal not allowed", 403);
153403
- }
153404
- let content;
153405
- if (ref === "working-tree") {
153426
+ try {
153427
+ content = await git.show([`HEAD:${filePath}`]);
153428
+ } catch {
153406
153429
  if (!fs11.existsSync(resolved)) throw createAppError("File not found", 404);
153407
153430
  const stat = fs11.statSync(resolved);
153408
153431
  if (!stat.isFile()) throw createAppError("Path is not a file", 400);
153409
153432
  content = fs11.readFileSync(resolved, "utf-8");
153410
- } else {
153411
- const git = await getGit(repo.path);
153412
- try {
153413
- content = await git.show([`HEAD:${filePath}`]);
153414
- } catch {
153415
- if (!fs11.existsSync(resolved)) throw createAppError("File not found", 404);
153416
- const stat = fs11.statSync(resolved);
153417
- if (!stat.isFile()) throw createAppError("Path is not a file", 400);
153418
- content = fs11.readFileSync(resolved, "utf-8");
153419
- }
153420
- }
153421
- const ext2 = path15.extname(resolved).slice(1).toLowerCase();
153422
- const langMap = {
153423
- ts: "typescript",
153424
- tsx: "typescript",
153425
- js: "javascript",
153426
- jsx: "javascript",
153427
- json: "json",
153428
- md: "markdown",
153429
- css: "css",
153430
- html: "html",
153431
- yaml: "yaml",
153432
- yml: "yaml",
153433
- sql: "sql",
153434
- sh: "shell",
153435
- py: "python",
153436
- go: "go",
153437
- rs: "rust",
153438
- java: "java",
153439
- rb: "ruby",
153440
- php: "php",
153441
- c: "c",
153442
- cpp: "cpp",
153443
- h: "c",
153444
- hpp: "cpp"
153445
- };
153446
- const language = langMap[ext2] || "text";
153447
- res.json({ content, language });
153448
- } catch (error) {
153449
- next(error);
153433
+ }
153450
153434
  }
153435
+ const ext2 = path15.extname(resolved).slice(1).toLowerCase();
153436
+ const langMap = {
153437
+ ts: "typescript",
153438
+ tsx: "typescript",
153439
+ js: "javascript",
153440
+ jsx: "javascript",
153441
+ json: "json",
153442
+ md: "markdown",
153443
+ css: "css",
153444
+ html: "html",
153445
+ yaml: "yaml",
153446
+ yml: "yaml",
153447
+ sql: "sql",
153448
+ sh: "shell",
153449
+ py: "python",
153450
+ go: "go",
153451
+ rs: "rust",
153452
+ java: "java",
153453
+ rb: "ruby",
153454
+ php: "php",
153455
+ c: "c",
153456
+ cpp: "cpp",
153457
+ h: "c",
153458
+ hpp: "cpp"
153459
+ };
153460
+ const language = langMap[ext2] || "text";
153461
+ res.json({ content, language });
153462
+ } catch (error) {
153463
+ next(error);
153451
153464
  }
153452
- );
153453
- async function findAnalysisFilename(repoPath, analysisId) {
153454
- for (const name of listAnalyses(repoPath).reverse()) {
153455
- const snap = readAnalysis(repoPath, name);
153456
- if (snap?.id === analysisId) return name;
153465
+ });
153466
+ router4.get("/:id/changes", async (req, res, next) => {
153467
+ try {
153468
+ const id = req.params.id;
153469
+ const repo = resolveProjectForRequest(id);
153470
+ const git = await getGit(repo.path);
153471
+ const statusResult = await git.status();
153472
+ const changedFiles = [];
153473
+ for (const f2 of statusResult.not_added) changedFiles.push({ path: f2, status: "new" });
153474
+ for (const f2 of statusResult.created) changedFiles.push({ path: f2, status: "new" });
153475
+ for (const f2 of statusResult.modified) changedFiles.push({ path: f2, status: "modified" });
153476
+ for (const f2 of statusResult.staged) {
153477
+ if (!changedFiles.some((cf) => cf.path === f2)) changedFiles.push({ path: f2, status: "modified" });
153478
+ }
153479
+ for (const f2 of statusResult.deleted) changedFiles.push({ path: f2, status: "deleted" });
153480
+ const latest = readLatest(repo.path);
153481
+ const affectedServices = [];
153482
+ if (latest) {
153483
+ for (const svc of latest.graph.services) {
153484
+ const svcRoot = svc.rootPath;
153485
+ const isAffected = changedFiles.some(
153486
+ (cf) => cf.path.startsWith(svcRoot + "/") || cf.path === svcRoot
153487
+ );
153488
+ if (isAffected) affectedServices.push(svc.id);
153489
+ }
153490
+ }
153491
+ res.json({ changedFiles, affectedServices });
153492
+ } catch (error) {
153493
+ next(error);
153457
153494
  }
153458
- return null;
153459
- }
153460
- var analysis_default = router3;
153495
+ });
153496
+ var files_default = router4;
153461
153497
 
153462
153498
  // apps/server/src/routes/violations.ts
153463
- var import_express4 = __toESM(require_express2(), 1);
153499
+ var import_express5 = __toESM(require_express2(), 1);
153464
153500
  init_analysis_store();
153465
- var router4 = (0, import_express4.Router)();
153466
- var SEVERITY_ORDER2 = {
153467
- critical: 0,
153468
- high: 1,
153469
- medium: 2,
153470
- low: 3,
153471
- info: 4
153472
- };
153473
- router4.get(
153501
+ var router5 = (0, import_express5.Router)();
153502
+ router5.get(
153474
153503
  "/:id/violations",
153475
153504
  async (req, res, next) => {
153476
153505
  try {
153477
153506
  const id = req.params.id;
153478
153507
  const repo = resolveProjectForRequest(id);
153479
- const analysisIdParam = req.query.analysisId;
153480
- const fileParam = req.query.file;
153481
- const statusParam = req.query.status;
153482
153508
  const limitParam = parseInt(req.query.limit) || 0;
153483
153509
  const offsetParam = parseInt(req.query.offset) || 0;
153484
- let violations;
153485
- if (analysisIdParam) {
153486
- const latest = readLatest(repo.path);
153487
- if (latest && latest.analysis.id === analysisIdParam) {
153488
- violations = latest.violations;
153489
- } else {
153490
- res.json(limitParam > 0 ? { violations: [], total: 0 } : []);
153491
- return;
153492
- }
153493
- } else {
153494
- const latest = readLatest(repo.path);
153495
- if (!latest) {
153496
- res.json(limitParam > 0 ? { violations: [], total: 0 } : []);
153497
- return;
153498
- }
153499
- violations = latest.violations;
153500
- }
153501
- let filtered;
153502
- if (statusParam === "resolved") {
153503
- filtered = violations.filter((v) => v.status === "resolved");
153504
- } else if (statusParam === "all") {
153505
- filtered = violations;
153506
- } else {
153507
- const active = ["new", "unchanged"];
153508
- filtered = violations.filter((v) => active.includes(v.status));
153509
- }
153510
- if (fileParam) {
153511
- const absPath = fileParam.startsWith("/") ? fileParam : `${repo.path}/${fileParam}`;
153512
- filtered = filtered.filter(
153513
- (v) => v.type === "code" && (v.filePath === absPath || v.filePath === fileParam)
153514
- );
153515
- }
153516
- filtered.sort((a, b) => {
153517
- const sa = SEVERITY_ORDER2[a.severity] ?? 5;
153518
- const sb = SEVERITY_ORDER2[b.severity] ?? 5;
153519
- if (sa !== sb) return sa - sb;
153520
- return b.createdAt.localeCompare(a.createdAt);
153510
+ const statusParam = req.query.status;
153511
+ const status = statusParam === "resolved" ? "resolved" : statusParam === "all" ? "all" : "active";
153512
+ const { violations, total } = listViolations(repo.path, {
153513
+ analysisId: req.query.analysisId,
153514
+ filePath: req.query.file,
153515
+ status,
153516
+ limit: limitParam,
153517
+ offset: offsetParam
153521
153518
  });
153522
- const total = filtered.length;
153523
- const paged = limitParam > 0 ? filtered.slice(offsetParam, offsetParam + limitParam) : filtered;
153524
153519
  if (limitParam > 0 || offsetParam > 0) {
153525
- res.json({ violations: paged, total });
153520
+ res.json({ violations, total });
153526
153521
  } else {
153527
- res.json(paged);
153522
+ res.json(violations);
153528
153523
  }
153529
153524
  } catch (error) {
153530
153525
  next(error);
153531
153526
  }
153532
153527
  }
153533
153528
  );
153534
- router4.get(
153529
+ router5.get(
153535
153530
  "/:id/violations/summary",
153536
153531
  async (req, res, next) => {
153537
153532
  try {
@@ -153587,13 +153582,13 @@ async function findAnalysisFilename2(repoPath, analysisId) {
153587
153582
  }
153588
153583
  return null;
153589
153584
  }
153590
- var violations_default = router4;
153585
+ var violations_default = router5;
153591
153586
 
153592
153587
  // apps/server/src/routes/databases.ts
153593
- var import_express5 = __toESM(require_express2(), 1);
153588
+ var import_express6 = __toESM(require_express2(), 1);
153594
153589
  init_analysis_store();
153595
- var router5 = (0, import_express5.Router)();
153596
- router5.get(
153590
+ var router6 = (0, import_express6.Router)();
153591
+ router6.get(
153597
153592
  "/:id/databases",
153598
153593
  async (req, res, next) => {
153599
153594
  try {
@@ -153619,7 +153614,7 @@ router5.get(
153619
153614
  }
153620
153615
  }
153621
153616
  );
153622
- router5.get(
153617
+ router6.get(
153623
153618
  "/:id/databases/:dbId/schema",
153624
153619
  async (req, res, next) => {
153625
153620
  try {
@@ -153643,21 +153638,21 @@ router5.get(
153643
153638
  }
153644
153639
  }
153645
153640
  );
153646
- var databases_default = router5;
153641
+ var databases_default = router6;
153647
153642
 
153648
153643
  // apps/server/src/routes/rules.ts
153649
- var import_express6 = __toESM(require_express2(), 1);
153650
- var router6 = (0, import_express6.Router)();
153651
- router6.get("/", async (_req, res) => {
153644
+ var import_express7 = __toESM(require_express2(), 1);
153645
+ var router7 = (0, import_express7.Router)();
153646
+ router7.get("/", async (_req, res) => {
153652
153647
  res.json(await getRules());
153653
153648
  });
153654
- var rules_default = router6;
153649
+ var rules_default = router7;
153655
153650
 
153656
153651
  // apps/server/src/routes/flows.ts
153657
- var import_express7 = __toESM(require_express2(), 1);
153652
+ var import_express8 = __toESM(require_express2(), 1);
153658
153653
  init_analysis_store();
153659
- var router7 = (0, import_express7.Router)();
153660
- router7.get(
153654
+ var router8 = (0, import_express8.Router)();
153655
+ router8.get(
153661
153656
  "/:id/flows",
153662
153657
  async (req, res, next) => {
153663
153658
  try {
@@ -153674,7 +153669,7 @@ router7.get(
153674
153669
  }
153675
153670
  }
153676
153671
  );
153677
- router7.get(
153672
+ router8.get(
153678
153673
  "/:id/flows/:flowId",
153679
153674
  async (req, res, next) => {
153680
153675
  try {
@@ -153689,7 +153684,7 @@ router7.get(
153689
153684
  }
153690
153685
  }
153691
153686
  );
153692
- router7.post(
153687
+ router8.post(
153693
153688
  "/:id/flows/:flowId/enrich",
153694
153689
  async (req, res, next) => {
153695
153690
  try {
@@ -153706,10 +153701,10 @@ router7.post(
153706
153701
  }
153707
153702
  }
153708
153703
  );
153709
- var flows_default = router7;
153704
+ var flows_default = router8;
153710
153705
 
153711
153706
  // apps/server/src/routes/analytics.ts
153712
- var import_express8 = __toESM(require_express2(), 1);
153707
+ var import_express9 = __toESM(require_express2(), 1);
153713
153708
 
153714
153709
  // apps/server/src/services/analytics.service.ts
153715
153710
  init_analysis_store();
@@ -153865,8 +153860,8 @@ function loadActiveViolations(repoPath, branch, specificAnalysisId) {
153865
153860
  }
153866
153861
 
153867
153862
  // apps/server/src/routes/analytics.ts
153868
- var router8 = (0, import_express8.Router)();
153869
- router8.get(
153863
+ var router9 = (0, import_express9.Router)();
153864
+ router9.get(
153870
153865
  "/:id/analytics/trend",
153871
153866
  async (req, res, next) => {
153872
153867
  try {
@@ -153880,7 +153875,7 @@ router8.get(
153880
153875
  }
153881
153876
  }
153882
153877
  );
153883
- router8.get(
153878
+ router9.get(
153884
153879
  "/:id/analytics/breakdown",
153885
153880
  async (req, res, next) => {
153886
153881
  try {
@@ -153894,7 +153889,7 @@ router8.get(
153894
153889
  }
153895
153890
  }
153896
153891
  );
153897
- router8.get(
153892
+ router9.get(
153898
153893
  "/:id/analytics/top-offenders",
153899
153894
  async (req, res, next) => {
153900
153895
  try {
@@ -153908,7 +153903,7 @@ router8.get(
153908
153903
  }
153909
153904
  }
153910
153905
  );
153911
- router8.get(
153906
+ router9.get(
153912
153907
  "/:id/analytics/resolution",
153913
153908
  async (req, res, next) => {
153914
153909
  try {
@@ -153921,7 +153916,7 @@ router8.get(
153921
153916
  }
153922
153917
  }
153923
153918
  );
153924
- var analytics_default = router8;
153919
+ var analytics_default = router9;
153925
153920
 
153926
153921
  // apps/server/src/services/watcher.service.ts
153927
153922
  init_logger();
@@ -153958,14 +153953,15 @@ async function main() {
153958
153953
  log.info("[Storage] Legacy Postgres data wiped. Re-analyze to repopulate.");
153959
153954
  }
153960
153955
  log.info(`[LLM] Provider: claude-code, model: ${config.claudeCodeModel || "default"}`);
153961
- const app = (0, import_express9.default)();
153956
+ const app = (0, import_express10.default)();
153962
153957
  const httpServer = createServer(app);
153963
153958
  setupSocket(httpServer);
153964
153959
  app.use((0, import_cors.default)());
153965
- app.use(import_express9.default.json());
153960
+ app.use(import_express10.default.json());
153966
153961
  app.use("/api/repos", repos_default);
153967
- app.use("/api/repos", analyze_default);
153968
- app.use("/api/repos", projectResolver, analysis_default);
153962
+ app.use("/api/repos", projectResolver, analyses_default);
153963
+ app.use("/api/repos", projectResolver, graph_default);
153964
+ app.use("/api/repos", projectResolver, files_default);
153969
153965
  app.use("/api/repos", projectResolver, violations_default);
153970
153966
  app.use("/api/repos", projectResolver, databases_default);
153971
153967
  app.use("/api/repos", projectResolver, flows_default);
@@ -153977,7 +153973,7 @@ async function main() {
153977
153973
  app.use(errorHandler);
153978
153974
  const staticDir = path16.join(__dirname3, "public");
153979
153975
  if (fs12.existsSync(staticDir)) {
153980
- app.use(import_express9.default.static(staticDir));
153976
+ app.use(import_express10.default.static(staticDir));
153981
153977
  app.get("*", (_req, res) => {
153982
153978
  res.sendFile(path16.join(staticDir, "index.html"));
153983
153979
  });