@lousy-agents/lint 5.14.3 → 5.14.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 +197 -0
- package/dist/index.js +44 -30
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# @lousy-agents/lint
|
|
2
|
+
|
|
3
|
+
Programmatic lint API for AI agent skill, agent, hook, and instruction files. Use this package to integrate lousy-agents lint checks into your own tools, web apps, or CI pipelines without the CLI.
|
|
4
|
+
|
|
5
|
+
For CLI-based linting, see the [`lint` command docs](https://github.com/zpratt/lousy-agents/blob/main/docs/lint.md).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @lousy-agents/lint
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { runLint } from '@lousy-agents/lint';
|
|
17
|
+
|
|
18
|
+
const result = await runLint({ directory: '/path/to/project' });
|
|
19
|
+
|
|
20
|
+
if (result.hasErrors) {
|
|
21
|
+
console.error('Lint failed');
|
|
22
|
+
for (const output of result.outputs) {
|
|
23
|
+
for (const diagnostic of output.diagnostics) {
|
|
24
|
+
console.error(`${diagnostic.filePath}:${diagnostic.line} [${diagnostic.severity}] ${diagnostic.message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## API
|
|
31
|
+
|
|
32
|
+
### `runLint(options): Promise<LintResult>`
|
|
33
|
+
|
|
34
|
+
Runs all lint checks on a project directory and returns structured results.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { runLint, LintValidationError } from '@lousy-agents/lint';
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const result = await runLint({
|
|
41
|
+
directory: '/path/to/project',
|
|
42
|
+
targets: {
|
|
43
|
+
skills: true,
|
|
44
|
+
agents: true,
|
|
45
|
+
hooks: false, // skip hook linting
|
|
46
|
+
instructions: false, // skip instruction linting
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
console.log('Has errors:', result.hasErrors);
|
|
51
|
+
console.log('Outputs:', result.outputs.length);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (error instanceof LintValidationError) {
|
|
54
|
+
console.error('Invalid input:', error.message);
|
|
55
|
+
} else {
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Options:**
|
|
62
|
+
|
|
63
|
+
| Property | Type | Required | Description |
|
|
64
|
+
| --- | --- | --- | --- |
|
|
65
|
+
| `directory` | `string` | ✅ | Absolute or relative path to the project directory to lint |
|
|
66
|
+
| `targets.skills` | `boolean` | — | Lint skill files (`.github/skills/`) |
|
|
67
|
+
| `targets.agents` | `boolean` | — | Lint agent files (`.github/agents/`) |
|
|
68
|
+
| `targets.hooks` | `boolean` | — | Lint hook configuration: `.github/hooks/agent-shell/hooks.json` (Copilot), `.claude/settings.json` and `.claude/settings.local.json` (Claude) |
|
|
69
|
+
| `targets.instructions` | `boolean` | — | Lint instruction files (`.github/instructions/`, `.github/copilot-instructions.md`) |
|
|
70
|
+
| `logger` | `LintLogger` | — | Custom logger for gateway diagnostics (must have a `.warn` method); defaults to `consola` |
|
|
71
|
+
|
|
72
|
+
When `targets` is omitted or all flags are `false`, all targets are linted.
|
|
73
|
+
|
|
74
|
+
**Throws `LintValidationError`** when `directory` is empty, contains control characters, path traversal sequences, does not exist, or is not a directory.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### `createFormatter(format): LintFormatter`
|
|
79
|
+
|
|
80
|
+
Creates an output formatter for rendering `LintOutput[]` to a string.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { runLint, createFormatter } from '@lousy-agents/lint';
|
|
84
|
+
|
|
85
|
+
const result = await runLint({ directory: '/path/to/project' });
|
|
86
|
+
const formatter = createFormatter('human');
|
|
87
|
+
console.log(formatter.format(result.outputs));
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
| Format | Description |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| `'human'` | Human-readable text, one diagnostic per line |
|
|
93
|
+
| `'json'` | JSON array of all diagnostics |
|
|
94
|
+
| `'rdjsonl'` | [Reviewdog Diagnostic Format](https://github.com/reviewdog/reviewdog) JSONL (one JSON object per line) — for CI integrations |
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### `DEFAULT_LINT_RULES`
|
|
99
|
+
|
|
100
|
+
The default rule configuration used when no `lousy-agents.config.ts` is present. Use this to inspect or extend the default rule set.
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { DEFAULT_LINT_RULES } from '@lousy-agents/lint';
|
|
104
|
+
|
|
105
|
+
console.log(DEFAULT_LINT_RULES.skills);
|
|
106
|
+
// { 'skill/missing-name': 'error', 'skill/missing-description': 'error', ... }
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### `LintValidationError`
|
|
112
|
+
|
|
113
|
+
Thrown when user-supplied input fails validation. Catch this to distinguish user-input errors from system errors.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { runLint, LintValidationError } from '@lousy-agents/lint';
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
await runLint({ directory: '' });
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (error instanceof LintValidationError) {
|
|
122
|
+
// directory was empty, missing, or not a directory
|
|
123
|
+
console.error(error.message);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Types
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import type {
|
|
134
|
+
LintResult,
|
|
135
|
+
LintOutput,
|
|
136
|
+
LintDiagnostic,
|
|
137
|
+
LintSeverity,
|
|
138
|
+
LintTarget,
|
|
139
|
+
LintOptions,
|
|
140
|
+
LintLogger,
|
|
141
|
+
LintRulesConfig,
|
|
142
|
+
LintFormatType,
|
|
143
|
+
LintFormatter,
|
|
144
|
+
InstructionQualityResult,
|
|
145
|
+
} from '@lousy-agents/lint';
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Key types:**
|
|
149
|
+
|
|
150
|
+
| Type | Description |
|
|
151
|
+
| --- | --- |
|
|
152
|
+
| `LintResult` | Top-level result: `outputs` array + `hasErrors` boolean |
|
|
153
|
+
| `LintOutput` | Per-target result: `diagnostics`, `filesAnalyzed`, `summary`, optional `qualityResult` |
|
|
154
|
+
| `LintDiagnostic` | Single diagnostic: `filePath`, `line`, `severity`, `message`, `ruleId`, `target` |
|
|
155
|
+
| `LintSeverity` | `"error" \| "warning" \| "info"` |
|
|
156
|
+
| `LintTarget` | `"skill" \| "agent" \| "instruction" \| "hook"` |
|
|
157
|
+
| `InstructionQualityResult` | Instruction quality scores and suggestions (populated when `instructions` target runs) |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Custom Logger
|
|
162
|
+
|
|
163
|
+
Pass any object with a `.warn` method to suppress or redirect gateway diagnostics:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { runLint } from '@lousy-agents/lint';
|
|
167
|
+
import { createLogger } from 'your-logger';
|
|
168
|
+
|
|
169
|
+
const logger = createLogger('lint');
|
|
170
|
+
await runLint({
|
|
171
|
+
directory: '/path/to/project',
|
|
172
|
+
logger: { warn: (msg, ...args) => logger.warn(msg, ...args) },
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## CI Integration (rdjsonl)
|
|
179
|
+
|
|
180
|
+
Output in Reviewdog Diagnostic Format for annotation-based CI feedback:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
node -e "
|
|
184
|
+
const { runLint, createFormatter } = require('@lousy-agents/lint');
|
|
185
|
+
runLint({ directory: '.' }).then(r => {
|
|
186
|
+
process.stdout.write(createFormatter('rdjsonl').format(r.outputs));
|
|
187
|
+
process.exit(r.hasErrors ? 1 : 0);
|
|
188
|
+
});
|
|
189
|
+
"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Related docs
|
|
195
|
+
|
|
196
|
+
- [`lint` CLI command](https://github.com/zpratt/lousy-agents/blob/main/docs/lint.md) — CLI-based linting with `npx @lousy-agents/cli lint`
|
|
197
|
+
- [GitHub Action](https://github.com/zpratt/lousy-agents/blob/main/docs/lint.md#github-action) — run linting in GitHub Actions without installing Node.js
|
package/dist/index.js
CHANGED
|
@@ -37930,8 +37930,10 @@ class Composer {
|
|
|
37930
37930
|
}
|
|
37931
37931
|
}
|
|
37932
37932
|
if (afterDoc) {
|
|
37933
|
-
|
|
37934
|
-
|
|
37933
|
+
for (let i = 0; i < this.errors.length; ++i)
|
|
37934
|
+
doc.errors.push(this.errors[i]);
|
|
37935
|
+
for (let i = 0; i < this.warnings.length; ++i)
|
|
37936
|
+
doc.warnings.push(this.warnings[i]);
|
|
37935
37937
|
}
|
|
37936
37938
|
else {
|
|
37937
37939
|
doc.errors = this.errors;
|
|
@@ -41746,7 +41748,7 @@ class Lexer {
|
|
|
41746
41748
|
const n = (yield* this.pushCount(1)) + (yield* this.pushSpaces(true));
|
|
41747
41749
|
this.indentNext = this.indentValue + 1;
|
|
41748
41750
|
this.indentValue += n;
|
|
41749
|
-
return
|
|
41751
|
+
return 'block-start';
|
|
41750
41752
|
}
|
|
41751
41753
|
return 'doc';
|
|
41752
41754
|
}
|
|
@@ -42067,32 +42069,36 @@ class Lexer {
|
|
|
42067
42069
|
return 0;
|
|
42068
42070
|
}
|
|
42069
42071
|
*pushIndicators() {
|
|
42070
|
-
|
|
42071
|
-
|
|
42072
|
-
|
|
42073
|
-
|
|
42074
|
-
|
|
42075
|
-
|
|
42076
|
-
|
|
42077
|
-
|
|
42078
|
-
|
|
42079
|
-
|
|
42080
|
-
|
|
42081
|
-
|
|
42082
|
-
|
|
42083
|
-
|
|
42084
|
-
|
|
42085
|
-
|
|
42086
|
-
|
|
42087
|
-
|
|
42088
|
-
|
|
42089
|
-
|
|
42090
|
-
|
|
42091
|
-
|
|
42072
|
+
let n = 0;
|
|
42073
|
+
loop: while (true) {
|
|
42074
|
+
switch (this.charAt(0)) {
|
|
42075
|
+
case '!':
|
|
42076
|
+
n += yield* this.pushTag();
|
|
42077
|
+
n += yield* this.pushSpaces(true);
|
|
42078
|
+
continue loop;
|
|
42079
|
+
case '&':
|
|
42080
|
+
n += yield* this.pushUntil(isNotAnchorChar);
|
|
42081
|
+
n += yield* this.pushSpaces(true);
|
|
42082
|
+
continue loop;
|
|
42083
|
+
case '-': // this is an error
|
|
42084
|
+
case '?': // this is an error outside flow collections
|
|
42085
|
+
case ':': {
|
|
42086
|
+
const inFlow = this.flowLevel > 0;
|
|
42087
|
+
const ch1 = this.charAt(1);
|
|
42088
|
+
if (isEmpty(ch1) || (inFlow && flowIndicatorChars.has(ch1))) {
|
|
42089
|
+
if (!inFlow)
|
|
42090
|
+
this.indentNext = this.indentValue + 1;
|
|
42091
|
+
else if (this.flowKey)
|
|
42092
|
+
this.flowKey = false;
|
|
42093
|
+
n += yield* this.pushCount(1);
|
|
42094
|
+
n += yield* this.pushSpaces(true);
|
|
42095
|
+
continue loop;
|
|
42096
|
+
}
|
|
42092
42097
|
}
|
|
42093
42098
|
}
|
|
42099
|
+
break loop;
|
|
42094
42100
|
}
|
|
42095
|
-
return
|
|
42101
|
+
return n;
|
|
42096
42102
|
}
|
|
42097
42103
|
*pushTag() {
|
|
42098
42104
|
if (this.charAt(1) === '<') {
|
|
@@ -42272,6 +42278,14 @@ function getFirstKeyStartProps(prev) {
|
|
|
42272
42278
|
}
|
|
42273
42279
|
return prev.splice(i, prev.length);
|
|
42274
42280
|
}
|
|
42281
|
+
function arrayPushArray(target, source) {
|
|
42282
|
+
// May exhaust call stack with large `source` array
|
|
42283
|
+
if (source.length < 1e5)
|
|
42284
|
+
Array.prototype.push.apply(target, source);
|
|
42285
|
+
else
|
|
42286
|
+
for (let i = 0; i < source.length; ++i)
|
|
42287
|
+
target.push(source[i]);
|
|
42288
|
+
}
|
|
42275
42289
|
function fixFlowSeqItems(fc) {
|
|
42276
42290
|
if (fc.start.type === 'flow-seq-start') {
|
|
42277
42291
|
for (const it of fc.items) {
|
|
@@ -42284,12 +42298,12 @@ function fixFlowSeqItems(fc) {
|
|
|
42284
42298
|
delete it.key;
|
|
42285
42299
|
if (isFlowToken(it.value)) {
|
|
42286
42300
|
if (it.value.end)
|
|
42287
|
-
|
|
42301
|
+
arrayPushArray(it.value.end, it.sep);
|
|
42288
42302
|
else
|
|
42289
42303
|
it.value.end = it.sep;
|
|
42290
42304
|
}
|
|
42291
42305
|
else
|
|
42292
|
-
|
|
42306
|
+
arrayPushArray(it.start, it.sep);
|
|
42293
42307
|
delete it.sep;
|
|
42294
42308
|
}
|
|
42295
42309
|
}
|
|
@@ -42709,7 +42723,7 @@ class Parser {
|
|
|
42709
42723
|
const prev = map.items[map.items.length - 2];
|
|
42710
42724
|
const end = prev?.value?.end;
|
|
42711
42725
|
if (Array.isArray(end)) {
|
|
42712
|
-
|
|
42726
|
+
arrayPushArray(end, it.start);
|
|
42713
42727
|
end.push(this.sourceToken);
|
|
42714
42728
|
map.items.pop();
|
|
42715
42729
|
return;
|
|
@@ -42924,7 +42938,7 @@ class Parser {
|
|
|
42924
42938
|
const prev = seq.items[seq.items.length - 2];
|
|
42925
42939
|
const end = prev?.value?.end;
|
|
42926
42940
|
if (Array.isArray(end)) {
|
|
42927
|
-
|
|
42941
|
+
arrayPushArray(end, it.start);
|
|
42928
42942
|
end.push(this.sourceToken);
|
|
42929
42943
|
seq.items.pop();
|
|
42930
42944
|
return;
|
package/package.json
CHANGED