agent-rules-lint 0.1.1__py3-none-any.whl

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.
@@ -0,0 +1,3 @@
1
+ """Lint AI agent instruction files."""
2
+
3
+ __version__ = "0.1.1"
@@ -0,0 +1,5 @@
1
+ from .cli import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(main())
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from pathlib import Path
5
+ import sys
6
+
7
+ from . import __version__
8
+ from .linter import LintResult, lint_path
9
+
10
+
11
+ def build_parser() -> argparse.ArgumentParser:
12
+ parser = argparse.ArgumentParser(
13
+ prog="agent-rules-lint",
14
+ description="Lint AI agent instruction files such as AGENTS.md, CLAUDE.md, Cursor rules, and Copilot instructions.",
15
+ )
16
+ parser.add_argument("path", nargs="?", default=".", help="Repository or directory to scan.")
17
+ parser.add_argument("--format", choices=("text", "json"), default="text", help="Output format.")
18
+ parser.add_argument("--max-chars", type=int, default=12000, help="Warn when a file exceeds this many characters.")
19
+ parser.add_argument("--warnings-as-errors", action="store_true", help="Exit non-zero when warnings are found.")
20
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
21
+ return parser
22
+
23
+
24
+ def render_text(result: LintResult) -> str:
25
+ lines = [
26
+ f"agent-rules-lint checked {len(result.files_checked)} file(s)",
27
+ f"errors={result.error_count} warnings={result.warning_count} info={result.info_count}",
28
+ ]
29
+ if result.files_checked:
30
+ lines.append("")
31
+ lines.append("Files:")
32
+ for file in result.files_checked:
33
+ lines.append(f" - {file}")
34
+
35
+ if result.findings:
36
+ lines.append("")
37
+ lines.append("Findings:")
38
+ for finding in result.findings:
39
+ location = finding.file
40
+ if finding.line is not None:
41
+ location = f"{location}:{finding.line}"
42
+ lines.append(f" [{finding.severity}] {location} {finding.rule}: {finding.message}")
43
+ else:
44
+ lines.append("")
45
+ lines.append("No findings.")
46
+
47
+ return "\n".join(lines)
48
+
49
+
50
+ def exit_code(result: LintResult, warnings_as_errors: bool) -> int:
51
+ if result.error_count:
52
+ return 1
53
+ if warnings_as_errors and result.warning_count:
54
+ return 1
55
+ return 0
56
+
57
+
58
+ def main(argv: list[str] | None = None) -> int:
59
+ parser = build_parser()
60
+ args = parser.parse_args(argv)
61
+ result = lint_path(Path(args.path), max_chars=args.max_chars)
62
+
63
+ if args.format == "json":
64
+ print(result.to_json())
65
+ else:
66
+ print(render_text(result))
67
+
68
+ return exit_code(result, warnings_as_errors=args.warnings_as_errors)
69
+
70
+
71
+ if __name__ == "__main__":
72
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,284 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ import fnmatch
6
+ import json
7
+ import re
8
+ from typing import Iterable
9
+
10
+
11
+ DEFAULT_PATTERNS = (
12
+ "AGENTS.md",
13
+ "CLAUDE.md",
14
+ "GEMINI.md",
15
+ ".cursorrules",
16
+ ".cursor/rules/*.md",
17
+ ".github/copilot-instructions.md",
18
+ ".github/instructions/*.md",
19
+ )
20
+
21
+ DEFAULT_IGNORE_DIRS = {
22
+ ".git",
23
+ ".hg",
24
+ ".svn",
25
+ ".venv",
26
+ "venv",
27
+ "node_modules",
28
+ "__pycache__",
29
+ "dist",
30
+ "build",
31
+ }
32
+
33
+ SECRET_PATTERNS = (
34
+ re.compile(r"\bghp_[A-Za-z0-9_]{20,}\b"),
35
+ re.compile(r"\bgho_[A-Za-z0-9_]{20,}\b"),
36
+ re.compile(r"\bgithub_pat_[A-Za-z0-9_]{20,}\b"),
37
+ re.compile(r"\bsk-[A-Za-z0-9_-]{20,}\b"),
38
+ re.compile(r"\bAKIA[0-9A-Z]{16}\b"),
39
+ re.compile(r"-----BEGIN (RSA |OPENSSH |EC |DSA )?PRIVATE KEY-----"),
40
+ )
41
+
42
+ RISKY_COMMAND_PATTERNS = (
43
+ re.compile(r"\brm\s+-rf\s+[/~$*]", re.IGNORECASE),
44
+ re.compile(r"\bgit\s+reset\s+--hard\b", re.IGNORECASE),
45
+ re.compile(r"\bgit\s+clean\s+-fdx\b", re.IGNORECASE),
46
+ re.compile(r"\bRemove-Item\b.+\b-Recurse\b.+\b-Force\b", re.IGNORECASE),
47
+ re.compile(r"\bcurl\b.+\|\s*(bash|sh)\b", re.IGNORECASE),
48
+ re.compile(r"\bwget\b.+\|\s*(bash|sh)\b", re.IGNORECASE),
49
+ )
50
+
51
+ PROMPT_INJECTION_PATTERNS = (
52
+ re.compile(r"ignore (all )?(previous|prior|above) instructions", re.IGNORECASE),
53
+ re.compile(r"reveal (the )?(system|developer) (prompt|message)", re.IGNORECASE),
54
+ re.compile(r"bypass (safety|policy|guardrails)", re.IGNORECASE),
55
+ )
56
+
57
+ SECTION_HINTS = {
58
+ "purpose": ("purpose", "goal", "objective", "what this does", "overview", "目标", "目的"),
59
+ "scope": ("scope", "when to use", "applies to", "boundaries", "范围", "适用"),
60
+ "commands": ("commands", "scripts", "verification", "tests", "test", "build", "命令", "验证", "测试"),
61
+ "safety": ("safety", "security", "secrets", "do not", "never", "安全", "密钥", "不要"),
62
+ "style": ("style", "formatting", "conventions", "tone", "风格", "格式", "约定"),
63
+ }
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class Finding:
68
+ file: str
69
+ severity: str
70
+ rule: str
71
+ message: str
72
+ line: int | None = None
73
+
74
+ def to_dict(self) -> dict:
75
+ data = {
76
+ "file": self.file,
77
+ "severity": self.severity,
78
+ "rule": self.rule,
79
+ "message": self.message,
80
+ }
81
+ if self.line is not None:
82
+ data["line"] = self.line
83
+ return data
84
+
85
+
86
+ @dataclass
87
+ class LintResult:
88
+ root: str
89
+ files_checked: list[str] = field(default_factory=list)
90
+ findings: list[Finding] = field(default_factory=list)
91
+
92
+ @property
93
+ def error_count(self) -> int:
94
+ return sum(1 for finding in self.findings if finding.severity == "error")
95
+
96
+ @property
97
+ def warning_count(self) -> int:
98
+ return sum(1 for finding in self.findings if finding.severity == "warning")
99
+
100
+ @property
101
+ def info_count(self) -> int:
102
+ return sum(1 for finding in self.findings if finding.severity == "info")
103
+
104
+ def to_dict(self) -> dict:
105
+ return {
106
+ "root": self.root,
107
+ "files_checked": self.files_checked,
108
+ "summary": {
109
+ "errors": self.error_count,
110
+ "warnings": self.warning_count,
111
+ "info": self.info_count,
112
+ },
113
+ "findings": [finding.to_dict() for finding in self.findings],
114
+ }
115
+
116
+ def to_json(self) -> str:
117
+ return json.dumps(self.to_dict(), ensure_ascii=False, indent=2)
118
+
119
+
120
+ def discover_instruction_files(root: Path, patterns: Iterable[str] = DEFAULT_PATTERNS) -> list[Path]:
121
+ root = root.resolve()
122
+ matches: list[Path] = []
123
+ normalized_patterns = tuple(pattern.replace("\\", "/") for pattern in patterns)
124
+
125
+ for path in root.rglob("*"):
126
+ if not path.is_file():
127
+ continue
128
+ if any(part in DEFAULT_IGNORE_DIRS for part in path.relative_to(root).parts):
129
+ continue
130
+ relative = path.relative_to(root).as_posix()
131
+ if any(fnmatch.fnmatch(relative, pattern) for pattern in normalized_patterns):
132
+ matches.append(path)
133
+
134
+ return sorted(matches, key=lambda item: item.relative_to(root).as_posix())
135
+
136
+
137
+ def lint_path(root: Path, max_chars: int = 12000) -> LintResult:
138
+ root = root.resolve()
139
+ result = LintResult(root=str(root))
140
+ files = discover_instruction_files(root)
141
+
142
+ if not files:
143
+ result.findings.append(
144
+ Finding(
145
+ file=".",
146
+ severity="warning",
147
+ rule="no-instruction-files",
148
+ message="No known agent instruction files found.",
149
+ )
150
+ )
151
+ return result
152
+
153
+ for path in files:
154
+ relative = path.relative_to(root).as_posix()
155
+ result.files_checked.append(relative)
156
+ result.findings.extend(lint_file(path, root, max_chars=max_chars))
157
+
158
+ return result
159
+
160
+
161
+ def lint_file(path: Path, root: Path, max_chars: int = 12000) -> list[Finding]:
162
+ relative = path.relative_to(root).as_posix()
163
+ text = path.read_text(encoding="utf-8", errors="replace")
164
+ lines = text.splitlines()
165
+ findings: list[Finding] = []
166
+
167
+ if not text.strip():
168
+ findings.append(Finding(relative, "error", "empty-file", "Instruction file is empty."))
169
+ return findings
170
+
171
+ if len(text) > max_chars:
172
+ findings.append(
173
+ Finding(
174
+ relative,
175
+ "warning",
176
+ "too-long",
177
+ f"Instruction file is {len(text)} characters; consider splitting or tightening it.",
178
+ )
179
+ )
180
+
181
+ if not re.search(r"^#\s+\S+", text, flags=re.MULTILINE):
182
+ findings.append(Finding(relative, "warning", "missing-title", "Add a top-level heading."))
183
+
184
+ present_sections = detect_sections(text)
185
+ for section in ("purpose", "scope", "commands", "safety"):
186
+ if section not in present_sections:
187
+ findings.append(
188
+ Finding(
189
+ relative,
190
+ "info",
191
+ f"missing-{section}",
192
+ f"Consider adding a clear {section} section.",
193
+ )
194
+ )
195
+
196
+ findings.extend(scan_patterns(relative, lines, SECRET_PATTERNS, "error", "secret-like-value", "Looks like a secret or private key."))
197
+ findings.extend(scan_patterns(relative, lines, RISKY_COMMAND_PATTERNS, "warning", "risky-command", "Risky command should include explicit safeguards or approval rules."))
198
+ findings.extend(scan_patterns(relative, lines, PROMPT_INJECTION_PATTERNS, "warning", "prompt-injection", "Instruction resembles prompt-injection language."))
199
+ findings.extend(scan_weak_language(relative, lines))
200
+ findings.extend(scan_absolute_claims(relative, lines))
201
+ findings.extend(scan_conflicts(relative, lines))
202
+
203
+ return findings
204
+
205
+
206
+ def detect_sections(text: str) -> set[str]:
207
+ lowered = text.lower()
208
+ present: set[str] = set()
209
+ for section, hints in SECTION_HINTS.items():
210
+ if any(hint in lowered for hint in hints):
211
+ present.add(section)
212
+ return present
213
+
214
+
215
+ def scan_patterns(
216
+ relative: str,
217
+ lines: list[str],
218
+ patterns: Iterable[re.Pattern[str]],
219
+ severity: str,
220
+ rule: str,
221
+ message: str,
222
+ ) -> list[Finding]:
223
+ findings: list[Finding] = []
224
+ for line_number, line in enumerate(lines, start=1):
225
+ if any(pattern.search(line) for pattern in patterns):
226
+ findings.append(Finding(relative, severity, rule, message, line_number))
227
+ return findings
228
+
229
+
230
+ def scan_weak_language(relative: str, lines: list[str]) -> list[Finding]:
231
+ findings: list[Finding] = []
232
+ pattern = re.compile(r"\b(try to|maybe|if possible|do your best|尽量|可能的话)\b", re.IGNORECASE)
233
+ for line_number, line in enumerate(lines, start=1):
234
+ if pattern.search(line):
235
+ findings.append(
236
+ Finding(
237
+ relative,
238
+ "info",
239
+ "weak-language",
240
+ "Vague language can make agent behavior inconsistent.",
241
+ line_number,
242
+ )
243
+ )
244
+ return findings
245
+
246
+
247
+ def scan_absolute_claims(relative: str, lines: list[str]) -> list[Finding]:
248
+ findings: list[Finding] = []
249
+ pattern = re.compile(r"\b(always|never|must|禁止|必须|永远|绝不)\b", re.IGNORECASE)
250
+ for line_number, line in enumerate(lines, start=1):
251
+ if pattern.search(line) and len(line) > 180:
252
+ findings.append(
253
+ Finding(
254
+ relative,
255
+ "info",
256
+ "long-absolute-rule",
257
+ "Long absolute rules are hard to follow; split into short, testable bullets.",
258
+ line_number,
259
+ )
260
+ )
261
+ return findings
262
+
263
+
264
+ def scan_conflicts(relative: str, lines: list[str]) -> list[Finding]:
265
+ text = "\n".join(lines).lower()
266
+ findings: list[Finding] = []
267
+ conflict_pairs = (
268
+ ("always ask", "never ask"),
269
+ ("always commit", "never commit"),
270
+ ("always browse", "never browse"),
271
+ ("must use python", "never use python"),
272
+ ("必须询问", "不要询问"),
273
+ )
274
+ for left, right in conflict_pairs:
275
+ if left in text and right in text:
276
+ findings.append(
277
+ Finding(
278
+ relative,
279
+ "warning",
280
+ "possible-conflict",
281
+ f"Possible conflicting instructions: '{left}' and '{right}'.",
282
+ )
283
+ )
284
+ return findings
@@ -0,0 +1,194 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-rules-lint
3
+ Version: 0.1.1
4
+ Summary: Lint AI agent instruction files such as AGENTS.md, CLAUDE.md, Cursor rules, and Copilot instructions.
5
+ Project-URL: Homepage, https://github.com/Uky0Yang/agent-rules-lint
6
+ Project-URL: Repository, https://github.com/Uky0Yang/agent-rules-lint
7
+ Project-URL: Issues, https://github.com/Uky0Yang/agent-rules-lint/issues
8
+ Project-URL: Changelog, https://github.com/Uky0Yang/agent-rules-lint/blob/main/CHANGELOG.md
9
+ Author: Ukyo
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai-agents,claude-code,copilot,cursor,developer-tools,lint,llm
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Software Development :: Quality Assurance
24
+ Classifier: Topic :: Utilities
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+
28
+ # agent-rules-lint
29
+
30
+ [![PyPI](https://img.shields.io/pypi/v/agent-rules-lint)](https://pypi.org/project/agent-rules-lint/)
31
+ [![Python](https://img.shields.io/pypi/pyversions/agent-rules-lint)](https://pypi.org/project/agent-rules-lint/)
32
+ [![CI](https://github.com/Uky0Yang/agent-rules-lint/actions/workflows/ci.yml/badge.svg)](https://github.com/Uky0Yang/agent-rules-lint/actions/workflows/ci.yml)
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
34
+
35
+ Lint AI agent instruction files before they confuse your coding agent.
36
+
37
+ `agent-rules-lint` is a small, dependency-free Python CLI that scans files such as `AGENTS.md`, `CLAUDE.md`, Cursor rules, and GitHub Copilot instructions for common quality and safety problems.
38
+
39
+ ## Why
40
+
41
+ Recent high-growth developer repos show a clear pattern: teams are adding more agent skills, rule files, memory files, and coding-agent playbooks. The missing piece is a quick quality gate for those instructions.
42
+
43
+ Bad agent instructions are expensive. They can be too long, vague, contradictory, unsafe, or accidentally include secrets. This tool gives you a fast local and CI check before those rules affect real work.
44
+
45
+ ## What It Checks
46
+
47
+ - Common instruction files:
48
+ - `AGENTS.md`
49
+ - `CLAUDE.md`
50
+ - `GEMINI.md`
51
+ - `.cursorrules`
52
+ - `.cursor/rules/*.md`
53
+ - `.github/copilot-instructions.md`
54
+ - `.github/instructions/*.md`
55
+ - Secret-like values
56
+ - Risky shell and Git commands
57
+ - Prompt-injection-like language
58
+ - Missing title
59
+ - Missing purpose, scope, command, or safety guidance
60
+ - Vague language
61
+ - Files that are too long for practical agent use
62
+ - A small set of obvious contradictory rules
63
+
64
+ ## Install
65
+
66
+ Run it without installing:
67
+
68
+ ```bash
69
+ uvx agent-rules-lint .
70
+ ```
71
+
72
+ Or install it as an isolated CLI:
73
+
74
+ ```bash
75
+ pipx install agent-rules-lint
76
+ agent-rules-lint .
77
+ ```
78
+
79
+ Standard pip installation also works:
80
+
81
+ ```bash
82
+ python -m pip install agent-rules-lint
83
+ ```
84
+
85
+ For local development:
86
+
87
+ ```bash
88
+ python -m pip install -e .
89
+ ```
90
+
91
+ ## Usage
92
+
93
+ Scan the current repository:
94
+
95
+ ```bash
96
+ agent-rules-lint .
97
+ ```
98
+
99
+ Return JSON for CI or custom reports:
100
+
101
+ ```bash
102
+ agent-rules-lint . --format json
103
+ ```
104
+
105
+ Fail CI on warnings:
106
+
107
+ ```bash
108
+ agent-rules-lint . --warnings-as-errors
109
+ ```
110
+
111
+ Change the long-file threshold:
112
+
113
+ ```bash
114
+ agent-rules-lint . --max-chars 8000
115
+ ```
116
+
117
+ ## Example Output
118
+
119
+ ```text
120
+ agent-rules-lint checked 1 file(s)
121
+ errors=0 warnings=1 info=2
122
+
123
+ Files:
124
+ - AGENTS.md
125
+
126
+ Findings:
127
+ [warning] AGENTS.md:42 risky-command: Risky command should include explicit safeguards or approval rules.
128
+ [info] AGENTS.md missing-safety: Consider adding a clear safety section.
129
+ ```
130
+
131
+ ## CI
132
+
133
+ Add this to GitHub Actions:
134
+
135
+ ```yaml
136
+ name: Lint agent rules
137
+
138
+ on:
139
+ pull_request:
140
+ push:
141
+ branches:
142
+ - main
143
+
144
+ jobs:
145
+ lint-agent-rules:
146
+ runs-on: ubuntu-latest
147
+ steps:
148
+ - uses: actions/checkout@v7.0.0
149
+ - uses: actions/setup-python@v6.3.0
150
+ with:
151
+ python-version: "3.12"
152
+ - run: pipx run agent-rules-lint . --warnings-as-errors
153
+ ```
154
+
155
+ ## Design Principles
156
+
157
+ - No model calls
158
+ - No network calls
159
+ - No dependencies
160
+ - Safe to run in CI
161
+ - Findings should be specific enough to act on
162
+ - Warnings should improve instruction quality, not enforce one writing style
163
+
164
+ ## Current Limitations
165
+
166
+ - Conflict detection is intentionally conservative and only catches obvious pairs.
167
+ - It does not estimate real tokenizer counts; it uses character length as a practical proxy.
168
+ - It does not validate whether an instruction is correct for a specific tool.
169
+
170
+ ## Development
171
+
172
+ ```bash
173
+ python -m pip install -e .
174
+ python -m unittest discover -s tests -v
175
+ python -m agent_rules_lint .
176
+ ```
177
+
178
+ ## Agent OSS Toolkit
179
+
180
+ This project is part of a small toolkit for building and launching agent-ready open-source repositories:
181
+
182
+ - [agent-repo-kit](https://github.com/Uky0Yang/agent-repo-kit): scaffold launch-ready, AI-agent-friendly repositories
183
+ - [oss-launch-check](https://github.com/Uky0Yang/oss-launch-check): audit whether a repository is ready to launch as open source
184
+ - [repo-context-card](https://github.com/Uky0Yang/repo-context-card): generate compact repository context cards for coding agents
185
+ - [agent-rules-lint](https://github.com/Uky0Yang/agent-rules-lint): lint AGENTS.md, CLAUDE.md, Cursor rules, and Copilot instructions
186
+ - [awesome-ai-agents-zh](https://github.com/Uky0Yang/awesome-ai-agents-zh): Chinese AI Agents / MCP / AI DevTools directory
187
+
188
+ ## Contributing
189
+
190
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
191
+
192
+ ## License
193
+
194
+ MIT
@@ -0,0 +1,9 @@
1
+ agent_rules_lint/__init__.py,sha256=l5U8ogAsFaBaSMZ5cXevA-NplExp0w6FiD7ViaYuu28,62
2
+ agent_rules_lint/__main__.py,sha256=PSQ4rpL0dG6f-qH4N7H-gD9igQkdHzH4yVZDcW8lfZo,80
3
+ agent_rules_lint/cli.py,sha256=p2gYnrDGDpzTMbbX3Ysb8_o3Pzehsx-yCsN7mswiy1c,2434
4
+ agent_rules_lint/linter.py,sha256=jkHZCaikGIFjDvSk5a7wB8u1b_Hzyd0nJ2oF_8Ala28,9498
5
+ agent_rules_lint-0.1.1.dist-info/METADATA,sha256=NS2php8QAFVxrIkjT0zQYaaOJ8giLs1FTvauaHjZ5ws,5688
6
+ agent_rules_lint-0.1.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
7
+ agent_rules_lint-0.1.1.dist-info/entry_points.txt,sha256=Fsvlfc1uKJEcYfOWer795pEkNDzCG2jlMnaXUbbjs8M,63
8
+ agent_rules_lint-0.1.1.dist-info/licenses/LICENSE,sha256=bz40dlj4pNYWq2XDJAm42nrzAgR8y2Yi5PCRzKDnzbw,1061
9
+ agent_rules_lint-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ agent-rules-lint = agent_rules_lint.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ukyo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.