truecourse 0.5.3 → 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 +370 -64
- package/package.json +1 -1
- package/server.mjs +376 -62
package/cli.mjs
CHANGED
|
@@ -4335,8 +4335,8 @@ function resolveSkillsSrcDir() {
|
|
|
4335
4335
|
const candidate = resolve(__dirname4, "skills", "truecourse");
|
|
4336
4336
|
return existsSync(candidate) ? candidate : null;
|
|
4337
4337
|
}
|
|
4338
|
-
function
|
|
4339
|
-
return resolve(repoPath, ".claude", "skills"
|
|
4338
|
+
function skillsParentDir(repoPath) {
|
|
4339
|
+
return resolve(repoPath, ".claude", "skills");
|
|
4340
4340
|
}
|
|
4341
4341
|
function listSkillDirs(root) {
|
|
4342
4342
|
if (!existsSync(root)) return [];
|
|
@@ -4345,9 +4345,9 @@ function listSkillDirs(root) {
|
|
|
4345
4345
|
function computeMissingSkills(repoPath) {
|
|
4346
4346
|
const src = resolveSkillsSrcDir();
|
|
4347
4347
|
if (!src) return [];
|
|
4348
|
-
const shipped =
|
|
4349
|
-
const
|
|
4350
|
-
return
|
|
4348
|
+
const shipped = listSkillDirs(src);
|
|
4349
|
+
const parent = skillsParentDir(repoPath);
|
|
4350
|
+
return shipped.filter((name) => !existsSync(resolve(parent, name)));
|
|
4351
4351
|
}
|
|
4352
4352
|
function hasInstalledSkills(repoPath) {
|
|
4353
4353
|
return computeMissingSkills(repoPath).length === 0;
|
|
@@ -4358,11 +4358,11 @@ function copySkills(repoPath, skillNames) {
|
|
|
4358
4358
|
O2.warn("Skills directory not found in package \u2014 skipping.");
|
|
4359
4359
|
return;
|
|
4360
4360
|
}
|
|
4361
|
-
const
|
|
4362
|
-
mkdirSync(
|
|
4361
|
+
const parent = skillsParentDir(repoPath);
|
|
4362
|
+
mkdirSync(parent, { recursive: true });
|
|
4363
4363
|
for (const name of skillNames) {
|
|
4364
4364
|
const skillSrc = resolve(src, name);
|
|
4365
|
-
const skillDest = resolve(
|
|
4365
|
+
const skillDest = resolve(parent, name);
|
|
4366
4366
|
if (existsSync(skillDest)) continue;
|
|
4367
4367
|
cpSync(skillSrc, skillDest, { recursive: true });
|
|
4368
4368
|
}
|
|
@@ -4380,8 +4380,11 @@ async function promptInstallSkills(repoPath, { install } = {}) {
|
|
|
4380
4380
|
}
|
|
4381
4381
|
if (install === false) return;
|
|
4382
4382
|
if (!isInteractive()) return;
|
|
4383
|
-
const
|
|
4384
|
-
const
|
|
4383
|
+
const src = resolveSkillsSrcDir();
|
|
4384
|
+
const shipped = src ? listSkillDirs(src) : [];
|
|
4385
|
+
const parent = skillsParentDir(repoPath);
|
|
4386
|
+
const alreadyInstalled = shipped.some((name) => existsSync(resolve(parent, name)));
|
|
4387
|
+
const message = alreadyInstalled ? `New Claude Code skill${missing.length === 1 ? "" : "s"} available: ${missing.join(", ")}. Install?` : "Would you like to install Claude Code skills?";
|
|
4385
4388
|
const answer = await ot2({ message });
|
|
4386
4389
|
if (q(answer) || !answer) return;
|
|
4387
4390
|
copySkills(repoPath, missing);
|
|
@@ -121033,6 +121036,172 @@ var init_rules_service = __esm({
|
|
|
121033
121036
|
}
|
|
121034
121037
|
});
|
|
121035
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
|
+
|
|
121036
121205
|
// apps/server/dist/services/analysis-registry.js
|
|
121037
121206
|
function registerChildProcess(repoId, child) {
|
|
121038
121207
|
const entry = activeAnalyses.get(repoId);
|
|
@@ -123004,6 +123173,15 @@ var init_env = __esm({
|
|
|
123004
123173
|
});
|
|
123005
123174
|
|
|
123006
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
|
+
}
|
|
123007
123185
|
var config;
|
|
123008
123186
|
var init_config2 = __esm({
|
|
123009
123187
|
"apps/server/dist/config/index.js"() {
|
|
@@ -123016,7 +123194,8 @@ var init_config2 = __esm({
|
|
|
123016
123194
|
claudeCodeBinary: process.env.CLAUDE_CODE_BINARY || "claude",
|
|
123017
123195
|
claudeCodeModel: process.env.CLAUDE_CODE_MODEL || "",
|
|
123018
123196
|
claudeCodeTimeoutMs: parseInt(process.env.CLAUDE_CODE_TIMEOUT_MS || "120000", 10),
|
|
123019
|
-
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)
|
|
123020
123199
|
};
|
|
123021
123200
|
}
|
|
123022
123201
|
});
|
|
@@ -123687,6 +123866,7 @@ var init_cli_provider = __esm({
|
|
|
123687
123866
|
"apps/server/dist/services/llm/cli-provider.js"() {
|
|
123688
123867
|
"use strict";
|
|
123689
123868
|
init_logger();
|
|
123869
|
+
init_p_limit();
|
|
123690
123870
|
init_analysis_registry();
|
|
123691
123871
|
init_esm5();
|
|
123692
123872
|
init_config2();
|
|
@@ -123694,6 +123874,7 @@ var init_cli_provider = __esm({
|
|
|
123694
123874
|
init_schemas2();
|
|
123695
123875
|
BaseCLIProvider = class {
|
|
123696
123876
|
maxRetries = config.claudeCodeMaxRetries ?? 2;
|
|
123877
|
+
limit = pLimit(config.claudeCodeMaxConcurrency);
|
|
123697
123878
|
debugDir = null;
|
|
123698
123879
|
callCounter = 0;
|
|
123699
123880
|
_analysisId = null;
|
|
@@ -123886,36 +124067,43 @@ var init_cli_provider = __esm({
|
|
|
123886
124067
|
}
|
|
123887
124068
|
/** Spawn CLI with retry on parse/validation failure. */
|
|
123888
124069
|
async spawnAndParse(prompt, schema2, opts) {
|
|
123889
|
-
|
|
123890
|
-
|
|
123891
|
-
|
|
123892
|
-
|
|
123893
|
-
|
|
123894
|
-
|
|
123895
|
-
|
|
123896
|
-
|
|
123897
|
-
|
|
123898
|
-
|
|
123899
|
-
|
|
123900
|
-
|
|
123901
|
-
|
|
123902
|
-
|
|
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
|
+
}
|
|
123903
124090
|
}
|
|
123904
124091
|
}
|
|
123905
|
-
|
|
123906
|
-
|
|
124092
|
+
throw lastError;
|
|
124093
|
+
});
|
|
123907
124094
|
}
|
|
123908
124095
|
// ---------------------------------------------------------------------------
|
|
123909
124096
|
// LLMProvider implementation
|
|
123910
124097
|
// ---------------------------------------------------------------------------
|
|
123911
|
-
async generateServiceViolations(context) {
|
|
124098
|
+
async generateServiceViolations(context, opts) {
|
|
123912
124099
|
const { vars, idMap } = buildServiceTemplateVars(context);
|
|
123913
124100
|
const prompt = getPrompt("violations-service", vars);
|
|
123914
124101
|
log.info("[CLI] Service violations call starting...");
|
|
123915
124102
|
const t0 = Date.now();
|
|
123916
124103
|
const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, ServiceViolationOutputSchema, {
|
|
123917
124104
|
extraArgs: ["--tools", ""],
|
|
123918
|
-
label: "service"
|
|
124105
|
+
label: "service",
|
|
124106
|
+
onStart: opts?.onStart
|
|
123919
124107
|
});
|
|
123920
124108
|
const dur = Date.now() - t0;
|
|
123921
124109
|
log.info(`[CLI] Service violations call done in ${dur}ms \u2014 ${object.violations.length} violations`);
|
|
@@ -123938,14 +124126,15 @@ var init_cli_provider = __esm({
|
|
|
123938
124126
|
}))
|
|
123939
124127
|
};
|
|
123940
124128
|
}
|
|
123941
|
-
async generateDatabaseViolations(context) {
|
|
124129
|
+
async generateDatabaseViolations(context, opts) {
|
|
123942
124130
|
const { vars, idMap } = buildDatabaseTemplateVars(context);
|
|
123943
124131
|
const prompt = getPrompt("violations-database", vars);
|
|
123944
124132
|
log.info("[CLI] Database violations call starting...");
|
|
123945
124133
|
const t0 = Date.now();
|
|
123946
124134
|
const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, DatabaseViolationOutputSchema, {
|
|
123947
124135
|
extraArgs: ["--tools", ""],
|
|
123948
|
-
label: "database"
|
|
124136
|
+
label: "database",
|
|
124137
|
+
onStart: opts?.onStart
|
|
123949
124138
|
});
|
|
123950
124139
|
const dur = Date.now() - t0;
|
|
123951
124140
|
log.info(`[CLI] Database violations call done in ${dur}ms \u2014 ${object.violations.length} violations`);
|
|
@@ -123965,7 +124154,7 @@ var init_cli_provider = __esm({
|
|
|
123965
124154
|
}))
|
|
123966
124155
|
};
|
|
123967
124156
|
}
|
|
123968
|
-
async generateModuleViolations(context) {
|
|
124157
|
+
async generateModuleViolations(context, opts) {
|
|
123969
124158
|
const { vars, idMap } = buildModuleTemplateVars(context);
|
|
123970
124159
|
const prompt = getPrompt("violations-module", vars);
|
|
123971
124160
|
const moduleIdToServiceId = new Map(context.modules.filter((m) => m.serviceId).map((m) => [m.id, m.serviceId]));
|
|
@@ -123975,7 +124164,8 @@ var init_cli_provider = __esm({
|
|
|
123975
124164
|
const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, ModuleViolationOutputSchema, {
|
|
123976
124165
|
extraArgs: ["--tools", ""],
|
|
123977
124166
|
label: "module",
|
|
123978
|
-
timeoutMs: moduleTimeoutMs
|
|
124167
|
+
timeoutMs: moduleTimeoutMs,
|
|
124168
|
+
onStart: opts?.onStart
|
|
123979
124169
|
});
|
|
123980
124170
|
const dur = Date.now() - t0;
|
|
123981
124171
|
log.info(`[CLI] Module violations call done in ${dur}ms \u2014 ${object.violations.length} violations`);
|
|
@@ -124002,15 +124192,23 @@ var init_cli_provider = __esm({
|
|
|
124002
124192
|
}
|
|
124003
124193
|
async generateAllViolations(contexts) {
|
|
124004
124194
|
const onStepComplete = contexts.onStepComplete;
|
|
124195
|
+
const onCallStart = contexts.onCallStart;
|
|
124196
|
+
const onCallDone = contexts.onCallDone;
|
|
124005
124197
|
const promises = [];
|
|
124006
124198
|
if (contexts.service) {
|
|
124007
|
-
promises.push(["service", this.generateServiceViolations(contexts.service
|
|
124199
|
+
promises.push(["service", this.generateServiceViolations(contexts.service, {
|
|
124200
|
+
onStart: () => onCallStart?.("service")
|
|
124201
|
+
})]);
|
|
124008
124202
|
}
|
|
124009
124203
|
if (contexts.database) {
|
|
124010
|
-
promises.push(["database", this.generateDatabaseViolations(contexts.database
|
|
124204
|
+
promises.push(["database", this.generateDatabaseViolations(contexts.database, {
|
|
124205
|
+
onStart: () => onCallStart?.("database")
|
|
124206
|
+
})]);
|
|
124011
124207
|
}
|
|
124012
124208
|
if (contexts.module) {
|
|
124013
|
-
promises.push(["module", this.generateModuleViolations(contexts.module
|
|
124209
|
+
promises.push(["module", this.generateModuleViolations(contexts.module, {
|
|
124210
|
+
onStart: () => onCallStart?.("module")
|
|
124211
|
+
})]);
|
|
124014
124212
|
}
|
|
124015
124213
|
const stepLabels = {
|
|
124016
124214
|
service: "Service architecture checks done",
|
|
@@ -124019,7 +124217,11 @@ var init_cli_provider = __esm({
|
|
|
124019
124217
|
};
|
|
124020
124218
|
const settled = await Promise.allSettled(promises.map(([key, p2]) => p2.then((v) => {
|
|
124021
124219
|
onStepComplete?.(stepLabels[key] || `${key} done`);
|
|
124220
|
+
onCallDone?.(key, true);
|
|
124022
124221
|
return v;
|
|
124222
|
+
}, (err) => {
|
|
124223
|
+
onCallDone?.(key, false);
|
|
124224
|
+
throw err;
|
|
124023
124225
|
})));
|
|
124024
124226
|
const result = {};
|
|
124025
124227
|
for (let i = 0; i < promises.length; i++) {
|
|
@@ -124034,6 +124236,7 @@ var init_cli_provider = __esm({
|
|
|
124034
124236
|
return result;
|
|
124035
124237
|
}
|
|
124036
124238
|
async generateAllViolationsWithLifecycle(contexts, onStepComplete) {
|
|
124239
|
+
const onCallStart = contexts.onCallStart;
|
|
124037
124240
|
const allResolved = [];
|
|
124038
124241
|
const allNew = [];
|
|
124039
124242
|
let serviceDescriptions = [];
|
|
@@ -124050,7 +124253,8 @@ var init_cli_provider = __esm({
|
|
|
124050
124253
|
const t0 = Date.now();
|
|
124051
124254
|
const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, LifecycleServiceOutputSchema, {
|
|
124052
124255
|
extraArgs: ["--tools", ""],
|
|
124053
|
-
label: "service-lifecycle"
|
|
124256
|
+
label: "service-lifecycle",
|
|
124257
|
+
onStart: () => onCallStart?.("service")
|
|
124054
124258
|
});
|
|
124055
124259
|
const dur = Date.now() - t0;
|
|
124056
124260
|
log.info(`[CLI] Lifecycle service call done in ${dur}ms \u2014 resolved: ${object.resolvedViolationIds.length}, new: ${object.newViolations.length}`);
|
|
@@ -124058,7 +124262,9 @@ var init_cli_provider = __esm({
|
|
|
124058
124262
|
return object;
|
|
124059
124263
|
})()]);
|
|
124060
124264
|
} else {
|
|
124061
|
-
promises.push(["service-normal", this.generateServiceViolations(ctx
|
|
124265
|
+
promises.push(["service-normal", this.generateServiceViolations(ctx, {
|
|
124266
|
+
onStart: () => onCallStart?.("service")
|
|
124267
|
+
})]);
|
|
124062
124268
|
}
|
|
124063
124269
|
}
|
|
124064
124270
|
if (contexts.database) {
|
|
@@ -124072,7 +124278,8 @@ var init_cli_provider = __esm({
|
|
|
124072
124278
|
const t0 = Date.now();
|
|
124073
124279
|
const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, DiffViolationOutputSchema, {
|
|
124074
124280
|
extraArgs: ["--tools", ""],
|
|
124075
|
-
label: "database-lifecycle"
|
|
124281
|
+
label: "database-lifecycle",
|
|
124282
|
+
onStart: () => onCallStart?.("database")
|
|
124076
124283
|
});
|
|
124077
124284
|
const dur = Date.now() - t0;
|
|
124078
124285
|
log.info(`[CLI] Lifecycle database call done in ${dur}ms \u2014 resolved: ${object.resolvedViolationIds.length}, new: ${object.newViolations.length}`);
|
|
@@ -124080,7 +124287,9 @@ var init_cli_provider = __esm({
|
|
|
124080
124287
|
return object;
|
|
124081
124288
|
})()]);
|
|
124082
124289
|
} else {
|
|
124083
|
-
promises.push(["database-normal", this.generateDatabaseViolations(ctx
|
|
124290
|
+
promises.push(["database-normal", this.generateDatabaseViolations(ctx, {
|
|
124291
|
+
onStart: () => onCallStart?.("database")
|
|
124292
|
+
})]);
|
|
124084
124293
|
}
|
|
124085
124294
|
}
|
|
124086
124295
|
if (contexts.module) {
|
|
@@ -124095,7 +124304,8 @@ var init_cli_provider = __esm({
|
|
|
124095
124304
|
const t0 = Date.now();
|
|
124096
124305
|
const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, DiffViolationOutputSchema, {
|
|
124097
124306
|
extraArgs: ["--tools", ""],
|
|
124098
|
-
label: "module-lifecycle"
|
|
124307
|
+
label: "module-lifecycle",
|
|
124308
|
+
onStart: () => onCallStart?.("module")
|
|
124099
124309
|
});
|
|
124100
124310
|
const dur = Date.now() - t0;
|
|
124101
124311
|
log.info(`[CLI] Lifecycle module call done in ${dur}ms \u2014 resolved: ${object.resolvedViolationIds.length}, new: ${object.newViolations.length}`);
|
|
@@ -124116,7 +124326,9 @@ var init_cli_provider = __esm({
|
|
|
124116
124326
|
};
|
|
124117
124327
|
})()]);
|
|
124118
124328
|
} else {
|
|
124119
|
-
promises.push(["module-normal", this.generateModuleViolations(ctx
|
|
124329
|
+
promises.push(["module-normal", this.generateModuleViolations(ctx, {
|
|
124330
|
+
onStart: () => onCallStart?.("module")
|
|
124331
|
+
})]);
|
|
124120
124332
|
}
|
|
124121
124333
|
}
|
|
124122
124334
|
const stepLabels = {
|
|
@@ -124127,9 +124339,15 @@ var init_cli_provider = __esm({
|
|
|
124127
124339
|
module: "Module checks done",
|
|
124128
124340
|
"module-normal": "Module checks done"
|
|
124129
124341
|
};
|
|
124342
|
+
const baseKey = (key) => key.replace("-normal", "");
|
|
124343
|
+
const onCallDone = contexts.onCallDone;
|
|
124130
124344
|
const settled = await Promise.allSettled(promises.map(([key, p2]) => p2.then((v) => {
|
|
124131
124345
|
onStepComplete?.(stepLabels[key] || `${key} done`);
|
|
124346
|
+
onCallDone?.(baseKey(key), true);
|
|
124132
124347
|
return v;
|
|
124348
|
+
}, (err) => {
|
|
124349
|
+
onCallDone?.(baseKey(key), false);
|
|
124350
|
+
throw err;
|
|
124133
124351
|
})));
|
|
124134
124352
|
for (let i = 0; i < promises.length; i++) {
|
|
124135
124353
|
const [key] = promises[i];
|
|
@@ -124221,7 +124439,7 @@ var init_cli_provider = __esm({
|
|
|
124221
124439
|
}
|
|
124222
124440
|
return { resolvedViolationIds: allResolved, newViolations: allNew, serviceDescriptions };
|
|
124223
124441
|
}
|
|
124224
|
-
async generateCodeViolations(context) {
|
|
124442
|
+
async generateCodeViolations(context, opts) {
|
|
124225
124443
|
const hasExisting = context.existingViolations && context.existingViolations.length > 0;
|
|
124226
124444
|
let promptName;
|
|
124227
124445
|
if (context.tier === "metadata") {
|
|
@@ -124242,7 +124460,8 @@ var init_cli_provider = __esm({
|
|
|
124242
124460
|
const { data: object2, usage: cliUsage2 } = await this.spawnAndParse(prompt, CodeViolationLifecycleOutputSchema, {
|
|
124243
124461
|
extraArgs: codeExtraArgs,
|
|
124244
124462
|
label: "code-lifecycle",
|
|
124245
|
-
timeoutMs: codeTimeoutMs
|
|
124463
|
+
timeoutMs: codeTimeoutMs,
|
|
124464
|
+
onStart: opts?.onStart
|
|
124246
124465
|
});
|
|
124247
124466
|
const dur2 = Date.now() - t0;
|
|
124248
124467
|
log.info(`[CLI] Code violations call done in ${dur2}ms \u2014 new: ${object2.newViolations.length}, resolved: ${object2.resolvedViolationIds.length}, unchanged: ${object2.unchangedViolationIds.length}`);
|
|
@@ -124265,7 +124484,8 @@ var init_cli_provider = __esm({
|
|
|
124265
124484
|
const { data: object, usage: cliUsage } = await this.spawnAndParse(prompt, CodeViolationOutputSchema, {
|
|
124266
124485
|
extraArgs: codeExtraArgs,
|
|
124267
124486
|
label: "code",
|
|
124268
|
-
timeoutMs: codeTimeoutMs
|
|
124487
|
+
timeoutMs: codeTimeoutMs,
|
|
124488
|
+
onStart: opts?.onStart
|
|
124269
124489
|
});
|
|
124270
124490
|
const dur = Date.now() - t0;
|
|
124271
124491
|
log.info(`[CLI] Code violations call done in ${dur}ms \u2014 ${object.violations.length} violations`);
|
|
@@ -124755,7 +124975,7 @@ var init_context_router = __esm({
|
|
|
124755
124975
|
});
|
|
124756
124976
|
|
|
124757
124977
|
// apps/server/dist/services/violation.service.js
|
|
124758
|
-
async function generateViolations(input, onProgress, externalProvider) {
|
|
124978
|
+
async function generateViolations(input, onProgress, externalProvider, onCallStart, onCallDone) {
|
|
124759
124979
|
const provider = externalProvider ?? createLLMProvider();
|
|
124760
124980
|
const archRules = (input.llmRules || []).filter((r) => r.category === "service");
|
|
124761
124981
|
const dbRules = (input.llmRules || []).filter((r) => r.category === "database");
|
|
@@ -124799,7 +125019,9 @@ async function generateViolations(input, onProgress, externalProvider) {
|
|
|
124799
125019
|
service: serviceContext,
|
|
124800
125020
|
database: dbContext,
|
|
124801
125021
|
module: moduleContext,
|
|
124802
|
-
onStepComplete: onProgress
|
|
125022
|
+
onStepComplete: onProgress,
|
|
125023
|
+
onCallStart,
|
|
125024
|
+
onCallDone
|
|
124803
125025
|
});
|
|
124804
125026
|
const allViolations = [];
|
|
124805
125027
|
let serviceDescriptions = [];
|
|
@@ -124836,7 +125058,7 @@ async function generateViolations(input, onProgress, externalProvider) {
|
|
|
124836
125058
|
}
|
|
124837
125059
|
return { violations: allViolations, serviceDescriptions };
|
|
124838
125060
|
}
|
|
124839
|
-
async function generateViolationsWithLifecycle(input, onProgress, externalProvider) {
|
|
125061
|
+
async function generateViolationsWithLifecycle(input, onProgress, externalProvider, onCallStart, onCallDone) {
|
|
124840
125062
|
const provider = externalProvider ?? createLLMProvider();
|
|
124841
125063
|
const archRules = (input.llmRules || []).filter((r) => r.category === "service");
|
|
124842
125064
|
const dbRules = (input.llmRules || []).filter((r) => r.category === "database");
|
|
@@ -124877,7 +125099,9 @@ async function generateViolationsWithLifecycle(input, onProgress, externalProvid
|
|
|
124877
125099
|
const result = await provider.generateAllViolationsWithLifecycle({
|
|
124878
125100
|
service: serviceContext,
|
|
124879
125101
|
database: dbContext,
|
|
124880
|
-
module: moduleContext
|
|
125102
|
+
module: moduleContext,
|
|
125103
|
+
onCallStart,
|
|
125104
|
+
onCallDone
|
|
124881
125105
|
}, (step) => {
|
|
124882
125106
|
onProgress?.(step);
|
|
124883
125107
|
});
|
|
@@ -125142,6 +125366,48 @@ function throwIfAborted(signal) {
|
|
|
125142
125366
|
if (signal?.aborted)
|
|
125143
125367
|
throw new DOMException("Analysis cancelled", "AbortError");
|
|
125144
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
|
+
}
|
|
125145
125411
|
function getDetComparisonKey(v) {
|
|
125146
125412
|
return `${v.ruleKey}::${v.serviceName}::${v.moduleName || ""}::${v.methodName || ""}::${v.title}`;
|
|
125147
125413
|
}
|
|
@@ -125480,12 +125746,6 @@ async function runViolationPipeline(input) {
|
|
|
125480
125746
|
const allNewLlmItems = [];
|
|
125481
125747
|
const allResolvedLlmIds = [];
|
|
125482
125748
|
const hasArchLlm = enableLlmRules !== false && !llmSkipped;
|
|
125483
|
-
if (hasArchLlm)
|
|
125484
|
-
tracker?.start("architecture", "Running LLM analysis...");
|
|
125485
|
-
for (const [domain] of domainCodeBatches) {
|
|
125486
|
-
const detCount = violationsByDomain.get(domain) ?? 0;
|
|
125487
|
-
tracker?.start(domain, detCount > 0 ? `${detCount} det, running LLM...` : "Running LLM...");
|
|
125488
|
-
}
|
|
125489
125749
|
const previousDetForComparison = previousDetViolations.map((v) => ({
|
|
125490
125750
|
ruleKey: v.ruleKey,
|
|
125491
125751
|
serviceName: v.targetServiceName || "",
|
|
@@ -125712,13 +125972,42 @@ async function runViolationPipeline(input) {
|
|
|
125712
125972
|
existingDatabaseViolations: void 0,
|
|
125713
125973
|
existingModuleViolations: hasLlmOnlyExistingViolations ? existingModuleViolations : void 0
|
|
125714
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
|
+
}
|
|
125715
125995
|
const domainLlmPromises = [];
|
|
125716
125996
|
for (const [domain, batches] of domainCodeBatches) {
|
|
125717
125997
|
domainLlmPromises.push((async () => {
|
|
125718
125998
|
const detCount = violationsByDomain.get(domain) ?? 0;
|
|
125719
125999
|
log.info(`[LLM] ${domain}: starting (${batches.length} code batches)`);
|
|
125720
126000
|
const t0 = Date.now();
|
|
125721
|
-
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
|
+
}));
|
|
125722
126011
|
const rawViolations = [];
|
|
125723
126012
|
const resolvedIds = [];
|
|
125724
126013
|
const unchangedIds = [];
|
|
@@ -125744,15 +126033,19 @@ async function runViolationPipeline(input) {
|
|
|
125744
126033
|
}
|
|
125745
126034
|
let dbSchemaViolations = [];
|
|
125746
126035
|
if (dbSchemaContext && !llmSkipped) {
|
|
125747
|
-
|
|
125748
|
-
const detCount = violationsByDomain.get("database") ?? 0;
|
|
125749
|
-
tracker?.start("database", detCount > 0 ? `${detCount} det, running LLM...` : "Running LLM...");
|
|
125750
|
-
}
|
|
126036
|
+
const schemaLl = domainCodeBatches.has("database") ? void 0 : llmTrackers.get("database");
|
|
125751
126037
|
domainLlmPromises.push((async () => {
|
|
125752
126038
|
log.info(`[LLM] database-schema: starting`);
|
|
125753
126039
|
const t0 = Date.now();
|
|
126040
|
+
let started = false;
|
|
125754
126041
|
try {
|
|
125755
|
-
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);
|
|
125756
126049
|
const dur = Date.now() - t0;
|
|
125757
126050
|
log.info(`[LLM] database-schema: done in ${dur}ms \u2014 ${dbResult.violations.length} violations`);
|
|
125758
126051
|
for (const v of dbResult.violations) {
|
|
@@ -125792,6 +126085,7 @@ async function runViolationPipeline(input) {
|
|
|
125792
126085
|
}
|
|
125793
126086
|
return { domain: "database-schema", violations: [], resolvedIds: [], unchangedIds: [] };
|
|
125794
126087
|
} catch (err) {
|
|
126088
|
+
schemaLl?.onCallDone(started);
|
|
125795
126089
|
const dur = Date.now() - t0;
|
|
125796
126090
|
log.warn(`[LLM] database-schema: failed in ${dur}ms \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
125797
126091
|
if (!domainCodeBatches.has("database"))
|
|
@@ -125805,8 +126099,17 @@ async function runViolationPipeline(input) {
|
|
|
125805
126099
|
const llmRulePromise = (async () => {
|
|
125806
126100
|
if (enableLlmRules === false || llmSkipped)
|
|
125807
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
|
+
};
|
|
125808
126111
|
if (hasLlmOnlyExistingViolations) {
|
|
125809
|
-
const archResult = await generateViolationsWithLifecycle(violationInput,
|
|
126112
|
+
const archResult = await generateViolationsWithLifecycle(violationInput, void 0, provider, archOnCallStart, archOnCallDone);
|
|
125810
126113
|
serviceDescriptions = archResult.serviceDescriptions;
|
|
125811
126114
|
allResolvedLlmIds.push(...archResult.resolvedViolationIds);
|
|
125812
126115
|
allNewLlmItems.push(...archResult.newViolations);
|
|
@@ -125828,7 +126131,7 @@ async function runViolationPipeline(input) {
|
|
|
125828
126131
|
resolved.push(...lifecycle.resolved);
|
|
125829
126132
|
resolvedRefs.push(...lifecycle.resolvedRefs);
|
|
125830
126133
|
} else {
|
|
125831
|
-
const archResult = await generateViolations(violationInput,
|
|
126134
|
+
const archResult = await generateViolations(violationInput, void 0, provider, archOnCallStart, archOnCallDone);
|
|
125832
126135
|
serviceDescriptions = archResult.serviceDescriptions;
|
|
125833
126136
|
for (const v of archResult.violations) {
|
|
125834
126137
|
added.push({
|
|
@@ -126632,8 +126935,11 @@ import path16 from "node:path";
|
|
|
126632
126935
|
init_analysis_store();
|
|
126633
126936
|
init_analyze_core();
|
|
126634
126937
|
init_analyze_persist();
|
|
126938
|
+
init_config2();
|
|
126939
|
+
init_logger();
|
|
126635
126940
|
async function analyzeInProcess(project, options = {}) {
|
|
126636
126941
|
const startedAt = Date.now();
|
|
126942
|
+
log.info(`[LLM] Provider: claude-code, model: ${config.claudeCodeModel || "default"}, maxConcurrency: ${config.claudeCodeMaxConcurrency}`);
|
|
126637
126943
|
const core2 = await analyzeCore(project, { ...options, mode: "full" });
|
|
126638
126944
|
return persistFullAnalysis(project, core2, startedAt);
|
|
126639
126945
|
}
|
|
@@ -130730,7 +131036,7 @@ async function runHooksRun() {
|
|
|
130730
131036
|
|
|
130731
131037
|
// tools/cli/src/index.ts
|
|
130732
131038
|
var program2 = new Command();
|
|
130733
|
-
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");
|
|
130734
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) => {
|
|
130735
131041
|
if (options.service && options.console) {
|
|
130736
131042
|
console.error("error: --service and --console are mutually exclusive");
|