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.
- package/README.md +22 -0
- package/cli.mjs +357 -54
- package/package.json +1 -1
- 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
|
-
|
|
123893
|
-
|
|
123894
|
-
|
|
123895
|
-
|
|
123896
|
-
|
|
123897
|
-
|
|
123898
|
-
|
|
123899
|
-
|
|
123900
|
-
|
|
123901
|
-
|
|
123902
|
-
|
|
123903
|
-
|
|
123904
|
-
|
|
123905
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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.
|
|
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");
|