instrlint 0.1.2 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/README.zh-TW.md +3 -1
- package/dist/cli.cjs +341 -118
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +337 -114
- package/dist/cli.js.map +1 -1
- package/package.json +5 -2
- package/skills/claude-code/SKILL.md +24 -25
package/dist/cli.cjs
CHANGED
|
@@ -32,7 +32,8 @@ var import_commander = require("commander");
|
|
|
32
32
|
|
|
33
33
|
// src/commands/run-command.ts
|
|
34
34
|
var import_child_process = require("child_process");
|
|
35
|
-
var
|
|
35
|
+
var import_readline = require("readline");
|
|
36
|
+
var import_path10 = require("path");
|
|
36
37
|
var import_chalk4 = __toESM(require("chalk"), 1);
|
|
37
38
|
|
|
38
39
|
// src/core/scanner.ts
|
|
@@ -814,8 +815,12 @@ function analyzeBudget(instructions) {
|
|
|
814
815
|
autoFixable: false
|
|
815
816
|
});
|
|
816
817
|
}
|
|
817
|
-
const { tokens: rulesTokens, method: rulesMethod } = sumTokens(
|
|
818
|
-
|
|
818
|
+
const { tokens: rulesTokens, method: rulesMethod } = sumTokens(
|
|
819
|
+
instructions.rules
|
|
820
|
+
);
|
|
821
|
+
const { tokens: skillsTokens, method: skillsMethod } = sumTokens(
|
|
822
|
+
instructions.skills
|
|
823
|
+
);
|
|
819
824
|
const { tokens: subFilesTokens, method: subFilesMethod } = sumTokens(
|
|
820
825
|
instructions.subFiles
|
|
821
826
|
);
|
|
@@ -853,11 +858,18 @@ function analyzeBudget(instructions) {
|
|
|
853
858
|
autoFixable: false
|
|
854
859
|
});
|
|
855
860
|
}
|
|
856
|
-
const tokenMethod = [
|
|
857
|
-
|
|
858
|
-
|
|
861
|
+
const tokenMethod = [
|
|
862
|
+
rootFileMethod,
|
|
863
|
+
rulesMethod,
|
|
864
|
+
skillsMethod,
|
|
865
|
+
subFilesMethod
|
|
866
|
+
].every((m) => m === "measured") ? "measured" : "estimated";
|
|
859
867
|
const fileBreakdown = [
|
|
860
|
-
{
|
|
868
|
+
{
|
|
869
|
+
path: instructions.rootFile.path,
|
|
870
|
+
tokenCount: rootFileTokens,
|
|
871
|
+
tokenMethod: rootFileMethod
|
|
872
|
+
},
|
|
861
873
|
...instructions.rules.map((r) => ({
|
|
862
874
|
path: r.path,
|
|
863
875
|
tokenCount: r.tokenCount,
|
|
@@ -877,6 +889,7 @@ function analyzeBudget(instructions) {
|
|
|
877
889
|
const summary = {
|
|
878
890
|
systemPromptTokens: SYSTEM_PROMPT_TOKENS,
|
|
879
891
|
rootFileTokens,
|
|
892
|
+
rootFileLines: rootLines,
|
|
880
893
|
rootFileMethod,
|
|
881
894
|
rulesTokens,
|
|
882
895
|
rulesMethod,
|
|
@@ -1453,6 +1466,8 @@ var INFO_DEDUCTION = 1;
|
|
|
1453
1466
|
var MAX_CRITICAL_DEDUCTION = 40;
|
|
1454
1467
|
var MAX_WARNING_DEDUCTION = 30;
|
|
1455
1468
|
var MAX_INFO_DEDUCTION = 10;
|
|
1469
|
+
var MAX_ROOT_FILE_PENALTY = 30;
|
|
1470
|
+
var MAX_BUDGET_DEDUCTION = 30;
|
|
1456
1471
|
function gradeFromScore(score) {
|
|
1457
1472
|
if (score >= 90) return "A";
|
|
1458
1473
|
if (score >= 80) return "B";
|
|
@@ -1464,16 +1479,32 @@ function calculateScore(findings, budget) {
|
|
|
1464
1479
|
const criticals = findings.filter((f) => f.severity === "critical").length;
|
|
1465
1480
|
const warnings = findings.filter((f) => f.severity === "warning").length;
|
|
1466
1481
|
const infos = findings.filter((f) => f.severity === "info").length;
|
|
1467
|
-
const criticalDeduction = Math.min(
|
|
1468
|
-
|
|
1482
|
+
const criticalDeduction = Math.min(
|
|
1483
|
+
criticals * CRITICAL_DEDUCTION,
|
|
1484
|
+
MAX_CRITICAL_DEDUCTION
|
|
1485
|
+
);
|
|
1486
|
+
const warningDeduction = Math.min(
|
|
1487
|
+
warnings * WARNING_DEDUCTION,
|
|
1488
|
+
MAX_WARNING_DEDUCTION
|
|
1489
|
+
);
|
|
1469
1490
|
const infoDeduction = Math.min(infos * INFO_DEDUCTION, MAX_INFO_DEDUCTION);
|
|
1491
|
+
const rootLines = budget.rootFileLines;
|
|
1492
|
+
let rootFilePenalty = 0;
|
|
1493
|
+
if (rootLines > 400) {
|
|
1494
|
+
rootFilePenalty = 10 + Math.floor((rootLines - 400) / 100) * 5;
|
|
1495
|
+
} else if (rootLines > 200) {
|
|
1496
|
+
rootFilePenalty = 5 + Math.floor((rootLines - 200) / 100) * 3;
|
|
1497
|
+
}
|
|
1498
|
+
rootFilePenalty = Math.min(rootFilePenalty, MAX_ROOT_FILE_PENALTY);
|
|
1470
1499
|
const baselinePct = budget.totalBaseline / CONTEXT_WINDOW2;
|
|
1471
1500
|
let budgetDeduction = 0;
|
|
1472
|
-
if (baselinePct > 0.
|
|
1473
|
-
|
|
1501
|
+
if (baselinePct > 0.25) {
|
|
1502
|
+
budgetDeduction = 5 + Math.floor((baselinePct - 0.25) * 40);
|
|
1503
|
+
}
|
|
1504
|
+
budgetDeduction = Math.min(budgetDeduction, MAX_BUDGET_DEDUCTION);
|
|
1474
1505
|
const score = Math.max(
|
|
1475
1506
|
0,
|
|
1476
|
-
100 - criticalDeduction - warningDeduction - infoDeduction - budgetDeduction
|
|
1507
|
+
100 - criticalDeduction - warningDeduction - infoDeduction - rootFilePenalty - budgetDeduction
|
|
1477
1508
|
);
|
|
1478
1509
|
return { score, grade: gradeFromScore(score) };
|
|
1479
1510
|
}
|
|
@@ -1585,6 +1616,8 @@ var en_default = {
|
|
|
1585
1616
|
"markdown.budgetIssues": "Budget Issues",
|
|
1586
1617
|
"markdown.refactoringOpportunities": "Refactoring Opportunities",
|
|
1587
1618
|
"markdown.lineRef": "(line {{line}})",
|
|
1619
|
+
"markdown.budgetCategory": "Category",
|
|
1620
|
+
"markdown.budgetTokens": "Tokens",
|
|
1588
1621
|
"markdown.actionPlan": "## Action Plan",
|
|
1589
1622
|
"markdown.attribution": "*Generated by [instrlint](https://github.com/jed1978/instrlint)*",
|
|
1590
1623
|
"ci.passed": "\u2713 instrlint passed score={{score}} grade={{grade}}",
|
|
@@ -1592,9 +1625,13 @@ var en_default = {
|
|
|
1592
1625
|
"ci.writtenTo": "\u2192 written to {{file}}",
|
|
1593
1626
|
"initCi.created": "\u2713 Created {{path}}",
|
|
1594
1627
|
"initCi.alreadyExists": "{{path}} already exists. Use --force to overwrite.",
|
|
1595
|
-
"install.installed": "\u2713 Installed to {{path}}",
|
|
1628
|
+
"install.installed": "\u2713 Installed to {{path}}\n \u2192 Restart Claude Code to activate /instrlint",
|
|
1596
1629
|
"install.alreadyExists": "{{path}} already exists. Use --force to overwrite.",
|
|
1597
1630
|
"install.unknownTarget": "Specify --claude-code or --codex",
|
|
1631
|
+
"install.outdatedTitle": "instrlint skill is outdated",
|
|
1632
|
+
"install.outdatedVersions": "installed: {{installed}} \u2192 current: {{current}}",
|
|
1633
|
+
"install.updateCmd": "npx instrlint install {{flag}} --force",
|
|
1634
|
+
"install.updatePrompt": "Update skill now?",
|
|
1598
1635
|
"fix.manualActions": "MANUAL ACTIONS NEEDED",
|
|
1599
1636
|
"fix.hookCreate": "Add to .claude/settings.json:",
|
|
1600
1637
|
"fix.hookWarning": "\u26A0 Hook executes shell commands \u2014 review carefully before adding",
|
|
@@ -1686,6 +1723,8 @@ var zh_TW_default = {
|
|
|
1686
1723
|
"markdown.budgetIssues": "Token \u9810\u7B97\u554F\u984C",
|
|
1687
1724
|
"markdown.refactoringOpportunities": "\u91CD\u69CB\u5EFA\u8B70",
|
|
1688
1725
|
"markdown.lineRef": "\uFF08\u7B2C {{line}} \u884C\uFF09",
|
|
1726
|
+
"markdown.budgetCategory": "\u985E\u5225",
|
|
1727
|
+
"markdown.budgetTokens": "Token \u6578",
|
|
1689
1728
|
"markdown.actionPlan": "## \u884C\u52D5\u8A08\u756B",
|
|
1690
1729
|
"markdown.attribution": "*\u7531 [instrlint](https://github.com/jed1978/instrlint) \u751F\u6210*",
|
|
1691
1730
|
"ci.passed": "\u2713 instrlint \u901A\u904E \u5206\u6578={{score}} \u7B49\u7D1A={{grade}}",
|
|
@@ -1693,9 +1732,13 @@ var zh_TW_default = {
|
|
|
1693
1732
|
"ci.writtenTo": "\u2192 \u5DF2\u5BEB\u5165 {{file}}",
|
|
1694
1733
|
"initCi.created": "\u2713 \u5DF2\u5EFA\u7ACB {{path}}",
|
|
1695
1734
|
"initCi.alreadyExists": "{{path}} \u5DF2\u5B58\u5728\u3002\u4F7F\u7528 --force \u8986\u84CB\u3002",
|
|
1696
|
-
"install.installed": "\u2713 \u5DF2\u5B89\u88DD\u81F3 {{path}}",
|
|
1735
|
+
"install.installed": "\u2713 \u5DF2\u5B89\u88DD\u81F3 {{path}}\n \u2192 \u91CD\u65B0\u555F\u52D5 Claude Code \u5F8C\u5373\u53EF\u4F7F\u7528 /instrlint",
|
|
1697
1736
|
"install.alreadyExists": "{{path}} \u5DF2\u5B58\u5728\u3002\u4F7F\u7528 --force \u8986\u84CB\u3002",
|
|
1698
1737
|
"install.unknownTarget": "\u8ACB\u6307\u5B9A --claude-code \u6216 --codex",
|
|
1738
|
+
"install.outdatedTitle": "instrlint skill \u7248\u672C\u904E\u820A",
|
|
1739
|
+
"install.outdatedVersions": "\u5DF2\u5B89\u88DD\uFF1A{{installed}} \u2192 \u6700\u65B0\uFF1A{{current}}",
|
|
1740
|
+
"install.updateCmd": "npx instrlint install {{flag}} --force",
|
|
1741
|
+
"install.updatePrompt": "\u662F\u5426\u7ACB\u5373\u66F4\u65B0\uFF1F",
|
|
1699
1742
|
"fix.manualActions": "\u9700\u8981\u624B\u52D5\u64CD\u4F5C",
|
|
1700
1743
|
"fix.hookCreate": "\u52A0\u5165 .claude/settings.json\uFF1A",
|
|
1701
1744
|
"fix.hookWarning": "\u26A0 Hook \u6703\u57F7\u884C shell command\uFF0C\u8ACB\u4ED4\u7D30\u78BA\u8A8D\u5F8C\u518D\u52A0\u5165",
|
|
@@ -2025,15 +2068,25 @@ function mdSeverityIcon(f) {
|
|
|
2025
2068
|
if (f.severity === "warning") return "\u{1F7E1}";
|
|
2026
2069
|
return "\u2139\uFE0F";
|
|
2027
2070
|
}
|
|
2071
|
+
function mdBar(fraction, width = 20) {
|
|
2072
|
+
const filled = Math.round(Math.min(1, Math.max(0, fraction)) * width);
|
|
2073
|
+
const empty = width - filled;
|
|
2074
|
+
return "`" + "\u2588".repeat(filled) + "\u2591".repeat(empty) + "`";
|
|
2075
|
+
}
|
|
2076
|
+
function mdFormatTokens(count, method) {
|
|
2077
|
+
const fmt = new Intl.NumberFormat(getLocale());
|
|
2078
|
+
return method === "estimated" ? `~${fmt.format(count)}` : fmt.format(count);
|
|
2079
|
+
}
|
|
2028
2080
|
function reportMarkdown(report, extraSections = []) {
|
|
2029
|
-
const { project, tool, score, grade, findings } = report;
|
|
2081
|
+
const { project, tool, score, grade, findings, budget } = report;
|
|
2030
2082
|
const criticals = findings.filter((f) => f.severity === "critical").length;
|
|
2031
2083
|
const warnings = findings.filter((f) => f.severity === "warning").length;
|
|
2032
2084
|
const infos = findings.filter((f) => f.severity === "info").length;
|
|
2085
|
+
const gradeEmoji = grade === "A" ? "\u{1F7E2}" : grade === "B" ? "\u{1F535}" : grade === "C" ? "\u{1F7E1}" : grade === "D" ? "\u{1F7E0}" : "\u{1F534}";
|
|
2033
2086
|
const lines = [
|
|
2034
2087
|
`# ${t("markdown.title", { project })}`,
|
|
2035
2088
|
"",
|
|
2036
|
-
|
|
2089
|
+
`${gradeEmoji} **${score}/100 (${grade})** ${mdBar(score / 100, 25)} \xB7 \`${tool}\``,
|
|
2037
2090
|
"",
|
|
2038
2091
|
t("markdown.summary"),
|
|
2039
2092
|
"",
|
|
@@ -2044,6 +2097,58 @@ function reportMarkdown(report, extraSections = []) {
|
|
|
2044
2097
|
`| ${t("markdown.info")} | ${infos} |`,
|
|
2045
2098
|
""
|
|
2046
2099
|
];
|
|
2100
|
+
const window = budget.totalBaseline + budget.availableTokens;
|
|
2101
|
+
const budgetRows = [
|
|
2102
|
+
{
|
|
2103
|
+
labelKey: "label.systemPrompt",
|
|
2104
|
+
tokens: budget.systemPromptTokens,
|
|
2105
|
+
method: "estimated"
|
|
2106
|
+
},
|
|
2107
|
+
{
|
|
2108
|
+
labelKey: "label.rootFile",
|
|
2109
|
+
tokens: budget.rootFileTokens,
|
|
2110
|
+
method: budget.rootFileMethod
|
|
2111
|
+
},
|
|
2112
|
+
{
|
|
2113
|
+
labelKey: "label.ruleFiles",
|
|
2114
|
+
tokens: budget.rulesTokens,
|
|
2115
|
+
method: budget.rulesMethod
|
|
2116
|
+
},
|
|
2117
|
+
{
|
|
2118
|
+
labelKey: "label.skillFiles",
|
|
2119
|
+
tokens: budget.skillsTokens,
|
|
2120
|
+
method: budget.skillsMethod
|
|
2121
|
+
},
|
|
2122
|
+
{
|
|
2123
|
+
labelKey: "label.subDirFiles",
|
|
2124
|
+
tokens: budget.subFilesTokens,
|
|
2125
|
+
method: budget.subFilesMethod
|
|
2126
|
+
},
|
|
2127
|
+
{
|
|
2128
|
+
labelKey: "label.mcpServers",
|
|
2129
|
+
tokens: budget.mcpTokens,
|
|
2130
|
+
method: "estimated"
|
|
2131
|
+
}
|
|
2132
|
+
].filter((r) => r.tokens > 0);
|
|
2133
|
+
lines.push(`## ${t("label.tokenBudget")}`, "");
|
|
2134
|
+
lines.push(
|
|
2135
|
+
`| ${t("markdown.budgetCategory")} | ${t("markdown.budgetTokens")} | % | |`,
|
|
2136
|
+
"|------|--------|---|---|"
|
|
2137
|
+
);
|
|
2138
|
+
for (const row of budgetRows) {
|
|
2139
|
+
const pctVal = Math.round(row.tokens / window * 100);
|
|
2140
|
+
lines.push(
|
|
2141
|
+
`| ${t(row.labelKey)} | ${mdFormatTokens(row.tokens, row.method)} | ${pctVal}% | ${mdBar(row.tokens / window, 12)} |`
|
|
2142
|
+
);
|
|
2143
|
+
}
|
|
2144
|
+
const baselinePct = Math.round(budget.totalBaseline / window * 100);
|
|
2145
|
+
lines.push(
|
|
2146
|
+
`| **${t("label.baselineTotal")}** | **${mdFormatTokens(budget.totalBaseline, budget.tokenMethod)}** | **${baselinePct}%** | ${mdBar(budget.totalBaseline / window, 12)} |`
|
|
2147
|
+
);
|
|
2148
|
+
lines.push(
|
|
2149
|
+
`| ${t("label.available")} | ${mdFormatTokens(budget.availableTokens, "estimated")} | ${100 - baselinePct}% | |`
|
|
2150
|
+
);
|
|
2151
|
+
lines.push("");
|
|
2047
2152
|
const categories = [
|
|
2048
2153
|
{
|
|
2049
2154
|
labelKey: "markdown.contradictions",
|
|
@@ -2290,6 +2395,163 @@ function markdownStructureSuggestions(suggestions, projectRoot) {
|
|
|
2290
2395
|
return lines;
|
|
2291
2396
|
}
|
|
2292
2397
|
|
|
2398
|
+
// src/utils/skill-version.ts
|
|
2399
|
+
var import_fs10 = require("fs");
|
|
2400
|
+
var import_path8 = require("path");
|
|
2401
|
+
var import_os = require("os");
|
|
2402
|
+
var import_url = require("url");
|
|
2403
|
+
function readPackageVersion() {
|
|
2404
|
+
const thisFile = (0, import_url.fileURLToPath)(importMetaUrl);
|
|
2405
|
+
for (const levels of [2, 3]) {
|
|
2406
|
+
const pkgPath = (0, import_path8.join)(thisFile, ...Array(levels).fill(".."), "package.json");
|
|
2407
|
+
if ((0, import_fs10.existsSync)(pkgPath)) {
|
|
2408
|
+
const raw = JSON.parse((0, import_fs10.readFileSync)(pkgPath, "utf8"));
|
|
2409
|
+
if (typeof raw === "object" && raw !== null && "version" in raw && typeof raw.version === "string") {
|
|
2410
|
+
return raw.version;
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
return "0.0.0";
|
|
2415
|
+
}
|
|
2416
|
+
var CURRENT_VERSION = readPackageVersion();
|
|
2417
|
+
var VERSION_RE = /^instrlint-version:\s*(.+)$/m;
|
|
2418
|
+
function extractVersion(content) {
|
|
2419
|
+
const m = VERSION_RE.exec(content);
|
|
2420
|
+
return m ? m[1].trim() : null;
|
|
2421
|
+
}
|
|
2422
|
+
function readInstalledVersion(path) {
|
|
2423
|
+
if (!(0, import_fs10.existsSync)(path)) return null;
|
|
2424
|
+
try {
|
|
2425
|
+
return extractVersion((0, import_fs10.readFileSync)(path, "utf8"));
|
|
2426
|
+
} catch {
|
|
2427
|
+
return null;
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
function checkSkillUpdate(projectRoot) {
|
|
2431
|
+
const candidates = [
|
|
2432
|
+
{
|
|
2433
|
+
path: (0, import_path8.join)(projectRoot, ".claude", "commands", "instrlint.md"),
|
|
2434
|
+
isProject: true
|
|
2435
|
+
},
|
|
2436
|
+
{
|
|
2437
|
+
path: (0, import_path8.join)((0, import_os.homedir)(), ".claude", "commands", "instrlint.md"),
|
|
2438
|
+
isProject: false
|
|
2439
|
+
}
|
|
2440
|
+
];
|
|
2441
|
+
for (const { path, isProject } of candidates) {
|
|
2442
|
+
const installed = readInstalledVersion(path);
|
|
2443
|
+
if (installed !== null && installed !== CURRENT_VERSION) {
|
|
2444
|
+
return {
|
|
2445
|
+
installedVersion: installed,
|
|
2446
|
+
currentVersion: CURRENT_VERSION,
|
|
2447
|
+
installPath: path,
|
|
2448
|
+
isProject
|
|
2449
|
+
};
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
return null;
|
|
2453
|
+
}
|
|
2454
|
+
function injectVersion(content, version) {
|
|
2455
|
+
if (/^instrlint-version:/m.test(content)) {
|
|
2456
|
+
return content.replace(
|
|
2457
|
+
/^instrlint-version:.*$/m,
|
|
2458
|
+
`instrlint-version: ${version}`
|
|
2459
|
+
);
|
|
2460
|
+
}
|
|
2461
|
+
const updated = content.replace(
|
|
2462
|
+
/^(---\n[\s\S]*?)(---)$/m,
|
|
2463
|
+
`$1instrlint-version: ${version}
|
|
2464
|
+
$2`
|
|
2465
|
+
);
|
|
2466
|
+
if (updated === content) {
|
|
2467
|
+
throw new Error("injectVersion: no YAML frontmatter found in skill file");
|
|
2468
|
+
}
|
|
2469
|
+
return updated;
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
// src/commands/install-command.ts
|
|
2473
|
+
var import_fs11 = require("fs");
|
|
2474
|
+
var import_path9 = require("path");
|
|
2475
|
+
var import_os2 = require("os");
|
|
2476
|
+
var import_url2 = require("url");
|
|
2477
|
+
function resolveSkillFile(target) {
|
|
2478
|
+
const thisFile = (0, import_url2.fileURLToPath)(importMetaUrl);
|
|
2479
|
+
const subDir = target === "claude-code" ? "claude-code" : "codex";
|
|
2480
|
+
for (const levels of [2, 3]) {
|
|
2481
|
+
const parts = Array(levels).fill("..");
|
|
2482
|
+
const candidate = (0, import_path9.join)(thisFile, ...parts, "skills", subDir, "SKILL.md");
|
|
2483
|
+
if ((0, import_fs11.existsSync)(candidate)) return candidate;
|
|
2484
|
+
}
|
|
2485
|
+
return (0, import_path9.join)(thisFile, "..", "..", "skills", subDir, "SKILL.md");
|
|
2486
|
+
}
|
|
2487
|
+
function readSkillContent(target) {
|
|
2488
|
+
const skillPath = resolveSkillFile(target);
|
|
2489
|
+
try {
|
|
2490
|
+
const raw = (0, import_fs11.readFileSync)(skillPath, "utf8");
|
|
2491
|
+
return injectVersion(raw, CURRENT_VERSION);
|
|
2492
|
+
} catch {
|
|
2493
|
+
throw new Error(
|
|
2494
|
+
`Could not read skill file at ${skillPath}. Make sure the package is properly installed.`
|
|
2495
|
+
);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
function installClaudeCode(content, projectRoot, isProject, force, output) {
|
|
2499
|
+
const targetDir = isProject ? (0, import_path9.join)(projectRoot, ".claude", "commands") : (0, import_path9.join)((0, import_os2.homedir)(), ".claude", "commands");
|
|
2500
|
+
const targetPath = (0, import_path9.join)(targetDir, "instrlint.md");
|
|
2501
|
+
if ((0, import_fs11.existsSync)(targetPath) && !force) {
|
|
2502
|
+
output.error(t("install.alreadyExists", { path: targetPath }));
|
|
2503
|
+
return { exitCode: 1, errorMessage: "file already exists" };
|
|
2504
|
+
}
|
|
2505
|
+
(0, import_fs11.mkdirSync)(targetDir, { recursive: true });
|
|
2506
|
+
(0, import_fs11.writeFileSync)(targetPath, content, "utf8");
|
|
2507
|
+
output.log(t("install.installed", { path: targetPath }));
|
|
2508
|
+
return { exitCode: 0 };
|
|
2509
|
+
}
|
|
2510
|
+
function installCodex(content, projectRoot, force, output) {
|
|
2511
|
+
const targetDir = (0, import_path9.join)(projectRoot, ".agents", "skills", "instrlint");
|
|
2512
|
+
const targetPath = (0, import_path9.join)(targetDir, "SKILL.md");
|
|
2513
|
+
if ((0, import_fs11.existsSync)(targetPath) && !force) {
|
|
2514
|
+
output.error(t("install.alreadyExists", { path: targetPath }));
|
|
2515
|
+
return { exitCode: 1, errorMessage: "file already exists" };
|
|
2516
|
+
}
|
|
2517
|
+
(0, import_fs11.mkdirSync)(targetDir, { recursive: true });
|
|
2518
|
+
(0, import_fs11.writeFileSync)(targetPath, content, "utf8");
|
|
2519
|
+
output.log(t("install.installed", { path: targetPath }));
|
|
2520
|
+
return { exitCode: 0 };
|
|
2521
|
+
}
|
|
2522
|
+
function runInstall(opts, output = console) {
|
|
2523
|
+
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
2524
|
+
const force = opts.force ?? false;
|
|
2525
|
+
if (opts.claudeCode) {
|
|
2526
|
+
let content;
|
|
2527
|
+
try {
|
|
2528
|
+
content = readSkillContent("claude-code");
|
|
2529
|
+
} catch (err) {
|
|
2530
|
+
output.error(String(err));
|
|
2531
|
+
return { exitCode: 1, errorMessage: String(err) };
|
|
2532
|
+
}
|
|
2533
|
+
return installClaudeCode(
|
|
2534
|
+
content,
|
|
2535
|
+
projectRoot,
|
|
2536
|
+
opts.project ?? false,
|
|
2537
|
+
force,
|
|
2538
|
+
output
|
|
2539
|
+
);
|
|
2540
|
+
}
|
|
2541
|
+
if (opts.codex) {
|
|
2542
|
+
let content;
|
|
2543
|
+
try {
|
|
2544
|
+
content = readSkillContent("codex");
|
|
2545
|
+
} catch (err) {
|
|
2546
|
+
output.error(String(err));
|
|
2547
|
+
return { exitCode: 1, errorMessage: String(err) };
|
|
2548
|
+
}
|
|
2549
|
+
return installCodex(content, projectRoot, force, output);
|
|
2550
|
+
}
|
|
2551
|
+
output.error(t("install.unknownTarget"));
|
|
2552
|
+
return { exitCode: 1, errorMessage: "no target specified" };
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2293
2555
|
// src/commands/run-command.ts
|
|
2294
2556
|
function isGitClean(cwd) {
|
|
2295
2557
|
try {
|
|
@@ -2334,7 +2596,7 @@ async function runAll(opts, output = console) {
|
|
|
2334
2596
|
const { score, grade } = calculateScore(allFindings, summary);
|
|
2335
2597
|
const actionPlan = buildActionPlan(allFindings);
|
|
2336
2598
|
const report = {
|
|
2337
|
-
project: (0,
|
|
2599
|
+
project: (0, import_path10.basename)(projectRoot),
|
|
2338
2600
|
tool: instructions.tool,
|
|
2339
2601
|
score,
|
|
2340
2602
|
grade,
|
|
@@ -2379,6 +2641,7 @@ async function runAll(opts, output = console) {
|
|
|
2379
2641
|
printStructureSuggestions(suggestions, projectRoot, output);
|
|
2380
2642
|
return { exitCode: 0 };
|
|
2381
2643
|
}
|
|
2644
|
+
const skillUpdate = checkSkillUpdate(projectRoot);
|
|
2382
2645
|
if (opts.format === "json") {
|
|
2383
2646
|
output.log(reportJson(report));
|
|
2384
2647
|
return { exitCode: 0 };
|
|
@@ -2386,12 +2649,54 @@ async function runAll(opts, output = console) {
|
|
|
2386
2649
|
if (opts.format === "markdown") {
|
|
2387
2650
|
const mdSuggestions = buildStructureSuggestions(allFindings);
|
|
2388
2651
|
const mdExtra = markdownStructureSuggestions(mdSuggestions, projectRoot);
|
|
2389
|
-
|
|
2652
|
+
const updateSection = skillUpdate ? [
|
|
2653
|
+
"",
|
|
2654
|
+
`> \u26A0\uFE0F **${t("install.outdatedTitle")}** (${t("install.outdatedVersions", { installed: skillUpdate.installedVersion, current: skillUpdate.currentVersion })})`,
|
|
2655
|
+
`> \`${t("install.updateCmd", { flag: skillUpdate.isProject ? "--claude-code --project" : "--claude-code" })}\``
|
|
2656
|
+
] : [];
|
|
2657
|
+
output.log(reportMarkdown(report, [...mdExtra, ...updateSection]));
|
|
2390
2658
|
return { exitCode: 0 };
|
|
2391
2659
|
}
|
|
2392
2660
|
printCombinedTerminal(report, output);
|
|
2661
|
+
if (skillUpdate && process.stdout.isTTY) {
|
|
2662
|
+
output.log("");
|
|
2663
|
+
output.log(
|
|
2664
|
+
` ${import_chalk4.default.yellow("\u26A0")} ${import_chalk4.default.bold(t("install.outdatedTitle"))} (${t("install.outdatedVersions", { installed: skillUpdate.installedVersion, current: skillUpdate.currentVersion })})`
|
|
2665
|
+
);
|
|
2666
|
+
const confirmed = await promptYesNo(` ${t("install.updatePrompt")}`);
|
|
2667
|
+
if (confirmed) {
|
|
2668
|
+
const installResult = runInstall(
|
|
2669
|
+
{
|
|
2670
|
+
claudeCode: true,
|
|
2671
|
+
project: skillUpdate.isProject,
|
|
2672
|
+
force: true,
|
|
2673
|
+
projectRoot
|
|
2674
|
+
},
|
|
2675
|
+
output
|
|
2676
|
+
);
|
|
2677
|
+
if (installResult.exitCode !== 0) {
|
|
2678
|
+
output.error?.(
|
|
2679
|
+
`Update failed: ${installResult.errorMessage ?? "unknown error"}`
|
|
2680
|
+
);
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
output.log("");
|
|
2684
|
+
}
|
|
2393
2685
|
return { exitCode: 0 };
|
|
2394
2686
|
}
|
|
2687
|
+
function promptYesNo(question) {
|
|
2688
|
+
return new Promise((resolve) => {
|
|
2689
|
+
const rl = (0, import_readline.createInterface)({
|
|
2690
|
+
input: process.stdin,
|
|
2691
|
+
output: process.stdout
|
|
2692
|
+
});
|
|
2693
|
+
rl.question(`${question} ${import_chalk4.default.gray("[Y/n]")} `, (answer) => {
|
|
2694
|
+
rl.close();
|
|
2695
|
+
const trimmed = answer.trim().toLowerCase();
|
|
2696
|
+
resolve(trimmed === "" || trimmed === "y");
|
|
2697
|
+
});
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2395
2700
|
|
|
2396
2701
|
// src/commands/deadrules-command.ts
|
|
2397
2702
|
var import_chalk5 = __toESM(require("chalk"), 1);
|
|
@@ -2552,8 +2857,8 @@ async function runStructure(opts, output = console) {
|
|
|
2552
2857
|
}
|
|
2553
2858
|
|
|
2554
2859
|
// src/commands/ci-command.ts
|
|
2555
|
-
var
|
|
2556
|
-
var
|
|
2860
|
+
var import_fs12 = require("fs");
|
|
2861
|
+
var import_path11 = require("path");
|
|
2557
2862
|
|
|
2558
2863
|
// src/reporters/sarif.ts
|
|
2559
2864
|
function severityToLevel(severity) {
|
|
@@ -2659,7 +2964,7 @@ async function runCi(opts, output = console) {
|
|
|
2659
2964
|
const { score, grade } = calculateScore(allFindings, summary);
|
|
2660
2965
|
const actionPlan = buildActionPlan(allFindings);
|
|
2661
2966
|
const report = {
|
|
2662
|
-
project: (0,
|
|
2967
|
+
project: (0, import_path11.basename)(projectRoot),
|
|
2663
2968
|
tool: instructions.tool,
|
|
2664
2969
|
score,
|
|
2665
2970
|
grade,
|
|
@@ -2680,7 +2985,7 @@ async function runCi(opts, output = console) {
|
|
|
2680
2985
|
formatted = reportJson(report);
|
|
2681
2986
|
}
|
|
2682
2987
|
if (opts.output != null) {
|
|
2683
|
-
(0,
|
|
2988
|
+
(0, import_fs12.writeFileSync)(opts.output, formatted, "utf8");
|
|
2684
2989
|
const pass = !shouldFail(allFindings, failOn);
|
|
2685
2990
|
const statusKey = pass ? "ci.passed" : "ci.failed";
|
|
2686
2991
|
output.error(
|
|
@@ -2694,8 +2999,8 @@ async function runCi(opts, output = console) {
|
|
|
2694
2999
|
}
|
|
2695
3000
|
|
|
2696
3001
|
// src/commands/init-ci-command.ts
|
|
2697
|
-
var
|
|
2698
|
-
var
|
|
3002
|
+
var import_fs13 = require("fs");
|
|
3003
|
+
var import_path12 = require("path");
|
|
2699
3004
|
function githubWorkflow() {
|
|
2700
3005
|
return `name: instrlint
|
|
2701
3006
|
|
|
@@ -2764,14 +3069,14 @@ instrlint:
|
|
|
2764
3069
|
function runInitCi(opts, output = console) {
|
|
2765
3070
|
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
2766
3071
|
if (opts.github) {
|
|
2767
|
-
const workflowDir = (0,
|
|
2768
|
-
const workflowPath = (0,
|
|
2769
|
-
if ((0,
|
|
3072
|
+
const workflowDir = (0, import_path12.join)(projectRoot, ".github", "workflows");
|
|
3073
|
+
const workflowPath = (0, import_path12.join)(workflowDir, "instrlint.yml");
|
|
3074
|
+
if ((0, import_fs13.existsSync)(workflowPath) && !opts.force) {
|
|
2770
3075
|
output.error(t("initCi.alreadyExists", { path: workflowPath }));
|
|
2771
3076
|
return { exitCode: 1, errorMessage: "file already exists" };
|
|
2772
3077
|
}
|
|
2773
|
-
(0,
|
|
2774
|
-
(0,
|
|
3078
|
+
(0, import_fs13.mkdirSync)(workflowDir, { recursive: true });
|
|
3079
|
+
(0, import_fs13.writeFileSync)(workflowPath, githubWorkflow(), "utf8");
|
|
2775
3080
|
output.log(t("initCi.created", { path: workflowPath }));
|
|
2776
3081
|
return { exitCode: 0 };
|
|
2777
3082
|
}
|
|
@@ -2783,93 +3088,11 @@ function runInitCi(opts, output = console) {
|
|
|
2783
3088
|
return { exitCode: 1, errorMessage: "no target specified" };
|
|
2784
3089
|
}
|
|
2785
3090
|
|
|
2786
|
-
// src/commands/install-command.ts
|
|
2787
|
-
var import_fs12 = require("fs");
|
|
2788
|
-
var import_path11 = require("path");
|
|
2789
|
-
var import_os = require("os");
|
|
2790
|
-
var import_url = require("url");
|
|
2791
|
-
function resolveSkillFile(target) {
|
|
2792
|
-
const thisFile = (0, import_url.fileURLToPath)(importMetaUrl);
|
|
2793
|
-
const subDir = target === "claude-code" ? "claude-code" : "codex";
|
|
2794
|
-
for (const levels of [2, 3]) {
|
|
2795
|
-
const parts = Array(levels).fill("..");
|
|
2796
|
-
const candidate = (0, import_path11.join)(thisFile, ...parts, "skills", subDir, "SKILL.md");
|
|
2797
|
-
if ((0, import_fs12.existsSync)(candidate)) return candidate;
|
|
2798
|
-
}
|
|
2799
|
-
return (0, import_path11.join)(thisFile, "..", "..", "skills", subDir, "SKILL.md");
|
|
2800
|
-
}
|
|
2801
|
-
function readSkillContent(target) {
|
|
2802
|
-
const skillPath = resolveSkillFile(target);
|
|
2803
|
-
try {
|
|
2804
|
-
return (0, import_fs12.readFileSync)(skillPath, "utf8");
|
|
2805
|
-
} catch {
|
|
2806
|
-
throw new Error(
|
|
2807
|
-
`Could not read skill file at ${skillPath}. Make sure the package is properly installed.`
|
|
2808
|
-
);
|
|
2809
|
-
}
|
|
2810
|
-
}
|
|
2811
|
-
function installClaudeCode(content, projectRoot, isProject, force, output) {
|
|
2812
|
-
const targetDir = isProject ? (0, import_path11.join)(projectRoot, ".claude", "commands") : (0, import_path11.join)((0, import_os.homedir)(), ".claude", "commands");
|
|
2813
|
-
const targetPath = (0, import_path11.join)(targetDir, "instrlint.md");
|
|
2814
|
-
if ((0, import_fs12.existsSync)(targetPath) && !force) {
|
|
2815
|
-
output.error(t("install.alreadyExists", { path: targetPath }));
|
|
2816
|
-
return { exitCode: 1, errorMessage: "file already exists" };
|
|
2817
|
-
}
|
|
2818
|
-
(0, import_fs12.mkdirSync)(targetDir, { recursive: true });
|
|
2819
|
-
(0, import_fs12.writeFileSync)(targetPath, content, "utf8");
|
|
2820
|
-
output.log(t("install.installed", { path: targetPath }));
|
|
2821
|
-
return { exitCode: 0 };
|
|
2822
|
-
}
|
|
2823
|
-
function installCodex(content, projectRoot, force, output) {
|
|
2824
|
-
const targetDir = (0, import_path11.join)(projectRoot, ".agents", "skills", "instrlint");
|
|
2825
|
-
const targetPath = (0, import_path11.join)(targetDir, "SKILL.md");
|
|
2826
|
-
if ((0, import_fs12.existsSync)(targetPath) && !force) {
|
|
2827
|
-
output.error(t("install.alreadyExists", { path: targetPath }));
|
|
2828
|
-
return { exitCode: 1, errorMessage: "file already exists" };
|
|
2829
|
-
}
|
|
2830
|
-
(0, import_fs12.mkdirSync)(targetDir, { recursive: true });
|
|
2831
|
-
(0, import_fs12.writeFileSync)(targetPath, content, "utf8");
|
|
2832
|
-
output.log(t("install.installed", { path: targetPath }));
|
|
2833
|
-
return { exitCode: 0 };
|
|
2834
|
-
}
|
|
2835
|
-
function runInstall(opts, output = console) {
|
|
2836
|
-
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
2837
|
-
const force = opts.force ?? false;
|
|
2838
|
-
if (opts.claudeCode) {
|
|
2839
|
-
let content;
|
|
2840
|
-
try {
|
|
2841
|
-
content = readSkillContent("claude-code");
|
|
2842
|
-
} catch (err) {
|
|
2843
|
-
output.error(String(err));
|
|
2844
|
-
return { exitCode: 1, errorMessage: String(err) };
|
|
2845
|
-
}
|
|
2846
|
-
return installClaudeCode(
|
|
2847
|
-
content,
|
|
2848
|
-
projectRoot,
|
|
2849
|
-
opts.project ?? false,
|
|
2850
|
-
force,
|
|
2851
|
-
output
|
|
2852
|
-
);
|
|
2853
|
-
}
|
|
2854
|
-
if (opts.codex) {
|
|
2855
|
-
let content;
|
|
2856
|
-
try {
|
|
2857
|
-
content = readSkillContent("codex");
|
|
2858
|
-
} catch (err) {
|
|
2859
|
-
output.error(String(err));
|
|
2860
|
-
return { exitCode: 1, errorMessage: String(err) };
|
|
2861
|
-
}
|
|
2862
|
-
return installCodex(content, projectRoot, force, output);
|
|
2863
|
-
}
|
|
2864
|
-
output.error(t("install.unknownTarget"));
|
|
2865
|
-
return { exitCode: 1, errorMessage: "no target specified" };
|
|
2866
|
-
}
|
|
2867
|
-
|
|
2868
3091
|
// src/cli.ts
|
|
2869
3092
|
var program = new import_commander.Command();
|
|
2870
3093
|
program.enablePositionalOptions().name("instrlint").description(
|
|
2871
3094
|
"Lint and optimize your CLAUDE.md / AGENTS.md \u2014 find dead rules, token waste, and structural issues"
|
|
2872
|
-
).version(
|
|
3095
|
+
).version(CURRENT_VERSION).option(
|
|
2873
3096
|
"--format <type>",
|
|
2874
3097
|
"output format (terminal|json|markdown)",
|
|
2875
3098
|
"terminal"
|
|
@@ -2882,27 +3105,27 @@ program.command("budget").description("Token budget analysis only").option(
|
|
|
2882
3105
|
"--format <type>",
|
|
2883
3106
|
"output format (terminal|json|markdown)",
|
|
2884
3107
|
"terminal"
|
|
2885
|
-
).option("--tool <name>", "force tool detection (claude-code|codex|cursor)").action(async function() {
|
|
3108
|
+
).option("--lang <locale>", "output language (en|zh-TW)").option("--tool <name>", "force tool detection (claude-code|codex|cursor)").action(async function() {
|
|
2886
3109
|
const opts = this.opts();
|
|
2887
|
-
const lang = this.parent?.opts()?.lang;
|
|
3110
|
+
const lang = opts.lang ?? this.parent?.opts()?.lang;
|
|
2888
3111
|
const result = await runBudget({
|
|
2889
3112
|
...opts,
|
|
2890
3113
|
...lang !== void 0 && { lang }
|
|
2891
3114
|
});
|
|
2892
3115
|
if (result.exitCode !== 0) process.exit(result.exitCode);
|
|
2893
3116
|
});
|
|
2894
|
-
program.command("deadrules").description("Dead rule detection only").option("--format <type>", "output format (terminal|json)", "terminal").option("--tool <name>", "force tool detection (claude-code|codex|cursor)").action(async function() {
|
|
3117
|
+
program.command("deadrules").description("Dead rule detection only").option("--format <type>", "output format (terminal|json)", "terminal").option("--lang <locale>", "output language (en|zh-TW)").option("--tool <name>", "force tool detection (claude-code|codex|cursor)").action(async function() {
|
|
2895
3118
|
const opts = this.opts();
|
|
2896
|
-
const lang = this.parent?.opts()?.lang;
|
|
3119
|
+
const lang = opts.lang ?? this.parent?.opts()?.lang;
|
|
2897
3120
|
const result = await runDeadRules({
|
|
2898
3121
|
...opts,
|
|
2899
3122
|
...lang !== void 0 && { lang }
|
|
2900
3123
|
});
|
|
2901
3124
|
if (result.exitCode !== 0) process.exit(result.exitCode);
|
|
2902
3125
|
});
|
|
2903
|
-
program.command("structure").description("Structural analysis only").option("--format <type>", "output format (terminal|json)", "terminal").option("--tool <name>", "force tool detection (claude-code|codex|cursor)").action(async function() {
|
|
3126
|
+
program.command("structure").description("Structural analysis only").option("--format <type>", "output format (terminal|json)", "terminal").option("--lang <locale>", "output language (en|zh-TW)").option("--tool <name>", "force tool detection (claude-code|codex|cursor)").action(async function() {
|
|
2904
3127
|
const opts = this.opts();
|
|
2905
|
-
const lang = this.parent?.opts()?.lang;
|
|
3128
|
+
const lang = opts.lang ?? this.parent?.opts()?.lang;
|
|
2906
3129
|
const result = await runStructure({
|
|
2907
3130
|
...opts,
|
|
2908
3131
|
...lang !== void 0 && { lang }
|