@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.
Files changed (3) hide show
  1. package/README.md +197 -0
  2. package/dist/index.js +44 -30
  3. 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
- Array.prototype.push.apply(doc.errors, this.errors);
37934
- Array.prototype.push.apply(doc.warnings, this.warnings);
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 yield* this.parseBlockStart();
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
- switch (this.charAt(0)) {
42071
- case '!':
42072
- return ((yield* this.pushTag()) +
42073
- (yield* this.pushSpaces(true)) +
42074
- (yield* this.pushIndicators()));
42075
- case '&':
42076
- return ((yield* this.pushUntil(isNotAnchorChar)) +
42077
- (yield* this.pushSpaces(true)) +
42078
- (yield* this.pushIndicators()));
42079
- case '-': // this is an error
42080
- case '?': // this is an error outside flow collections
42081
- case ':': {
42082
- const inFlow = this.flowLevel > 0;
42083
- const ch1 = this.charAt(1);
42084
- if (isEmpty(ch1) || (inFlow && flowIndicatorChars.has(ch1))) {
42085
- if (!inFlow)
42086
- this.indentNext = this.indentValue + 1;
42087
- else if (this.flowKey)
42088
- this.flowKey = false;
42089
- return ((yield* this.pushCount(1)) +
42090
- (yield* this.pushSpaces(true)) +
42091
- (yield* this.pushIndicators()));
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 0;
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
- Array.prototype.push.apply(it.value.end, it.sep);
42301
+ arrayPushArray(it.value.end, it.sep);
42288
42302
  else
42289
42303
  it.value.end = it.sep;
42290
42304
  }
42291
42305
  else
42292
- Array.prototype.push.apply(it.start, it.sep);
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
- Array.prototype.push.apply(end, it.start);
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
- Array.prototype.push.apply(end, it.start);
42941
+ arrayPushArray(end, it.start);
42928
42942
  end.push(this.sourceToken);
42929
42943
  seq.items.pop();
42930
42944
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lousy-agents/lint",
3
- "version": "5.14.3",
3
+ "version": "5.14.5",
4
4
  "description": "Programmatic lint API for validating AI coding assistant configurations — skills, agents, hooks, and instructions",
5
5
  "type": "module",
6
6
  "repository": {