@heyramzi/lint 0.0.0-stage → 1.32.2
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/LICENSE +21 -0
- package/README.md +277 -2
- package/bin/heyramzi-comment-length.js +99 -0
- package/bin/heyramzi-commit-msg.js +31 -0
- package/bin/heyramzi-file-tokens.js +66 -0
- package/bin/heyramzi-inline-path.js +79 -0
- package/bin/heyramzi-jev-code.js +107 -0
- package/bin/heyramzi-push-diff.js +93 -0
- package/bin/heyramzi-repo-rules.js +70 -0
- package/bin/heyramzi-slop.js +314 -0
- package/bin/heyramzi-svelte-markup.js +50 -0
- package/bin/heyramzi-ui-rules.js +56 -0
- package/comment-length.js +368 -0
- package/commit-msg.js +57 -0
- package/config/oxlintrc.json +87 -0
- package/data/invisibles.js +218 -0
- package/data/jev-bench.js +115 -0
- package/data/jev-code-bench.js +313 -0
- package/data/slop-words.d.ts +64 -0
- package/data/slop-words.js +2681 -0
- package/file-tokens.js +122 -0
- package/gate-files.js +14 -0
- package/index.js +116 -0
- package/inline-path.js +90 -0
- package/jev-code.js +194 -0
- package/jev.js +341 -0
- package/package.json +80 -4
- package/processors/markdown.js +22 -0
- package/push-diff.js +55 -0
- package/register.d.ts +43 -0
- package/register.js +255 -0
- package/repo-rules/packages.js +164 -0
- package/repo-rules/paths.js +101 -0
- package/repo-rules/toolchain.js +172 -0
- package/repo-rules/tree.js +51 -0
- package/repo-rules/workflows.js +116 -0
- package/repo-rules.js +54 -0
- package/rules/convex-ast.js +66 -0
- package/rules/convex-index-name.js +40 -0
- package/rules/convex-internal-only.js +59 -0
- package/rules/convex-no-date-now-in-query.js +48 -0
- package/rules/convex-no-deprecated-crons.js +40 -0
- package/rules/convex-no-filter.js +41 -0
- package/rules/convex-no-get-metadata.js +26 -0
- package/rules/convex-no-v-any.js +24 -0
- package/rules/convex-react-client-module-scope.js +37 -0
- package/rules/convex-require-args.js +39 -0
- package/rules/convex-require-returns.js +31 -0
- package/rules/convex-runtime-imports.js +48 -0
- package/rules/max-function-lines.js +105 -0
- package/rules/max-object-methods.js +86 -0
- package/rules/no-ai-words.js +78 -0
- package/rules/no-app-stores.js +25 -0
- package/rules/no-chained-type-assertions.js +91 -0
- package/rules/no-class.js +47 -0
- package/rules/no-conditional-empty-object-spread.js +64 -0
- package/rules/no-deep-phosphor-imports.js +29 -0
- package/rules/no-default-export.js +70 -0
- package/rules/no-em-dash.js +194 -0
- package/rules/no-emoji.js +56 -0
- package/rules/no-hardcoded-models.js +63 -0
- package/rules/no-inline-path.js +46 -0
- package/rules/no-inline-svg.js +67 -0
- package/rules/no-known-value-widening.js +289 -0
- package/rules/no-legacy-compat.js +35 -0
- package/rules/no-overkill-fallback.js +77 -0
- package/rules/no-raw-try-catch.js +26 -0
- package/rules/no-redundant-prop-fallback.js +153 -0
- package/rules/no-relative-imports.js +213 -0
- package/rules/no-relative-type-imports.js +40 -0
- package/rules/no-rune-outside-svelte-file.js +56 -0
- package/rules/no-rune-shadowing-binding.js +85 -0
- package/rules/no-skipped-or-focused-tests.js +74 -0
- package/rules/no-snake-case-props.js +45 -0
- package/rules/no-svelte4-syntax.js +33 -0
- package/rules/no-vitest-import-in-vite-plus.js +64 -0
- package/rules/props-interface.js +44 -0
- package/rules/redirect-external-needs-option.js +45 -0
- package/rules/require-safety-comment-for-type-assertion.js +110 -0
- package/rules/svelte-section-order.js +115 -0
- package/rules/test-file.js +8 -0
- package/scripts/count-hits.mjs +213 -0
- package/scripts/harvest-voice.mjs +161 -0
- package/scripts/jev-bench.mjs +41 -0
- package/scripts/jev-code-bench.mjs +61 -0
- package/scripts/measure-register.mjs +61 -0
- package/slop.d.ts +53 -0
- package/slop.js +644 -0
- package/svelte-markup.js +107 -0
- package/svelte-template.js +258 -0
- package/ui-rules.js +324 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Scores every held-out line against every Jev shape. Exits 1 when a bad line fails nothing or a plain line fails something. Run after any change to SHAPES: `node scripts/jev-bench.mjs`.
|
|
3
|
+
|
|
4
|
+
// Needs KILO_API_KEY, from the env or Proton Pass (Dev vault, item Kilo).
|
|
5
|
+
|
|
6
|
+
import { PARAGRAPHS, SENTENCES } from "#lint/data/jev-bench.js";
|
|
7
|
+
import { PARAGRAPH_SHAPES, SHAPES, auditShapes, kiloKey } from "#lint/jev.js";
|
|
8
|
+
|
|
9
|
+
// Measuring the model, so no cached score stands in for a fresh answer.
|
|
10
|
+
process.env.HEYRAMZI_SLOP_CACHE = "off";
|
|
11
|
+
|
|
12
|
+
const FAIL = 0.75;
|
|
13
|
+
let wrong = 0;
|
|
14
|
+
const key = kiloKey();
|
|
15
|
+
|
|
16
|
+
async function score(rows, shapes, wrap) {
|
|
17
|
+
for (const row of rows) {
|
|
18
|
+
const { findings, review } = await auditShapes(wrap(row.text), { key });
|
|
19
|
+
const scores = Object.fromEntries(Object.keys(shapes).map((id) => [id, 0]));
|
|
20
|
+
for (const hit of [...findings, ...review])
|
|
21
|
+
if (hit.id in scores) scores[hit.id] = Math.max(scores[hit.id], hit.p);
|
|
22
|
+
const failed = Object.entries(scores)
|
|
23
|
+
.filter(([, p]) => p >= FAIL)
|
|
24
|
+
.map(([id]) => id);
|
|
25
|
+
const ok = row.shape ? failed.length > 0 : failed.length === 0;
|
|
26
|
+
if (!ok) wrong += 1;
|
|
27
|
+
const label =
|
|
28
|
+
ok && row.shape && !failed.includes(row.shape) ? ` (caught as ${failed.join(", ")})` : "";
|
|
29
|
+
const shown = Object.entries(scores)
|
|
30
|
+
.map(([id, p]) => `${id} ${p.toFixed(2)}`)
|
|
31
|
+
.join(" ");
|
|
32
|
+
console.log(
|
|
33
|
+
`${ok ? "ok " : "MISS"} ${(row.shape ?? "plain").padEnd(11)} ${row.text.slice(0, 60)}${label}\n ${shown}`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await score(SENTENCES, SHAPES, (text) => text);
|
|
39
|
+
await score(PARAGRAPHS, PARAGRAPH_SHAPES, (text) => text);
|
|
40
|
+
console.log(`\n${wrong} on the wrong side of ${FAIL}`);
|
|
41
|
+
process.exit(wrong ? 1 : 0);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Scores the Jev code rules on the synthetic set, per tier. Exits 1 when HELDOUT misses its bar.
|
|
3
|
+
// `node scripts/jev-code-bench.mjs [train|heldout|all]`. Needs KILO_API_KEY (env or Proton Pass). Why: TECHLOG.md.
|
|
4
|
+
|
|
5
|
+
import { HELDOUT, TRAIN } from "#lint/data/jev-code-bench.js";
|
|
6
|
+
import { auditCode } from "#lint/jev-code.js";
|
|
7
|
+
import { kiloKey } from "#lint/jev.js";
|
|
8
|
+
|
|
9
|
+
const set = process.argv[2] ?? "all";
|
|
10
|
+
const key = kiloKey();
|
|
11
|
+
|
|
12
|
+
async function score(name, cases) {
|
|
13
|
+
const tally = { bad: 0, high: 0, medium: 0, plain: 0, fpHigh: 0, fpMedium: 0 };
|
|
14
|
+
const rows = await Promise.all(
|
|
15
|
+
cases.map(async (c) => ({ c, r: await auditCode(c.code, c.file, { key }) })),
|
|
16
|
+
);
|
|
17
|
+
for (const { c, r } of rows) {
|
|
18
|
+
const high = new Set(r.findings.map((h) => h.id));
|
|
19
|
+
const medium = new Set(r.review.map((h) => h.id).filter((id) => !high.has(id)));
|
|
20
|
+
const fired =
|
|
21
|
+
[...high]
|
|
22
|
+
.map((id) => `${id}!`)
|
|
23
|
+
.concat([...medium].map((id) => `${id}?`))
|
|
24
|
+
.join(" ") || "-";
|
|
25
|
+
let verdict;
|
|
26
|
+
if (c.rule) {
|
|
27
|
+
tally.bad++;
|
|
28
|
+
if (high.has(c.rule)) {
|
|
29
|
+
tally.high++;
|
|
30
|
+
verdict = "high";
|
|
31
|
+
} else if (medium.has(c.rule)) {
|
|
32
|
+
tally.medium++;
|
|
33
|
+
verdict = "med ";
|
|
34
|
+
} else verdict = "MISS";
|
|
35
|
+
} else {
|
|
36
|
+
tally.plain++;
|
|
37
|
+
if (high.size) {
|
|
38
|
+
tally.fpHigh++;
|
|
39
|
+
verdict = "FP! ";
|
|
40
|
+
} else if (medium.size) {
|
|
41
|
+
tally.fpMedium++;
|
|
42
|
+
verdict = "fp? ";
|
|
43
|
+
} else verdict = "ok ";
|
|
44
|
+
}
|
|
45
|
+
console.log(`${verdict} ${(c.rule ?? "plain").padEnd(11)} ${c.file.padEnd(36)} ${fired}`);
|
|
46
|
+
}
|
|
47
|
+
const pct = (n, d) => `${Math.round((100 * n) / d)}%`;
|
|
48
|
+
console.log(
|
|
49
|
+
`\n${name}: caught ${pct(tally.high, tally.bad)} at high, ${pct(tally.high + tally.medium, tally.bad)} with medium; ` +
|
|
50
|
+
`false alarms on plain code ${pct(tally.fpHigh, tally.plain)} at high, ${pct(tally.fpHigh + tally.fpMedium, tally.plain)} with medium\n`,
|
|
51
|
+
);
|
|
52
|
+
return tally;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (set !== "heldout") await score("train", TRAIN);
|
|
56
|
+
if (set !== "train") {
|
|
57
|
+
const t = await score("heldout", HELDOUT);
|
|
58
|
+
// The bar: medium tier catches 3 in 4, and a high-tier alarm never fires on plain code.
|
|
59
|
+
const ok = (t.high + t.medium) / t.bad >= 0.75 && t.fpHigh === 0;
|
|
60
|
+
process.exit(ok ? 0 : 1);
|
|
61
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Recount a corpus, so a band in `register.js` is never a typed number.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* node scripts/measure-register.mjs <file|dir> [more...]
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* A directory is read one level deep for .txt and .md. A `.json` file is read
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* as an array of posts and every string field longer than 40 characters is treated as body text, which is the shape `voice-corpus` writes.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { measureRegister } from "#lint/register.js";
|
|
21
|
+
|
|
22
|
+
function bodies(target) {
|
|
23
|
+
const st = statSync(target);
|
|
24
|
+
if (st.isDirectory()) {
|
|
25
|
+
return readdirSync(target)
|
|
26
|
+
.filter((f) => /\.(txt|md)$/i.test(f))
|
|
27
|
+
.flatMap((f) => bodies(join(target, f)));
|
|
28
|
+
}
|
|
29
|
+
if (target.endsWith(".json")) {
|
|
30
|
+
const data = JSON.parse(readFileSync(target, "utf8"));
|
|
31
|
+
if (!Array.isArray(data)) throw new Error(`${target} is not a JSON array of posts.`);
|
|
32
|
+
return data
|
|
33
|
+
.map((r) =>
|
|
34
|
+
typeof r === "string"
|
|
35
|
+
? r
|
|
36
|
+
: Object.values(r).find((v) => typeof v === "string" && v.length > 40),
|
|
37
|
+
)
|
|
38
|
+
.filter(Boolean);
|
|
39
|
+
}
|
|
40
|
+
return [readFileSync(target, "utf8")];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const targets = process.argv.slice(2);
|
|
44
|
+
if (targets.length === 0) {
|
|
45
|
+
process.stderr.write("usage: measure-register.mjs <file|dir|json> [more...]\n");
|
|
46
|
+
process.exit(2);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const m = measureRegister(targets.flatMap(bodies).join("\n\n"));
|
|
50
|
+
const row = (k, v) => console.log(` ${k.padEnd(16)} ${v.toFixed(1)}`);
|
|
51
|
+
console.log(`${m.words} words, ${m.sentences} sentences`);
|
|
52
|
+
row("meanSentence", m.meanSentence);
|
|
53
|
+
row("medianSentence", m.medianSentence);
|
|
54
|
+
row("fragmentShare", m.fragmentShare);
|
|
55
|
+
row("longShare", m.longShare);
|
|
56
|
+
row("lengthVariation", m.lengthVariation);
|
|
57
|
+
row("contractions", m.contractions);
|
|
58
|
+
row("secondPerson", m.secondPerson);
|
|
59
|
+
row("firstPerson", m.firstPerson);
|
|
60
|
+
row("chaining", m.chaining);
|
|
61
|
+
row("egoRatio", m.egoRatio);
|
package/slop.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type SlopFinding = {
|
|
2
|
+
/** `word`, `phrase`, `pattern`, `artifact`, `structure`, `dash`, `emoji`, `hashtag` or `ledger`. */
|
|
3
|
+
kind: string;
|
|
4
|
+
/** The banned word, the phrase, the pattern id, or the check name. */
|
|
5
|
+
id: string;
|
|
6
|
+
/** The offending text, trimmed to something readable in a terminal. */
|
|
7
|
+
detail: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type SlopAudit = {
|
|
11
|
+
/** Every hit. A non-empty list fails the gate. */
|
|
12
|
+
findings: SlopFinding[];
|
|
13
|
+
/** Shapes a regex cannot judge. Reported, never failed. */
|
|
14
|
+
review: SlopFinding[];
|
|
15
|
+
/** The language the checks ran in. English on a tie. */
|
|
16
|
+
language: "en" | "fr";
|
|
17
|
+
/** First-person against second-person word counts, in that language. */
|
|
18
|
+
ledger: { first: number; second: number };
|
|
19
|
+
words: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type SlopLint = SlopAudit & {
|
|
23
|
+
/** The text with every invisible character removed. Always safe to send. */
|
|
24
|
+
text: string;
|
|
25
|
+
/** What was removed, one row per character kind. */
|
|
26
|
+
removed: { what: string; count: number }[];
|
|
27
|
+
/** True when `findings` is empty. Says whether the words are good enough. */
|
|
28
|
+
ok: boolean;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** HTML in, plain text out, so an email body audits the same way a post does. */
|
|
32
|
+
export declare function stripMarkup(raw: string): string;
|
|
33
|
+
|
|
34
|
+
/** Remove the invisible characters and normalise the fake spaces. */
|
|
35
|
+
export declare function stripInvisibles(
|
|
36
|
+
text: string,
|
|
37
|
+
options?: { normalizeSpaces?: boolean },
|
|
38
|
+
): { text: string; removed: { what: string; count: number }[] };
|
|
39
|
+
|
|
40
|
+
/** True when the text carries nothing invisible. */
|
|
41
|
+
export declare function isClean(text: string): boolean;
|
|
42
|
+
|
|
43
|
+
/** Which language the draft is in, by stopword count. English on a tie. */
|
|
44
|
+
export declare function detectLanguage(text: string): "en" | "fr";
|
|
45
|
+
|
|
46
|
+
/** The enumerable half of the humanizer, over one string. Never rewrites. */
|
|
47
|
+
export declare function auditSlop(text: string): SlopAudit;
|
|
48
|
+
|
|
49
|
+
/** Clean, then audit. The one call a publishing path makes. */
|
|
50
|
+
export declare function slopGate(
|
|
51
|
+
raw: string,
|
|
52
|
+
options?: { markup?: boolean; normalizeSpaces?: boolean },
|
|
53
|
+
): SlopLint;
|