truecourse 0.5.8-windows.3 → 0.5.9
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 +17 -0
- package/cli.mjs +176 -44
- package/package.json +1 -1
- package/public/assets/index-CfvS0ccv.css +1 -0
- package/public/assets/index-DwMO-NQg.js +671 -0
- package/public/index.html +2 -2
- package/server.mjs +232 -144
- package/public/assets/index-Bw6ucreX.js +0 -661
- package/public/assets/index-DU0Bp1u7.css +0 -1
package/README.md
CHANGED
|
@@ -114,8 +114,25 @@ truecourse rules categories --disable style # Disable a category
|
|
|
114
114
|
truecourse rules llm # Show LLM rules status
|
|
115
115
|
truecourse rules llm --enable # Enable LLM rules
|
|
116
116
|
truecourse rules llm --disable # Disable LLM rules
|
|
117
|
+
|
|
118
|
+
# Individual rules
|
|
119
|
+
truecourse rules list # List rules with on/off status
|
|
120
|
+
truecourse rules list --disabled # Show only disabled rules
|
|
121
|
+
truecourse rules disable <ruleKey> # Disable a single rule
|
|
122
|
+
truecourse rules enable <ruleKey> # Re-enable a single rule
|
|
123
|
+
truecourse rules reset [ruleKey] # Clear per-rule overrides (one or all)
|
|
117
124
|
```
|
|
118
125
|
|
|
126
|
+
Disabled rules are skipped at analyze time (no detection cost, no LLM
|
|
127
|
+
calls) and any existing violations from them are hidden from the
|
|
128
|
+
dashboard and `truecourse list` until re-enabled. The list of disabled
|
|
129
|
+
rule keys lives in `<repo>/.truecourse/config.json` under
|
|
130
|
+
`disabledRules`, which is intended to be committed.
|
|
131
|
+
|
|
132
|
+
In the dashboard you can also toggle rules from the Rules panel
|
|
133
|
+
(Shield icon in the top-right) or silence a noisy rule directly from
|
|
134
|
+
any violation card via the **⋮** menu → **Disable rule for this repo**.
|
|
135
|
+
|
|
119
136
|
### Git Hooks
|
|
120
137
|
|
|
121
138
|
TrueCourse can install a pre-commit hook that blocks commits introducing new violations at or above a configured severity:
|
package/cli.mjs
CHANGED
|
@@ -11168,6 +11168,11 @@ var init_service_patterns = __esm({
|
|
|
11168
11168
|
import * as ts from "typescript";
|
|
11169
11169
|
import { dirname as dirname2, join as join2 } from "path";
|
|
11170
11170
|
import { existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
11171
|
+
function createRepoScopedCompilerHost(repoPath, options) {
|
|
11172
|
+
const host = ts.createCompilerHost(options);
|
|
11173
|
+
host.getCurrentDirectory = () => repoPath;
|
|
11174
|
+
return host;
|
|
11175
|
+
}
|
|
11171
11176
|
function buildScopedCompilerOptions(rootPath) {
|
|
11172
11177
|
const result = [];
|
|
11173
11178
|
const candidates = [join2(rootPath, "tsconfig.json")];
|
|
@@ -11219,14 +11224,15 @@ function resolveModule(specifier, containingFile, scoped) {
|
|
|
11219
11224
|
return null;
|
|
11220
11225
|
return resolved;
|
|
11221
11226
|
}
|
|
11222
|
-
function analyzeSemantics(filePaths, scoped) {
|
|
11227
|
+
function analyzeSemantics(filePaths, scoped, repoPath) {
|
|
11223
11228
|
const baseOptions = scoped[scoped.length - 1]?.options ?? {};
|
|
11224
11229
|
const options = {
|
|
11225
11230
|
...baseOptions,
|
|
11226
11231
|
skipLibCheck: true,
|
|
11227
11232
|
noEmit: true
|
|
11228
11233
|
};
|
|
11229
|
-
const
|
|
11234
|
+
const host = createRepoScopedCompilerHost(repoPath, options);
|
|
11235
|
+
const program3 = ts.createProgram(filePaths, options, host);
|
|
11230
11236
|
const checker = program3.getTypeChecker();
|
|
11231
11237
|
const exportMap = /* @__PURE__ */ new Map();
|
|
11232
11238
|
const interfaceMethods = /* @__PURE__ */ new Set();
|
|
@@ -11321,7 +11327,7 @@ function getNodeAtPosition(sourceFile, line, column, endLine, endColumn) {
|
|
|
11321
11327
|
}
|
|
11322
11328
|
return candidate;
|
|
11323
11329
|
}
|
|
11324
|
-
function createTypeQueryService(filePaths, scopedOptions) {
|
|
11330
|
+
function createTypeQueryService(filePaths, scopedOptions, repoPath) {
|
|
11325
11331
|
const scopedPrograms = [];
|
|
11326
11332
|
const fileToProgram = /* @__PURE__ */ new Map();
|
|
11327
11333
|
const filesByScope = /* @__PURE__ */ new Map();
|
|
@@ -11348,7 +11354,8 @@ function createTypeQueryService(filePaths, scopedOptions) {
|
|
|
11348
11354
|
skipLibCheck: true,
|
|
11349
11355
|
noEmit: true
|
|
11350
11356
|
};
|
|
11351
|
-
const
|
|
11357
|
+
const host = createRepoScopedCompilerHost(repoPath, options);
|
|
11358
|
+
const program3 = ts.createProgram(files, options, host);
|
|
11352
11359
|
const checker = program3.getTypeChecker();
|
|
11353
11360
|
const sp = { program: program3, checker, files: new Set(files) };
|
|
11354
11361
|
scopedPrograms.push(sp);
|
|
@@ -11356,7 +11363,9 @@ function createTypeQueryService(filePaths, scopedOptions) {
|
|
|
11356
11363
|
fileToProgram.set(f, sp);
|
|
11357
11364
|
}
|
|
11358
11365
|
if (scopedPrograms.length === 0) {
|
|
11359
|
-
const
|
|
11366
|
+
const options = { skipLibCheck: true, noEmit: true };
|
|
11367
|
+
const host = createRepoScopedCompilerHost(repoPath, options);
|
|
11368
|
+
const program3 = ts.createProgram(filePaths, options, host);
|
|
11360
11369
|
const checker = program3.getTypeChecker();
|
|
11361
11370
|
const sp = { program: program3, checker, files: new Set(filePaths) };
|
|
11362
11371
|
scopedPrograms.push(sp);
|
|
@@ -95191,7 +95200,7 @@ async function runAnalysis(repoPath, _branch, onProgress, options) {
|
|
|
95191
95200
|
const scopedOptions = buildScopedCompilerOptions(repoPath);
|
|
95192
95201
|
if (scopedOptions.length > 0) {
|
|
95193
95202
|
const filePaths = fileAnalyses.map((fa) => fa.filePath);
|
|
95194
|
-
const { exportMap } = analyzeSemantics(filePaths, scopedOptions);
|
|
95203
|
+
const { exportMap } = analyzeSemantics(filePaths, scopedOptions, repoPath);
|
|
95195
95204
|
for (const fa of fileAnalyses) {
|
|
95196
95205
|
const fileExports = exportMap.get(fa.filePath);
|
|
95197
95206
|
if (!fileExports)
|
|
@@ -100451,7 +100460,7 @@ function deriveDomain(key) {
|
|
|
100451
100460
|
const validDomains = new Set(DOMAIN_ORDER);
|
|
100452
100461
|
return validDomains.has(prefix) ? prefix : void 0;
|
|
100453
100462
|
}
|
|
100454
|
-
function toAnalysisRule(rule) {
|
|
100463
|
+
function toAnalysisRule(rule, enabled = rule.enabled) {
|
|
100455
100464
|
return {
|
|
100456
100465
|
key: rule.key,
|
|
100457
100466
|
category: rule.category,
|
|
@@ -100459,20 +100468,25 @@ function toAnalysisRule(rule) {
|
|
|
100459
100468
|
name: rule.name,
|
|
100460
100469
|
description: rule.description,
|
|
100461
100470
|
prompt: rule.prompt ?? void 0,
|
|
100462
|
-
enabled
|
|
100471
|
+
enabled,
|
|
100463
100472
|
severity: rule.severity,
|
|
100464
100473
|
type: rule.type,
|
|
100465
100474
|
contextRequirement: rule.contextRequirement
|
|
100466
100475
|
};
|
|
100467
100476
|
}
|
|
100477
|
+
async function getRules(repoPath) {
|
|
100478
|
+
const disabled = repoPath ? new Set(readProjectConfig(repoPath).disabledRules ?? []) : null;
|
|
100479
|
+
return getAllDefaultRules().map((r) => toAnalysisRule(r, disabled ? r.enabled && !disabled.has(r.key) : r.enabled));
|
|
100480
|
+
}
|
|
100468
100481
|
async function getEnabledRules() {
|
|
100469
|
-
return getAllDefaultRules().filter((r) => r.enabled).map(toAnalysisRule);
|
|
100482
|
+
return getAllDefaultRules().filter((r) => r.enabled).map((r) => toAnalysisRule(r));
|
|
100470
100483
|
}
|
|
100471
100484
|
var init_rules_service = __esm({
|
|
100472
100485
|
"packages/core/dist/services/rules.service.js"() {
|
|
100473
100486
|
"use strict";
|
|
100474
100487
|
init_dist6();
|
|
100475
100488
|
init_dist7();
|
|
100489
|
+
init_project_config();
|
|
100476
100490
|
}
|
|
100477
100491
|
});
|
|
100478
100492
|
|
|
@@ -102811,7 +102825,7 @@ var require_main = __commonJS({
|
|
|
102811
102825
|
"node_modules/.pnpm/dotenv@16.6.1/node_modules/dotenv/lib/main.js"(exports, module) {
|
|
102812
102826
|
var fs17 = __require("fs");
|
|
102813
102827
|
var path23 = __require("path");
|
|
102814
|
-
var
|
|
102828
|
+
var os9 = __require("os");
|
|
102815
102829
|
var crypto2 = __require("crypto");
|
|
102816
102830
|
var packageJson = require_package();
|
|
102817
102831
|
var version = packageJson.version;
|
|
@@ -102934,7 +102948,7 @@ var require_main = __commonJS({
|
|
|
102934
102948
|
return null;
|
|
102935
102949
|
}
|
|
102936
102950
|
function _resolveHome(envPath) {
|
|
102937
|
-
return envPath[0] === "~" ? path23.join(
|
|
102951
|
+
return envPath[0] === "~" ? path23.join(os9.homedir(), envPath.slice(1)) : envPath;
|
|
102938
102952
|
}
|
|
102939
102953
|
function _configVault(options) {
|
|
102940
102954
|
const debug2 = Boolean(options && options.debug);
|
|
@@ -105410,7 +105424,8 @@ function compareDeterministicViolations(current, previous) {
|
|
|
105410
105424
|
}
|
|
105411
105425
|
async function runViolationPipeline(input) {
|
|
105412
105426
|
await initParsers();
|
|
105413
|
-
const { repoPath, analysisId, now, result, serviceIdMap, moduleIdMap, methodIdMap, dbIdMap, previousActiveViolations, changedFileSet, onProgress, tracker, provider: externalProvider, enabledCategories, enableLlmRules, signal } = input;
|
|
105427
|
+
const { repoPath, analysisId, now, result, serviceIdMap, moduleIdMap, methodIdMap, dbIdMap, previousActiveViolations, changedFileSet, onProgress, tracker, provider: externalProvider, enabledCategories, enableLlmRules, disabledRules, signal } = input;
|
|
105428
|
+
const disabledRuleSet = new Set(disabledRules ?? []);
|
|
105414
105429
|
const added = [];
|
|
105415
105430
|
const unchanged = [];
|
|
105416
105431
|
const resolved = [];
|
|
@@ -105428,7 +105443,7 @@ async function runViolationPipeline(input) {
|
|
|
105428
105443
|
for (const [name, id] of dbIdMap)
|
|
105429
105444
|
databaseIdToName.set(id, name);
|
|
105430
105445
|
const previousActiveCodeViolations = previousActiveViolations.filter((v) => v.filePath != null);
|
|
105431
|
-
let allRules = (await getEnabledRules()).filter((r) => !enabledCategories || enabledCategories.includes(r.domain ?? r.category)).filter((r) => enableLlmRules !== false || r.type !== "llm");
|
|
105446
|
+
let allRules = (await getEnabledRules()).filter((r) => !enabledCategories || enabledCategories.includes(r.domain ?? r.category)).filter((r) => enableLlmRules !== false || r.type !== "llm").filter((r) => !disabledRuleSet.has(r.key));
|
|
105432
105447
|
let llmSkipped = false;
|
|
105433
105448
|
const enabledDeterministic = allRules.filter((r) => r.type === "deterministic");
|
|
105434
105449
|
const enabledLlm = allRules.filter((r) => r.type === "llm");
|
|
@@ -105469,7 +105484,7 @@ async function runViolationPipeline(input) {
|
|
|
105469
105484
|
const tsFiles = filesToScan.filter(({ filePath: fp }) => /\.(ts|tsx|js|jsx)$/.test(fp)).map(({ filePath: fp, resolve: res }) => res ? path12.resolve(repoPath, fp) : path12.isAbsolute(fp) ? fp : path12.join(repoPath, fp));
|
|
105470
105485
|
if (tsFiles.length > 0) {
|
|
105471
105486
|
const scoped = buildScopedCompilerOptions(repoPath);
|
|
105472
|
-
typeQuery = createTypeQueryService(tsFiles, scoped);
|
|
105487
|
+
typeQuery = createTypeQueryService(tsFiles, scoped, repoPath);
|
|
105473
105488
|
}
|
|
105474
105489
|
}
|
|
105475
105490
|
let schemaIndex;
|
|
@@ -106626,6 +106641,7 @@ async function analyzeCore(project, options) {
|
|
|
106626
106641
|
tracker: options.tracker,
|
|
106627
106642
|
enabledCategories: effectiveCategories,
|
|
106628
106643
|
enableLlmRules: effectiveLlmRules,
|
|
106644
|
+
disabledRules: projectConfig.disabledRules,
|
|
106629
106645
|
provider,
|
|
106630
106646
|
signal,
|
|
106631
106647
|
onLlmEstimate: options.onLlmEstimate ? async (estimate) => {
|
|
@@ -107459,6 +107475,7 @@ var MacOSService = class {
|
|
|
107459
107475
|
envVars.PATH = process.env.PATH;
|
|
107460
107476
|
}
|
|
107461
107477
|
envVars.TRUECOURSE_LOG_DIR = path17.dirname(logPath);
|
|
107478
|
+
envVars.TRUECOURSE_HOME = path17.join(os5.homedir(), ".truecourse");
|
|
107462
107479
|
fs12.mkdirSync(PLIST_DIR, { recursive: true });
|
|
107463
107480
|
fs12.mkdirSync(path17.dirname(logPath), { recursive: true });
|
|
107464
107481
|
const plist = buildPlist(serverPath, logPath, envVars);
|
|
@@ -107518,7 +107535,8 @@ var SERVICE_NAME = "truecourse";
|
|
|
107518
107535
|
var UNIT_DIR = path18.join(os6.homedir(), ".config", "systemd", "user");
|
|
107519
107536
|
var UNIT_PATH = path18.join(UNIT_DIR, `${SERVICE_NAME}.service`);
|
|
107520
107537
|
function buildUnitFile(serverPath, logPath) {
|
|
107521
|
-
const
|
|
107538
|
+
const truecourseHome = path18.join(os6.homedir(), ".truecourse");
|
|
107539
|
+
const envFile = path18.join(truecourseHome, ".env");
|
|
107522
107540
|
const logDir = path18.dirname(logPath);
|
|
107523
107541
|
return `[Unit]
|
|
107524
107542
|
Description=TrueCourse Server
|
|
@@ -107530,6 +107548,7 @@ ExecStart=${process.execPath} ${serverPath}
|
|
|
107530
107548
|
Restart=on-failure
|
|
107531
107549
|
RestartSec=5
|
|
107532
107550
|
EnvironmentFile=${envFile}
|
|
107551
|
+
Environment=TRUECOURSE_HOME=${truecourseHome}
|
|
107533
107552
|
Environment=TRUECOURSE_LOG_DIR=${logDir}
|
|
107534
107553
|
StandardOutput=append:${path18.join(logDir, "dashboard.out.log")}
|
|
107535
107554
|
StandardError=append:${path18.join(logDir, "dashboard.err.log")}
|
|
@@ -107592,11 +107611,11 @@ var LinuxService = class {
|
|
|
107592
107611
|
|
|
107593
107612
|
// tools/cli/src/commands/service/windows.ts
|
|
107594
107613
|
import { execSync as execSync3 } from "node:child_process";
|
|
107614
|
+
import os7 from "node:os";
|
|
107595
107615
|
import path19 from "node:path";
|
|
107596
|
-
var
|
|
107597
|
-
var
|
|
107616
|
+
var SERVICE_DISPLAY_NAME = "TrueCourse";
|
|
107617
|
+
var SERVICE_SCM_NAME = "truecourse.exe";
|
|
107598
107618
|
var WindowsService = class {
|
|
107599
|
-
svc;
|
|
107600
107619
|
async getNodeWindows() {
|
|
107601
107620
|
try {
|
|
107602
107621
|
return __require("node-windows");
|
|
@@ -107610,20 +107629,20 @@ var WindowsService = class {
|
|
|
107610
107629
|
const nw = await this.getNodeWindows();
|
|
107611
107630
|
const { Service } = nw;
|
|
107612
107631
|
const logDir = path19.dirname(logPath);
|
|
107632
|
+
const truecourseHome = path19.join(os7.homedir(), ".truecourse");
|
|
107613
107633
|
return new Promise((resolve8, reject) => {
|
|
107614
107634
|
const svc = new Service({
|
|
107615
|
-
name:
|
|
107616
|
-
id: SERVICE_ID,
|
|
107635
|
+
name: SERVICE_DISPLAY_NAME,
|
|
107617
107636
|
description: "TrueCourse Server",
|
|
107618
107637
|
script: serverPath,
|
|
107619
107638
|
nodeOptions: [],
|
|
107620
107639
|
// Land wrapper logs in our shared log dir (default is the
|
|
107621
107640
|
// node-windows install dir, often inside an npx cache).
|
|
107622
107641
|
logpath: logDir,
|
|
107623
|
-
env: [
|
|
107624
|
-
name: "
|
|
107625
|
-
value: logDir
|
|
107626
|
-
|
|
107642
|
+
env: [
|
|
107643
|
+
{ name: "TRUECOURSE_HOME", value: truecourseHome },
|
|
107644
|
+
{ name: "TRUECOURSE_LOG_DIR", value: logDir }
|
|
107645
|
+
]
|
|
107627
107646
|
});
|
|
107628
107647
|
svc.on("install", () => {
|
|
107629
107648
|
svc.start();
|
|
@@ -107638,7 +107657,7 @@ var WindowsService = class {
|
|
|
107638
107657
|
const { Service } = nw;
|
|
107639
107658
|
return new Promise((resolve8, reject) => {
|
|
107640
107659
|
const svc = new Service({
|
|
107641
|
-
name:
|
|
107660
|
+
name: SERVICE_DISPLAY_NAME,
|
|
107642
107661
|
script: ""
|
|
107643
107662
|
// Not needed for uninstall
|
|
107644
107663
|
});
|
|
@@ -107648,14 +107667,14 @@ var WindowsService = class {
|
|
|
107648
107667
|
});
|
|
107649
107668
|
}
|
|
107650
107669
|
async start() {
|
|
107651
|
-
execSync3(`sc.exe start ${
|
|
107670
|
+
execSync3(`sc.exe start ${SERVICE_SCM_NAME}`, { stdio: "pipe" });
|
|
107652
107671
|
}
|
|
107653
107672
|
async stop() {
|
|
107654
|
-
execSync3(`sc.exe stop ${
|
|
107673
|
+
execSync3(`sc.exe stop ${SERVICE_SCM_NAME}`, { stdio: "pipe" });
|
|
107655
107674
|
}
|
|
107656
107675
|
async status() {
|
|
107657
107676
|
try {
|
|
107658
|
-
const output = execSync3(`sc.exe query ${
|
|
107677
|
+
const output = execSync3(`sc.exe query ${SERVICE_SCM_NAME}`, {
|
|
107659
107678
|
stdio: ["pipe", "pipe", "pipe"],
|
|
107660
107679
|
encoding: "utf-8"
|
|
107661
107680
|
});
|
|
@@ -107671,7 +107690,7 @@ var WindowsService = class {
|
|
|
107671
107690
|
}
|
|
107672
107691
|
async isInstalled() {
|
|
107673
107692
|
try {
|
|
107674
|
-
execSync3(`sc.exe query ${
|
|
107693
|
+
execSync3(`sc.exe query ${SERVICE_SCM_NAME}`, { stdio: "pipe" });
|
|
107675
107694
|
return true;
|
|
107676
107695
|
} catch {
|
|
107677
107696
|
return false;
|
|
@@ -107698,28 +107717,24 @@ function getPlatform() {
|
|
|
107698
107717
|
// tools/cli/src/commands/service/logs.ts
|
|
107699
107718
|
import fs14 from "node:fs";
|
|
107700
107719
|
import path20 from "node:path";
|
|
107701
|
-
import
|
|
107720
|
+
import os8 from "node:os";
|
|
107702
107721
|
var MAX_LOG_SIZE2 = 10 * 1024 * 1024;
|
|
107703
107722
|
var MAX_LOG_FILES2 = 5;
|
|
107704
107723
|
var POLL_INTERVAL_MS = 500;
|
|
107705
|
-
var
|
|
107706
|
-
|
|
107707
|
-
|
|
107708
|
-
"dashboard.
|
|
107709
|
-
"dashboard.wrapper.log",
|
|
107710
|
-
// Legacy (pre-unified-layout) — kept for backward compatibility with
|
|
107711
|
-
// existing macOS/Linux service registrations.
|
|
107712
|
-
"truecourse.log",
|
|
107713
|
-
"truecourse.error.log"
|
|
107724
|
+
var ROTATABLE_LOG_NAMES = [
|
|
107725
|
+
// Server's structured log — always rotated as a known target so we don't
|
|
107726
|
+
// have to detect it among arbitrary other .log files in the dir.
|
|
107727
|
+
"dashboard.log"
|
|
107714
107728
|
];
|
|
107715
107729
|
function getLogDir() {
|
|
107716
|
-
return path20.join(
|
|
107730
|
+
return path20.join(os8.homedir(), ".truecourse", "logs");
|
|
107717
107731
|
}
|
|
107718
107732
|
function getLogPath() {
|
|
107719
107733
|
return path20.join(getLogDir(), "dashboard.log");
|
|
107720
107734
|
}
|
|
107721
107735
|
function existingLogFiles(logDir) {
|
|
107722
|
-
|
|
107736
|
+
if (!fs14.existsSync(logDir)) return [];
|
|
107737
|
+
return fs14.readdirSync(logDir).filter((name) => /\.log$/.test(name)).sort().map((name) => path20.join(logDir, name));
|
|
107723
107738
|
}
|
|
107724
107739
|
function rotateOne(logFile) {
|
|
107725
107740
|
if (!fs14.existsSync(logFile)) return;
|
|
@@ -107736,9 +107751,11 @@ function rotateOne(logFile) {
|
|
|
107736
107751
|
fs14.renameSync(logFile, `${logFile}.1`);
|
|
107737
107752
|
}
|
|
107738
107753
|
function rotateLogs(logDir) {
|
|
107739
|
-
for (const name of
|
|
107754
|
+
for (const name of ROTATABLE_LOG_NAMES) {
|
|
107740
107755
|
rotateOne(path20.join(logDir, name));
|
|
107741
107756
|
}
|
|
107757
|
+
if (!fs14.existsSync(logDir)) return;
|
|
107758
|
+
for (const file of existingLogFiles(logDir)) rotateOne(file);
|
|
107742
107759
|
}
|
|
107743
107760
|
function readLastLines(filePath, maxLines) {
|
|
107744
107761
|
const content = fs14.readFileSync(filePath, "utf-8");
|
|
@@ -108065,6 +108082,16 @@ init_dist4();
|
|
|
108065
108082
|
|
|
108066
108083
|
// packages/core/dist/services/violation-query.service.js
|
|
108067
108084
|
init_analysis_store();
|
|
108085
|
+
init_project_config();
|
|
108086
|
+
function getDisabledRuleKeys(repoPath) {
|
|
108087
|
+
return new Set(readProjectConfig(repoPath).disabledRules ?? []);
|
|
108088
|
+
}
|
|
108089
|
+
function filterDisabled(repoPath, violations) {
|
|
108090
|
+
const disabled = getDisabledRuleKeys(repoPath);
|
|
108091
|
+
if (disabled.size === 0)
|
|
108092
|
+
return violations;
|
|
108093
|
+
return violations.filter((v) => !disabled.has(v.ruleKey));
|
|
108094
|
+
}
|
|
108068
108095
|
var SEVERITY_ORDER = {
|
|
108069
108096
|
critical: 0,
|
|
108070
108097
|
high: 1,
|
|
@@ -108086,6 +108113,7 @@ function listViolations(repoPath, options = {}) {
|
|
|
108086
108113
|
return { violations: [], total: 0 };
|
|
108087
108114
|
violations = historical;
|
|
108088
108115
|
}
|
|
108116
|
+
violations = filterDisabled(repoPath, violations);
|
|
108089
108117
|
const statusMode = options.status ?? "active";
|
|
108090
108118
|
let filtered;
|
|
108091
108119
|
if (statusMode === "resolved") {
|
|
@@ -108159,7 +108187,22 @@ function getDiffResult(repoPath) {
|
|
|
108159
108187
|
return null;
|
|
108160
108188
|
const latest = readLatest(repoPath);
|
|
108161
108189
|
const isStale = latest ? latest.analysis.id !== diff.baseAnalysisId : false;
|
|
108162
|
-
|
|
108190
|
+
const disabled = getDisabledRuleKeys(repoPath);
|
|
108191
|
+
if (disabled.size === 0)
|
|
108192
|
+
return { diff, isStale };
|
|
108193
|
+
const newViolations = diff.newViolations.filter((v) => !disabled.has(v.ruleKey));
|
|
108194
|
+
const resolvedViolations = diff.resolvedViolations.filter((v) => !disabled.has(v.ruleKey));
|
|
108195
|
+
const filteredDiff = {
|
|
108196
|
+
...diff,
|
|
108197
|
+
newViolations,
|
|
108198
|
+
resolvedViolations,
|
|
108199
|
+
summary: {
|
|
108200
|
+
...diff.summary,
|
|
108201
|
+
newCount: newViolations.length,
|
|
108202
|
+
resolvedCount: resolvedViolations.length
|
|
108203
|
+
}
|
|
108204
|
+
};
|
|
108205
|
+
return { diff: filteredDiff, isStale };
|
|
108163
108206
|
}
|
|
108164
108207
|
|
|
108165
108208
|
// tools/cli/src/commands/list.ts
|
|
@@ -108221,6 +108264,7 @@ async function runListDiff() {
|
|
|
108221
108264
|
init_dist4();
|
|
108222
108265
|
init_dist7();
|
|
108223
108266
|
init_project_config();
|
|
108267
|
+
init_rules_service();
|
|
108224
108268
|
init_helpers();
|
|
108225
108269
|
var ALL_CATEGORIES = [...DOMAIN_ORDER];
|
|
108226
108270
|
async function runRulesCategories(options) {
|
|
@@ -108284,6 +108328,82 @@ async function runRulesLlm(options) {
|
|
|
108284
108328
|
O2.info("Override with: truecourse rules llm --enable/--disable");
|
|
108285
108329
|
}
|
|
108286
108330
|
}
|
|
108331
|
+
var COLOR_ENABLED = "\x1B[32menabled\x1B[0m";
|
|
108332
|
+
var COLOR_DISABLED = "\x1B[31mdisabled\x1B[0m";
|
|
108333
|
+
var COLOR_DIM = (text) => `\x1B[2m${text}\x1B[0m`;
|
|
108334
|
+
function setRuleEnabled(repoPath, ruleKey, enabled) {
|
|
108335
|
+
const current = readProjectConfig(repoPath);
|
|
108336
|
+
const set2 = new Set(current.disabledRules ?? []);
|
|
108337
|
+
if (enabled) set2.delete(ruleKey);
|
|
108338
|
+
else set2.add(ruleKey);
|
|
108339
|
+
updateProjectConfig(repoPath, { disabledRules: [...set2].sort() });
|
|
108340
|
+
}
|
|
108341
|
+
async function requireRuleKey(ruleKey) {
|
|
108342
|
+
const all = await getRules();
|
|
108343
|
+
if (!all.some((r) => r.key === ruleKey)) {
|
|
108344
|
+
O2.error(`Unknown rule: ${ruleKey}. Run 'truecourse rules list' to see available rules.`);
|
|
108345
|
+
process.exit(1);
|
|
108346
|
+
}
|
|
108347
|
+
}
|
|
108348
|
+
async function runRulesEnable({ ruleKey }) {
|
|
108349
|
+
const repo = requireRegisteredRepo();
|
|
108350
|
+
await requireRuleKey(ruleKey);
|
|
108351
|
+
setRuleEnabled(repo.path, ruleKey, true);
|
|
108352
|
+
O2.success(`Enabled rule '${ruleKey}' for ${repo.name}.`);
|
|
108353
|
+
}
|
|
108354
|
+
async function runRulesDisable({ ruleKey }) {
|
|
108355
|
+
const repo = requireRegisteredRepo();
|
|
108356
|
+
await requireRuleKey(ruleKey);
|
|
108357
|
+
setRuleEnabled(repo.path, ruleKey, false);
|
|
108358
|
+
O2.success(`Disabled rule '${ruleKey}' for ${repo.name}.`);
|
|
108359
|
+
}
|
|
108360
|
+
async function runRulesList(options) {
|
|
108361
|
+
const repo = requireRegisteredRepo();
|
|
108362
|
+
const rules = await getRules(repo.path);
|
|
108363
|
+
const search = options.search?.toLowerCase();
|
|
108364
|
+
let filtered = rules;
|
|
108365
|
+
if (options.domain) {
|
|
108366
|
+
filtered = filtered.filter((r) => (r.domain ?? r.category) === options.domain);
|
|
108367
|
+
}
|
|
108368
|
+
if (options.enabled) filtered = filtered.filter((r) => r.enabled);
|
|
108369
|
+
if (options.disabled) filtered = filtered.filter((r) => !r.enabled);
|
|
108370
|
+
if (search) {
|
|
108371
|
+
filtered = filtered.filter(
|
|
108372
|
+
(r) => r.key.toLowerCase().includes(search) || r.name.toLowerCase().includes(search) || r.description?.toLowerCase().includes(search)
|
|
108373
|
+
);
|
|
108374
|
+
}
|
|
108375
|
+
if (filtered.length === 0) {
|
|
108376
|
+
O2.info("No rules match the given filters.");
|
|
108377
|
+
return;
|
|
108378
|
+
}
|
|
108379
|
+
const enabledCount = filtered.filter((r) => r.enabled).length;
|
|
108380
|
+
const disabledCount = filtered.length - enabledCount;
|
|
108381
|
+
O2.info(
|
|
108382
|
+
`Rules for ${repo.name}: ${filtered.length} shown (${enabledCount} enabled, ${disabledCount} disabled).`
|
|
108383
|
+
);
|
|
108384
|
+
const keyWidth = Math.min(
|
|
108385
|
+
60,
|
|
108386
|
+
filtered.reduce((max, r) => Math.max(max, r.key.length), 0)
|
|
108387
|
+
);
|
|
108388
|
+
for (const r of filtered) {
|
|
108389
|
+
const status = r.enabled ? COLOR_ENABLED : COLOR_DISABLED;
|
|
108390
|
+
const domain = r.domain ?? r.category;
|
|
108391
|
+
console.log(` ${r.key.padEnd(keyWidth)} ${status} ${COLOR_DIM(`[${domain}/${r.severity}]`)} ${r.name}`);
|
|
108392
|
+
}
|
|
108393
|
+
console.log("");
|
|
108394
|
+
O2.info("Toggle with: truecourse rules enable <key> | truecourse rules disable <key>");
|
|
108395
|
+
}
|
|
108396
|
+
async function runRulesReset({ ruleKey }) {
|
|
108397
|
+
const repo = requireRegisteredRepo();
|
|
108398
|
+
if (ruleKey) {
|
|
108399
|
+
await requireRuleKey(ruleKey);
|
|
108400
|
+
setRuleEnabled(repo.path, ruleKey, true);
|
|
108401
|
+
O2.success(`Re-enabled '${ruleKey}' for ${repo.name}.`);
|
|
108402
|
+
return;
|
|
108403
|
+
}
|
|
108404
|
+
updateProjectConfig(repo.path, { disabledRules: [] });
|
|
108405
|
+
O2.success(`Cleared per-rule overrides for ${repo.name}.`);
|
|
108406
|
+
}
|
|
108287
108407
|
|
|
108288
108408
|
// tools/cli/src/commands/hooks.ts
|
|
108289
108409
|
import { execSync as execSync4 } from "node:child_process";
|
|
@@ -111191,7 +111311,7 @@ async function runHooksRun() {
|
|
|
111191
111311
|
|
|
111192
111312
|
// tools/cli/src/index.ts
|
|
111193
111313
|
var program2 = new Command();
|
|
111194
|
-
program2.name("truecourse").version("0.5.
|
|
111314
|
+
program2.name("truecourse").version("0.5.9").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
|
|
111195
111315
|
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) => {
|
|
111196
111316
|
if (options.service && options.console) {
|
|
111197
111317
|
console.error("error: --service and --console are mutually exclusive");
|
|
@@ -111251,6 +111371,18 @@ rulesCmd.command("categories").description("View or override rule categories for
|
|
|
111251
111371
|
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) => {
|
|
111252
111372
|
await runRulesLlm(options);
|
|
111253
111373
|
});
|
|
111374
|
+
rulesCmd.command("list").description("List rules with their enabled/disabled status for this repository").option("--domain <name>", "Only show rules in this domain (e.g. security, bugs)").option("--enabled", "Only show enabled rules").option("--disabled", "Only show disabled rules").option("--search <text>", "Filter by key, name, or description").action(async (options) => {
|
|
111375
|
+
await runRulesList(options);
|
|
111376
|
+
});
|
|
111377
|
+
rulesCmd.command("enable <ruleKey>").description("Enable a single rule for this repository").action(async (ruleKey) => {
|
|
111378
|
+
await runRulesEnable({ ruleKey });
|
|
111379
|
+
});
|
|
111380
|
+
rulesCmd.command("disable <ruleKey>").description("Disable a single rule for this repository").action(async (ruleKey) => {
|
|
111381
|
+
await runRulesDisable({ ruleKey });
|
|
111382
|
+
});
|
|
111383
|
+
rulesCmd.command("reset [ruleKey]").description("Clear per-rule overrides (one rule, or all if no key given)").action(async (ruleKey) => {
|
|
111384
|
+
await runRulesReset({ ruleKey });
|
|
111385
|
+
});
|
|
111254
111386
|
var telemetryCmd = program2.command("telemetry").description("Manage anonymous usage telemetry");
|
|
111255
111387
|
telemetryCmd.command("enable").description("Enable anonymous usage telemetry").action(() => {
|
|
111256
111388
|
writeTelemetryConfig({ enabled: true });
|