@aurelienbbn/agentlint 0.1.0
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 +158 -0
- package/dist/bin.d.mts +1 -0
- package/dist/bin.mjs +1355 -0
- package/dist/bin.mjs.map +1 -0
- package/dist/index.d.mts +265 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +98 -0
- package/dist/index.mjs.map +1 -0
- package/dist/node-yh9mLvnE.mjs +137 -0
- package/dist/node-yh9mLvnE.mjs.map +1 -0
- package/dist/wasm/tree-sitter-javascript.wasm +0 -0
- package/dist/wasm/tree-sitter-tsx.wasm +0 -0
- package/dist/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/wasm/tree-sitter.wasm +0 -0
- package/package.json +69 -0
- package/skills/agentlint/rule-advisor/SKILL.md +131 -0
- package/skills/agentlint/usage/SKILL.md +157 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
//#region src/domain/flag.ts
|
|
3
|
+
/**
|
|
4
|
+
* Flag types — what rules produce when they detect patterns.
|
|
5
|
+
*
|
|
6
|
+
* {@link FlagOptions} is the rule-author API (passed to `context.flag()`).
|
|
7
|
+
* {@link FlagRecord} is the enriched internal record used by the reporter.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Enriched flag record after processing — ready for the reporter.
|
|
14
|
+
*
|
|
15
|
+
* Uses `Schema.Class` for structural equality AND runtime validation
|
|
16
|
+
* on construction — invalid fields throw a clear `ParseError`.
|
|
17
|
+
*
|
|
18
|
+
* @since 0.1.0
|
|
19
|
+
* @category models
|
|
20
|
+
*/
|
|
21
|
+
var FlagRecord = class extends Schema.Class("FlagRecord")({
|
|
22
|
+
ruleName: Schema.String,
|
|
23
|
+
filename: Schema.String,
|
|
24
|
+
line: Schema.Number,
|
|
25
|
+
col: Schema.Number,
|
|
26
|
+
message: Schema.String,
|
|
27
|
+
sourceSnippet: Schema.String,
|
|
28
|
+
hash: Schema.String,
|
|
29
|
+
instruction: Schema.UndefinedOr(Schema.String),
|
|
30
|
+
suggest: Schema.UndefinedOr(Schema.String)
|
|
31
|
+
}) {};
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/domain/node.ts
|
|
34
|
+
/**
|
|
35
|
+
* Lazy wrapper around tree-sitter's `Node`.
|
|
36
|
+
*
|
|
37
|
+
* Provides a stable public API that keeps the tree-sitter dependency
|
|
38
|
+
* out of the consumer-facing surface. Children and parent are wrapped
|
|
39
|
+
* on first access — nodes that are never inspected cost nothing.
|
|
40
|
+
*
|
|
41
|
+
* @module
|
|
42
|
+
* @since 0.1.0
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* 0-indexed position in source text.
|
|
46
|
+
*
|
|
47
|
+
* Defined as a `Schema.Struct` so positions can be decoded and
|
|
48
|
+
* validated from external sources if needed.
|
|
49
|
+
*
|
|
50
|
+
* @since 0.1.0
|
|
51
|
+
* @category models
|
|
52
|
+
*/
|
|
53
|
+
const Position = Schema.Struct({
|
|
54
|
+
row: Schema.Number,
|
|
55
|
+
column: Schema.Number
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Private implementation of {@link AgentReviewNode}.
|
|
59
|
+
*
|
|
60
|
+
* Wraps a tree-sitter `Node` and lazily creates child/parent wrappers
|
|
61
|
+
* on first access. Nodes that are never traversed incur zero allocation.
|
|
62
|
+
*
|
|
63
|
+
* @since 0.1.0
|
|
64
|
+
* @category internals
|
|
65
|
+
*/
|
|
66
|
+
var AgentReviewNodeImpl = class AgentReviewNodeImpl {
|
|
67
|
+
#inner;
|
|
68
|
+
#children;
|
|
69
|
+
#parent;
|
|
70
|
+
constructor(inner) {
|
|
71
|
+
this.#inner = inner;
|
|
72
|
+
}
|
|
73
|
+
get type() {
|
|
74
|
+
return this.#inner.type;
|
|
75
|
+
}
|
|
76
|
+
get text() {
|
|
77
|
+
return this.#inner.text;
|
|
78
|
+
}
|
|
79
|
+
get startPosition() {
|
|
80
|
+
return this.#inner.startPosition;
|
|
81
|
+
}
|
|
82
|
+
get endPosition() {
|
|
83
|
+
return this.#inner.endPosition;
|
|
84
|
+
}
|
|
85
|
+
get isNamed() {
|
|
86
|
+
return this.#inner.isNamed;
|
|
87
|
+
}
|
|
88
|
+
get childCount() {
|
|
89
|
+
return this.#inner.childCount;
|
|
90
|
+
}
|
|
91
|
+
get children() {
|
|
92
|
+
if (this.#children === void 0) {
|
|
93
|
+
const result = [];
|
|
94
|
+
for (const c of this.#inner.children) if (c !== null) result.push(new AgentReviewNodeImpl(c));
|
|
95
|
+
this.#children = result;
|
|
96
|
+
}
|
|
97
|
+
return this.#children;
|
|
98
|
+
}
|
|
99
|
+
get parent() {
|
|
100
|
+
if (this.#parent === void 0) {
|
|
101
|
+
const p = this.#inner.parent;
|
|
102
|
+
this.#parent = p ? new AgentReviewNodeImpl(p) : null;
|
|
103
|
+
}
|
|
104
|
+
return this.#parent;
|
|
105
|
+
}
|
|
106
|
+
childByFieldName(name) {
|
|
107
|
+
const child = this.#inner.childForFieldName(name);
|
|
108
|
+
return child ? new AgentReviewNodeImpl(child) : null;
|
|
109
|
+
}
|
|
110
|
+
childrenByType(type) {
|
|
111
|
+
const result = [];
|
|
112
|
+
for (const c of this.#inner.children) if (c !== null && c.type === type) result.push(new AgentReviewNodeImpl(c));
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
descendantsOfType(type) {
|
|
116
|
+
const result = [];
|
|
117
|
+
for (const c of this.#inner.descendantsOfType(type)) if (c !== null) result.push(new AgentReviewNodeImpl(c));
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Wrap a raw tree-sitter node in the public {@link AgentReviewNode} interface.
|
|
123
|
+
*
|
|
124
|
+
* This is the only bridge between the internal tree-sitter dependency
|
|
125
|
+
* and the consumer-facing API. All child/parent nodes are lazily wrapped
|
|
126
|
+
* on access.
|
|
127
|
+
*
|
|
128
|
+
* @since 0.1.0
|
|
129
|
+
* @category constructors
|
|
130
|
+
*/
|
|
131
|
+
function wrapNode(inner) {
|
|
132
|
+
return new AgentReviewNodeImpl(inner);
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
export { wrapNode as n, FlagRecord as r, Position as t };
|
|
136
|
+
|
|
137
|
+
//# sourceMappingURL=node-yh9mLvnE.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-yh9mLvnE.mjs","names":["#inner","#children","#parent"],"sources":["../src/domain/flag.ts","../src/domain/node.ts"],"sourcesContent":["/**\n * Flag types — what rules produce when they detect patterns.\n *\n * {@link FlagOptions} is the rule-author API (passed to `context.flag()`).\n * {@link FlagRecord} is the enriched internal record used by the reporter.\n *\n * @module\n * @since 0.1.0\n */\n\nimport { Schema } from \"effect\";\nimport type { AgentReviewNode } from \"./node.js\";\n\n/**\n * Options passed to `context.flag()` by rule visitors.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface FlagOptions {\n /** The AST node that triggered the match. */\n readonly node: AgentReviewNode;\n /** Short one-liner displayed next to file:line in output. */\n readonly message: string;\n /**\n * Override `meta.instruction` for this specific match.\n * Appears inline in per-match notes.\n */\n readonly instruction?: string | undefined;\n /** Hint toward the fix. Not a command - just a nudge. */\n readonly suggest?: string | undefined;\n}\n\n/**\n * Enriched flag record after processing — ready for the reporter.\n *\n * Uses `Schema.Class` for structural equality AND runtime validation\n * on construction — invalid fields throw a clear `ParseError`.\n *\n * @since 0.1.0\n * @category models\n */\nexport class FlagRecord extends Schema.Class<FlagRecord>(\"FlagRecord\")({\n ruleName: Schema.String,\n filename: Schema.String,\n /** 1-based line number. */\n line: Schema.Number,\n /** 1-based column number. */\n col: Schema.Number,\n message: Schema.String,\n sourceSnippet: Schema.String,\n /** 7-char hex hash for stable match identification. */\n hash: Schema.String,\n instruction: Schema.UndefinedOr(Schema.String),\n suggest: Schema.UndefinedOr(Schema.String),\n}) {}\n","/**\n * Lazy wrapper around tree-sitter's `Node`.\n *\n * Provides a stable public API that keeps the tree-sitter dependency\n * out of the consumer-facing surface. Children and parent are wrapped\n * on first access — nodes that are never inspected cost nothing.\n *\n * @module\n * @since 0.1.0\n */\n\nimport { Schema } from \"effect\";\nimport type { Node as TSNode } from \"web-tree-sitter\";\n\n/**\n * 0-indexed position in source text.\n *\n * Defined as a `Schema.Struct` so positions can be decoded and\n * validated from external sources if needed.\n *\n * @since 0.1.0\n * @category models\n */\nexport const Position = Schema.Struct({\n row: Schema.Number,\n column: Schema.Number,\n});\n\n/** @since 0.1.0 */\nexport type Position = Schema.Schema.Type<typeof Position>;\n\n/**\n * Read-only view of a syntax tree node.\n *\n * One universal type — no per-kind subtypes. Rules narrow via\n * `node.type` string checks and field accessors.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface AgentReviewNode {\n /** tree-sitter grammar node type (e.g. `\"function_declaration\"`, `\"comment\"`) */\n readonly type: string;\n /** Full source text covered by this node. */\n readonly text: string;\n /** 0-indexed start position. */\n readonly startPosition: Position;\n /** 0-indexed end position. */\n readonly endPosition: Position;\n /** Whether this is a named node in the grammar. */\n readonly isNamed: boolean;\n /** Direct child nodes (lazily wrapped). */\n readonly children: ReadonlyArray<AgentReviewNode>;\n /** Parent node, or null for the root. */\n readonly parent: AgentReviewNode | null;\n /** Number of direct children. */\n readonly childCount: number;\n\n /** Get a child by its grammar field name (e.g. `\"name\"`, `\"body\"`). */\n childByFieldName(name: string): AgentReviewNode | null;\n /** All direct children matching the given node type. */\n childrenByType(type: string): ReadonlyArray<AgentReviewNode>;\n /** Recursively collect all descendants matching the given node type. */\n descendantsOfType(type: string): ReadonlyArray<AgentReviewNode>;\n}\n\n/**\n * Private implementation of {@link AgentReviewNode}.\n *\n * Wraps a tree-sitter `Node` and lazily creates child/parent wrappers\n * on first access. Nodes that are never traversed incur zero allocation.\n *\n * @since 0.1.0\n * @category internals\n */\nclass AgentReviewNodeImpl implements AgentReviewNode {\n readonly #inner: TSNode;\n #children: ReadonlyArray<AgentReviewNode> | undefined;\n #parent: AgentReviewNode | null | undefined;\n\n constructor(inner: TSNode) {\n this.#inner = inner;\n }\n\n get type(): string {\n return this.#inner.type;\n }\n\n get text(): string {\n return this.#inner.text;\n }\n\n get startPosition(): Position {\n return this.#inner.startPosition;\n }\n\n get endPosition(): Position {\n return this.#inner.endPosition;\n }\n\n get isNamed(): boolean {\n return this.#inner.isNamed;\n }\n\n get childCount(): number {\n return this.#inner.childCount;\n }\n\n get children(): ReadonlyArray<AgentReviewNode> {\n if (this.#children === undefined) {\n const result: AgentReviewNode[] = [];\n for (const c of this.#inner.children) {\n if (c !== null) result.push(new AgentReviewNodeImpl(c));\n }\n this.#children = result;\n }\n return this.#children;\n }\n\n get parent(): AgentReviewNode | null {\n if (this.#parent === undefined) {\n const p = this.#inner.parent;\n this.#parent = p ? new AgentReviewNodeImpl(p) : null;\n }\n return this.#parent;\n }\n\n childByFieldName(name: string): AgentReviewNode | null {\n const child = this.#inner.childForFieldName(name);\n return child ? new AgentReviewNodeImpl(child) : null;\n }\n\n childrenByType(type: string): ReadonlyArray<AgentReviewNode> {\n const result: AgentReviewNode[] = [];\n for (const c of this.#inner.children) {\n if (c !== null && c.type === type) result.push(new AgentReviewNodeImpl(c));\n }\n return result;\n }\n\n descendantsOfType(type: string): ReadonlyArray<AgentReviewNode> {\n const result: AgentReviewNode[] = [];\n for (const c of this.#inner.descendantsOfType(type)) {\n if (c !== null) result.push(new AgentReviewNodeImpl(c));\n }\n return result;\n }\n}\n\n/**\n * Wrap a raw tree-sitter node in the public {@link AgentReviewNode} interface.\n *\n * This is the only bridge between the internal tree-sitter dependency\n * and the consumer-facing API. All child/parent nodes are lazily wrapped\n * on access.\n *\n * @since 0.1.0\n * @category constructors\n */\nexport function wrapNode(inner: TSNode): AgentReviewNode {\n return new AgentReviewNodeImpl(inner);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0CA,IAAa,aAAb,cAAgC,OAAO,MAAkB,aAAa,CAAC;CACrE,UAAU,OAAO;CACjB,UAAU,OAAO;CAEjB,MAAM,OAAO;CAEb,KAAK,OAAO;CACZ,SAAS,OAAO;CAChB,eAAe,OAAO;CAEtB,MAAM,OAAO;CACb,aAAa,OAAO,YAAY,OAAO,OAAO;CAC9C,SAAS,OAAO,YAAY,OAAO,OAAO;CAC3C,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;AChCH,MAAa,WAAW,OAAO,OAAO;CACpC,KAAK,OAAO;CACZ,QAAQ,OAAO;CAChB,CAAC;;;;;;;;;;AAiDF,IAAM,sBAAN,MAAM,oBAA+C;CACnD;CACA;CACA;CAEA,YAAY,OAAe;AACzB,QAAA,QAAc;;CAGhB,IAAI,OAAe;AACjB,SAAO,MAAA,MAAY;;CAGrB,IAAI,OAAe;AACjB,SAAO,MAAA,MAAY;;CAGrB,IAAI,gBAA0B;AAC5B,SAAO,MAAA,MAAY;;CAGrB,IAAI,cAAwB;AAC1B,SAAO,MAAA,MAAY;;CAGrB,IAAI,UAAmB;AACrB,SAAO,MAAA,MAAY;;CAGrB,IAAI,aAAqB;AACvB,SAAO,MAAA,MAAY;;CAGrB,IAAI,WAA2C;AAC7C,MAAI,MAAA,aAAmB,KAAA,GAAW;GAChC,MAAM,SAA4B,EAAE;AACpC,QAAK,MAAM,KAAK,MAAA,MAAY,SAC1B,KAAI,MAAM,KAAM,QAAO,KAAK,IAAI,oBAAoB,EAAE,CAAC;AAEzD,SAAA,WAAiB;;AAEnB,SAAO,MAAA;;CAGT,IAAI,SAAiC;AACnC,MAAI,MAAA,WAAiB,KAAA,GAAW;GAC9B,MAAM,IAAI,MAAA,MAAY;AACtB,SAAA,SAAe,IAAI,IAAI,oBAAoB,EAAE,GAAG;;AAElD,SAAO,MAAA;;CAGT,iBAAiB,MAAsC;EACrD,MAAM,QAAQ,MAAA,MAAY,kBAAkB,KAAK;AACjD,SAAO,QAAQ,IAAI,oBAAoB,MAAM,GAAG;;CAGlD,eAAe,MAA8C;EAC3D,MAAM,SAA4B,EAAE;AACpC,OAAK,MAAM,KAAK,MAAA,MAAY,SAC1B,KAAI,MAAM,QAAQ,EAAE,SAAS,KAAM,QAAO,KAAK,IAAI,oBAAoB,EAAE,CAAC;AAE5E,SAAO;;CAGT,kBAAkB,MAA8C;EAC9D,MAAM,SAA4B,EAAE;AACpC,OAAK,MAAM,KAAK,MAAA,MAAY,kBAAkB,KAAK,CACjD,KAAI,MAAM,KAAM,QAAO,KAAK,IAAI,oBAAoB,EAAE,CAAC;AAEzD,SAAO;;;;;;;;;;;;;AAcX,SAAgB,SAAS,OAAgC;AACvD,QAAO,IAAI,oBAAoB,MAAM"}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aurelienbbn/agentlint",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Stateless, deterministic CLI that bridges traditional linters and AI-assisted code review",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent",
|
|
7
|
+
"ai",
|
|
8
|
+
"ast",
|
|
9
|
+
"code-review",
|
|
10
|
+
"linter",
|
|
11
|
+
"tanstack-intent",
|
|
12
|
+
"tree-sitter"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Aurelien Bobenrieth",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/aurelienbobenrieth/agentlint"
|
|
19
|
+
},
|
|
20
|
+
"bin": {
|
|
21
|
+
"agentlint": "dist/bin.mjs"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"skills",
|
|
26
|
+
"!skills/_artifacts"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.mts",
|
|
32
|
+
"import": "./dist/index.mjs"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@effect/platform-node": "4.0.0-beta.43",
|
|
37
|
+
"effect": "^4.0.0-beta.43",
|
|
38
|
+
"jiti": "^2.4.2",
|
|
39
|
+
"picomatch": "^4.0.2",
|
|
40
|
+
"web-tree-sitter": "^0.25.3"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@changesets/changelog-github": "^0.6.0",
|
|
44
|
+
"@changesets/cli": "^2.30.0",
|
|
45
|
+
"@tanstack/intent": "latest",
|
|
46
|
+
"@types/node": "^22.13.14",
|
|
47
|
+
"@types/picomatch": "^4.0.0",
|
|
48
|
+
"oxfmt": "latest",
|
|
49
|
+
"oxlint": "^0.16.7",
|
|
50
|
+
"tree-sitter-wasms": "^0.1.13",
|
|
51
|
+
"tsdown": "latest",
|
|
52
|
+
"typescript": "^5.7.3",
|
|
53
|
+
"vitest": "^3.1.1"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=22"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsdown",
|
|
60
|
+
"typecheck": "tsc --noEmit",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"test:watch": "vitest",
|
|
63
|
+
"lint": "oxlint",
|
|
64
|
+
"fmt": "oxfmt --write",
|
|
65
|
+
"fmt:check": "oxfmt --check",
|
|
66
|
+
"changeset": "changeset",
|
|
67
|
+
"check": "pnpm typecheck && pnpm lint && pnpm fmt:check && pnpm test"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentlint/rule-advisor
|
|
3
|
+
description: >
|
|
4
|
+
Classify a code quality concern into the right enforcement tool and act on it.
|
|
5
|
+
Activate when the user wants to enforce a pattern, catch a mistake, add a
|
|
6
|
+
check, create a rule, prevent a practice, guard against regressions, set up
|
|
7
|
+
linting, improve their feedback loop, or asks "how do I make sure X."
|
|
8
|
+
type: core
|
|
9
|
+
library: agentlint
|
|
10
|
+
library_version: "0.1.0"
|
|
11
|
+
sources:
|
|
12
|
+
- "aurelienbobenrieth/agentlint:README.md"
|
|
13
|
+
- "aurelienbobenrieth/agentlint:CONTRIBUTING.md"
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Rule Advisor
|
|
17
|
+
|
|
18
|
+
Classify what the user wants to enforce, pick the right tool, act on it.
|
|
19
|
+
|
|
20
|
+
## How to use this skill
|
|
21
|
+
|
|
22
|
+
1. Read the user's intent
|
|
23
|
+
2. If the intent is too vague to classify, clarify with one targeted question
|
|
24
|
+
3. Once clear, classify using the decision tree below
|
|
25
|
+
4. State your classification and reasoning in one sentence
|
|
26
|
+
5. Implement or guide the user to implement it
|
|
27
|
+
|
|
28
|
+
## Decision tree
|
|
29
|
+
|
|
30
|
+
**Can a pattern be detected by looking at code structure (AST, file, imports)?**
|
|
31
|
+
|
|
32
|
+
NO, and no metric or structure can trigger detection either
|
|
33
|
+
→ Document as a code review guideline. This is a team convention.
|
|
34
|
+
|
|
35
|
+
YES, and the correct action is always the same (mechanical fix)
|
|
36
|
+
→ Existing linter rule (e.g. oxlint, eslint, biome, or similar)
|
|
37
|
+
if a well-known rule exists for it.
|
|
38
|
+
→ Custom lint rule (e.g. eslint plugin, or similar)
|
|
39
|
+
if it's project-specific but still deterministic.
|
|
40
|
+
→ Codemod (e.g. jscodeshift, ts-morph, or similar)
|
|
41
|
+
if it needs automated rewriting across files.
|
|
42
|
+
|
|
43
|
+
YES, but what to do about each finding requires judgment
|
|
44
|
+
→ **agentlint rule.** Deterministic detection, AI-evaluated action.
|
|
45
|
+
|
|
46
|
+
**Is it about module or dependency structure?**
|
|
47
|
+
→ Dependency analysis (e.g. dependency-cruiser, madge, or similar)
|
|
48
|
+
for import direction and boundaries.
|
|
49
|
+
→ CI check or agentlint rule for file/directory naming conventions.
|
|
50
|
+
|
|
51
|
+
**Is it about runtime behavior?**
|
|
52
|
+
→ Type-level enforcement (e.g. branded types, schema validation, or similar)
|
|
53
|
+
if it can be caught at compile time.
|
|
54
|
+
→ Tests (unit, integration, property-based) if it needs execution.
|
|
55
|
+
→ Monitoring if it needs production signals.
|
|
56
|
+
|
|
57
|
+
## When the answer is agentlint
|
|
58
|
+
|
|
59
|
+
agentlint's niche: deterministic detection + non-deterministic evaluation.
|
|
60
|
+
|
|
61
|
+
Three scopes are available:
|
|
62
|
+
|
|
63
|
+
- **Single-node visitor** - flag individual AST nodes (comments, functions,
|
|
64
|
+
catch blocks, magic numbers)
|
|
65
|
+
- **Program visitor** - analyze the full file AST (duplicate function bodies,
|
|
66
|
+
nested loops, export counts, prop counts)
|
|
67
|
+
- **File-level targeting** - use include/ignore globs to scope rules to
|
|
68
|
+
specific directories or file patterns
|
|
69
|
+
|
|
70
|
+
### Rule template
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { defineRule } from "agentlint";
|
|
74
|
+
|
|
75
|
+
const myRule = defineRule({
|
|
76
|
+
meta: {
|
|
77
|
+
name: "rule-name",
|
|
78
|
+
description: "One-line description of what this detects",
|
|
79
|
+
languages: ["ts", "tsx"],
|
|
80
|
+
include: ["src/**"], // optional: scope to paths
|
|
81
|
+
ignore: ["**/*.test.*"], // optional: exclude paths
|
|
82
|
+
instruction: `[Be specific. Tell the AI agent exactly what to evaluate
|
|
83
|
+
for each finding, when a finding is acceptable, and what action to take
|
|
84
|
+
when it is a true positive.]`,
|
|
85
|
+
},
|
|
86
|
+
createOnce(context) {
|
|
87
|
+
return {
|
|
88
|
+
// Available visitors: program, function_declaration, arrow_function,
|
|
89
|
+
// call_expression, identifier, string, comment, class_declaration,
|
|
90
|
+
// method_definition, property_identifier, type_annotation,
|
|
91
|
+
// interface_declaration, import_statement, export_statement,
|
|
92
|
+
// variable_declarator, if_statement, return_statement,
|
|
93
|
+
// object, array, pair, jsx_element, jsx_self_closing_element
|
|
94
|
+
comment(node) {
|
|
95
|
+
context.flag({ node, message: node.text.trim() });
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### AgentReviewNode API
|
|
103
|
+
|
|
104
|
+
- `text` - source text of the node
|
|
105
|
+
- `type` - tree-sitter node type
|
|
106
|
+
- `startPosition` / `endPosition` - `{ row, column }`
|
|
107
|
+
- `children` - child nodes array
|
|
108
|
+
- `parent` - parent node or undefined
|
|
109
|
+
- `child(i)` / `namedChild(i)` - get child by index
|
|
110
|
+
- `childForFieldName(name)` - get child by field name
|
|
111
|
+
- `descendantsOfType(type)` - find all descendants matching a type
|
|
112
|
+
|
|
113
|
+
### Writing good instructions
|
|
114
|
+
|
|
115
|
+
The instruction is what the AI agent reads to evaluate each finding.
|
|
116
|
+
It determines whether findings are actionable or noise.
|
|
117
|
+
|
|
118
|
+
Bad: "Review this code"
|
|
119
|
+
|
|
120
|
+
Good: "Evaluate each flagged catch block. If the error is logged and
|
|
121
|
+
re-thrown, it is acceptable. If the error is silently swallowed - empty
|
|
122
|
+
catch or catch with only a console.log - suggest adding proper error
|
|
123
|
+
handling or propagation."
|
|
124
|
+
|
|
125
|
+
### Testing
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npx agentlint check --all --rule rule-name
|
|
129
|
+
npx agentlint check src/handlers/checkout.ts --rule rule-name
|
|
130
|
+
npx agentlint check --all --rule rule-name --dry-run
|
|
131
|
+
```
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentlint/usage
|
|
3
|
+
description: >
|
|
4
|
+
Run agentlint CLI after code changes to catch patterns for AI evaluation.
|
|
5
|
+
Activate when finishing code modifications, before committing, or when
|
|
6
|
+
the developer asks to lint, scan, or review code with agentlint. Covers
|
|
7
|
+
agentlint check, agentlint list, agentlint review, agentlint init,
|
|
8
|
+
inline suppression, and output interpretation.
|
|
9
|
+
type: core
|
|
10
|
+
library: agentlint
|
|
11
|
+
library_version: "0.1.0"
|
|
12
|
+
sources:
|
|
13
|
+
- "aurelienbobenrieth/agentlint:README.md"
|
|
14
|
+
- "aurelienbobenrieth/agentlint:src/bin.ts"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# agentlint
|
|
18
|
+
|
|
19
|
+
Stateless, deterministic linter whose output is designed for you (an AI coding agent) to evaluate. It uses tree-sitter to parse code, runs visitor-based rules that flag suspicious patterns, and outputs structured reports with natural language instructions.
|
|
20
|
+
|
|
21
|
+
## Setup
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install agentlint
|
|
25
|
+
npx agentlint init
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This creates `agentlint.config.ts` with a starter template. Add rules to the `rules` object.
|
|
29
|
+
|
|
30
|
+
## Core Patterns
|
|
31
|
+
|
|
32
|
+
### Run after code changes
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Default: scan files changed in current branch
|
|
36
|
+
npx agentlint check
|
|
37
|
+
|
|
38
|
+
# Scan specific files or globs
|
|
39
|
+
npx agentlint check src/utils.ts "src/**/*.tsx"
|
|
40
|
+
|
|
41
|
+
# Scan all files
|
|
42
|
+
npx agentlint check --all
|
|
43
|
+
|
|
44
|
+
# Only run a specific rule
|
|
45
|
+
npx agentlint check --rule no-noise-comments
|
|
46
|
+
|
|
47
|
+
# Dry-run (counts only, no instruction blocks)
|
|
48
|
+
npx agentlint check --dry-run
|
|
49
|
+
|
|
50
|
+
# Diff against a specific branch
|
|
51
|
+
npx agentlint check --base main
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Read and act on output
|
|
55
|
+
|
|
56
|
+
Output is grouped by rule. Each rule section contains:
|
|
57
|
+
|
|
58
|
+
1. **Match listings** with `[hash] file:line:col source-line`
|
|
59
|
+
2. **Instruction block** explaining how to evaluate the matches
|
|
60
|
+
|
|
61
|
+
Process one rule section at a time:
|
|
62
|
+
|
|
63
|
+
1. Read the instruction block for the rule
|
|
64
|
+
2. Evaluate each match against the criteria in the instruction
|
|
65
|
+
3. For matches that are genuine issues: fix them
|
|
66
|
+
4. For matches that are acceptable: move on
|
|
67
|
+
5. Re-run `agentlint check` after fixes — resolved matches disappear
|
|
68
|
+
|
|
69
|
+
### Mark findings as reviewed
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Mark specific hashes as reviewed (they disappear from future output)
|
|
73
|
+
npx agentlint review abc1234 def5678
|
|
74
|
+
|
|
75
|
+
# Mark all current flags as reviewed
|
|
76
|
+
npx agentlint review --all
|
|
77
|
+
|
|
78
|
+
# Reset reviewed state (see all flags again)
|
|
79
|
+
npx agentlint review --reset
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Suppress inline
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// agentlint-ignore no-noise-comments -- explains the retry formula
|
|
86
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Write a rule
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { defineConfig, defineRule } from "agentlint";
|
|
93
|
+
|
|
94
|
+
const noTodos = defineRule({
|
|
95
|
+
meta: {
|
|
96
|
+
name: "no-todos",
|
|
97
|
+
description: "Flags TODO comments for evaluation",
|
|
98
|
+
languages: ["ts", "tsx"],
|
|
99
|
+
instruction: "Evaluate each TODO. Convert actionable ones to issues, remove stale ones.",
|
|
100
|
+
},
|
|
101
|
+
createOnce(context) {
|
|
102
|
+
return {
|
|
103
|
+
comment(node) {
|
|
104
|
+
if (node.text.includes("TODO")) {
|
|
105
|
+
context.flag({ node, message: node.text.trim() });
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export default defineConfig({
|
|
113
|
+
include: ["src/**/*.{ts,tsx}"],
|
|
114
|
+
rules: { "no-todos": noTodos },
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Common Mistakes
|
|
119
|
+
|
|
120
|
+
### HIGH Exit code 1 is not an error
|
|
121
|
+
|
|
122
|
+
Wrong:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Treating non-zero exit as a failure and stopping
|
|
126
|
+
npx agentlint check || echo "agentlint failed"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Correct:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Exit code 1 means findings exist — read and evaluate the output
|
|
133
|
+
npx agentlint check
|
|
134
|
+
# Then process the stdout, don't treat it as a crash
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
agentlint exits with code 1 when findings exist. This is expected behavior, not a crash. Read the output and evaluate each finding.
|
|
138
|
+
|
|
139
|
+
### HIGH Fixing all findings without reading instructions
|
|
140
|
+
|
|
141
|
+
Wrong: Blindly "fixing" every flagged line without reading the rule's instruction block.
|
|
142
|
+
|
|
143
|
+
Correct: Read the instruction block first. It tells you the evaluation criteria. Some findings are intentionally acceptable — the instruction explains which.
|
|
144
|
+
|
|
145
|
+
### MEDIUM Looping on already-evaluated findings
|
|
146
|
+
|
|
147
|
+
Wrong: Re-evaluating the same findings across multiple agentlint runs in one session.
|
|
148
|
+
|
|
149
|
+
Correct: Evaluate each finding once. If it persists after your fix, tell the developer rather than retrying. Use `agentlint review <hash>` to mark evaluated findings.
|
|
150
|
+
|
|
151
|
+
## Constraints
|
|
152
|
+
|
|
153
|
+
- Process one rule section at a time, not all findings at once
|
|
154
|
+
- Never attempt more than one fix per finding
|
|
155
|
+
- If agentlint still flags something after your fix, tell the developer
|
|
156
|
+
- When the instruction says "ask the developer," do that instead of guessing
|
|
157
|
+
- Do not loop on findings you already evaluated in this session
|