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/cli.mjs CHANGED
@@ -121036,6 +121036,172 @@ var init_rules_service = __esm({
121036
121036
  }
121037
121037
  });
121038
121038
 
121039
+ // node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
121040
+ var Node, Queue;
121041
+ var init_yocto_queue = __esm({
121042
+ "node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js"() {
121043
+ Node = class {
121044
+ value;
121045
+ next;
121046
+ constructor(value) {
121047
+ this.value = value;
121048
+ }
121049
+ };
121050
+ Queue = class {
121051
+ #head;
121052
+ #tail;
121053
+ #size;
121054
+ constructor() {
121055
+ this.clear();
121056
+ }
121057
+ enqueue(value) {
121058
+ const node = new Node(value);
121059
+ if (this.#head) {
121060
+ this.#tail.next = node;
121061
+ this.#tail = node;
121062
+ } else {
121063
+ this.#head = node;
121064
+ this.#tail = node;
121065
+ }
121066
+ this.#size++;
121067
+ }
121068
+ dequeue() {
121069
+ const current = this.#head;
121070
+ if (!current) {
121071
+ return;
121072
+ }
121073
+ this.#head = this.#head.next;
121074
+ this.#size--;
121075
+ if (!this.#head) {
121076
+ this.#tail = void 0;
121077
+ }
121078
+ return current.value;
121079
+ }
121080
+ peek() {
121081
+ if (!this.#head) {
121082
+ return;
121083
+ }
121084
+ return this.#head.value;
121085
+ }
121086
+ clear() {
121087
+ this.#head = void 0;
121088
+ this.#tail = void 0;
121089
+ this.#size = 0;
121090
+ }
121091
+ get size() {
121092
+ return this.#size;
121093
+ }
121094
+ *[Symbol.iterator]() {
121095
+ let current = this.#head;
121096
+ while (current) {
121097
+ yield current.value;
121098
+ current = current.next;
121099
+ }
121100
+ }
121101
+ *drain() {
121102
+ while (this.#head) {
121103
+ yield this.dequeue();
121104
+ }
121105
+ }
121106
+ };
121107
+ }
121108
+ });
121109
+
121110
+ // node_modules/.pnpm/p-limit@7.3.0/node_modules/p-limit/index.js
121111
+ function pLimit(concurrency) {
121112
+ let rejectOnClear = false;
121113
+ if (typeof concurrency === "object") {
121114
+ ({ concurrency, rejectOnClear = false } = concurrency);
121115
+ }
121116
+ validateConcurrency(concurrency);
121117
+ if (typeof rejectOnClear !== "boolean") {
121118
+ throw new TypeError("Expected `rejectOnClear` to be a boolean");
121119
+ }
121120
+ const queue = new Queue();
121121
+ let activeCount = 0;
121122
+ const resumeNext = () => {
121123
+ if (activeCount < concurrency && queue.size > 0) {
121124
+ activeCount++;
121125
+ queue.dequeue().run();
121126
+ }
121127
+ };
121128
+ const next = () => {
121129
+ activeCount--;
121130
+ resumeNext();
121131
+ };
121132
+ const run = async (function_, resolve8, arguments_) => {
121133
+ const result = (async () => function_(...arguments_))();
121134
+ resolve8(result);
121135
+ try {
121136
+ await result;
121137
+ } catch {
121138
+ }
121139
+ next();
121140
+ };
121141
+ const enqueue = (function_, resolve8, reject, arguments_) => {
121142
+ const queueItem = { reject };
121143
+ new Promise((internalResolve) => {
121144
+ queueItem.run = internalResolve;
121145
+ queue.enqueue(queueItem);
121146
+ }).then(run.bind(void 0, function_, resolve8, arguments_));
121147
+ if (activeCount < concurrency) {
121148
+ resumeNext();
121149
+ }
121150
+ };
121151
+ const generator = (function_, ...arguments_) => new Promise((resolve8, reject) => {
121152
+ enqueue(function_, resolve8, reject, arguments_);
121153
+ });
121154
+ Object.defineProperties(generator, {
121155
+ activeCount: {
121156
+ get: () => activeCount
121157
+ },
121158
+ pendingCount: {
121159
+ get: () => queue.size
121160
+ },
121161
+ clearQueue: {
121162
+ value() {
121163
+ if (!rejectOnClear) {
121164
+ queue.clear();
121165
+ return;
121166
+ }
121167
+ const abortError = AbortSignal.abort().reason;
121168
+ while (queue.size > 0) {
121169
+ queue.dequeue().reject(abortError);
121170
+ }
121171
+ }
121172
+ },
121173
+ concurrency: {
121174
+ get: () => concurrency,
121175
+ set(newConcurrency) {
121176
+ validateConcurrency(newConcurrency);
121177
+ concurrency = newConcurrency;
121178
+ queueMicrotask(() => {
121179
+ while (activeCount < concurrency && queue.size > 0) {
121180
+ resumeNext();
121181
+ }
121182
+ });
121183
+ }
121184
+ },
121185
+ map: {
121186
+ async value(iterable, function_) {
121187
+ const promises = Array.from(iterable, (value, index) => this(function_, value, index));
121188
+ return Promise.all(promises);
121189
+ }
121190
+ }
121191
+ });
121192
+ return generator;
121193
+ }
121194
+ function validateConcurrency(concurrency) {
121195
+ if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
121196
+ throw new TypeError("Expected `concurrency` to be a number from 1 and up");
121197
+ }
121198
+ }
121199
+ var init_p_limit = __esm({
121200
+ "node_modules/.pnpm/p-limit@7.3.0/node_modules/p-limit/index.js"() {
121201
+ init_yocto_queue();
121202
+ }
121203
+ });
121204
+
121039
121205
  // apps/server/dist/services/analysis-registry.js
121040
121206
  function registerChildProcess(repoId, child) {
121041
121207
  const entry = activeAnalyses.get(repoId);
@@ -123007,6 +123173,15 @@ var init_env = __esm({
123007
123173
  });
123008
123174
 
123009
123175
  // apps/server/dist/config/index.js
123176
+ function parsePositiveInt(envVar, raw, defaultValue) {
123177
+ if (raw === void 0 || raw === "")
123178
+ return defaultValue;
123179
+ const parsed = Number(raw);
123180
+ if (!Number.isInteger(parsed) || parsed < 1) {
123181
+ throw new Error(`${envVar} must be a positive integer, got "${raw}"`);
123182
+ }
123183
+ return parsed;
123184
+ }
123010
123185
  var config;
123011
123186
  var init_config2 = __esm({
123012
123187
  "apps/server/dist/config/index.js"() {
@@ -123019,7 +123194,8 @@ var init_config2 = __esm({
123019
123194
  claudeCodeBinary: process.env.CLAUDE_CODE_BINARY || "claude",
123020
123195
  claudeCodeModel: process.env.CLAUDE_CODE_MODEL || "",
123021
123196
  claudeCodeTimeoutMs: parseInt(process.env.CLAUDE_CODE_TIMEOUT_MS || "120000", 10),
123022
- claudeCodeMaxRetries: parseInt(process.env.CLAUDE_CODE_MAX_RETRIES || "2", 10)
123197
+ claudeCodeMaxRetries: parseInt(process.env.CLAUDE_CODE_MAX_RETRIES || "2", 10),
123198
+ claudeCodeMaxConcurrency: parsePositiveInt("CLAUDE_CODE_MAX_CONCURRENCY", process.env.CLAUDE_CODE_MAX_CONCURRENCY, 10)
123023
123199
  };
123024
123200
  }
123025
123201
  });
@@ -123690,6 +123866,7 @@ var init_cli_provider = __esm({
123690
123866
  "apps/server/dist/services/llm/cli-provider.js"() {
123691
123867
  "use strict";
123692
123868
  init_logger();
123869
+ init_p_limit();
123693
123870
  init_analysis_registry();
123694
123871
  init_esm5();
123695
123872
  init_config2();
@@ -123697,6 +123874,7 @@ var init_cli_provider = __esm({
123697
123874
  init_schemas2();
123698
123875
  BaseCLIProvider = class {
123699
123876
  maxRetries = config.claudeCodeMaxRetries ?? 2;
123877
+ limit = pLimit(config.claudeCodeMaxConcurrency);
123700
123878
  debugDir = null;
123701
123879
  callCounter = 0;
123702
123880
  _analysisId = null;
@@ -123889,36 +124067,43 @@ var init_cli_provider = __esm({
123889
124067
  }
123890
124068
  /** Spawn CLI with retry on parse/validation failure. */
123891
124069
  async spawnAndParse(prompt, schema2, opts) {
123892
- const jsonSchemaStr = this.toJsonSchema(schema2);
123893
- const label = opts?.label ?? "call";
123894
- let lastError = null;
123895
- for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
123896
- try {
123897
- const raw = await this.spawnCLI(prompt, jsonSchemaStr, opts);
123898
- this.dumpDebug(label, prompt, raw, jsonSchemaStr);
123899
- return this.parseAndValidate(raw, schema2);
123900
- } catch (err) {
123901
- lastError = err;
123902
- if (this._abortSignal?.aborted)
123903
- throw lastError;
123904
- if (attempt < this.maxRetries) {
123905
- log.warn(`[CLI] Attempt ${attempt + 1} failed, retrying... (${lastError.message})`);
124070
+ return this.limit(async () => {
124071
+ if (this._abortSignal?.aborted) {
124072
+ throw this._abortSignal.reason ?? new DOMException("Analysis cancelled", "AbortError");
124073
+ }
124074
+ opts?.onStart?.();
124075
+ const jsonSchemaStr = this.toJsonSchema(schema2);
124076
+ const label = opts?.label ?? "call";
124077
+ let lastError = null;
124078
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
124079
+ try {
124080
+ const raw = await this.spawnCLI(prompt, jsonSchemaStr, opts);
124081
+ this.dumpDebug(label, prompt, raw, jsonSchemaStr);
124082
+ return this.parseAndValidate(raw, schema2);
124083
+ } catch (err) {
124084
+ lastError = err;
124085
+ if (this._abortSignal?.aborted)
124086
+ throw lastError;
124087
+ if (attempt < this.maxRetries) {
124088
+ log.warn(`[CLI] Attempt ${attempt + 1} failed, retrying... (${lastError.message})`);
124089
+ }
123906
124090
  }
123907
124091
  }
123908
- }
123909
- throw lastError;
124092
+ throw lastError;
124093
+ });
123910
124094
  }
123911
124095
  // ---------------------------------------------------------------------------
123912
124096
  // LLMProvider implementation
123913
124097
  // ---------------------------------------------------------------------------
123914
- async generateServiceViolations(context) {
124098
+ async generateServiceViolations(context, opts) {
123915
124099
  const { vars, idMap } = buildServiceTemplateVars(context);
123916
124100
  const prompt = getPrompt("violations-service", vars);
123917
124101
  log.info("[CLI] Service violations call starting...");
123918
124102
  const t0 = Date.now();
123919
124103
  const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, ServiceViolationOutputSchema, {
123920
124104
  extraArgs: ["--tools", ""],
123921
- label: "service"
124105
+ label: "service",
124106
+ onStart: opts?.onStart
123922
124107
  });
123923
124108
  const dur = Date.now() - t0;
123924
124109
  log.info(`[CLI] Service violations call done in ${dur}ms \u2014 ${object.violations.length} violations`);
@@ -123941,14 +124126,15 @@ var init_cli_provider = __esm({
123941
124126
  }))
123942
124127
  };
123943
124128
  }
123944
- async generateDatabaseViolations(context) {
124129
+ async generateDatabaseViolations(context, opts) {
123945
124130
  const { vars, idMap } = buildDatabaseTemplateVars(context);
123946
124131
  const prompt = getPrompt("violations-database", vars);
123947
124132
  log.info("[CLI] Database violations call starting...");
123948
124133
  const t0 = Date.now();
123949
124134
  const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, DatabaseViolationOutputSchema, {
123950
124135
  extraArgs: ["--tools", ""],
123951
- label: "database"
124136
+ label: "database",
124137
+ onStart: opts?.onStart
123952
124138
  });
123953
124139
  const dur = Date.now() - t0;
123954
124140
  log.info(`[CLI] Database violations call done in ${dur}ms \u2014 ${object.violations.length} violations`);
@@ -123968,7 +124154,7 @@ var init_cli_provider = __esm({
123968
124154
  }))
123969
124155
  };
123970
124156
  }
123971
- async generateModuleViolations(context) {
124157
+ async generateModuleViolations(context, opts) {
123972
124158
  const { vars, idMap } = buildModuleTemplateVars(context);
123973
124159
  const prompt = getPrompt("violations-module", vars);
123974
124160
  const moduleIdToServiceId = new Map(context.modules.filter((m) => m.serviceId).map((m) => [m.id, m.serviceId]));
@@ -123978,7 +124164,8 @@ var init_cli_provider = __esm({
123978
124164
  const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, ModuleViolationOutputSchema, {
123979
124165
  extraArgs: ["--tools", ""],
123980
124166
  label: "module",
123981
- timeoutMs: moduleTimeoutMs
124167
+ timeoutMs: moduleTimeoutMs,
124168
+ onStart: opts?.onStart
123982
124169
  });
123983
124170
  const dur = Date.now() - t0;
123984
124171
  log.info(`[CLI] Module violations call done in ${dur}ms \u2014 ${object.violations.length} violations`);
@@ -124005,15 +124192,23 @@ var init_cli_provider = __esm({
124005
124192
  }
124006
124193
  async generateAllViolations(contexts) {
124007
124194
  const onStepComplete = contexts.onStepComplete;
124195
+ const onCallStart = contexts.onCallStart;
124196
+ const onCallDone = contexts.onCallDone;
124008
124197
  const promises = [];
124009
124198
  if (contexts.service) {
124010
- promises.push(["service", this.generateServiceViolations(contexts.service)]);
124199
+ promises.push(["service", this.generateServiceViolations(contexts.service, {
124200
+ onStart: () => onCallStart?.("service")
124201
+ })]);
124011
124202
  }
124012
124203
  if (contexts.database) {
124013
- promises.push(["database", this.generateDatabaseViolations(contexts.database)]);
124204
+ promises.push(["database", this.generateDatabaseViolations(contexts.database, {
124205
+ onStart: () => onCallStart?.("database")
124206
+ })]);
124014
124207
  }
124015
124208
  if (contexts.module) {
124016
- promises.push(["module", this.generateModuleViolations(contexts.module)]);
124209
+ promises.push(["module", this.generateModuleViolations(contexts.module, {
124210
+ onStart: () => onCallStart?.("module")
124211
+ })]);
124017
124212
  }
124018
124213
  const stepLabels = {
124019
124214
  service: "Service architecture checks done",
@@ -124022,7 +124217,11 @@ var init_cli_provider = __esm({
124022
124217
  };
124023
124218
  const settled = await Promise.allSettled(promises.map(([key, p2]) => p2.then((v) => {
124024
124219
  onStepComplete?.(stepLabels[key] || `${key} done`);
124220
+ onCallDone?.(key, true);
124025
124221
  return v;
124222
+ }, (err) => {
124223
+ onCallDone?.(key, false);
124224
+ throw err;
124026
124225
  })));
124027
124226
  const result = {};
124028
124227
  for (let i = 0; i < promises.length; i++) {
@@ -124037,6 +124236,7 @@ var init_cli_provider = __esm({
124037
124236
  return result;
124038
124237
  }
124039
124238
  async generateAllViolationsWithLifecycle(contexts, onStepComplete) {
124239
+ const onCallStart = contexts.onCallStart;
124040
124240
  const allResolved = [];
124041
124241
  const allNew = [];
124042
124242
  let serviceDescriptions = [];
@@ -124053,7 +124253,8 @@ var init_cli_provider = __esm({
124053
124253
  const t0 = Date.now();
124054
124254
  const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, LifecycleServiceOutputSchema, {
124055
124255
  extraArgs: ["--tools", ""],
124056
- label: "service-lifecycle"
124256
+ label: "service-lifecycle",
124257
+ onStart: () => onCallStart?.("service")
124057
124258
  });
124058
124259
  const dur = Date.now() - t0;
124059
124260
  log.info(`[CLI] Lifecycle service call done in ${dur}ms \u2014 resolved: ${object.resolvedViolationIds.length}, new: ${object.newViolations.length}`);
@@ -124061,7 +124262,9 @@ var init_cli_provider = __esm({
124061
124262
  return object;
124062
124263
  })()]);
124063
124264
  } else {
124064
- promises.push(["service-normal", this.generateServiceViolations(ctx)]);
124265
+ promises.push(["service-normal", this.generateServiceViolations(ctx, {
124266
+ onStart: () => onCallStart?.("service")
124267
+ })]);
124065
124268
  }
124066
124269
  }
124067
124270
  if (contexts.database) {
@@ -124075,7 +124278,8 @@ var init_cli_provider = __esm({
124075
124278
  const t0 = Date.now();
124076
124279
  const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, DiffViolationOutputSchema, {
124077
124280
  extraArgs: ["--tools", ""],
124078
- label: "database-lifecycle"
124281
+ label: "database-lifecycle",
124282
+ onStart: () => onCallStart?.("database")
124079
124283
  });
124080
124284
  const dur = Date.now() - t0;
124081
124285
  log.info(`[CLI] Lifecycle database call done in ${dur}ms \u2014 resolved: ${object.resolvedViolationIds.length}, new: ${object.newViolations.length}`);
@@ -124083,7 +124287,9 @@ var init_cli_provider = __esm({
124083
124287
  return object;
124084
124288
  })()]);
124085
124289
  } else {
124086
- promises.push(["database-normal", this.generateDatabaseViolations(ctx)]);
124290
+ promises.push(["database-normal", this.generateDatabaseViolations(ctx, {
124291
+ onStart: () => onCallStart?.("database")
124292
+ })]);
124087
124293
  }
124088
124294
  }
124089
124295
  if (contexts.module) {
@@ -124098,7 +124304,8 @@ var init_cli_provider = __esm({
124098
124304
  const t0 = Date.now();
124099
124305
  const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, DiffViolationOutputSchema, {
124100
124306
  extraArgs: ["--tools", ""],
124101
- label: "module-lifecycle"
124307
+ label: "module-lifecycle",
124308
+ onStart: () => onCallStart?.("module")
124102
124309
  });
124103
124310
  const dur = Date.now() - t0;
124104
124311
  log.info(`[CLI] Lifecycle module call done in ${dur}ms \u2014 resolved: ${object.resolvedViolationIds.length}, new: ${object.newViolations.length}`);
@@ -124119,7 +124326,9 @@ var init_cli_provider = __esm({
124119
124326
  };
124120
124327
  })()]);
124121
124328
  } else {
124122
- promises.push(["module-normal", this.generateModuleViolations(ctx)]);
124329
+ promises.push(["module-normal", this.generateModuleViolations(ctx, {
124330
+ onStart: () => onCallStart?.("module")
124331
+ })]);
124123
124332
  }
124124
124333
  }
124125
124334
  const stepLabels = {
@@ -124130,9 +124339,15 @@ var init_cli_provider = __esm({
124130
124339
  module: "Module checks done",
124131
124340
  "module-normal": "Module checks done"
124132
124341
  };
124342
+ const baseKey = (key) => key.replace("-normal", "");
124343
+ const onCallDone = contexts.onCallDone;
124133
124344
  const settled = await Promise.allSettled(promises.map(([key, p2]) => p2.then((v) => {
124134
124345
  onStepComplete?.(stepLabels[key] || `${key} done`);
124346
+ onCallDone?.(baseKey(key), true);
124135
124347
  return v;
124348
+ }, (err) => {
124349
+ onCallDone?.(baseKey(key), false);
124350
+ throw err;
124136
124351
  })));
124137
124352
  for (let i = 0; i < promises.length; i++) {
124138
124353
  const [key] = promises[i];
@@ -124224,7 +124439,7 @@ var init_cli_provider = __esm({
124224
124439
  }
124225
124440
  return { resolvedViolationIds: allResolved, newViolations: allNew, serviceDescriptions };
124226
124441
  }
124227
- async generateCodeViolations(context) {
124442
+ async generateCodeViolations(context, opts) {
124228
124443
  const hasExisting = context.existingViolations && context.existingViolations.length > 0;
124229
124444
  let promptName;
124230
124445
  if (context.tier === "metadata") {
@@ -124245,7 +124460,8 @@ var init_cli_provider = __esm({
124245
124460
  const { data: object2, usage: cliUsage2 } = await this.spawnAndParse(prompt, CodeViolationLifecycleOutputSchema, {
124246
124461
  extraArgs: codeExtraArgs,
124247
124462
  label: "code-lifecycle",
124248
- timeoutMs: codeTimeoutMs
124463
+ timeoutMs: codeTimeoutMs,
124464
+ onStart: opts?.onStart
124249
124465
  });
124250
124466
  const dur2 = Date.now() - t0;
124251
124467
  log.info(`[CLI] Code violations call done in ${dur2}ms \u2014 new: ${object2.newViolations.length}, resolved: ${object2.resolvedViolationIds.length}, unchanged: ${object2.unchangedViolationIds.length}`);
@@ -124268,7 +124484,8 @@ var init_cli_provider = __esm({
124268
124484
  const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, CodeViolationOutputSchema, {
124269
124485
  extraArgs: codeExtraArgs,
124270
124486
  label: "code",
124271
- timeoutMs: codeTimeoutMs
124487
+ timeoutMs: codeTimeoutMs,
124488
+ onStart: opts?.onStart
124272
124489
  });
124273
124490
  const dur = Date.now() - t0;
124274
124491
  log.info(`[CLI] Code violations call done in ${dur}ms \u2014 ${object.violations.length} violations`);
@@ -124758,7 +124975,7 @@ var init_context_router = __esm({
124758
124975
  });
124759
124976
 
124760
124977
  // apps/server/dist/services/violation.service.js
124761
- async function generateViolations(input, onProgress, externalProvider) {
124978
+ async function generateViolations(input, onProgress, externalProvider, onCallStart, onCallDone) {
124762
124979
  const provider = externalProvider ?? createLLMProvider();
124763
124980
  const archRules = (input.llmRules || []).filter((r) => r.category === "service");
124764
124981
  const dbRules = (input.llmRules || []).filter((r) => r.category === "database");
@@ -124802,7 +125019,9 @@ async function generateViolations(input, onProgress, externalProvider) {
124802
125019
  service: serviceContext,
124803
125020
  database: dbContext,
124804
125021
  module: moduleContext,
124805
- onStepComplete: onProgress
125022
+ onStepComplete: onProgress,
125023
+ onCallStart,
125024
+ onCallDone
124806
125025
  });
124807
125026
  const allViolations = [];
124808
125027
  let serviceDescriptions = [];
@@ -124839,7 +125058,7 @@ async function generateViolations(input, onProgress, externalProvider) {
124839
125058
  }
124840
125059
  return { violations: allViolations, serviceDescriptions };
124841
125060
  }
124842
- async function generateViolationsWithLifecycle(input, onProgress, externalProvider) {
125061
+ async function generateViolationsWithLifecycle(input, onProgress, externalProvider, onCallStart, onCallDone) {
124843
125062
  const provider = externalProvider ?? createLLMProvider();
124844
125063
  const archRules = (input.llmRules || []).filter((r) => r.category === "service");
124845
125064
  const dbRules = (input.llmRules || []).filter((r) => r.category === "database");
@@ -124880,7 +125099,9 @@ async function generateViolationsWithLifecycle(input, onProgress, externalProvid
124880
125099
  const result = await provider.generateAllViolationsWithLifecycle({
124881
125100
  service: serviceContext,
124882
125101
  database: dbContext,
124883
- module: moduleContext
125102
+ module: moduleContext,
125103
+ onCallStart,
125104
+ onCallDone
124884
125105
  }, (step) => {
124885
125106
  onProgress?.(step);
124886
125107
  });
@@ -125145,6 +125366,48 @@ function throwIfAborted(signal) {
125145
125366
  if (signal?.aborted)
125146
125367
  throw new DOMException("Analysis cancelled", "AbortError");
125147
125368
  }
125369
+ function formatElapsed(ms) {
125370
+ const totalSec = Math.floor(ms / 1e3);
125371
+ const min = Math.floor(totalSec / 60);
125372
+ const sec = totalSec % 60;
125373
+ return min === 0 ? `${sec}s` : `${min}m ${sec}s`;
125374
+ }
125375
+ function renderLlmDetail(s) {
125376
+ const parts = [];
125377
+ if (s.detCount > 0)
125378
+ parts.push(`${s.detCount} det`);
125379
+ parts.push(`LLM ${s.done}/${s.total}`);
125380
+ if (s.running > 0)
125381
+ parts.push(`${s.running} running`);
125382
+ if (s.elapsedMs >= 1e3)
125383
+ parts.push(formatElapsed(s.elapsedMs));
125384
+ return parts.join(" \xB7 ");
125385
+ }
125386
+ function createLlmTracker(tracker, domain, detCount, total) {
125387
+ let done = 0;
125388
+ let running = 0;
125389
+ const t0 = Date.now();
125390
+ const render = () => tracker?.detail(domain, renderLlmDetail({
125391
+ detCount,
125392
+ total,
125393
+ done,
125394
+ running,
125395
+ elapsedMs: Date.now() - t0
125396
+ }));
125397
+ return {
125398
+ initialDetail: renderLlmDetail({ detCount, total, done: 0, running: 0, elapsedMs: 0 }),
125399
+ onCallStart: () => {
125400
+ running++;
125401
+ render();
125402
+ },
125403
+ onCallDone: (started) => {
125404
+ if (started)
125405
+ running--;
125406
+ done++;
125407
+ render();
125408
+ }
125409
+ };
125410
+ }
125148
125411
  function getDetComparisonKey(v) {
125149
125412
  return `${v.ruleKey}::${v.serviceName}::${v.moduleName || ""}::${v.methodName || ""}::${v.title}`;
125150
125413
  }
@@ -125483,12 +125746,6 @@ async function runViolationPipeline(input) {
125483
125746
  const allNewLlmItems = [];
125484
125747
  const allResolvedLlmIds = [];
125485
125748
  const hasArchLlm = enableLlmRules !== false && !llmSkipped;
125486
- if (hasArchLlm)
125487
- tracker?.start("architecture", "Running LLM analysis...");
125488
- for (const [domain] of domainCodeBatches) {
125489
- const detCount = violationsByDomain.get(domain) ?? 0;
125490
- tracker?.start(domain, detCount > 0 ? `${detCount} det, running LLM...` : "Running LLM...");
125491
- }
125492
125749
  const previousDetForComparison = previousDetViolations.map((v) => ({
125493
125750
  ruleKey: v.ruleKey,
125494
125751
  serviceName: v.targetServiceName || "",
@@ -125715,13 +125972,42 @@ async function runViolationPipeline(input) {
125715
125972
  existingDatabaseViolations: void 0,
125716
125973
  existingModuleViolations: hasLlmOnlyExistingViolations ? existingModuleViolations : void 0
125717
125974
  };
125975
+ const llmTrackers = /* @__PURE__ */ new Map();
125976
+ for (const [domain, batches] of domainCodeBatches) {
125977
+ const detCount = violationsByDomain.get(domain) ?? 0;
125978
+ const ll = createLlmTracker(tracker, domain, detCount, batches.length);
125979
+ llmTrackers.set(domain, ll);
125980
+ tracker?.start(domain, ll.initialDetail);
125981
+ }
125982
+ if (dbSchemaContext && !llmSkipped && !domainCodeBatches.has("database")) {
125983
+ const detCount = violationsByDomain.get("database") ?? 0;
125984
+ const ll = createLlmTracker(tracker, "database", detCount, 1);
125985
+ llmTrackers.set("database", ll);
125986
+ tracker?.start("database", ll.initialDetail);
125987
+ }
125988
+ if (hasArchLlm) {
125989
+ const detCount = violationsByDomain.get("architecture") ?? 0;
125990
+ const archTotal2 = 1 + (violationModules && violationModules.length > 0 ? 1 : 0);
125991
+ const ll = createLlmTracker(tracker, "architecture", detCount, archTotal2);
125992
+ llmTrackers.set("architecture", ll);
125993
+ tracker?.start("architecture", ll.initialDetail);
125994
+ }
125718
125995
  const domainLlmPromises = [];
125719
125996
  for (const [domain, batches] of domainCodeBatches) {
125720
125997
  domainLlmPromises.push((async () => {
125721
125998
  const detCount = violationsByDomain.get(domain) ?? 0;
125722
125999
  log.info(`[LLM] ${domain}: starting (${batches.length} code batches)`);
125723
126000
  const t0 = Date.now();
125724
- const codeResults = await Promise.allSettled(batches.map((b) => provider.generateCodeViolations(b)));
126001
+ const ll = llmTrackers.get(domain);
126002
+ const codeResults = await Promise.allSettled(batches.map((b) => {
126003
+ let started = false;
126004
+ return provider.generateCodeViolations(b, {
126005
+ onStart: () => {
126006
+ started = true;
126007
+ ll.onCallStart();
126008
+ }
126009
+ }).finally(() => ll.onCallDone(started));
126010
+ }));
125725
126011
  const rawViolations = [];
125726
126012
  const resolvedIds = [];
125727
126013
  const unchangedIds = [];
@@ -125747,15 +126033,19 @@ async function runViolationPipeline(input) {
125747
126033
  }
125748
126034
  let dbSchemaViolations = [];
125749
126035
  if (dbSchemaContext && !llmSkipped) {
125750
- if (!domainCodeBatches.has("database")) {
125751
- const detCount = violationsByDomain.get("database") ?? 0;
125752
- tracker?.start("database", detCount > 0 ? `${detCount} det, running LLM...` : "Running LLM...");
125753
- }
126036
+ const schemaLl = domainCodeBatches.has("database") ? void 0 : llmTrackers.get("database");
125754
126037
  domainLlmPromises.push((async () => {
125755
126038
  log.info(`[LLM] database-schema: starting`);
125756
126039
  const t0 = Date.now();
126040
+ let started = false;
125757
126041
  try {
125758
- const dbResult = await provider.generateDatabaseViolations(dbSchemaContext);
126042
+ const dbResult = await provider.generateDatabaseViolations(dbSchemaContext, {
126043
+ onStart: () => {
126044
+ started = true;
126045
+ schemaLl?.onCallStart();
126046
+ }
126047
+ });
126048
+ schemaLl?.onCallDone(started);
125759
126049
  const dur = Date.now() - t0;
125760
126050
  log.info(`[LLM] database-schema: done in ${dur}ms \u2014 ${dbResult.violations.length} violations`);
125761
126051
  for (const v of dbResult.violations) {
@@ -125795,6 +126085,7 @@ async function runViolationPipeline(input) {
125795
126085
  }
125796
126086
  return { domain: "database-schema", violations: [], resolvedIds: [], unchangedIds: [] };
125797
126087
  } catch (err) {
126088
+ schemaLl?.onCallDone(started);
125798
126089
  const dur = Date.now() - t0;
125799
126090
  log.warn(`[LLM] database-schema: failed in ${dur}ms \u2014 ${err instanceof Error ? err.message : String(err)}`);
125800
126091
  if (!domainCodeBatches.has("database"))
@@ -125808,8 +126099,17 @@ async function runViolationPipeline(input) {
125808
126099
  const llmRulePromise = (async () => {
125809
126100
  if (enableLlmRules === false || llmSkipped)
125810
126101
  return;
126102
+ const archLl = llmTrackers.get("architecture");
126103
+ const archStarted = /* @__PURE__ */ new Set();
126104
+ const archOnCallStart = (key) => {
126105
+ archStarted.add(key);
126106
+ archLl?.onCallStart();
126107
+ };
126108
+ const archOnCallDone = (key) => {
126109
+ archLl?.onCallDone(archStarted.has(key));
126110
+ };
125811
126111
  if (hasLlmOnlyExistingViolations) {
125812
- const archResult = await generateViolationsWithLifecycle(violationInput, (step) => tracker?.detail("architecture", step), provider);
126112
+ const archResult = await generateViolationsWithLifecycle(violationInput, void 0, provider, archOnCallStart, archOnCallDone);
125813
126113
  serviceDescriptions = archResult.serviceDescriptions;
125814
126114
  allResolvedLlmIds.push(...archResult.resolvedViolationIds);
125815
126115
  allNewLlmItems.push(...archResult.newViolations);
@@ -125831,7 +126131,7 @@ async function runViolationPipeline(input) {
125831
126131
  resolved.push(...lifecycle.resolved);
125832
126132
  resolvedRefs.push(...lifecycle.resolvedRefs);
125833
126133
  } else {
125834
- const archResult = await generateViolations(violationInput, (step) => tracker?.detail("architecture", step), provider);
126134
+ const archResult = await generateViolations(violationInput, void 0, provider, archOnCallStart, archOnCallDone);
125835
126135
  serviceDescriptions = archResult.serviceDescriptions;
125836
126136
  for (const v of archResult.violations) {
125837
126137
  added.push({
@@ -126635,8 +126935,11 @@ import path16 from "node:path";
126635
126935
  init_analysis_store();
126636
126936
  init_analyze_core();
126637
126937
  init_analyze_persist();
126938
+ init_config2();
126939
+ init_logger();
126638
126940
  async function analyzeInProcess(project, options = {}) {
126639
126941
  const startedAt = Date.now();
126942
+ log.info(`[LLM] Provider: claude-code, model: ${config.claudeCodeModel || "default"}, maxConcurrency: ${config.claudeCodeMaxConcurrency}`);
126640
126943
  const core2 = await analyzeCore(project, { ...options, mode: "full" });
126641
126944
  return persistFullAnalysis(project, core2, startedAt);
126642
126945
  }
@@ -130733,7 +131036,7 @@ async function runHooksRun() {
130733
131036
 
130734
131037
  // tools/cli/src/index.ts
130735
131038
  var program2 = new Command();
130736
- program2.name("truecourse").version("0.5.4").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
131039
+ program2.name("truecourse").version("0.5.5").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
130737
131040
  var dashboardCmd = program2.command("dashboard").description("Start the TrueCourse dashboard and open it in your browser").option("--reconfigure", "Re-prompt for console vs background service mode").option("--service", "Run as a background service (skips mode prompt)").option("--console", "Run in this terminal (skips mode prompt)").action(async (options) => {
130738
131041
  if (options.service && options.console) {
130739
131042
  console.error("error: --service and --console are mutually exclusive");