truecourse 0.4.1 → 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 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}/projects/${entry.slug}`;
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 runMode = configured ? readConfig().runMode : await promptRunMode();
127402
- if (!configured) writeConfig({ runMode });
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 serverUrl = getServerUrl();
127506
- const showAll = !isFinite(limit);
127507
- const params = new URLSearchParams();
127508
- if (!showAll) {
127509
- params.set("limit", String(limit));
127510
- params.set("offset", String(offset));
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 serverUrl = getServerUrl();
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
- if (result.isStale) {
127505
+ const { diff, isStale } = result;
127506
+ if (isStale) {
127542
127507
  O2.warn("Results may be stale \u2014 baseline analysis has changed.");
127543
127508
  }
127544
- renderDiffResults(result);
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/index.ts
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.1").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
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 requireDashboard();
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 requireDashboard();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "truecourse",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Visualize your codebase architecture as an interactive graph",
5
5
  "type": "module",
6
6
  "bin": {
package/postinstall.cjs CHANGED
@@ -42,30 +42,26 @@ try {
42
42
 
43
43
  const { execSync } = require('child_process');
44
44
 
45
- // Read versions from our own package.json to pin exact ranges
46
- let packages;
47
- try {
48
- const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'));
49
- const opt = pkg.optionalDependencies || {};
50
- packages = ['tree-sitter', 'tree-sitter-typescript', 'tree-sitter-javascript', 'tree-sitter-python']
51
- .map(name => opt[name] ? `${name}@"${opt[name]}"` : name)
52
- .join(' ');
53
- } catch {
54
- packages = 'tree-sitter tree-sitter-typescript tree-sitter-javascript tree-sitter-python';
55
- }
45
+ // The optional-dep install already extracted tree-sitter's source into
46
+ // node_modules/tree-sitter/ — only its native .node binary failed to build
47
+ // (Node 24 needs C++20, default is C++17). `npm rebuild` runs the build
48
+ // scripts on the existing install with our CXXFLAGS, leaving manifests
49
+ // untouched so the install survives later `npm exec` integrity passes
50
+ // that would prune an `npm install --no-save` addition as extraneous.
51
+ const packages = ['tree-sitter', 'tree-sitter-typescript', 'tree-sitter-javascript', 'tree-sitter-python'];
56
52
 
57
- log('Installing tree-sitter with CXXFLAGS="-std=c++20"...');
53
+ log('Rebuilding tree-sitter with CXXFLAGS="-std=c++20"...');
58
54
 
59
55
  try {
60
- execSync(`npm install --no-save ${packages}`, {
56
+ execSync(`npm rebuild ${packages.join(' ')}`, {
61
57
  stdio: 'inherit',
62
58
  env: { ...process.env, CXXFLAGS: '-std=c++20' },
63
59
  });
64
- log('tree-sitter: installed OK');
60
+ log('tree-sitter: rebuilt OK');
65
61
  } catch {
66
- log('tree-sitter: install failed');
62
+ log('tree-sitter: rebuild failed');
67
63
  console.warn(
68
- '\n[TrueCourse] Could not install tree-sitter automatically.\n' +
64
+ '\n[TrueCourse] Could not rebuild tree-sitter automatically.\n' +
69
65
  'Fix: set the C++20 flag manually and reinstall:\n' +
70
66
  ' export CXXFLAGS="-std=c++20"\n' +
71
67
  ' npx truecourse\n\n' +
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 diff = readDiff(repo.path);
153371
- if (!diff) {
153423
+ const result = getDiffResult(repo.path);
153424
+ if (!result) {
153372
153425
  res.json(null);
153373
153426
  return;
153374
153427
  }
153375
- const latest = readLatest(repo.path);
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
- let violations;
153485
- if (analysisIdParam) {
153486
- const latest = readLatest(repo.path);
153487
- if (latest && latest.analysis.id === analysisIdParam) {
153488
- violations = latest.violations;
153489
- } else {
153490
- res.json(limitParam > 0 ? { violations: [], total: 0 } : []);
153491
- return;
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: paged, total });
153536
+ res.json({ violations, total });
153526
153537
  } else {
153527
- res.json(paged);
153538
+ res.json(violations);
153528
153539
  }
153529
153540
  } catch (error) {
153530
153541
  next(error);