instrlint 0.1.3 → 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/dist/cli.cjs CHANGED
@@ -815,8 +815,12 @@ function analyzeBudget(instructions) {
815
815
  autoFixable: false
816
816
  });
817
817
  }
818
- const { tokens: rulesTokens, method: rulesMethod } = sumTokens(instructions.rules);
819
- const { tokens: skillsTokens, method: skillsMethod } = sumTokens(instructions.skills);
818
+ const { tokens: rulesTokens, method: rulesMethod } = sumTokens(
819
+ instructions.rules
820
+ );
821
+ const { tokens: skillsTokens, method: skillsMethod } = sumTokens(
822
+ instructions.skills
823
+ );
820
824
  const { tokens: subFilesTokens, method: subFilesMethod } = sumTokens(
821
825
  instructions.subFiles
822
826
  );
@@ -854,11 +858,18 @@ function analyzeBudget(instructions) {
854
858
  autoFixable: false
855
859
  });
856
860
  }
857
- const tokenMethod = [rootFileMethod, rulesMethod, skillsMethod, subFilesMethod].every(
858
- (m) => m === "measured"
859
- ) ? "measured" : "estimated";
861
+ const tokenMethod = [
862
+ rootFileMethod,
863
+ rulesMethod,
864
+ skillsMethod,
865
+ subFilesMethod
866
+ ].every((m) => m === "measured") ? "measured" : "estimated";
860
867
  const fileBreakdown = [
861
- { path: instructions.rootFile.path, tokenCount: rootFileTokens, tokenMethod: rootFileMethod },
868
+ {
869
+ path: instructions.rootFile.path,
870
+ tokenCount: rootFileTokens,
871
+ tokenMethod: rootFileMethod
872
+ },
862
873
  ...instructions.rules.map((r) => ({
863
874
  path: r.path,
864
875
  tokenCount: r.tokenCount,
@@ -878,6 +889,7 @@ function analyzeBudget(instructions) {
878
889
  const summary = {
879
890
  systemPromptTokens: SYSTEM_PROMPT_TOKENS,
880
891
  rootFileTokens,
892
+ rootFileLines: rootLines,
881
893
  rootFileMethod,
882
894
  rulesTokens,
883
895
  rulesMethod,
@@ -1454,6 +1466,8 @@ var INFO_DEDUCTION = 1;
1454
1466
  var MAX_CRITICAL_DEDUCTION = 40;
1455
1467
  var MAX_WARNING_DEDUCTION = 30;
1456
1468
  var MAX_INFO_DEDUCTION = 10;
1469
+ var MAX_ROOT_FILE_PENALTY = 30;
1470
+ var MAX_BUDGET_DEDUCTION = 30;
1457
1471
  function gradeFromScore(score) {
1458
1472
  if (score >= 90) return "A";
1459
1473
  if (score >= 80) return "B";
@@ -1465,16 +1479,32 @@ function calculateScore(findings, budget) {
1465
1479
  const criticals = findings.filter((f) => f.severity === "critical").length;
1466
1480
  const warnings = findings.filter((f) => f.severity === "warning").length;
1467
1481
  const infos = findings.filter((f) => f.severity === "info").length;
1468
- const criticalDeduction = Math.min(criticals * CRITICAL_DEDUCTION, MAX_CRITICAL_DEDUCTION);
1469
- const warningDeduction = Math.min(warnings * WARNING_DEDUCTION, MAX_WARNING_DEDUCTION);
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
+ );
1470
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);
1471
1499
  const baselinePct = budget.totalBaseline / CONTEXT_WINDOW2;
1472
1500
  let budgetDeduction = 0;
1473
- if (baselinePct > 0.5) budgetDeduction = 15;
1474
- else if (baselinePct > 0.25) budgetDeduction = 5;
1501
+ if (baselinePct > 0.25) {
1502
+ budgetDeduction = 5 + Math.floor((baselinePct - 0.25) * 40);
1503
+ }
1504
+ budgetDeduction = Math.min(budgetDeduction, MAX_BUDGET_DEDUCTION);
1475
1505
  const score = Math.max(
1476
1506
  0,
1477
- 100 - criticalDeduction - warningDeduction - infoDeduction - budgetDeduction
1507
+ 100 - criticalDeduction - warningDeduction - infoDeduction - rootFilePenalty - budgetDeduction
1478
1508
  );
1479
1509
  return { score, grade: gradeFromScore(score) };
1480
1510
  }
@@ -1586,6 +1616,8 @@ var en_default = {
1586
1616
  "markdown.budgetIssues": "Budget Issues",
1587
1617
  "markdown.refactoringOpportunities": "Refactoring Opportunities",
1588
1618
  "markdown.lineRef": "(line {{line}})",
1619
+ "markdown.budgetCategory": "Category",
1620
+ "markdown.budgetTokens": "Tokens",
1589
1621
  "markdown.actionPlan": "## Action Plan",
1590
1622
  "markdown.attribution": "*Generated by [instrlint](https://github.com/jed1978/instrlint)*",
1591
1623
  "ci.passed": "\u2713 instrlint passed score={{score}} grade={{grade}}",
@@ -1691,6 +1723,8 @@ var zh_TW_default = {
1691
1723
  "markdown.budgetIssues": "Token \u9810\u7B97\u554F\u984C",
1692
1724
  "markdown.refactoringOpportunities": "\u91CD\u69CB\u5EFA\u8B70",
1693
1725
  "markdown.lineRef": "\uFF08\u7B2C {{line}} \u884C\uFF09",
1726
+ "markdown.budgetCategory": "\u985E\u5225",
1727
+ "markdown.budgetTokens": "Token \u6578",
1694
1728
  "markdown.actionPlan": "## \u884C\u52D5\u8A08\u756B",
1695
1729
  "markdown.attribution": "*\u7531 [instrlint](https://github.com/jed1978/instrlint) \u751F\u6210*",
1696
1730
  "ci.passed": "\u2713 instrlint \u901A\u904E \u5206\u6578={{score}} \u7B49\u7D1A={{grade}}",
@@ -2034,15 +2068,25 @@ function mdSeverityIcon(f) {
2034
2068
  if (f.severity === "warning") return "\u{1F7E1}";
2035
2069
  return "\u2139\uFE0F";
2036
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
+ }
2037
2080
  function reportMarkdown(report, extraSections = []) {
2038
- const { project, tool, score, grade, findings } = report;
2081
+ const { project, tool, score, grade, findings, budget } = report;
2039
2082
  const criticals = findings.filter((f) => f.severity === "critical").length;
2040
2083
  const warnings = findings.filter((f) => f.severity === "warning").length;
2041
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}";
2042
2086
  const lines = [
2043
2087
  `# ${t("markdown.title", { project })}`,
2044
2088
  "",
2045
- t("markdown.scoreLine", { score: String(score), grade, tool }),
2089
+ `${gradeEmoji} **${score}/100 (${grade})** ${mdBar(score / 100, 25)} \xB7 \`${tool}\``,
2046
2090
  "",
2047
2091
  t("markdown.summary"),
2048
2092
  "",
@@ -2053,6 +2097,58 @@ function reportMarkdown(report, extraSections = []) {
2053
2097
  `| ${t("markdown.info")} | ${infos} |`,
2054
2098
  ""
2055
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("");
2056
2152
  const categories = [
2057
2153
  {
2058
2154
  labelKey: "markdown.contradictions",
@@ -2303,7 +2399,21 @@ function markdownStructureSuggestions(suggestions, projectRoot) {
2303
2399
  var import_fs10 = require("fs");
2304
2400
  var import_path8 = require("path");
2305
2401
  var import_os = require("os");
2306
- var CURRENT_VERSION = "0.1.3";
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();
2307
2417
  var VERSION_RE = /^instrlint-version:\s*(.+)$/m;
2308
2418
  function extractVersion(content) {
2309
2419
  const m = VERSION_RE.exec(content);
@@ -2342,17 +2452,30 @@ function checkSkillUpdate(projectRoot) {
2342
2452
  return null;
2343
2453
  }
2344
2454
  function injectVersion(content, version) {
2345
- return content.replace(/^(---\n[\s\S]*?)(---)$/m, `$1instrlint-version: ${version}
2346
- $2`);
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;
2347
2470
  }
2348
2471
 
2349
2472
  // src/commands/install-command.ts
2350
2473
  var import_fs11 = require("fs");
2351
2474
  var import_path9 = require("path");
2352
2475
  var import_os2 = require("os");
2353
- var import_url = require("url");
2476
+ var import_url2 = require("url");
2354
2477
  function resolveSkillFile(target) {
2355
- const thisFile = (0, import_url.fileURLToPath)(importMetaUrl);
2478
+ const thisFile = (0, import_url2.fileURLToPath)(importMetaUrl);
2356
2479
  const subDir = target === "claude-code" ? "claude-code" : "codex";
2357
2480
  for (const levels of [2, 3]) {
2358
2481
  const parts = Array(levels).fill("..");
@@ -2540,12 +2663,9 @@ async function runAll(opts, output = console) {
2540
2663
  output.log(
2541
2664
  ` ${import_chalk4.default.yellow("\u26A0")} ${import_chalk4.default.bold(t("install.outdatedTitle"))} (${t("install.outdatedVersions", { installed: skillUpdate.installedVersion, current: skillUpdate.currentVersion })})`
2542
2665
  );
2543
- const confirmed = await promptYesNo(
2544
- ` ${t("install.updatePrompt")}`,
2545
- output
2546
- );
2666
+ const confirmed = await promptYesNo(` ${t("install.updatePrompt")}`);
2547
2667
  if (confirmed) {
2548
- runInstall(
2668
+ const installResult = runInstall(
2549
2669
  {
2550
2670
  claudeCode: true,
2551
2671
  project: skillUpdate.isProject,
@@ -2554,12 +2674,17 @@ async function runAll(opts, output = console) {
2554
2674
  },
2555
2675
  output
2556
2676
  );
2677
+ if (installResult.exitCode !== 0) {
2678
+ output.error?.(
2679
+ `Update failed: ${installResult.errorMessage ?? "unknown error"}`
2680
+ );
2681
+ }
2557
2682
  }
2558
2683
  output.log("");
2559
2684
  }
2560
2685
  return { exitCode: 0 };
2561
2686
  }
2562
- function promptYesNo(question, output) {
2687
+ function promptYesNo(question) {
2563
2688
  return new Promise((resolve) => {
2564
2689
  const rl = (0, import_readline.createInterface)({
2565
2690
  input: process.stdin,
@@ -2967,7 +3092,7 @@ function runInitCi(opts, output = console) {
2967
3092
  var program = new import_commander.Command();
2968
3093
  program.enablePositionalOptions().name("instrlint").description(
2969
3094
  "Lint and optimize your CLAUDE.md / AGENTS.md \u2014 find dead rules, token waste, and structural issues"
2970
- ).version("0.1.0").option(
3095
+ ).version(CURRENT_VERSION).option(
2971
3096
  "--format <type>",
2972
3097
  "output format (terminal|json|markdown)",
2973
3098
  "terminal"