@gd-kirie/alint-config 0.1.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/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # `@gd-kirie/alint-config`
2
+
3
+ Alint rules and presets for Kirie repository review experiments.
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ import gdKirieAlintConfig from "@gd-kirie/alint-config";
9
+
10
+ export default gdKirieAlintConfig;
11
+ ```
12
+
13
+ ## Rules
14
+
15
+ ### `@gd-kirie/no-stringified-rethrow`
16
+
17
+ LLM-backed `onFile` rule that warns when a `catch` block formats the caught
18
+ error into a string and throws a new error without preserving the original error
19
+ as `cause`.
20
+
21
+ Examples of incorrect code for this rule:
22
+
23
+ ```ts
24
+ try {
25
+ await run();
26
+ }
27
+ catch (error) {
28
+ throw new Error(`Failed to run: ${error}`);
29
+ }
30
+ ```
31
+
32
+ Will output a warning like:
33
+
34
+ ```
35
+ > alint /Path/to/file.ts
36
+
37
+ /Path/to/file.ts
38
+ 28:0 warning Caught error is stringified into a new Error message without preserving the original error as cause. @gd-kirie/no-stringified-rethrow
39
+
40
+
41
+ 1 warn / 0 error | 1,598 tokens
42
+ ```
43
+
44
+ Examples of correct code for this rule:
45
+
46
+ ```ts
47
+ try {
48
+ await run();
49
+ }
50
+ catch (error) {
51
+ throw new Error("Failed to run.", { cause: error });
52
+ }
53
+ ```
54
+
55
+ or a project-specific error type that preserves `cause`.
56
+
57
+ ## Planned Rules
58
+
59
+ The following rules are intentionally documented but not implemented yet:
60
+
61
+ - `@gd-kirie/no-lossy-record-narrowing`: detect broad `Record<string, unknown>`
62
+ narrowing helpers that erase expected payload or config structure.
63
+ - `@gd-kirie/no-polling-wait-loop`: detect in-process readiness waits that use
64
+ sleep-loop polling instead of events, lifecycle hooks, or explicit readiness
65
+ promises.
@@ -0,0 +1,87 @@
1
+ //#region src/types.d.ts
2
+ type Awaitable<T> = Promise<T> | T;
3
+ interface AlintConfig {
4
+ plugins?: PluginDefinition[];
5
+ rules?: Record<string, RuleConfigEntry>;
6
+ }
7
+ interface DiagnosticDescriptor {
8
+ evidence?: unknown;
9
+ filePath?: string;
10
+ loc?: DiagnosticLocation;
11
+ message: string;
12
+ }
13
+ interface DiagnosticLocation {
14
+ end?: SourcePosition;
15
+ start: SourcePosition;
16
+ }
17
+ interface InferenceUsageRecord {
18
+ inputTokens?: number;
19
+ modelId: string;
20
+ outputTokens?: number;
21
+ providerId: string;
22
+ totalTokens?: number;
23
+ }
24
+ interface PluginDefinition {
25
+ rules: Record<string, RuleDefinition>;
26
+ scope: string;
27
+ }
28
+ type RuleConfigEntry = [RuleSeverity] | RuleSeverity;
29
+ interface RuleContext {
30
+ metering?: {
31
+ recordUsage: (usage: InferenceUsageRecord) => void;
32
+ };
33
+ model: () => Promise<ResolvedModel>;
34
+ report: (diagnostic: DiagnosticDescriptor) => void;
35
+ }
36
+ interface RuleDefinition {
37
+ cache?: boolean | {
38
+ level?: "target";
39
+ };
40
+ create: (context: RuleContext) => RuleHandlers;
41
+ model?: ModelRequirement;
42
+ }
43
+ interface RuleHandlers {
44
+ onFile?: (file: SourceFile) => Awaitable<void>;
45
+ }
46
+ type RuleSeverity = "error" | "off" | "warn";
47
+ interface SourceFile {
48
+ language: "javascript" | "typescript" | "unknown";
49
+ lines: string[];
50
+ path: string;
51
+ text: string;
52
+ }
53
+ interface SourcePosition {
54
+ column: number;
55
+ line: number;
56
+ }
57
+ interface ResolvedModel {
58
+ id: string;
59
+ provider: {
60
+ endpoint: string;
61
+ headers?: Record<string, string>;
62
+ id: string;
63
+ };
64
+ }
65
+ interface ModelRequirement {
66
+ capabilities?: string[];
67
+ minContextWindow?: number;
68
+ params?: Record<string, unknown>;
69
+ size?: string;
70
+ }
71
+ //#endregion
72
+ //#region src/config.d.ts
73
+ declare const gdKirieAlintConfig: AlintConfig;
74
+ //#endregion
75
+ //#region src/plugin.d.ts
76
+ declare const gdKirieAlintPlugin: PluginDefinition;
77
+ //#endregion
78
+ //#region src/rules/no-stringified-rethrow.d.ts
79
+ interface ErrorWrappingReviewFinding {
80
+ evidence?: unknown;
81
+ line: number;
82
+ message?: string;
83
+ }
84
+ declare const noStringifiedRethrowRule: RuleDefinition;
85
+ //#endregion
86
+ export { type AlintConfig, type Awaitable, type DiagnosticDescriptor, type DiagnosticLocation, type ErrorWrappingReviewFinding, type InferenceUsageRecord, type ModelRequirement, type PluginDefinition, type ResolvedModel, type RuleConfigEntry, type RuleContext, type RuleDefinition, type RuleHandlers, type RuleSeverity, type SourceFile, type SourcePosition, gdKirieAlintConfig as default, gdKirieAlintConfig, gdKirieAlintPlugin, noStringifiedRethrowRule };
87
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/config.ts","../src/plugin.ts","../src/rules/no-stringified-rethrow.ts"],"mappings":";KAAY,SAAA,MAAe,OAAA,CAAQ,CAAA,IAAK,CAAA;AAAA,UAEvB,WAAA;EACf,OAAA,GAAU,gBAAA;EACV,KAAA,GAAQ,MAAA,SAAe,eAAA;AAAA;AAAA,UAGR,oBAAA;EACf,QAAA;EACA,QAAA;EACA,GAAA,GAAM,kBAAA;EACN,OAAA;AAAA;AAAA,UAGe,kBAAA;EACf,GAAA,GAAM,cAAA;EACN,KAAA,EAAO,cAAA;AAAA;AAAA,UAGQ,oBAAA;EACf,WAAA;EACA,OAAA;EACA,YAAA;EACA,UAAA;EACA,WAAA;AAAA;AAAA,UAGe,gBAAA;EACf,KAAA,EAAO,MAAA,SAAe,cAAA;EACtB,KAAA;AAAA;AAAA,KAGU,eAAA,IAAmB,YAAA,IAAgB,YAAA;AAAA,UAE9B,WAAA;EACf,QAAA;IACE,WAAA,GAAc,KAAA,EAAO,oBAAA;EAAA;EAEvB,KAAA,QAAa,OAAA,CAAQ,aAAA;EACrB,MAAA,GAAS,UAAA,EAAY,oBAAA;AAAA;AAAA,UAGN,cAAA;EACf,KAAA;IAAoB,KAAA;EAAA;EACpB,MAAA,GAAS,OAAA,EAAS,WAAA,KAAgB,YAAA;EAClC,KAAA,GAAQ,gBAAA;AAAA;AAAA,UAGO,YAAA;EACf,MAAA,IAAU,IAAA,EAAM,UAAA,KAAe,SAAA;AAAA;AAAA,KAGrB,YAAA;AAAA,UAEK,UAAA;EACf,QAAA;EACA,KAAA;EACA,IAAA;EACA,IAAA;AAAA;AAAA,UAGe,cAAA;EACf,MAAA;EACA,IAAA;AAAA;AAAA,UAGe,aAAA;EACf,EAAA;EACA,QAAA;IACE,QAAA;IACA,OAAA,GAAU,MAAA;IACV,EAAA;EAAA;AAAA;AAAA,UAIa,gBAAA;EACf,YAAA;EACA,gBAAA;EACA,MAAA,GAAS,MAAA;EACT,IAAA;AAAA;;;cC5EW,kBAAA,EAAoB,WAAA;;;cCApB,kBAAA,EAAoB,gBAAA;;;UCChB,0BAAA;EACf,QAAA;EACA,IAAA;EACA,OAAA;AAAA;AAAA,cAOW,wBAAA,EAA0B,cAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,88 @@
1
+ import { generateText } from "xsai";
2
+ //#region src/rules/no-stringified-rethrow.ts
3
+ const noStringifiedRethrowRule = { create: (ctx) => ({ async onFile(file) {
4
+ const findings = await reviewStringifiedRethrows(file, await ctx.model(), ctx.metering);
5
+ for (const finding of findings) ctx.report({
6
+ evidence: finding.evidence,
7
+ filePath: file.path,
8
+ loc: { start: {
9
+ column: 0,
10
+ line: finding.line
11
+ } },
12
+ message: finding.message ?? "Do not stringify and rethrow caught errors; preserve the original error as cause."
13
+ });
14
+ } }) };
15
+ async function reviewStringifiedRethrows(file, model, usage) {
16
+ const response = await generateText({
17
+ baseURL: model.provider.endpoint,
18
+ headers: model.provider.headers,
19
+ messages: createReviewMessages(file),
20
+ model: model.id,
21
+ response_format: { type: "json_object" },
22
+ temperature: 0
23
+ });
24
+ const content = response.text;
25
+ if (typeof content !== "string") throw new Error("alint model response did not include message content.");
26
+ recordUsage(usage, model, response);
27
+ const result = JSON.parse(content);
28
+ if (!Array.isArray(result.findings)) return [];
29
+ return result.findings.filter(isReviewFinding);
30
+ }
31
+ function createReviewMessages(file) {
32
+ return [{
33
+ content: [
34
+ "You are reviewing one JavaScript or TypeScript file.",
35
+ "",
36
+ "Task:",
37
+ "Find catch blocks that stringify, format, or otherwise collapse the caught error into a message string before throwing a new error, without preserving the original error as the error cause.",
38
+ "",
39
+ "Report only cases where the original caught error is not preserved through `cause` or an equivalent custom error cause field.",
40
+ "Do not report code that throws `new Error(message, { cause: error })`.",
41
+ "Do not report custom error classes that pass or store the caught error as cause.",
42
+ "Do not report code that logs an error and rethrows the same original error.",
43
+ "",
44
+ "Return JSON only with this shape:",
45
+ "{\"findings\":[{\"line\":1,\"message\":\"short diagnostic\",\"evidence\":{\"reason\":\"short reason\"}}]}",
46
+ "Use the line number of the throw statement.",
47
+ "Return an empty findings array when there is no issue."
48
+ ].join("\n"),
49
+ role: "system"
50
+ }, {
51
+ content: `File: ${file.path}\n\n${formatSourceWithLineNumbers(file.text)}`,
52
+ role: "user"
53
+ }];
54
+ }
55
+ function formatSourceWithLineNumbers(source) {
56
+ return source.split("\n").map((line, index) => `${index + 1} | ${line}`).join("\n");
57
+ }
58
+ function isReviewFinding(value) {
59
+ if (!value || typeof value !== "object") return false;
60
+ const finding = value;
61
+ return typeof finding.line === "number" && Number.isInteger(finding.line) && finding.line > 0;
62
+ }
63
+ function recordUsage(metering, model, response) {
64
+ if (!metering) return;
65
+ metering.recordUsage({
66
+ inputTokens: response.usage.inputTokens,
67
+ modelId: model.id,
68
+ outputTokens: response.usage.outputTokens,
69
+ providerId: model.provider.id,
70
+ totalTokens: response.usage.totalTokens
71
+ });
72
+ }
73
+ //#endregion
74
+ //#region src/plugin.ts
75
+ const gdKirieAlintPlugin = {
76
+ rules: { "no-stringified-rethrow": noStringifiedRethrowRule },
77
+ scope: "@gd-kirie"
78
+ };
79
+ //#endregion
80
+ //#region src/config.ts
81
+ const gdKirieAlintConfig = {
82
+ plugins: [gdKirieAlintPlugin],
83
+ rules: { "@gd-kirie/no-stringified-rethrow": "warn" }
84
+ };
85
+ //#endregion
86
+ export { gdKirieAlintConfig as default, gdKirieAlintConfig, gdKirieAlintPlugin, noStringifiedRethrowRule };
87
+
88
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/rules/no-stringified-rethrow.ts","../src/plugin.ts","../src/config.ts"],"sourcesContent":["import type { GenerateTextResult } from \"xsai\";\nimport { generateText } from \"xsai\";\nimport type { ResolvedModel, RuleContext, RuleDefinition, SourceFile } from \"../types\";\n\nexport interface ErrorWrappingReviewFinding {\n evidence?: unknown;\n line: number;\n message?: string;\n}\n\ninterface ReviewResponse {\n findings?: ErrorWrappingReviewFinding[];\n}\n\nexport const noStringifiedRethrowRule: RuleDefinition = {\n create: (ctx) => ({\n async onFile(file) {\n const model = await ctx.model();\n const findings = await reviewStringifiedRethrows(file, model, ctx.metering);\n\n for (const finding of findings) {\n ctx.report({\n evidence: finding.evidence,\n filePath: file.path,\n loc: {\n start: {\n column: 0,\n line: finding.line,\n },\n },\n message:\n finding.message ??\n \"Do not stringify and rethrow caught errors; preserve the original error as cause.\",\n });\n }\n },\n }),\n};\n\nasync function reviewStringifiedRethrows(\n file: SourceFile,\n model: ResolvedModel,\n usage: RuleContext[\"metering\"],\n): Promise<ErrorWrappingReviewFinding[]> {\n const response = await generateText({\n baseURL: model.provider.endpoint,\n headers: model.provider.headers,\n messages: createReviewMessages(file),\n model: model.id,\n response_format: {\n type: \"json_object\",\n },\n temperature: 0,\n });\n const content = response.text;\n\n if (typeof content !== \"string\") {\n throw new Error(\"alint model response did not include message content.\");\n }\n\n recordUsage(usage, model, response);\n\n const result = JSON.parse(content) as ReviewResponse;\n\n if (!Array.isArray(result.findings)) {\n return [];\n }\n\n return result.findings.filter(isReviewFinding);\n}\n\nfunction createReviewMessages(\n file: SourceFile,\n): Array<{ content: string; role: \"system\" | \"user\" }> {\n return [\n {\n content: [\n \"You are reviewing one JavaScript or TypeScript file.\",\n \"\",\n \"Task:\",\n \"Find catch blocks that stringify, format, or otherwise collapse the caught error into a message string before throwing a new error, without preserving the original error as the error cause.\",\n \"\",\n \"Report only cases where the original caught error is not preserved through `cause` or an equivalent custom error cause field.\",\n \"Do not report code that throws `new Error(message, { cause: error })`.\",\n \"Do not report custom error classes that pass or store the caught error as cause.\",\n \"Do not report code that logs an error and rethrows the same original error.\",\n \"\",\n \"Return JSON only with this shape:\",\n '{\"findings\":[{\"line\":1,\"message\":\"short diagnostic\",\"evidence\":{\"reason\":\"short reason\"}}]}',\n \"Use the line number of the throw statement.\",\n \"Return an empty findings array when there is no issue.\",\n ].join(\"\\n\"),\n role: \"system\",\n },\n {\n content: `File: ${file.path}\\n\\n${formatSourceWithLineNumbers(file.text)}`,\n role: \"user\",\n },\n ];\n}\n\nfunction formatSourceWithLineNumbers(source: string): string {\n return source\n .split(\"\\n\")\n .map((line, index) => `${index + 1} | ${line}`)\n .join(\"\\n\");\n}\n\nfunction isReviewFinding(value: unknown): value is ErrorWrappingReviewFinding {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const finding = value as Partial<ErrorWrappingReviewFinding>;\n\n return typeof finding.line === \"number\" && Number.isInteger(finding.line) && finding.line > 0;\n}\n\nfunction recordUsage(\n metering: RuleContext[\"metering\"],\n model: ResolvedModel,\n response: GenerateTextResult,\n): void {\n if (!metering) {\n return;\n }\n\n metering.recordUsage({\n inputTokens: response.usage.inputTokens,\n modelId: model.id,\n outputTokens: response.usage.outputTokens,\n providerId: model.provider.id,\n totalTokens: response.usage.totalTokens,\n });\n}\n","import { noStringifiedRethrowRule } from \"./rules/no-stringified-rethrow\";\nimport type { PluginDefinition } from \"./types\";\n\nexport const gdKirieAlintPlugin: PluginDefinition = {\n rules: {\n \"no-stringified-rethrow\": noStringifiedRethrowRule,\n },\n scope: \"@gd-kirie\",\n};\n","import { gdKirieAlintPlugin } from \"./plugin\";\nimport type { AlintConfig } from \"./types\";\n\nexport const gdKirieAlintConfig: AlintConfig = {\n plugins: [gdKirieAlintPlugin],\n rules: {\n \"@gd-kirie/no-stringified-rethrow\": \"warn\",\n },\n};\n"],"mappings":";;AAcA,MAAa,2BAA2C,EACtD,SAAS,SAAS,EAChB,MAAM,OAAO,MAAM;CAEjB,MAAM,WAAW,MAAM,0BAA0B,MAAM,MADnC,IAAI,MAAM,GACgC,IAAI,QAAQ;CAE1E,KAAK,MAAM,WAAW,UACpB,IAAI,OAAO;EACT,UAAU,QAAQ;EAClB,UAAU,KAAK;EACf,KAAK,EACH,OAAO;GACL,QAAQ;GACR,MAAM,QAAQ;EAChB,EACF;EACA,SACE,QAAQ,WACR;CACJ,CAAC;AAEL,EACF,GACF;AAEA,eAAe,0BACb,MACA,OACA,OACuC;CACvC,MAAM,WAAW,MAAM,aAAa;EAClC,SAAS,MAAM,SAAS;EACxB,SAAS,MAAM,SAAS;EACxB,UAAU,qBAAqB,IAAI;EACnC,OAAO,MAAM;EACb,iBAAiB,EACf,MAAM,cACR;EACA,aAAa;CACf,CAAC;CACD,MAAM,UAAU,SAAS;CAEzB,IAAI,OAAO,YAAY,UACrB,MAAM,IAAI,MAAM,uDAAuD;CAGzE,YAAY,OAAO,OAAO,QAAQ;CAElC,MAAM,SAAS,KAAK,MAAM,OAAO;CAEjC,IAAI,CAAC,MAAM,QAAQ,OAAO,QAAQ,GAChC,OAAO,CAAC;CAGV,OAAO,OAAO,SAAS,OAAO,eAAe;AAC/C;AAEA,SAAS,qBACP,MACqD;CACrD,OAAO,CACL;EACE,SAAS;GACP;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAAC,CAAC,KAAK,IAAI;EACX,MAAM;CACR,GACA;EACE,SAAS,SAAS,KAAK,KAAK,MAAM,4BAA4B,KAAK,IAAI;EACvE,MAAM;CACR,CACF;AACF;AAEA,SAAS,4BAA4B,QAAwB;CAC3D,OAAO,OACJ,MAAM,IAAI,CAAC,CACX,KAAK,MAAM,UAAU,GAAG,QAAQ,EAAE,KAAK,MAAM,CAAC,CAC9C,KAAK,IAAI;AACd;AAEA,SAAS,gBAAgB,OAAqD;CAC5E,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO;CAGT,MAAM,UAAU;CAEhB,OAAO,OAAO,QAAQ,SAAS,YAAY,OAAO,UAAU,QAAQ,IAAI,KAAK,QAAQ,OAAO;AAC9F;AAEA,SAAS,YACP,UACA,OACA,UACM;CACN,IAAI,CAAC,UACH;CAGF,SAAS,YAAY;EACnB,aAAa,SAAS,MAAM;EAC5B,SAAS,MAAM;EACf,cAAc,SAAS,MAAM;EAC7B,YAAY,MAAM,SAAS;EAC3B,aAAa,SAAS,MAAM;CAC9B,CAAC;AACH;;;ACnIA,MAAa,qBAAuC;CAClD,OAAO,EACL,0BAA0B,yBAC5B;CACA,OAAO;AACT;;;ACLA,MAAa,qBAAkC;CAC7C,SAAS,CAAC,kBAAkB;CAC5B,OAAO,EACL,oCAAoC,OACtC;AACF"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@gd-kirie/alint-config",
3
+ "version": "0.1.2",
4
+ "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/moeru-ai/godot-kirie.git",
8
+ "directory": "packages/alint-config"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "dependencies": {
23
+ "xsai": "^0.5.0-beta.6"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^25.6.0",
27
+ "tsdown": "^0.22.2",
28
+ "typescript": "^6.0.3"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public",
32
+ "registry": "https://registry.npmjs.org"
33
+ },
34
+ "scripts": {
35
+ "build": "tsdown",
36
+ "typecheck": "tsc -p tsconfig.json --noEmit"
37
+ }
38
+ }