truecourse 0.4.2 → 0.4.3
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 +1 -0
- package/cli.mjs +165 -212
- package/package.json +1 -1
- package/server.mjs +66 -55
package/README.md
CHANGED
|
@@ -65,6 +65,7 @@ truecourse add # Register repo without analyzing
|
|
|
65
65
|
|
|
66
66
|
# Dashboard (web UI)
|
|
67
67
|
truecourse dashboard # Start + open the dashboard
|
|
68
|
+
truecourse dashboard --reconfigure # Re-prompt for console vs background service mode
|
|
68
69
|
truecourse dashboard stop # Stop the dashboard
|
|
69
70
|
truecourse dashboard status # Show dashboard status
|
|
70
71
|
truecourse dashboard logs # Tail dashboard logs (service mode only)
|
package/cli.mjs
CHANGED
|
@@ -4148,7 +4148,6 @@ __export(helpers_exports, {
|
|
|
4148
4148
|
renderDiffResultsSummary: () => renderDiffResultsSummary,
|
|
4149
4149
|
renderViolations: () => renderViolations,
|
|
4150
4150
|
renderViolationsSummary: () => renderViolationsSummary,
|
|
4151
|
-
requireDashboard: () => requireDashboard,
|
|
4152
4151
|
requireRegisteredRepo: () => requireRegisteredRepo,
|
|
4153
4152
|
severityColor: () => severityColor,
|
|
4154
4153
|
severityIcon: () => severityIcon,
|
|
@@ -4185,19 +4184,6 @@ function getServerUrl() {
|
|
|
4185
4184
|
const port = process.env.PORT || DEFAULT_PORT;
|
|
4186
4185
|
return `http://localhost:${port}`;
|
|
4187
4186
|
}
|
|
4188
|
-
async function requireDashboard() {
|
|
4189
|
-
const url = getServerUrl();
|
|
4190
|
-
try {
|
|
4191
|
-
const res = await fetch(`${url}/api/health`);
|
|
4192
|
-
if (!res.ok) throw new Error();
|
|
4193
|
-
} catch {
|
|
4194
|
-
O2.error(
|
|
4195
|
-
`TrueCourse dashboard is not running at ${url}.
|
|
4196
|
-
Start it first with: truecourse dashboard`
|
|
4197
|
-
);
|
|
4198
|
-
process.exit(1);
|
|
4199
|
-
}
|
|
4200
|
-
}
|
|
4201
4187
|
function requireRegisteredRepo() {
|
|
4202
4188
|
const repoDir = resolveRepoDir(process.cwd());
|
|
4203
4189
|
if (!repoDir) {
|
|
@@ -9787,6 +9773,16 @@ function readProjectConfig(repoDir) {
|
|
|
9787
9773
|
return { ...EMPTY };
|
|
9788
9774
|
}
|
|
9789
9775
|
}
|
|
9776
|
+
function writeProjectConfig(repoDir, config2) {
|
|
9777
|
+
ensureRepoTruecourseDir(repoDir);
|
|
9778
|
+
fs5.writeFileSync(getRepoConfigPath(repoDir), JSON.stringify(config2, null, 2), "utf-8");
|
|
9779
|
+
}
|
|
9780
|
+
function updateProjectConfig(repoDir, patch) {
|
|
9781
|
+
const current = readProjectConfig(repoDir);
|
|
9782
|
+
const next = { ...current, ...patch };
|
|
9783
|
+
writeProjectConfig(repoDir, next);
|
|
9784
|
+
return next;
|
|
9785
|
+
}
|
|
9790
9786
|
var EMPTY;
|
|
9791
9787
|
var init_project_config = __esm({
|
|
9792
9788
|
"apps/server/dist/config/project-config.js"() {
|
|
@@ -94126,6 +94122,12 @@ function appendHistory(repoPath, entry) {
|
|
|
94126
94122
|
history.analyses.push(entry);
|
|
94127
94123
|
atomicWriteJson(historyPath(repoPath), history);
|
|
94128
94124
|
}
|
|
94125
|
+
function readDiff(repoPath) {
|
|
94126
|
+
const file = diffPath(repoPath);
|
|
94127
|
+
if (!fs7.existsSync(file))
|
|
94128
|
+
return null;
|
|
94129
|
+
return JSON.parse(fs7.readFileSync(file, "utf-8"));
|
|
94130
|
+
}
|
|
94129
94131
|
function writeDiff(repoPath, diff) {
|
|
94130
94132
|
atomicWriteJson(diffPath(repoPath), diff);
|
|
94131
94133
|
}
|
|
@@ -120642,80 +120644,6 @@ var init_schemas = __esm({
|
|
|
120642
120644
|
});
|
|
120643
120645
|
|
|
120644
120646
|
// packages/shared/dist/index.js
|
|
120645
|
-
var dist_exports3 = {};
|
|
120646
|
-
__export(dist_exports3, {
|
|
120647
|
-
AnalysisRuleSchema: () => AnalysisRuleSchema,
|
|
120648
|
-
AnalyzeRepoSchema: () => AnalyzeRepoSchema,
|
|
120649
|
-
ArchitectureSchema: () => ArchitectureSchema,
|
|
120650
|
-
ArchitectureSummarySchema: () => ArchitectureSummarySchema,
|
|
120651
|
-
ArchitectureSummaryServiceSchema: () => ArchitectureSummaryServiceSchema,
|
|
120652
|
-
BreakdownResponseSchema: () => BreakdownResponseSchema,
|
|
120653
|
-
CODE_DOMAINS: () => CODE_DOMAINS,
|
|
120654
|
-
CallExpressionSchema: () => CallExpressionSchema,
|
|
120655
|
-
ClassDefinitionSchema: () => ClassDefinitionSchema,
|
|
120656
|
-
ClassPropertySchema: () => ClassPropertySchema,
|
|
120657
|
-
ColumnInfoSchema: () => ColumnInfoSchema,
|
|
120658
|
-
ContextRequirementSchema: () => ContextRequirementSchema,
|
|
120659
|
-
ContextTierSchema: () => ContextTierSchema,
|
|
120660
|
-
CreateRepoSchema: () => CreateRepoSchema,
|
|
120661
|
-
DEFAULT_DOMAINS: () => DEFAULT_DOMAINS,
|
|
120662
|
-
DOMAIN_ORDER: () => DOMAIN_ORDER,
|
|
120663
|
-
DatabaseConnectionInfoSchema: () => DatabaseConnectionInfoSchema,
|
|
120664
|
-
DatabaseDetectionResultSchema: () => DatabaseDetectionResultSchema,
|
|
120665
|
-
DatabaseInfoSchema: () => DatabaseInfoSchema,
|
|
120666
|
-
DatabaseTypeSchema: () => DatabaseTypeSchema,
|
|
120667
|
-
EntityFieldSchema: () => EntityFieldSchema,
|
|
120668
|
-
EntityRelationshipSchema: () => EntityRelationshipSchema,
|
|
120669
|
-
EntitySchema: () => EntitySchema,
|
|
120670
|
-
ExportStatementSchema: () => ExportStatementSchema,
|
|
120671
|
-
FileAnalysisSchema: () => FileAnalysisSchema,
|
|
120672
|
-
FileFilterSchema: () => FileFilterSchema,
|
|
120673
|
-
FlowSchema: () => FlowSchema,
|
|
120674
|
-
FlowStepSchema: () => FlowStepSchema,
|
|
120675
|
-
FlowStepTypeSchema: () => FlowStepTypeSchema,
|
|
120676
|
-
FlowTriggerSchema: () => FlowTriggerSchema,
|
|
120677
|
-
FunctionDefinitionSchema: () => FunctionDefinitionSchema,
|
|
120678
|
-
FunctionFilterSchema: () => FunctionFilterSchema,
|
|
120679
|
-
GenerateViolationsSchema: () => GenerateViolationsSchema,
|
|
120680
|
-
HttpCallSchema: () => HttpCallSchema,
|
|
120681
|
-
ImportSpecifierSchema: () => ImportSpecifierSchema,
|
|
120682
|
-
ImportStatementSchema: () => ImportStatementSchema,
|
|
120683
|
-
IndexInfoSchema: () => IndexInfoSchema,
|
|
120684
|
-
LayerDependencyInfoSchema: () => LayerDependencyInfoSchema,
|
|
120685
|
-
LayerDetailSchema: () => LayerDetailSchema,
|
|
120686
|
-
LayerDetectionResultSchema: () => LayerDetectionResultSchema,
|
|
120687
|
-
LayerSchema: () => LayerSchema,
|
|
120688
|
-
MethodInfoSchema: () => MethodInfoSchema,
|
|
120689
|
-
MethodLevelDependencySchema: () => MethodLevelDependencySchema,
|
|
120690
|
-
ModuleDependencySchema: () => ModuleDependencySchema,
|
|
120691
|
-
ModuleInfoSchema: () => ModuleInfoSchema,
|
|
120692
|
-
ModuleKindSchema: () => ModuleKindSchema,
|
|
120693
|
-
ModuleLevelDependencySchema: () => ModuleLevelDependencySchema,
|
|
120694
|
-
ParameterSchema: () => ParameterSchema,
|
|
120695
|
-
RelationInfoSchema: () => RelationInfoSchema,
|
|
120696
|
-
ResolutionResponseSchema: () => ResolutionResponseSchema,
|
|
120697
|
-
RouteRegistrationSchema: () => RouteRegistrationSchema,
|
|
120698
|
-
RouterMountSchema: () => RouterMountSchema,
|
|
120699
|
-
RuleCategorySchema: () => RuleCategorySchema,
|
|
120700
|
-
RuleDomainSchema: () => RuleDomainSchema,
|
|
120701
|
-
RuleSeveritySchema: () => RuleSeveritySchema,
|
|
120702
|
-
RuleTypeSchema: () => RuleTypeSchema,
|
|
120703
|
-
ServiceDependencyDetailSchema: () => ServiceDependencyDetailSchema,
|
|
120704
|
-
ServiceDependencyInfoSchema: () => ServiceDependencyInfoSchema,
|
|
120705
|
-
ServiceInfoSchema: () => ServiceInfoSchema,
|
|
120706
|
-
ServiceTypeSchema: () => ServiceTypeSchema,
|
|
120707
|
-
SourceLocationSchema: () => SourceLocationSchema,
|
|
120708
|
-
SupportedLanguageSchema: () => SupportedLanguageSchema,
|
|
120709
|
-
TableInfoSchema: () => TableInfoSchema,
|
|
120710
|
-
TopOffenderSchema: () => TopOffenderSchema,
|
|
120711
|
-
TopOffendersResponseSchema: () => TopOffendersResponseSchema,
|
|
120712
|
-
TrendDataPointSchema: () => TrendDataPointSchema,
|
|
120713
|
-
TrendResponseSchema: () => TrendResponseSchema,
|
|
120714
|
-
ViolationSchema: () => ViolationSchema,
|
|
120715
|
-
ViolationSeveritySchema: () => ViolationSeveritySchema,
|
|
120716
|
-
ViolationStatusSchema: () => ViolationStatusSchema,
|
|
120717
|
-
ViolationTypeSchema: () => ViolationTypeSchema
|
|
120718
|
-
});
|
|
120719
120647
|
var init_dist7 = __esm({
|
|
120720
120648
|
"packages/shared/dist/index.js"() {
|
|
120721
120649
|
"use strict";
|
|
@@ -127313,7 +127241,7 @@ function targetUrlFor(baseUrl) {
|
|
|
127313
127241
|
const repoDir = resolveRepoDir(process.cwd());
|
|
127314
127242
|
if (!repoDir) return baseUrl;
|
|
127315
127243
|
const entry = getProjectByPath(repoDir) ?? registerProject(repoDir);
|
|
127316
|
-
return `${baseUrl}/
|
|
127244
|
+
return `${baseUrl}/repos/${entry.slug}`;
|
|
127317
127245
|
}
|
|
127318
127246
|
async function promptRunMode() {
|
|
127319
127247
|
const choice = await _t({
|
|
@@ -127388,7 +127316,7 @@ async function runServiceMode(serverEntry) {
|
|
|
127388
127316
|
O2.success(`Dashboard open at ${target}`);
|
|
127389
127317
|
O2.info("Stop the dashboard with: truecourse dashboard stop");
|
|
127390
127318
|
}
|
|
127391
|
-
async function runDashboard() {
|
|
127319
|
+
async function runDashboard(options = {}) {
|
|
127392
127320
|
mt("Opening TrueCourse dashboard");
|
|
127393
127321
|
const serverEntry = resolveServerEntry();
|
|
127394
127322
|
if (!serverEntry) {
|
|
@@ -127398,8 +127326,9 @@ async function runDashboard() {
|
|
|
127398
127326
|
process.exit(1);
|
|
127399
127327
|
}
|
|
127400
127328
|
const configured = fs14.existsSync(getConfigPath());
|
|
127401
|
-
const
|
|
127402
|
-
|
|
127329
|
+
const shouldPrompt = !configured || options.reconfigure;
|
|
127330
|
+
const runMode = shouldPrompt ? await promptRunMode() : readConfig().runMode;
|
|
127331
|
+
if (shouldPrompt) writeConfig({ runMode });
|
|
127403
127332
|
if (runMode === "service") {
|
|
127404
127333
|
try {
|
|
127405
127334
|
await runServiceMode(serverEntry);
|
|
@@ -127497,55 +127426,163 @@ async function runDashboardUninstall() {
|
|
|
127497
127426
|
|
|
127498
127427
|
// tools/cli/src/commands/list.ts
|
|
127499
127428
|
init_dist4();
|
|
127429
|
+
|
|
127430
|
+
// apps/server/dist/services/violation-query.service.js
|
|
127431
|
+
init_analysis_store();
|
|
127432
|
+
var SEVERITY_ORDER = {
|
|
127433
|
+
critical: 0,
|
|
127434
|
+
high: 1,
|
|
127435
|
+
medium: 2,
|
|
127436
|
+
low: 3,
|
|
127437
|
+
info: 4
|
|
127438
|
+
};
|
|
127439
|
+
function listViolations(repoPath, options = {}) {
|
|
127440
|
+
const latest = readLatest(repoPath);
|
|
127441
|
+
if (!latest)
|
|
127442
|
+
return { violations: [], total: 0 };
|
|
127443
|
+
if (options.analysisId && latest.analysis.id !== options.analysisId) {
|
|
127444
|
+
return { violations: [], total: 0 };
|
|
127445
|
+
}
|
|
127446
|
+
const statusMode = options.status ?? "active";
|
|
127447
|
+
let filtered;
|
|
127448
|
+
if (statusMode === "resolved") {
|
|
127449
|
+
filtered = latest.violations.filter((v) => v.status === "resolved");
|
|
127450
|
+
} else if (statusMode === "all") {
|
|
127451
|
+
filtered = latest.violations;
|
|
127452
|
+
} else {
|
|
127453
|
+
const active = ["new", "unchanged"];
|
|
127454
|
+
filtered = latest.violations.filter((v) => active.includes(v.status));
|
|
127455
|
+
}
|
|
127456
|
+
if (options.filePath) {
|
|
127457
|
+
const absPath = options.filePath.startsWith("/") ? options.filePath : `${repoPath}/${options.filePath}`;
|
|
127458
|
+
filtered = filtered.filter((v) => v.type === "code" && (v.filePath === absPath || v.filePath === options.filePath));
|
|
127459
|
+
}
|
|
127460
|
+
filtered.sort((a, b) => {
|
|
127461
|
+
const sa = SEVERITY_ORDER[a.severity] ?? 5;
|
|
127462
|
+
const sb = SEVERITY_ORDER[b.severity] ?? 5;
|
|
127463
|
+
if (sa !== sb)
|
|
127464
|
+
return sa - sb;
|
|
127465
|
+
return b.createdAt.localeCompare(a.createdAt);
|
|
127466
|
+
});
|
|
127467
|
+
const total = filtered.length;
|
|
127468
|
+
const limit = options.limit ?? 0;
|
|
127469
|
+
const offset = options.offset ?? 0;
|
|
127470
|
+
const paged = limit > 0 ? filtered.slice(offset, offset + limit) : filtered;
|
|
127471
|
+
return { violations: paged, total };
|
|
127472
|
+
}
|
|
127473
|
+
function getDiffResult(repoPath) {
|
|
127474
|
+
const diff = readDiff(repoPath);
|
|
127475
|
+
if (!diff)
|
|
127476
|
+
return null;
|
|
127477
|
+
const latest = readLatest(repoPath);
|
|
127478
|
+
const isStale = latest ? latest.analysis.id !== diff.baseAnalysisId : false;
|
|
127479
|
+
return { diff, isStale };
|
|
127480
|
+
}
|
|
127481
|
+
|
|
127482
|
+
// tools/cli/src/commands/list.ts
|
|
127500
127483
|
init_helpers();
|
|
127501
127484
|
async function runList({ limit = 20, offset = 0 } = {}) {
|
|
127502
127485
|
mt("Violations");
|
|
127503
|
-
await requireDashboard();
|
|
127504
127486
|
const repo = requireRegisteredRepo();
|
|
127505
|
-
const
|
|
127506
|
-
|
|
127507
|
-
|
|
127508
|
-
|
|
127509
|
-
|
|
127510
|
-
|
|
127511
|
-
|
|
127512
|
-
const url = `${serverUrl}/api/repos/${repo.id}/violations${params.toString() ? `?${params}` : ""}`;
|
|
127513
|
-
const res = await fetch(url);
|
|
127514
|
-
if (!res.ok) {
|
|
127515
|
-
O2.error(`Failed to fetch violations: ${res.status}`);
|
|
127516
|
-
process.exit(1);
|
|
127517
|
-
}
|
|
127518
|
-
const body = await res.json();
|
|
127519
|
-
if (Array.isArray(body)) {
|
|
127520
|
-
renderViolations(body, { total: body.length });
|
|
127521
|
-
} else {
|
|
127522
|
-
const { violations, total } = body;
|
|
127523
|
-
renderViolations(violations, { total, offset });
|
|
127487
|
+
const { violations, total } = listViolations(repo.path, {
|
|
127488
|
+
limit: isFinite(limit) ? limit : 0,
|
|
127489
|
+
offset
|
|
127490
|
+
});
|
|
127491
|
+
if (total === 0 && violations.length === 0) {
|
|
127492
|
+
O2.info("No violations. Run `truecourse analyze` if you haven't yet.");
|
|
127493
|
+
return;
|
|
127524
127494
|
}
|
|
127495
|
+
renderViolations(violations, { total, offset });
|
|
127525
127496
|
}
|
|
127526
127497
|
async function runListDiff() {
|
|
127527
127498
|
mt("Diff check results");
|
|
127528
|
-
await requireDashboard();
|
|
127529
127499
|
const repo = requireRegisteredRepo();
|
|
127530
|
-
const
|
|
127531
|
-
const res = await fetch(`${serverUrl}/api/repos/${repo.id}/diff-check`);
|
|
127532
|
-
if (!res.ok) {
|
|
127533
|
-
O2.error(`Failed to fetch diff check results: ${res.status}`);
|
|
127534
|
-
process.exit(1);
|
|
127535
|
-
}
|
|
127536
|
-
const result = await res.json();
|
|
127500
|
+
const result = getDiffResult(repo.path);
|
|
127537
127501
|
if (!result) {
|
|
127538
127502
|
O2.info("No diff check found. Run `truecourse analyze --diff` first.");
|
|
127539
127503
|
return;
|
|
127540
127504
|
}
|
|
127541
|
-
|
|
127505
|
+
const { diff, isStale } = result;
|
|
127506
|
+
if (isStale) {
|
|
127542
127507
|
O2.warn("Results may be stale \u2014 baseline analysis has changed.");
|
|
127543
127508
|
}
|
|
127544
|
-
|
|
127509
|
+
const rendered = {
|
|
127510
|
+
isStale,
|
|
127511
|
+
changedFiles: diff.changedFiles,
|
|
127512
|
+
newViolations: diff.newViolations,
|
|
127513
|
+
resolvedViolations: diff.resolvedViolations,
|
|
127514
|
+
summary: { newCount: diff.summary.newCount, resolvedCount: diff.summary.resolvedCount }
|
|
127515
|
+
};
|
|
127516
|
+
renderDiffResults(rendered);
|
|
127545
127517
|
}
|
|
127546
127518
|
|
|
127547
|
-
// tools/cli/src/
|
|
127519
|
+
// tools/cli/src/commands/rules.ts
|
|
127520
|
+
init_dist4();
|
|
127521
|
+
init_dist7();
|
|
127522
|
+
init_project_config();
|
|
127548
127523
|
init_helpers();
|
|
127524
|
+
var ALL_CATEGORIES = [...DOMAIN_ORDER];
|
|
127525
|
+
async function runRulesCategories(options) {
|
|
127526
|
+
const repo = requireRegisteredRepo();
|
|
127527
|
+
if (options.reset) {
|
|
127528
|
+
updateProjectConfig(repo.path, { enabledCategories: null });
|
|
127529
|
+
O2.success("Reset to global default categories.");
|
|
127530
|
+
return;
|
|
127531
|
+
}
|
|
127532
|
+
if (options.enable || options.disable) {
|
|
127533
|
+
const cat = options.enable ?? options.disable;
|
|
127534
|
+
if (!ALL_CATEGORIES.includes(cat)) {
|
|
127535
|
+
O2.error(`Invalid category: ${cat}. Valid: ${ALL_CATEGORIES.join(", ")}`);
|
|
127536
|
+
process.exit(1);
|
|
127537
|
+
}
|
|
127538
|
+
const config3 = readProjectConfig(repo.path);
|
|
127539
|
+
const hasOverride = config3.enabledCategories != null;
|
|
127540
|
+
const current = new Set(hasOverride ? config3.enabledCategories : ALL_CATEGORIES);
|
|
127541
|
+
if (options.enable) current.add(cat);
|
|
127542
|
+
else current.delete(cat);
|
|
127543
|
+
updateProjectConfig(repo.path, { enabledCategories: [...current] });
|
|
127544
|
+
O2.success(`${options.enable ? "Enabled" : "Disabled"} ${cat} rules for ${repo.name}.`);
|
|
127545
|
+
return;
|
|
127546
|
+
}
|
|
127547
|
+
const config2 = readProjectConfig(repo.path);
|
|
127548
|
+
const isOverride = config2.enabledCategories != null;
|
|
127549
|
+
const enabled = new Set(isOverride ? config2.enabledCategories : ALL_CATEGORIES);
|
|
127550
|
+
const status = (cat) => enabled.has(cat) ? "\x1B[32menabled\x1B[0m" : "\x1B[31mdisabled\x1B[0m";
|
|
127551
|
+
O2.info(
|
|
127552
|
+
`Rule categories for ${repo.name}${isOverride ? " (per-repo override)" : " (global default)"}:`
|
|
127553
|
+
);
|
|
127554
|
+
for (const cat of ALL_CATEGORIES) {
|
|
127555
|
+
console.log(` ${cat.padEnd(14)} ${status(cat)}`);
|
|
127556
|
+
}
|
|
127557
|
+
console.log("");
|
|
127558
|
+
if (!isOverride) {
|
|
127559
|
+
O2.info("Override with: truecourse rules categories --enable/--disable <name>");
|
|
127560
|
+
}
|
|
127561
|
+
}
|
|
127562
|
+
async function runRulesLlm(options) {
|
|
127563
|
+
const repo = requireRegisteredRepo();
|
|
127564
|
+
if (options.reset) {
|
|
127565
|
+
updateProjectConfig(repo.path, { enableLlmRules: null });
|
|
127566
|
+
O2.success("Reset LLM rules to global default.");
|
|
127567
|
+
return;
|
|
127568
|
+
}
|
|
127569
|
+
if (options.enable || options.disable) {
|
|
127570
|
+
const enabled = !!options.enable;
|
|
127571
|
+
updateProjectConfig(repo.path, { enableLlmRules: enabled });
|
|
127572
|
+
O2.success(`LLM rules ${enabled ? "enabled" : "disabled"} for ${repo.name}.`);
|
|
127573
|
+
return;
|
|
127574
|
+
}
|
|
127575
|
+
const config2 = readProjectConfig(repo.path);
|
|
127576
|
+
const isOverride = config2.enableLlmRules != null;
|
|
127577
|
+
const effective = isOverride ? config2.enableLlmRules : true;
|
|
127578
|
+
const status = effective ? "\x1B[32menabled\x1B[0m" : "\x1B[31mdisabled\x1B[0m";
|
|
127579
|
+
O2.info(
|
|
127580
|
+
`LLM rules for ${repo.name}${isOverride ? " (per-repo override)" : " (global default)"}: ${status}`
|
|
127581
|
+
);
|
|
127582
|
+
if (!isOverride) {
|
|
127583
|
+
O2.info("Override with: truecourse rules llm --enable/--disable");
|
|
127584
|
+
}
|
|
127585
|
+
}
|
|
127549
127586
|
|
|
127550
127587
|
// tools/cli/src/commands/hooks.ts
|
|
127551
127588
|
import { execSync as execSync5 } from "node:child_process";
|
|
@@ -130437,9 +130474,9 @@ async function runHooksRun() {
|
|
|
130437
130474
|
|
|
130438
130475
|
// tools/cli/src/index.ts
|
|
130439
130476
|
var program2 = new Command();
|
|
130440
|
-
program2.name("truecourse").version("0.4.
|
|
130441
|
-
var dashboardCmd = program2.command("dashboard").description("Start the TrueCourse dashboard and open it in your browser").action(async () => {
|
|
130442
|
-
await runDashboard();
|
|
130477
|
+
program2.name("truecourse").version("0.4.3").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
|
|
130478
|
+
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").action(async (options) => {
|
|
130479
|
+
await runDashboard({ reconfigure: options.reconfigure });
|
|
130443
130480
|
});
|
|
130444
130481
|
dashboardCmd.command("stop").description("Stop the dashboard").action(async () => {
|
|
130445
130482
|
await runDashboardStop();
|
|
@@ -130475,94 +130512,10 @@ program2.command("list").description("List violations from the latest analysis")
|
|
|
130475
130512
|
});
|
|
130476
130513
|
var rulesCmd = program2.command("rules").description("Manage analysis rules");
|
|
130477
130514
|
rulesCmd.command("categories").description("View or override rule categories for this repository").option("--enable <category>", "Enable a category").option("--disable <category>", "Disable a category").option("--reset", "Reset to global default").action(async (options) => {
|
|
130478
|
-
await
|
|
130479
|
-
const repo = requireRegisteredRepo();
|
|
130480
|
-
const serverUrl = getServerUrl();
|
|
130481
|
-
const { DOMAIN_ORDER: DOMAIN_ORDER2 } = await Promise.resolve().then(() => (init_dist7(), dist_exports3));
|
|
130482
|
-
const allCategories = [...DOMAIN_ORDER2];
|
|
130483
|
-
if (options.reset) {
|
|
130484
|
-
await fetch(`${serverUrl}/api/repos/${repo.id}/categories`, {
|
|
130485
|
-
method: "PUT",
|
|
130486
|
-
headers: { "Content-Type": "application/json" },
|
|
130487
|
-
body: JSON.stringify({ enabledCategories: null })
|
|
130488
|
-
});
|
|
130489
|
-
O2.success("Reset to global default categories.");
|
|
130490
|
-
return;
|
|
130491
|
-
}
|
|
130492
|
-
if (options.enable || options.disable) {
|
|
130493
|
-
const cat = options.enable || options.disable;
|
|
130494
|
-
if (!allCategories.includes(cat)) {
|
|
130495
|
-
O2.error(`Invalid category: ${cat}. Valid: ${allCategories.join(", ")}`);
|
|
130496
|
-
process.exit(1);
|
|
130497
|
-
}
|
|
130498
|
-
const repoRes2 = await fetch(`${serverUrl}/api/repos/${repo.id}`);
|
|
130499
|
-
const repoData2 = await repoRes2.json();
|
|
130500
|
-
const hasOverride = repoData2.enabledCategories !== null && repoData2.enabledCategories !== void 0;
|
|
130501
|
-
const current = new Set(
|
|
130502
|
-
hasOverride ? repoData2.enabledCategories : allCategories
|
|
130503
|
-
);
|
|
130504
|
-
if (options.enable) current.add(cat);
|
|
130505
|
-
else current.delete(cat);
|
|
130506
|
-
await fetch(`${serverUrl}/api/repos/${repo.id}/categories`, {
|
|
130507
|
-
method: "PUT",
|
|
130508
|
-
headers: { "Content-Type": "application/json" },
|
|
130509
|
-
body: JSON.stringify({ enabledCategories: [...current] })
|
|
130510
|
-
});
|
|
130511
|
-
O2.success(`${options.enable ? "Enabled" : "Disabled"} ${cat} rules for ${repo.name}.`);
|
|
130512
|
-
return;
|
|
130513
|
-
}
|
|
130514
|
-
const repoRes = await fetch(`${serverUrl}/api/repos/${repo.id}`);
|
|
130515
|
-
const repoData = await repoRes.json();
|
|
130516
|
-
const isOverride = repoData.enabledCategories !== null && repoData.enabledCategories !== void 0;
|
|
130517
|
-
const enabled = new Set(
|
|
130518
|
-
isOverride ? repoData.enabledCategories : allCategories
|
|
130519
|
-
);
|
|
130520
|
-
const status = (cat) => enabled.has(cat) ? "\x1B[32menabled\x1B[0m" : "\x1B[31mdisabled\x1B[0m";
|
|
130521
|
-
O2.info(
|
|
130522
|
-
`Rule categories for ${repo.name}${isOverride ? " (per-repo override)" : " (global default)"}:`
|
|
130523
|
-
);
|
|
130524
|
-
for (const cat of allCategories) {
|
|
130525
|
-
console.log(` ${cat.padEnd(14)} ${status(cat)}`);
|
|
130526
|
-
}
|
|
130527
|
-
console.log("");
|
|
130528
|
-
if (!isOverride) {
|
|
130529
|
-
O2.info("Override with: truecourse rules categories --enable/--disable <name>");
|
|
130530
|
-
}
|
|
130515
|
+
await runRulesCategories(options);
|
|
130531
130516
|
});
|
|
130532
130517
|
rulesCmd.command("llm").description("Enable or disable LLM-powered rules for this repository").option("--enable", "Enable LLM rules").option("--disable", "Disable LLM rules").option("--reset", "Reset to global default").action(async (options) => {
|
|
130533
|
-
await
|
|
130534
|
-
const repo = requireRegisteredRepo();
|
|
130535
|
-
const serverUrl = getServerUrl();
|
|
130536
|
-
if (options.reset) {
|
|
130537
|
-
await fetch(`${serverUrl}/api/repos/${repo.id}/llm`, {
|
|
130538
|
-
method: "PUT",
|
|
130539
|
-
headers: { "Content-Type": "application/json" },
|
|
130540
|
-
body: JSON.stringify({ enableLlmRules: null })
|
|
130541
|
-
});
|
|
130542
|
-
O2.success("Reset LLM rules to global default.");
|
|
130543
|
-
return;
|
|
130544
|
-
}
|
|
130545
|
-
if (options.enable || options.disable) {
|
|
130546
|
-
const enabled = !!options.enable;
|
|
130547
|
-
await fetch(`${serverUrl}/api/repos/${repo.id}/llm`, {
|
|
130548
|
-
method: "PUT",
|
|
130549
|
-
headers: { "Content-Type": "application/json" },
|
|
130550
|
-
body: JSON.stringify({ enableLlmRules: enabled })
|
|
130551
|
-
});
|
|
130552
|
-
O2.success(`LLM rules ${enabled ? "enabled" : "disabled"} for ${repo.name}.`);
|
|
130553
|
-
return;
|
|
130554
|
-
}
|
|
130555
|
-
const repoRes = await fetch(`${serverUrl}/api/repos/${repo.id}`);
|
|
130556
|
-
const repoData = await repoRes.json();
|
|
130557
|
-
const isOverride = repoData.enableLlmRules !== null && repoData.enableLlmRules !== void 0;
|
|
130558
|
-
const effective = isOverride ? repoData.enableLlmRules : true;
|
|
130559
|
-
const status = effective ? "\x1B[32menabled\x1B[0m" : "\x1B[31mdisabled\x1B[0m";
|
|
130560
|
-
O2.info(
|
|
130561
|
-
`LLM rules for ${repo.name}${isOverride ? " (per-repo override)" : " (global default)"}: ${status}`
|
|
130562
|
-
);
|
|
130563
|
-
if (!isOverride) {
|
|
130564
|
-
O2.info("Override with: truecourse rules llm --enable/--disable");
|
|
130565
|
-
}
|
|
130518
|
+
await runRulesLlm(options);
|
|
130566
130519
|
});
|
|
130567
130520
|
var telemetryCmd = program2.command("telemetry").description("Manage anonymous usage telemetry");
|
|
130568
130521
|
telemetryCmd.command("enable").description("Enable anonymous usage telemetry").action(() => {
|
package/package.json
CHANGED
package/server.mjs
CHANGED
|
@@ -152951,6 +152951,59 @@ function markDependencyViolations(edges, detViolations) {
|
|
|
152951
152951
|
|
|
152952
152952
|
// apps/server/src/routes/analysis.ts
|
|
152953
152953
|
init_analysis_store();
|
|
152954
|
+
|
|
152955
|
+
// apps/server/src/services/violation-query.service.ts
|
|
152956
|
+
init_analysis_store();
|
|
152957
|
+
var SEVERITY_ORDER2 = {
|
|
152958
|
+
critical: 0,
|
|
152959
|
+
high: 1,
|
|
152960
|
+
medium: 2,
|
|
152961
|
+
low: 3,
|
|
152962
|
+
info: 4
|
|
152963
|
+
};
|
|
152964
|
+
function listViolations(repoPath, options = {}) {
|
|
152965
|
+
const latest = readLatest(repoPath);
|
|
152966
|
+
if (!latest) return { violations: [], total: 0 };
|
|
152967
|
+
if (options.analysisId && latest.analysis.id !== options.analysisId) {
|
|
152968
|
+
return { violations: [], total: 0 };
|
|
152969
|
+
}
|
|
152970
|
+
const statusMode = options.status ?? "active";
|
|
152971
|
+
let filtered;
|
|
152972
|
+
if (statusMode === "resolved") {
|
|
152973
|
+
filtered = latest.violations.filter((v) => v.status === "resolved");
|
|
152974
|
+
} else if (statusMode === "all") {
|
|
152975
|
+
filtered = latest.violations;
|
|
152976
|
+
} else {
|
|
152977
|
+
const active = ["new", "unchanged"];
|
|
152978
|
+
filtered = latest.violations.filter((v) => active.includes(v.status));
|
|
152979
|
+
}
|
|
152980
|
+
if (options.filePath) {
|
|
152981
|
+
const absPath = options.filePath.startsWith("/") ? options.filePath : `${repoPath}/${options.filePath}`;
|
|
152982
|
+
filtered = filtered.filter(
|
|
152983
|
+
(v) => v.type === "code" && (v.filePath === absPath || v.filePath === options.filePath)
|
|
152984
|
+
);
|
|
152985
|
+
}
|
|
152986
|
+
filtered.sort((a, b) => {
|
|
152987
|
+
const sa = SEVERITY_ORDER2[a.severity] ?? 5;
|
|
152988
|
+
const sb = SEVERITY_ORDER2[b.severity] ?? 5;
|
|
152989
|
+
if (sa !== sb) return sa - sb;
|
|
152990
|
+
return b.createdAt.localeCompare(a.createdAt);
|
|
152991
|
+
});
|
|
152992
|
+
const total = filtered.length;
|
|
152993
|
+
const limit = options.limit ?? 0;
|
|
152994
|
+
const offset = options.offset ?? 0;
|
|
152995
|
+
const paged = limit > 0 ? filtered.slice(offset, offset + limit) : filtered;
|
|
152996
|
+
return { violations: paged, total };
|
|
152997
|
+
}
|
|
152998
|
+
function getDiffResult(repoPath) {
|
|
152999
|
+
const diff = readDiff(repoPath);
|
|
153000
|
+
if (!diff) return null;
|
|
153001
|
+
const latest = readLatest(repoPath);
|
|
153002
|
+
const isStale = latest ? latest.analysis.id !== diff.baseAnalysisId : false;
|
|
153003
|
+
return { diff, isStale };
|
|
153004
|
+
}
|
|
153005
|
+
|
|
153006
|
+
// apps/server/src/routes/analysis.ts
|
|
152954
153007
|
var router3 = (0, import_express3.Router)();
|
|
152955
153008
|
router3.get(
|
|
152956
153009
|
"/:id/analyses",
|
|
@@ -153367,13 +153420,12 @@ router3.get(
|
|
|
153367
153420
|
try {
|
|
153368
153421
|
const id = req.params.id;
|
|
153369
153422
|
const repo = resolveProjectForRequest(id);
|
|
153370
|
-
const
|
|
153371
|
-
if (!
|
|
153423
|
+
const result = getDiffResult(repo.path);
|
|
153424
|
+
if (!result) {
|
|
153372
153425
|
res.json(null);
|
|
153373
153426
|
return;
|
|
153374
153427
|
}
|
|
153375
|
-
const
|
|
153376
|
-
const isStale = latest ? latest.analysis.id !== diff.baseAnalysisId : false;
|
|
153428
|
+
const { diff, isStale } = result;
|
|
153377
153429
|
res.json({
|
|
153378
153430
|
resolvedViolations: diff.resolvedViolations,
|
|
153379
153431
|
newViolations: diff.newViolations,
|
|
@@ -153463,68 +153515,27 @@ var analysis_default = router3;
|
|
|
153463
153515
|
var import_express4 = __toESM(require_express2(), 1);
|
|
153464
153516
|
init_analysis_store();
|
|
153465
153517
|
var router4 = (0, import_express4.Router)();
|
|
153466
|
-
var SEVERITY_ORDER2 = {
|
|
153467
|
-
critical: 0,
|
|
153468
|
-
high: 1,
|
|
153469
|
-
medium: 2,
|
|
153470
|
-
low: 3,
|
|
153471
|
-
info: 4
|
|
153472
|
-
};
|
|
153473
153518
|
router4.get(
|
|
153474
153519
|
"/:id/violations",
|
|
153475
153520
|
async (req, res, next) => {
|
|
153476
153521
|
try {
|
|
153477
153522
|
const id = req.params.id;
|
|
153478
153523
|
const repo = resolveProjectForRequest(id);
|
|
153479
|
-
const analysisIdParam = req.query.analysisId;
|
|
153480
|
-
const fileParam = req.query.file;
|
|
153481
|
-
const statusParam = req.query.status;
|
|
153482
153524
|
const limitParam = parseInt(req.query.limit) || 0;
|
|
153483
153525
|
const offsetParam = parseInt(req.query.offset) || 0;
|
|
153484
|
-
|
|
153485
|
-
|
|
153486
|
-
|
|
153487
|
-
|
|
153488
|
-
|
|
153489
|
-
|
|
153490
|
-
|
|
153491
|
-
|
|
153492
|
-
}
|
|
153493
|
-
} else {
|
|
153494
|
-
const latest = readLatest(repo.path);
|
|
153495
|
-
if (!latest) {
|
|
153496
|
-
res.json(limitParam > 0 ? { violations: [], total: 0 } : []);
|
|
153497
|
-
return;
|
|
153498
|
-
}
|
|
153499
|
-
violations = latest.violations;
|
|
153500
|
-
}
|
|
153501
|
-
let filtered;
|
|
153502
|
-
if (statusParam === "resolved") {
|
|
153503
|
-
filtered = violations.filter((v) => v.status === "resolved");
|
|
153504
|
-
} else if (statusParam === "all") {
|
|
153505
|
-
filtered = violations;
|
|
153506
|
-
} else {
|
|
153507
|
-
const active = ["new", "unchanged"];
|
|
153508
|
-
filtered = violations.filter((v) => active.includes(v.status));
|
|
153509
|
-
}
|
|
153510
|
-
if (fileParam) {
|
|
153511
|
-
const absPath = fileParam.startsWith("/") ? fileParam : `${repo.path}/${fileParam}`;
|
|
153512
|
-
filtered = filtered.filter(
|
|
153513
|
-
(v) => v.type === "code" && (v.filePath === absPath || v.filePath === fileParam)
|
|
153514
|
-
);
|
|
153515
|
-
}
|
|
153516
|
-
filtered.sort((a, b) => {
|
|
153517
|
-
const sa = SEVERITY_ORDER2[a.severity] ?? 5;
|
|
153518
|
-
const sb = SEVERITY_ORDER2[b.severity] ?? 5;
|
|
153519
|
-
if (sa !== sb) return sa - sb;
|
|
153520
|
-
return b.createdAt.localeCompare(a.createdAt);
|
|
153526
|
+
const statusParam = req.query.status;
|
|
153527
|
+
const status = statusParam === "resolved" ? "resolved" : statusParam === "all" ? "all" : "active";
|
|
153528
|
+
const { violations, total } = listViolations(repo.path, {
|
|
153529
|
+
analysisId: req.query.analysisId,
|
|
153530
|
+
filePath: req.query.file,
|
|
153531
|
+
status,
|
|
153532
|
+
limit: limitParam,
|
|
153533
|
+
offset: offsetParam
|
|
153521
153534
|
});
|
|
153522
|
-
const total = filtered.length;
|
|
153523
|
-
const paged = limitParam > 0 ? filtered.slice(offsetParam, offsetParam + limitParam) : filtered;
|
|
153524
153535
|
if (limitParam > 0 || offsetParam > 0) {
|
|
153525
|
-
res.json({ violations
|
|
153536
|
+
res.json({ violations, total });
|
|
153526
153537
|
} else {
|
|
153527
|
-
res.json(
|
|
153538
|
+
res.json(violations);
|
|
153528
153539
|
}
|
|
153529
153540
|
} catch (error) {
|
|
153530
153541
|
next(error);
|