truecourse 0.5.4 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +22 -0
  2. package/cli.mjs +357 -54
  3. package/package.json +1 -1
  4. package/server.mjs +376 -62
package/server.mjs CHANGED
@@ -23239,6 +23239,14 @@ var init_env = __esm({
23239
23239
  });
23240
23240
 
23241
23241
  // apps/server/src/config/index.ts
23242
+ function parsePositiveInt(envVar, raw, defaultValue) {
23243
+ if (raw === void 0 || raw === "") return defaultValue;
23244
+ const parsed = Number(raw);
23245
+ if (!Number.isInteger(parsed) || parsed < 1) {
23246
+ throw new Error(`${envVar} must be a positive integer, got "${raw}"`);
23247
+ }
23248
+ return parsed;
23249
+ }
23242
23250
  var config;
23243
23251
  var init_config = __esm({
23244
23252
  "apps/server/src/config/index.ts"() {
@@ -23251,7 +23259,12 @@ var init_config = __esm({
23251
23259
  claudeCodeBinary: process.env.CLAUDE_CODE_BINARY || "claude",
23252
23260
  claudeCodeModel: process.env.CLAUDE_CODE_MODEL || "",
23253
23261
  claudeCodeTimeoutMs: parseInt(process.env.CLAUDE_CODE_TIMEOUT_MS || "120000", 10),
23254
- claudeCodeMaxRetries: parseInt(process.env.CLAUDE_CODE_MAX_RETRIES || "2", 10)
23262
+ claudeCodeMaxRetries: parseInt(process.env.CLAUDE_CODE_MAX_RETRIES || "2", 10),
23263
+ claudeCodeMaxConcurrency: parsePositiveInt(
23264
+ "CLAUDE_CODE_MAX_CONCURRENCY",
23265
+ process.env.CLAUDE_CODE_MAX_CONCURRENCY,
23266
+ 10
23267
+ )
23255
23268
  };
23256
23269
  }
23257
23270
  });
@@ -124048,6 +124061,172 @@ var init_dist2 = __esm({
124048
124061
  }
124049
124062
  });
124050
124063
 
124064
+ // node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
124065
+ var Node, Queue;
124066
+ var init_yocto_queue = __esm({
124067
+ "node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js"() {
124068
+ Node = class {
124069
+ value;
124070
+ next;
124071
+ constructor(value) {
124072
+ this.value = value;
124073
+ }
124074
+ };
124075
+ Queue = class {
124076
+ #head;
124077
+ #tail;
124078
+ #size;
124079
+ constructor() {
124080
+ this.clear();
124081
+ }
124082
+ enqueue(value) {
124083
+ const node2 = new Node(value);
124084
+ if (this.#head) {
124085
+ this.#tail.next = node2;
124086
+ this.#tail = node2;
124087
+ } else {
124088
+ this.#head = node2;
124089
+ this.#tail = node2;
124090
+ }
124091
+ this.#size++;
124092
+ }
124093
+ dequeue() {
124094
+ const current = this.#head;
124095
+ if (!current) {
124096
+ return;
124097
+ }
124098
+ this.#head = this.#head.next;
124099
+ this.#size--;
124100
+ if (!this.#head) {
124101
+ this.#tail = void 0;
124102
+ }
124103
+ return current.value;
124104
+ }
124105
+ peek() {
124106
+ if (!this.#head) {
124107
+ return;
124108
+ }
124109
+ return this.#head.value;
124110
+ }
124111
+ clear() {
124112
+ this.#head = void 0;
124113
+ this.#tail = void 0;
124114
+ this.#size = 0;
124115
+ }
124116
+ get size() {
124117
+ return this.#size;
124118
+ }
124119
+ *[Symbol.iterator]() {
124120
+ let current = this.#head;
124121
+ while (current) {
124122
+ yield current.value;
124123
+ current = current.next;
124124
+ }
124125
+ }
124126
+ *drain() {
124127
+ while (this.#head) {
124128
+ yield this.dequeue();
124129
+ }
124130
+ }
124131
+ };
124132
+ }
124133
+ });
124134
+
124135
+ // node_modules/.pnpm/p-limit@7.3.0/node_modules/p-limit/index.js
124136
+ function pLimit(concurrency) {
124137
+ let rejectOnClear = false;
124138
+ if (typeof concurrency === "object") {
124139
+ ({ concurrency, rejectOnClear = false } = concurrency);
124140
+ }
124141
+ validateConcurrency(concurrency);
124142
+ if (typeof rejectOnClear !== "boolean") {
124143
+ throw new TypeError("Expected `rejectOnClear` to be a boolean");
124144
+ }
124145
+ const queue = new Queue();
124146
+ let activeCount = 0;
124147
+ const resumeNext = () => {
124148
+ if (activeCount < concurrency && queue.size > 0) {
124149
+ activeCount++;
124150
+ queue.dequeue().run();
124151
+ }
124152
+ };
124153
+ const next = () => {
124154
+ activeCount--;
124155
+ resumeNext();
124156
+ };
124157
+ const run = async (function_, resolve7, arguments_) => {
124158
+ const result = (async () => function_(...arguments_))();
124159
+ resolve7(result);
124160
+ try {
124161
+ await result;
124162
+ } catch {
124163
+ }
124164
+ next();
124165
+ };
124166
+ const enqueue = (function_, resolve7, reject, arguments_) => {
124167
+ const queueItem = { reject };
124168
+ new Promise((internalResolve) => {
124169
+ queueItem.run = internalResolve;
124170
+ queue.enqueue(queueItem);
124171
+ }).then(run.bind(void 0, function_, resolve7, arguments_));
124172
+ if (activeCount < concurrency) {
124173
+ resumeNext();
124174
+ }
124175
+ };
124176
+ const generator = (function_, ...arguments_) => new Promise((resolve7, reject) => {
124177
+ enqueue(function_, resolve7, reject, arguments_);
124178
+ });
124179
+ Object.defineProperties(generator, {
124180
+ activeCount: {
124181
+ get: () => activeCount
124182
+ },
124183
+ pendingCount: {
124184
+ get: () => queue.size
124185
+ },
124186
+ clearQueue: {
124187
+ value() {
124188
+ if (!rejectOnClear) {
124189
+ queue.clear();
124190
+ return;
124191
+ }
124192
+ const abortError = AbortSignal.abort().reason;
124193
+ while (queue.size > 0) {
124194
+ queue.dequeue().reject(abortError);
124195
+ }
124196
+ }
124197
+ },
124198
+ concurrency: {
124199
+ get: () => concurrency,
124200
+ set(newConcurrency) {
124201
+ validateConcurrency(newConcurrency);
124202
+ concurrency = newConcurrency;
124203
+ queueMicrotask(() => {
124204
+ while (activeCount < concurrency && queue.size > 0) {
124205
+ resumeNext();
124206
+ }
124207
+ });
124208
+ }
124209
+ },
124210
+ map: {
124211
+ async value(iterable, function_) {
124212
+ const promises = Array.from(iterable, (value, index) => this(function_, value, index));
124213
+ return Promise.all(promises);
124214
+ }
124215
+ }
124216
+ });
124217
+ return generator;
124218
+ }
124219
+ function validateConcurrency(concurrency) {
124220
+ if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
124221
+ throw new TypeError("Expected `concurrency` to be a number from 1 and up");
124222
+ }
124223
+ }
124224
+ var init_p_limit = __esm({
124225
+ "node_modules/.pnpm/p-limit@7.3.0/node_modules/p-limit/index.js"() {
124226
+ init_yocto_queue();
124227
+ }
124228
+ });
124229
+
124051
124230
  // apps/server/src/services/analysis-registry.ts
124052
124231
  function registerAnalysis(repoId, analysisId) {
124053
124232
  const existing = activeAnalyses2.get(repoId);
@@ -126381,6 +126560,7 @@ var init_cli_provider = __esm({
126381
126560
  "apps/server/src/services/llm/cli-provider.ts"() {
126382
126561
  "use strict";
126383
126562
  init_logger();
126563
+ init_p_limit();
126384
126564
  init_analysis_registry();
126385
126565
  init_esm4();
126386
126566
  init_config();
@@ -126388,6 +126568,7 @@ var init_cli_provider = __esm({
126388
126568
  init_schemas();
126389
126569
  BaseCLIProvider = class {
126390
126570
  maxRetries = config.claudeCodeMaxRetries ?? 2;
126571
+ limit = pLimit(config.claudeCodeMaxConcurrency);
126391
126572
  debugDir = null;
126392
126573
  callCounter = 0;
126393
126574
  _analysisId = null;
@@ -126572,35 +126753,42 @@ var init_cli_provider = __esm({
126572
126753
  }
126573
126754
  /** Spawn CLI with retry on parse/validation failure. */
126574
126755
  async spawnAndParse(prompt, schema, opts) {
126575
- const jsonSchemaStr = this.toJsonSchema(schema);
126576
- const label = opts?.label ?? "call";
126577
- let lastError = null;
126578
- for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
126579
- try {
126580
- const raw = await this.spawnCLI(prompt, jsonSchemaStr, opts);
126581
- this.dumpDebug(label, prompt, raw, jsonSchemaStr);
126582
- return this.parseAndValidate(raw, schema);
126583
- } catch (err) {
126584
- lastError = err;
126585
- if (this._abortSignal?.aborted) throw lastError;
126586
- if (attempt < this.maxRetries) {
126587
- log.warn(`[CLI] Attempt ${attempt + 1} failed, retrying... (${lastError.message})`);
126756
+ return this.limit(async () => {
126757
+ if (this._abortSignal?.aborted) {
126758
+ throw this._abortSignal.reason ?? new DOMException("Analysis cancelled", "AbortError");
126759
+ }
126760
+ opts?.onStart?.();
126761
+ const jsonSchemaStr = this.toJsonSchema(schema);
126762
+ const label = opts?.label ?? "call";
126763
+ let lastError = null;
126764
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
126765
+ try {
126766
+ const raw = await this.spawnCLI(prompt, jsonSchemaStr, opts);
126767
+ this.dumpDebug(label, prompt, raw, jsonSchemaStr);
126768
+ return this.parseAndValidate(raw, schema);
126769
+ } catch (err) {
126770
+ lastError = err;
126771
+ if (this._abortSignal?.aborted) throw lastError;
126772
+ if (attempt < this.maxRetries) {
126773
+ log.warn(`[CLI] Attempt ${attempt + 1} failed, retrying... (${lastError.message})`);
126774
+ }
126588
126775
  }
126589
126776
  }
126590
- }
126591
- throw lastError;
126777
+ throw lastError;
126778
+ });
126592
126779
  }
126593
126780
  // ---------------------------------------------------------------------------
126594
126781
  // LLMProvider implementation
126595
126782
  // ---------------------------------------------------------------------------
126596
- async generateServiceViolations(context) {
126783
+ async generateServiceViolations(context, opts) {
126597
126784
  const { vars, idMap } = buildServiceTemplateVars(context);
126598
126785
  const prompt = getPrompt("violations-service", vars);
126599
126786
  log.info("[CLI] Service violations call starting...");
126600
126787
  const t0 = Date.now();
126601
126788
  const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, ServiceViolationOutputSchema, {
126602
126789
  extraArgs: ["--tools", ""],
126603
- label: "service"
126790
+ label: "service",
126791
+ onStart: opts?.onStart
126604
126792
  });
126605
126793
  const dur = Date.now() - t0;
126606
126794
  log.info(`[CLI] Service violations call done in ${dur}ms \u2014 ${object.violations.length} violations`);
@@ -126623,14 +126811,15 @@ var init_cli_provider = __esm({
126623
126811
  }))
126624
126812
  };
126625
126813
  }
126626
- async generateDatabaseViolations(context) {
126814
+ async generateDatabaseViolations(context, opts) {
126627
126815
  const { vars, idMap } = buildDatabaseTemplateVars(context);
126628
126816
  const prompt = getPrompt("violations-database", vars);
126629
126817
  log.info("[CLI] Database violations call starting...");
126630
126818
  const t0 = Date.now();
126631
126819
  const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, DatabaseViolationOutputSchema, {
126632
126820
  extraArgs: ["--tools", ""],
126633
- label: "database"
126821
+ label: "database",
126822
+ onStart: opts?.onStart
126634
126823
  });
126635
126824
  const dur = Date.now() - t0;
126636
126825
  log.info(`[CLI] Database violations call done in ${dur}ms \u2014 ${object.violations.length} violations`);
@@ -126650,7 +126839,7 @@ var init_cli_provider = __esm({
126650
126839
  }))
126651
126840
  };
126652
126841
  }
126653
- async generateModuleViolations(context) {
126842
+ async generateModuleViolations(context, opts) {
126654
126843
  const { vars, idMap } = buildModuleTemplateVars(context);
126655
126844
  const prompt = getPrompt("violations-module", vars);
126656
126845
  const moduleIdToServiceId = new Map(
@@ -126662,7 +126851,8 @@ var init_cli_provider = __esm({
126662
126851
  const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, ModuleViolationOutputSchema, {
126663
126852
  extraArgs: ["--tools", ""],
126664
126853
  label: "module",
126665
- timeoutMs: moduleTimeoutMs
126854
+ timeoutMs: moduleTimeoutMs,
126855
+ onStart: opts?.onStart
126666
126856
  });
126667
126857
  const dur = Date.now() - t0;
126668
126858
  log.info(`[CLI] Module violations call done in ${dur}ms \u2014 ${object.violations.length} violations`);
@@ -126689,15 +126879,23 @@ var init_cli_provider = __esm({
126689
126879
  }
126690
126880
  async generateAllViolations(contexts) {
126691
126881
  const onStepComplete = contexts.onStepComplete;
126882
+ const onCallStart = contexts.onCallStart;
126883
+ const onCallDone = contexts.onCallDone;
126692
126884
  const promises = [];
126693
126885
  if (contexts.service) {
126694
- promises.push(["service", this.generateServiceViolations(contexts.service)]);
126886
+ promises.push(["service", this.generateServiceViolations(contexts.service, {
126887
+ onStart: () => onCallStart?.("service")
126888
+ })]);
126695
126889
  }
126696
126890
  if (contexts.database) {
126697
- promises.push(["database", this.generateDatabaseViolations(contexts.database)]);
126891
+ promises.push(["database", this.generateDatabaseViolations(contexts.database, {
126892
+ onStart: () => onCallStart?.("database")
126893
+ })]);
126698
126894
  }
126699
126895
  if (contexts.module) {
126700
- promises.push(["module", this.generateModuleViolations(contexts.module)]);
126896
+ promises.push(["module", this.generateModuleViolations(contexts.module, {
126897
+ onStart: () => onCallStart?.("module")
126898
+ })]);
126701
126899
  }
126702
126900
  const stepLabels = {
126703
126901
  service: "Service architecture checks done",
@@ -126705,10 +126903,17 @@ var init_cli_provider = __esm({
126705
126903
  module: "Module & function checks done"
126706
126904
  };
126707
126905
  const settled = await Promise.allSettled(promises.map(
126708
- ([key, p]) => p.then((v) => {
126709
- onStepComplete?.(stepLabels[key] || `${key} done`);
126710
- return v;
126711
- })
126906
+ ([key, p]) => p.then(
126907
+ (v) => {
126908
+ onStepComplete?.(stepLabels[key] || `${key} done`);
126909
+ onCallDone?.(key, true);
126910
+ return v;
126911
+ },
126912
+ (err) => {
126913
+ onCallDone?.(key, false);
126914
+ throw err;
126915
+ }
126916
+ )
126712
126917
  ));
126713
126918
  const result = {};
126714
126919
  for (let i = 0; i < promises.length; i++) {
@@ -126723,6 +126928,7 @@ var init_cli_provider = __esm({
126723
126928
  return result;
126724
126929
  }
126725
126930
  async generateAllViolationsWithLifecycle(contexts, onStepComplete) {
126931
+ const onCallStart = contexts.onCallStart;
126726
126932
  const allResolved = [];
126727
126933
  const allNew = [];
126728
126934
  let serviceDescriptions = [];
@@ -126739,7 +126945,8 @@ var init_cli_provider = __esm({
126739
126945
  const t0 = Date.now();
126740
126946
  const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, LifecycleServiceOutputSchema, {
126741
126947
  extraArgs: ["--tools", ""],
126742
- label: "service-lifecycle"
126948
+ label: "service-lifecycle",
126949
+ onStart: () => onCallStart?.("service")
126743
126950
  });
126744
126951
  const dur = Date.now() - t0;
126745
126952
  log.info(`[CLI] Lifecycle service call done in ${dur}ms \u2014 resolved: ${object.resolvedViolationIds.length}, new: ${object.newViolations.length}`);
@@ -126747,7 +126954,9 @@ var init_cli_provider = __esm({
126747
126954
  return object;
126748
126955
  })()]);
126749
126956
  } else {
126750
- promises.push(["service-normal", this.generateServiceViolations(ctx)]);
126957
+ promises.push(["service-normal", this.generateServiceViolations(ctx, {
126958
+ onStart: () => onCallStart?.("service")
126959
+ })]);
126751
126960
  }
126752
126961
  }
126753
126962
  if (contexts.database) {
@@ -126761,7 +126970,8 @@ var init_cli_provider = __esm({
126761
126970
  const t0 = Date.now();
126762
126971
  const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, DiffViolationOutputSchema, {
126763
126972
  extraArgs: ["--tools", ""],
126764
- label: "database-lifecycle"
126973
+ label: "database-lifecycle",
126974
+ onStart: () => onCallStart?.("database")
126765
126975
  });
126766
126976
  const dur = Date.now() - t0;
126767
126977
  log.info(`[CLI] Lifecycle database call done in ${dur}ms \u2014 resolved: ${object.resolvedViolationIds.length}, new: ${object.newViolations.length}`);
@@ -126769,7 +126979,9 @@ var init_cli_provider = __esm({
126769
126979
  return object;
126770
126980
  })()]);
126771
126981
  } else {
126772
- promises.push(["database-normal", this.generateDatabaseViolations(ctx)]);
126982
+ promises.push(["database-normal", this.generateDatabaseViolations(ctx, {
126983
+ onStart: () => onCallStart?.("database")
126984
+ })]);
126773
126985
  }
126774
126986
  }
126775
126987
  if (contexts.module) {
@@ -126786,7 +126998,8 @@ var init_cli_provider = __esm({
126786
126998
  const t0 = Date.now();
126787
126999
  const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, DiffViolationOutputSchema, {
126788
127000
  extraArgs: ["--tools", ""],
126789
- label: "module-lifecycle"
127001
+ label: "module-lifecycle",
127002
+ onStart: () => onCallStart?.("module")
126790
127003
  });
126791
127004
  const dur = Date.now() - t0;
126792
127005
  log.info(`[CLI] Lifecycle module call done in ${dur}ms \u2014 resolved: ${object.resolvedViolationIds.length}, new: ${object.newViolations.length}`);
@@ -126807,7 +127020,9 @@ var init_cli_provider = __esm({
126807
127020
  };
126808
127021
  })()]);
126809
127022
  } else {
126810
- promises.push(["module-normal", this.generateModuleViolations(ctx)]);
127023
+ promises.push(["module-normal", this.generateModuleViolations(ctx, {
127024
+ onStart: () => onCallStart?.("module")
127025
+ })]);
126811
127026
  }
126812
127027
  }
126813
127028
  const stepLabels = {
@@ -126818,11 +127033,20 @@ var init_cli_provider = __esm({
126818
127033
  module: "Module checks done",
126819
127034
  "module-normal": "Module checks done"
126820
127035
  };
127036
+ const baseKey = (key) => key.replace("-normal", "");
127037
+ const onCallDone = contexts.onCallDone;
126821
127038
  const settled = await Promise.allSettled(promises.map(
126822
- ([key, p]) => p.then((v) => {
126823
- onStepComplete?.(stepLabels[key] || `${key} done`);
126824
- return v;
126825
- })
127039
+ ([key, p]) => p.then(
127040
+ (v) => {
127041
+ onStepComplete?.(stepLabels[key] || `${key} done`);
127042
+ onCallDone?.(baseKey(key), true);
127043
+ return v;
127044
+ },
127045
+ (err) => {
127046
+ onCallDone?.(baseKey(key), false);
127047
+ throw err;
127048
+ }
127049
+ )
126826
127050
  ));
126827
127051
  for (let i = 0; i < promises.length; i++) {
126828
127052
  const [key] = promises[i];
@@ -126914,7 +127138,7 @@ var init_cli_provider = __esm({
126914
127138
  }
126915
127139
  return { resolvedViolationIds: allResolved, newViolations: allNew, serviceDescriptions };
126916
127140
  }
126917
- async generateCodeViolations(context) {
127141
+ async generateCodeViolations(context, opts) {
126918
127142
  const hasExisting = context.existingViolations && context.existingViolations.length > 0;
126919
127143
  let promptName;
126920
127144
  if (context.tier === "metadata") {
@@ -126935,7 +127159,8 @@ var init_cli_provider = __esm({
126935
127159
  const { data: object2, usage: cliUsage2 } = await this.spawnAndParse(prompt, CodeViolationLifecycleOutputSchema, {
126936
127160
  extraArgs: codeExtraArgs,
126937
127161
  label: "code-lifecycle",
126938
- timeoutMs: codeTimeoutMs
127162
+ timeoutMs: codeTimeoutMs,
127163
+ onStart: opts?.onStart
126939
127164
  });
126940
127165
  const dur2 = Date.now() - t0;
126941
127166
  log.info(`[CLI] Code violations call done in ${dur2}ms \u2014 new: ${object2.newViolations.length}, resolved: ${object2.resolvedViolationIds.length}, unchanged: ${object2.unchangedViolationIds.length}`);
@@ -126958,7 +127183,8 @@ var init_cli_provider = __esm({
126958
127183
  const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, CodeViolationOutputSchema, {
126959
127184
  extraArgs: codeExtraArgs,
126960
127185
  label: "code",
126961
- timeoutMs: codeTimeoutMs
127186
+ timeoutMs: codeTimeoutMs,
127187
+ onStart: opts?.onStart
126962
127188
  });
126963
127189
  const dur = Date.now() - t0;
126964
127190
  log.info(`[CLI] Code violations call done in ${dur}ms \u2014 ${object.violations.length} violations`);
@@ -146488,7 +146714,7 @@ function routeContext(rules, fileAnalyses, fileContents) {
146488
146714
 
146489
146715
  // apps/server/src/services/violation.service.ts
146490
146716
  init_provider();
146491
- async function generateViolations(input, onProgress, externalProvider) {
146717
+ async function generateViolations(input, onProgress, externalProvider, onCallStart, onCallDone) {
146492
146718
  const provider = externalProvider ?? createLLMProvider();
146493
146719
  const archRules = (input.llmRules || []).filter((r) => r.category === "service");
146494
146720
  const dbRules = (input.llmRules || []).filter((r) => r.category === "database");
@@ -146532,7 +146758,9 @@ async function generateViolations(input, onProgress, externalProvider) {
146532
146758
  service: serviceContext,
146533
146759
  database: dbContext,
146534
146760
  module: moduleContext,
146535
- onStepComplete: onProgress
146761
+ onStepComplete: onProgress,
146762
+ onCallStart,
146763
+ onCallDone
146536
146764
  });
146537
146765
  const allViolations = [];
146538
146766
  let serviceDescriptions = [];
@@ -146569,7 +146797,7 @@ async function generateViolations(input, onProgress, externalProvider) {
146569
146797
  }
146570
146798
  return { violations: allViolations, serviceDescriptions };
146571
146799
  }
146572
- async function generateViolationsWithLifecycle(input, onProgress, externalProvider) {
146800
+ async function generateViolationsWithLifecycle(input, onProgress, externalProvider, onCallStart, onCallDone) {
146573
146801
  const provider = externalProvider ?? createLLMProvider();
146574
146802
  const archRules = (input.llmRules || []).filter((r) => r.category === "service");
146575
146803
  const dbRules = (input.llmRules || []).filter((r) => r.category === "database");
@@ -146610,7 +146838,9 @@ async function generateViolationsWithLifecycle(input, onProgress, externalProvid
146610
146838
  const result = await provider.generateAllViolationsWithLifecycle({
146611
146839
  service: serviceContext,
146612
146840
  database: dbContext,
146613
- module: moduleContext
146841
+ module: moduleContext,
146842
+ onCallStart,
146843
+ onCallDone
146614
146844
  }, (step) => {
146615
146845
  onProgress?.(step);
146616
146846
  });
@@ -146867,6 +147097,44 @@ init_logger();
146867
147097
  function throwIfAborted(signal) {
146868
147098
  if (signal?.aborted) throw new DOMException("Analysis cancelled", "AbortError");
146869
147099
  }
147100
+ function formatElapsed(ms) {
147101
+ const totalSec = Math.floor(ms / 1e3);
147102
+ const min = Math.floor(totalSec / 60);
147103
+ const sec = totalSec % 60;
147104
+ return min === 0 ? `${sec}s` : `${min}m ${sec}s`;
147105
+ }
147106
+ function renderLlmDetail(s) {
147107
+ const parts = [];
147108
+ if (s.detCount > 0) parts.push(`${s.detCount} det`);
147109
+ parts.push(`LLM ${s.done}/${s.total}`);
147110
+ if (s.running > 0) parts.push(`${s.running} running`);
147111
+ if (s.elapsedMs >= 1e3) parts.push(formatElapsed(s.elapsedMs));
147112
+ return parts.join(" \xB7 ");
147113
+ }
147114
+ function createLlmTracker(tracker, domain, detCount, total) {
147115
+ let done = 0;
147116
+ let running = 0;
147117
+ const t0 = Date.now();
147118
+ const render = () => tracker?.detail(domain, renderLlmDetail({
147119
+ detCount,
147120
+ total,
147121
+ done,
147122
+ running,
147123
+ elapsedMs: Date.now() - t0
147124
+ }));
147125
+ return {
147126
+ initialDetail: renderLlmDetail({ detCount, total, done: 0, running: 0, elapsedMs: 0 }),
147127
+ onCallStart: () => {
147128
+ running++;
147129
+ render();
147130
+ },
147131
+ onCallDone: (started) => {
147132
+ if (started) running--;
147133
+ done++;
147134
+ render();
147135
+ }
147136
+ };
147137
+ }
146870
147138
  function getDetComparisonKey(v) {
146871
147139
  return `${v.ruleKey}::${v.serviceName}::${v.moduleName || ""}::${v.methodName || ""}::${v.title}`;
146872
147140
  }
@@ -147210,11 +147478,6 @@ async function runViolationPipeline(input) {
147210
147478
  const allNewLlmItems = [];
147211
147479
  const allResolvedLlmIds = [];
147212
147480
  const hasArchLlm = enableLlmRules !== false && !llmSkipped;
147213
- if (hasArchLlm) tracker?.start("architecture", "Running LLM analysis...");
147214
- for (const [domain] of domainCodeBatches) {
147215
- const detCount = violationsByDomain.get(domain) ?? 0;
147216
- tracker?.start(domain, detCount > 0 ? `${detCount} det, running LLM...` : "Running LLM...");
147217
- }
147218
147481
  const previousDetForComparison = previousDetViolations.map((v) => ({
147219
147482
  ruleKey: v.ruleKey,
147220
147483
  serviceName: v.targetServiceName || "",
@@ -147439,14 +147702,43 @@ async function runViolationPipeline(input) {
147439
147702
  existingDatabaseViolations: void 0,
147440
147703
  existingModuleViolations: hasLlmOnlyExistingViolations ? existingModuleViolations : void 0
147441
147704
  };
147705
+ const llmTrackers = /* @__PURE__ */ new Map();
147706
+ for (const [domain, batches] of domainCodeBatches) {
147707
+ const detCount = violationsByDomain.get(domain) ?? 0;
147708
+ const ll = createLlmTracker(tracker, domain, detCount, batches.length);
147709
+ llmTrackers.set(domain, ll);
147710
+ tracker?.start(domain, ll.initialDetail);
147711
+ }
147712
+ if (dbSchemaContext && !llmSkipped && !domainCodeBatches.has("database")) {
147713
+ const detCount = violationsByDomain.get("database") ?? 0;
147714
+ const ll = createLlmTracker(tracker, "database", detCount, 1);
147715
+ llmTrackers.set("database", ll);
147716
+ tracker?.start("database", ll.initialDetail);
147717
+ }
147718
+ if (hasArchLlm) {
147719
+ const detCount = violationsByDomain.get("architecture") ?? 0;
147720
+ const archTotal2 = 1 + (violationModules && violationModules.length > 0 ? 1 : 0);
147721
+ const ll = createLlmTracker(tracker, "architecture", detCount, archTotal2);
147722
+ llmTrackers.set("architecture", ll);
147723
+ tracker?.start("architecture", ll.initialDetail);
147724
+ }
147442
147725
  const domainLlmPromises = [];
147443
147726
  for (const [domain, batches] of domainCodeBatches) {
147444
147727
  domainLlmPromises.push((async () => {
147445
147728
  const detCount = violationsByDomain.get(domain) ?? 0;
147446
147729
  log.info(`[LLM] ${domain}: starting (${batches.length} code batches)`);
147447
147730
  const t0 = Date.now();
147731
+ const ll = llmTrackers.get(domain);
147448
147732
  const codeResults = await Promise.allSettled(
147449
- batches.map((b) => provider.generateCodeViolations(b))
147733
+ batches.map((b) => {
147734
+ let started = false;
147735
+ return provider.generateCodeViolations(b, {
147736
+ onStart: () => {
147737
+ started = true;
147738
+ ll.onCallStart();
147739
+ }
147740
+ }).finally(() => ll.onCallDone(started));
147741
+ })
147450
147742
  );
147451
147743
  const rawViolations = [];
147452
147744
  const resolvedIds = [];
@@ -147471,15 +147763,19 @@ async function runViolationPipeline(input) {
147471
147763
  }
147472
147764
  let dbSchemaViolations = [];
147473
147765
  if (dbSchemaContext && !llmSkipped) {
147474
- if (!domainCodeBatches.has("database")) {
147475
- const detCount = violationsByDomain.get("database") ?? 0;
147476
- tracker?.start("database", detCount > 0 ? `${detCount} det, running LLM...` : "Running LLM...");
147477
- }
147766
+ const schemaLl = domainCodeBatches.has("database") ? void 0 : llmTrackers.get("database");
147478
147767
  domainLlmPromises.push((async () => {
147479
147768
  log.info(`[LLM] database-schema: starting`);
147480
147769
  const t0 = Date.now();
147770
+ let started = false;
147481
147771
  try {
147482
- const dbResult = await provider.generateDatabaseViolations(dbSchemaContext);
147772
+ const dbResult = await provider.generateDatabaseViolations(dbSchemaContext, {
147773
+ onStart: () => {
147774
+ started = true;
147775
+ schemaLl?.onCallStart();
147776
+ }
147777
+ });
147778
+ schemaLl?.onCallDone(started);
147483
147779
  const dur = Date.now() - t0;
147484
147780
  log.info(`[LLM] database-schema: done in ${dur}ms \u2014 ${dbResult.violations.length} violations`);
147485
147781
  for (const v of dbResult.violations) {
@@ -147519,6 +147815,7 @@ async function runViolationPipeline(input) {
147519
147815
  }
147520
147816
  return { domain: "database-schema", violations: [], resolvedIds: [], unchangedIds: [] };
147521
147817
  } catch (err) {
147818
+ schemaLl?.onCallDone(started);
147522
147819
  const dur = Date.now() - t0;
147523
147820
  log.warn(`[LLM] database-schema: failed in ${dur}ms \u2014 ${err instanceof Error ? err.message : String(err)}`);
147524
147821
  if (!domainCodeBatches.has("database")) tracker?.error("database", `Schema LLM failed`);
@@ -147530,11 +147827,22 @@ async function runViolationPipeline(input) {
147530
147827
  onProgress?.({ step: "analyzing", percent: 86, detail: "Analyzing architecture & modules..." });
147531
147828
  const llmRulePromise = (async () => {
147532
147829
  if (enableLlmRules === false || llmSkipped) return;
147830
+ const archLl = llmTrackers.get("architecture");
147831
+ const archStarted = /* @__PURE__ */ new Set();
147832
+ const archOnCallStart = (key) => {
147833
+ archStarted.add(key);
147834
+ archLl?.onCallStart();
147835
+ };
147836
+ const archOnCallDone = (key) => {
147837
+ archLl?.onCallDone(archStarted.has(key));
147838
+ };
147533
147839
  if (hasLlmOnlyExistingViolations) {
147534
147840
  const archResult = await generateViolationsWithLifecycle(
147535
147841
  violationInput,
147536
- (step) => tracker?.detail("architecture", step),
147537
- provider
147842
+ void 0,
147843
+ provider,
147844
+ archOnCallStart,
147845
+ archOnCallDone
147538
147846
  );
147539
147847
  serviceDescriptions = archResult.serviceDescriptions;
147540
147848
  allResolvedLlmIds.push(...archResult.resolvedViolationIds);
@@ -147563,8 +147871,10 @@ async function runViolationPipeline(input) {
147563
147871
  } else {
147564
147872
  const archResult = await generateViolations(
147565
147873
  violationInput,
147566
- (step) => tracker?.detail("architecture", step),
147567
- provider
147874
+ void 0,
147875
+ provider,
147876
+ archOnCallStart,
147877
+ archOnCallDone
147568
147878
  );
147569
147879
  serviceDescriptions = archResult.serviceDescriptions;
147570
147880
  for (const v of archResult.violations) {
@@ -148282,8 +148592,13 @@ function makeDenormalizer(graph) {
148282
148592
  }
148283
148593
 
148284
148594
  // apps/server/src/commands/analyze-in-process.ts
148595
+ init_config();
148596
+ init_logger();
148285
148597
  async function analyzeInProcess(project, options = {}) {
148286
148598
  const startedAt = Date.now();
148599
+ log.info(
148600
+ `[LLM] Provider: claude-code, model: ${config.claudeCodeModel || "default"}, maxConcurrency: ${config.claudeCodeMaxConcurrency}`
148601
+ );
148287
148602
  const core = await analyzeCore(project, { ...options, mode: "full" });
148288
148603
  return persistFullAnalysis(project, core, startedAt);
148289
148604
  }
@@ -154034,7 +154349,6 @@ async function main() {
154034
154349
  if (wipeLegacyPostgresData()) {
154035
154350
  log.info("[Storage] Legacy Postgres data wiped. Re-analyze to repopulate.");
154036
154351
  }
154037
- log.info(`[LLM] Provider: claude-code, model: ${config.claudeCodeModel || "default"}`);
154038
154352
  const app = (0, import_express10.default)();
154039
154353
  const httpServer = createServer(app);
154040
154354
  setupSocket(httpServer);