arbiter-dev 0.2.0__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.
arbiter/store.py ADDED
@@ -0,0 +1,249 @@
1
+ """Store — SQLite persistence for quality metrics, trends, and agent scores."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sqlite3
7
+ from contextlib import contextmanager
8
+ from dataclasses import dataclass
9
+ from datetime import datetime, timezone
10
+ from pathlib import Path
11
+ from typing import Any, Generator
12
+
13
+ from arbiter.scoring import AgentScore, RepoScore
14
+
15
+
16
+ _DEFAULT_DB = Path("arbiter_data.db")
17
+
18
+
19
+ @dataclass(frozen=True, slots=True)
20
+ class FileScore:
21
+ """Quality info for a single file."""
22
+
23
+ file_path: str
24
+ finding_count: int
25
+ worst_severity: str
26
+ last_agent: str
27
+
28
+
29
+ class Store:
30
+ """SQLite store for Arbiter quality data."""
31
+
32
+ def __init__(self, db_path: Path | str = _DEFAULT_DB):
33
+ self._db_path = Path(db_path)
34
+ self._init_db()
35
+
36
+ @contextmanager
37
+ def _conn(self) -> Generator[sqlite3.Connection, None, None]:
38
+ conn = sqlite3.connect(str(self._db_path))
39
+ conn.row_factory = sqlite3.Row
40
+ try:
41
+ yield conn
42
+ conn.commit()
43
+ finally:
44
+ conn.close()
45
+
46
+ def _init_db(self) -> None:
47
+ with self._conn() as conn:
48
+ conn.executescript("""
49
+ CREATE TABLE IF NOT EXISTS commit_quality (
50
+ commit_hash TEXT,
51
+ repo_name TEXT NOT NULL DEFAULT '',
52
+ timestamp TEXT NOT NULL,
53
+ agent TEXT NOT NULL,
54
+ files_changed INTEGER DEFAULT 0,
55
+ loc_added INTEGER DEFAULT 0,
56
+ loc_removed INTEGER DEFAULT 0,
57
+ overall_score REAL,
58
+ lint_score REAL,
59
+ security_score REAL,
60
+ complexity_score REAL,
61
+ total_findings INTEGER DEFAULT 0,
62
+ findings_json TEXT,
63
+ created_at TEXT NOT NULL
64
+ );
65
+
66
+ CREATE TABLE IF NOT EXISTS file_quality (
67
+ file_path TEXT,
68
+ repo_name TEXT NOT NULL DEFAULT '',
69
+ finding_count INTEGER DEFAULT 0,
70
+ worst_severity TEXT,
71
+ last_agent TEXT,
72
+ updated_at TEXT NOT NULL
73
+ );
74
+
75
+ CREATE TABLE IF NOT EXISTS repo_snapshots (
76
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
77
+ repo_name TEXT NOT NULL DEFAULT '',
78
+ timestamp TEXT NOT NULL,
79
+ overall_score REAL,
80
+ lint_score REAL,
81
+ security_score REAL,
82
+ complexity_score REAL,
83
+ total_findings INTEGER,
84
+ total_loc INTEGER,
85
+ findings_json TEXT
86
+ );
87
+
88
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_commit_repo ON commit_quality(commit_hash, repo_name);
89
+ CREATE INDEX IF NOT EXISTS idx_commit_agent ON commit_quality(agent);
90
+ CREATE INDEX IF NOT EXISTS idx_commit_repo_name ON commit_quality(repo_name);
91
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_file_repo ON file_quality(file_path, repo_name);
92
+ CREATE INDEX IF NOT EXISTS idx_snapshot_repo ON repo_snapshots(repo_name);
93
+ CREATE INDEX IF NOT EXISTS idx_commit_timestamp ON commit_quality(timestamp);
94
+ CREATE INDEX IF NOT EXISTS idx_snapshot_timestamp ON repo_snapshots(timestamp);
95
+ """)
96
+
97
+ def record_commit(
98
+ self,
99
+ commit_hash: str,
100
+ timestamp: str,
101
+ agent: str,
102
+ files_changed: int,
103
+ loc_added: int,
104
+ loc_removed: int,
105
+ score: RepoScore,
106
+ repo_name: str = "",
107
+ ) -> None:
108
+ """Record quality data for a single commit."""
109
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
110
+ with self._conn() as conn:
111
+ conn.execute(
112
+ """INSERT OR REPLACE INTO commit_quality
113
+ (commit_hash, repo_name, timestamp, agent, files_changed, loc_added, loc_removed,
114
+ overall_score, lint_score, security_score, complexity_score,
115
+ total_findings, findings_json, created_at)
116
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
117
+ (commit_hash, repo_name, timestamp, agent, files_changed, loc_added, loc_removed,
118
+ score.overall, score.lint_score, score.security_score, score.complexity_score,
119
+ score.total_findings, json.dumps(score.findings_by_severity), now),
120
+ )
121
+
122
+ def record_snapshot(self, score: RepoScore, total_loc: int, repo_name: str = "") -> None:
123
+ """Record a point-in-time repo quality snapshot."""
124
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
125
+ with self._conn() as conn:
126
+ conn.execute(
127
+ """INSERT INTO repo_snapshots
128
+ (repo_name, timestamp, overall_score, lint_score, security_score, complexity_score,
129
+ total_findings, total_loc, findings_json)
130
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
131
+ (repo_name, now, score.overall, score.lint_score, score.security_score,
132
+ score.complexity_score, score.total_findings, total_loc,
133
+ json.dumps(score.findings_by_tool)),
134
+ )
135
+
136
+ def update_file_quality(self, file_path: str, finding_count: int, worst_severity: str, agent: str, repo_name: str = "") -> None:
137
+ """Update quality record for a single file."""
138
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
139
+ with self._conn() as conn:
140
+ conn.execute(
141
+ """INSERT OR REPLACE INTO file_quality
142
+ (file_path, repo_name, finding_count, worst_severity, last_agent, updated_at)
143
+ VALUES (?, ?, ?, ?, ?, ?)""",
144
+ (file_path, repo_name, finding_count, worst_severity, agent, now),
145
+ )
146
+
147
+ def get_agent_leaderboard(self) -> list[AgentScore]:
148
+ """Get quality scores aggregated by agent."""
149
+ with self._conn() as conn:
150
+ rows = conn.execute("""
151
+ SELECT agent,
152
+ COUNT(*) as commit_count,
153
+ AVG(overall_score) as avg_score,
154
+ SUM(loc_added) as total_loc
155
+ FROM commit_quality
156
+ GROUP BY agent
157
+ ORDER BY avg_score DESC
158
+ """).fetchall()
159
+
160
+ return [
161
+ AgentScore(
162
+ agent_name=row["agent"],
163
+ commit_count=row["commit_count"],
164
+ avg_score=round(row["avg_score"] or 0, 1),
165
+ total_loc=row["total_loc"] or 0,
166
+ )
167
+ for row in rows
168
+ ]
169
+
170
+ def get_trend(self, days: int = 30) -> list[dict[str, Any]]:
171
+ """Get repo quality snapshots over time."""
172
+ with self._conn() as conn:
173
+ rows = conn.execute("""
174
+ SELECT timestamp, overall_score, lint_score, security_score,
175
+ complexity_score, total_findings, total_loc
176
+ FROM repo_snapshots
177
+ ORDER BY timestamp DESC
178
+ LIMIT ?
179
+ """, (days,)).fetchall()
180
+ return [dict(row) for row in reversed(rows)]
181
+
182
+ def get_worst_files(self, limit: int = 20) -> list[FileScore]:
183
+ """Get files ranked by worst quality."""
184
+ with self._conn() as conn:
185
+ rows = conn.execute("""
186
+ SELECT file_path, finding_count, worst_severity, last_agent
187
+ FROM file_quality
188
+ WHERE finding_count > 0
189
+ ORDER BY finding_count DESC
190
+ LIMIT ?
191
+ """, (limit,)).fetchall()
192
+ return [
193
+ FileScore(
194
+ file_path=row["file_path"],
195
+ finding_count=row["finding_count"],
196
+ worst_severity=row["worst_severity"] or "LOW",
197
+ last_agent=row["last_agent"] or "unknown",
198
+ )
199
+ for row in rows
200
+ ]
201
+
202
+ def get_agent_trend(self, agent: str, days: int = 30) -> list[dict[str, Any]]:
203
+ """Get quality trend for a specific agent over time."""
204
+ with self._conn() as conn:
205
+ rows = conn.execute("""
206
+ SELECT timestamp, overall_score, lint_score, security_score,
207
+ complexity_score, total_findings, loc_added, commit_hash
208
+ FROM commit_quality
209
+ WHERE agent = ?
210
+ ORDER BY timestamp DESC
211
+ LIMIT ?
212
+ """, (agent, days)).fetchall()
213
+ return [dict(row) for row in reversed(rows)]
214
+
215
+ def get_recent_commits(self, agent: str | None = None, limit: int = 50) -> list[dict[str, Any]]:
216
+ """Get recent commits with quality data."""
217
+ with self._conn() as conn:
218
+ if agent:
219
+ rows = conn.execute("""
220
+ SELECT * FROM commit_quality
221
+ WHERE agent = ?
222
+ ORDER BY timestamp DESC LIMIT ?
223
+ """, (agent, limit)).fetchall()
224
+ else:
225
+ rows = conn.execute("""
226
+ SELECT * FROM commit_quality
227
+ ORDER BY timestamp DESC LIMIT ?
228
+ """, (limit,)).fetchall()
229
+ return [dict(row) for row in rows]
230
+
231
+ def get_fleet_report(self) -> list[dict[str, Any]]:
232
+ """Get quality summary per repo — the fleet-wide view."""
233
+ with self._conn() as conn:
234
+ rows = conn.execute("""
235
+ SELECT repo_name,
236
+ overall_score,
237
+ lint_score,
238
+ security_score,
239
+ complexity_score,
240
+ total_findings,
241
+ total_loc,
242
+ timestamp
243
+ FROM repo_snapshots
244
+ WHERE id IN (
245
+ SELECT MAX(id) FROM repo_snapshots GROUP BY repo_name
246
+ )
247
+ ORDER BY overall_score DESC
248
+ """).fetchall()
249
+ return [dict(row) for row in rows]
@@ -0,0 +1,216 @@
1
+ Metadata-Version: 2.4
2
+ Name: arbiter-dev
3
+ Version: 0.2.0
4
+ Summary: Agent-aware code quality system for multi-agent codebases
5
+ Author: Reuben Bowlby, Daniel Matha
6
+ License-Expression: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Provides-Extra: analyzers
16
+ Requires-Dist: ruff>=0.4.0; extra == "analyzers"
17
+ Requires-Dist: radon>=6.0; extra == "analyzers"
18
+ Requires-Dist: vulture>=2.10; extra == "analyzers"
19
+ Requires-Dist: bandit>=1.7.0; extra == "analyzers"
20
+ Provides-Extra: test
21
+ Requires-Dist: pytest>=7.0; extra == "test"
22
+ Provides-Extra: all
23
+ Requires-Dist: arbiter[analyzers,test]; extra == "all"
24
+ Dynamic: license-file
25
+
26
+ # Arbiter
27
+
28
+ **Agent-aware code quality system for multi-agent codebases.**
29
+
30
+ In 2026, code is written by fleets of AI agents. Arbiter knows *who* wrote each line — human or AI — and scores quality accordingly.
31
+
32
+ ## What Makes Arbiter Different
33
+
34
+ | Feature | Traditional Tools | Arbiter |
35
+ |---------|------------------|---------|
36
+ | Agent attribution | None | First-class: tracks Claude, Codex, Gemini, Copilot, humans |
37
+ | Per-commit scoring | Repo-wide only | Scores each commit's changed files individually |
38
+ | Diff analysis | N/A | Score only what changed in a PR/branch |
39
+ | Transparency | Opaque score | Every score decomposes into lint + security + complexity |
40
+ | Agent-specific gates | N/A | Different quality thresholds per agent trust tier |
41
+ | Tool integration | Proprietary | Wraps tools you already trust: ruff, Bandit, radon, vulture |
42
+ | Dashboard | SaaS login | Single HTML file with per-agent timelines, commit feed, fleet view |
43
+ | Dependencies | Heavy | Analysis tools only; core is stdlib Python |
44
+
45
+ ## Quick Start
46
+
47
+ ```bash
48
+ git clone https://github.com/hummbl-dev/arbiter.git
49
+ cd arbiter
50
+
51
+ # Install (makes `arbiter` command available)
52
+ pip install ".[analyzers]"
53
+
54
+ # Quick score (no persistence)
55
+ arbiter score /path/to/your/repo
56
+
57
+ # Full analysis with per-commit agent attribution
58
+ arbiter analyze /path/to/your/repo
59
+
60
+ # Score only files changed since main
61
+ arbiter diff /path/to/your/repo --base main
62
+
63
+ # Agent leaderboard
64
+ arbiter agents
65
+
66
+ # Start dashboard
67
+ arbiter serve --port 8080
68
+ # Open http://localhost:8080
69
+ ```
70
+
71
+ ### Without install (PYTHONPATH)
72
+
73
+ ```bash
74
+ PYTHONPATH=src python -m arbiter score /path/to/your/repo
75
+ ```
76
+
77
+ ### With Docker
78
+
79
+ ```bash
80
+ docker build -t arbiter .
81
+ docker run -p 8080:8080 -v /path/to/repo:/repo:ro arbiter
82
+ ```
83
+
84
+ ## Architecture
85
+
86
+ ```
87
+ Git Repo ──→ [Git Historian] ──→ [Analyzer Runner] ──→ [Scoring Engine] ──→ [SQLite Store]
88
+ │ │ │ │
89
+ agent attribution tool invocation weighted rubric trend data
90
+ (Co-Authored-By, (ruff, radon, (lint 35%, │
91
+ email matching) vulture, bandit) security 30%, ├──→ REST API
92
+ complexity 35%) └──→ Dashboard
93
+ ┌────────────┐
94
+ │Diff Analyzer│ ←── v0.2: scores only changed files per commit/branch
95
+ └────────────┘
96
+ ```
97
+
98
+ ### Per-Commit Scoring (v0.2)
99
+
100
+ Every commit is scored against only the files it changed, not the entire repo. This makes the agent leaderboard meaningful — a commit that touches 1 clean file scores differently than one that touches 10 messy files.
101
+
102
+ ### Diff Mode (v0.2)
103
+
104
+ `arbiter diff` scores only files changed since a base branch. Ideal for CI/PR quality gates — fast, scoped, actionable.
105
+
106
+ ### Agent Attribution
107
+
108
+ Arbiter identifies which agent authored each commit:
109
+
110
+ 1. **Co-Authored-By trailer** — `Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>`
111
+ 2. **Author email** — maps `noreply@anthropic.com` → claude, `codex@openai.com` → codex
112
+ 3. **Default** — "human" if no agent pattern matches
113
+
114
+ Configure in `agents.yml`:
115
+ ```yaml
116
+ agents:
117
+ - name: claude
118
+ emails: [noreply@anthropic.com]
119
+ co_author_patterns: ["Claude\\s+(Opus|Sonnet|Haiku)"]
120
+ trust_tier: verified
121
+ quality_threshold: 70.0
122
+ - name: gemini
123
+ trust_tier: probation
124
+ quality_threshold: 80.0 # Higher bar for probationary agents
125
+ ```
126
+
127
+ ### Analyzers (pluggable)
128
+
129
+ | Analyzer | Tool | What It Finds |
130
+ |----------|------|--------------|
131
+ | Lint | ruff | Style violations, import errors, bugbear patterns |
132
+ | Complexity | radon | Cyclomatic complexity (grade A-F per function) |
133
+ | Security | bandit | Hardcoded secrets, shell injection, dangerous patterns |
134
+ | Dead Code | vulture | Unused functions, imports, variables |
135
+ | Duplication | AST hash | Near-duplicate function bodies |
136
+
137
+ ### Scoring
138
+
139
+ Deterministic. Same code → same score. Always.
140
+
141
+ ```
142
+ Overall = Lint (35%) + Security (30%) + Complexity (35%)
143
+
144
+ Penalty points by severity:
145
+ CRITICAL: 50 | HIGH: 20 | MEDIUM: 5 | LOW: 1
146
+
147
+ Score = 100 - (total_penalty / LOC) * normalization_factor
148
+ ```
149
+
150
+ Grades: A (90+) | B (80+) | C (70+) | D (60+) | F (<60)
151
+
152
+ ### Dashboard (v2)
153
+
154
+ Single HTML file with Chart.js. No build step, no React, no npm.
155
+
156
+ - **Score Card** — Big number + breakdown bars
157
+ - **Agent Leaderboard** — Who writes the best code? Color-coded by agent
158
+ - **Per-Agent Quality Timeline** — Score over time per agent (not just repo-wide)
159
+ - **Commit Feed** — Recent commits with agent, score, changes, timestamp
160
+ - **Hotspot Files** — Ranked by finding count
161
+ - **Fleet View** — Multi-repo quality grid with color-coded scores
162
+ - **Tabbed UI** — Overview, Commits, Fleet tabs
163
+
164
+ ### API
165
+
166
+ ```
167
+ GET /api/score Current repo score
168
+ GET /api/agents Agent leaderboard
169
+ GET /api/agents/{name}/trend Per-agent quality over time
170
+ GET /api/trend?days=30 Quality over time
171
+ GET /api/worst?limit=20 Worst files
172
+ GET /api/commits Recent commits with scores
173
+ GET /api/commits/{hash} Detail for one commit
174
+ GET /api/fleet Fleet report (multi-repo)
175
+ GET /api/health System health
176
+ ```
177
+
178
+ ## CLI Commands
179
+
180
+ ```bash
181
+ arbiter analyze <repo> # Full analysis + per-commit scoring + persist
182
+ arbiter score <repo> [--json] [--exclude] # Quick score (no persist)
183
+ arbiter diff <repo> [--base main] [--json] # Score only changed files vs base branch
184
+ arbiter agents # Agent leaderboard
185
+ arbiter trend [--days 30] # Quality trend
186
+ arbiter worst [--limit 20] # Worst files
187
+ arbiter commits [--agent claude] # Recent commits
188
+ arbiter audit-fleet <directory> # Audit all repos in a directory
189
+ arbiter fleet-report # Fleet quality summary
190
+ arbiter triage # Auto-classify repos: green/yellow/red/archive
191
+ arbiter fix <repo> [--dry-run] # Auto-fix ruff findings + before/after score
192
+ arbiter serve [--port 8080] # API + dashboard
193
+ ```
194
+
195
+ ## Tests
196
+
197
+ ```bash
198
+ pip install ".[test]"
199
+ PYTHONPATH=src python -m pytest tests/ -v
200
+ # 78 tests, <7 seconds
201
+ ```
202
+
203
+ ## Requirements
204
+
205
+ - Python 3.11+
206
+ - git (for historian)
207
+ - Optional: ruff, radon, vulture, bandit (for full analysis)
208
+ - Docker (for containerized deployment)
209
+
210
+ ## License
211
+
212
+ MIT — see [LICENSE](LICENSE).
213
+
214
+ ---
215
+
216
+ Built by [HUMMBL LLC](https://hummbl.io) from production experience coordinating Claude, Codex, Gemini, and human engineers on a 6,000+ test codebase.
@@ -0,0 +1,22 @@
1
+ arbiter/__init__.py,sha256=lSICWpF66jOKqVJoxYj0vZledRMNPty4ea779jJfU1M,74
2
+ arbiter/__main__.py,sha256=KUJxV2q_wgm7TFqX9je6IU4E9lAvxR_nU_XGU0GY80Q,23171
3
+ arbiter/agent_registry.py,sha256=IpEPKCUwlUuJA1GkIK6qsUnNSF9K48wLcm0697mh_GY,3777
4
+ arbiter/api.py,sha256=z0jannzi2DFrjo33cwu8Sleueweqzsbb4pkqhq0vcOA,6240
5
+ arbiter/bus_bridge.py,sha256=us_NNncATORbLYJya8k1g3hGw33CDX306xyyJleamJg,2496
6
+ arbiter/diff_analyzer.py,sha256=qkyuPHDC2Ck2ne9qbUPoh-37qNkG5TNp0NCKPbKV1Ts,4873
7
+ arbiter/git_historian.py,sha256=Gd9JWoYKt__sKpaI90uZxR5ILRb5tY_ujCngFc_CK6U,6411
8
+ arbiter/scoring.py,sha256=3RU_fvC3JWTuyf5_llkEuFe9tzlrfwjLjPB7Gz4vc60,3742
9
+ arbiter/store.py,sha256=tut9zJtONxrG0xHThDd9XSigkpqLfTTpPxuThyGNCd4,10072
10
+ arbiter/analyzers/__init__.py,sha256=0uuTqh9PRPE43SYBYoN2y2YXcLkROW6gBjfiqqgpE0g,67
11
+ arbiter/analyzers/base.py,sha256=hNrZTG4YQsXLfxbbhgrBtmal8HP_8KK33fALFoamxXI,1185
12
+ arbiter/analyzers/complexity_analyzer.py,sha256=oe-oq22L-h1GAhxdvTWq1QywrS-yPh2BbpA2ZwOujzE,2144
13
+ arbiter/analyzers/dead_code_analyzer.py,sha256=QIwnrdMdF3H3PiU3A0HSj_UM4y3_iviYkb7xgU4Uwu0,1704
14
+ arbiter/analyzers/duplication_analyzer.py,sha256=F2pcmFNnkJ5CGukMjuMI4uTGsPNUyQl1LRL2xpD4uFI,3553
15
+ arbiter/analyzers/ruff_analyzer.py,sha256=tANAuRri1X3jW6RdRjl9zaFplY7ufD2Alv_Wx5SQJdI,2449
16
+ arbiter/analyzers/security_analyzer.py,sha256=5qtbOMn-lAegvh9cZcf8kysbW3S5EBuoqTTjmPiO3-g,1910
17
+ arbiter_dev-0.2.0.dist-info/licenses/LICENSE,sha256=x6CmZmL3qaXphWZWt02ZP2p_BcsoaSQxLj_R9IyalcM,1097
18
+ arbiter_dev-0.2.0.dist-info/METADATA,sha256=FVND_2CIzLrIpx74dv66AQhgnrkpl74gQ9TnUO_V8-U,7647
19
+ arbiter_dev-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
20
+ arbiter_dev-0.2.0.dist-info/entry_points.txt,sha256=5UnDdGkHgctdc9b73lvcfv3lh8tUVaq2uc8HoleTy2I,50
21
+ arbiter_dev-0.2.0.dist-info/top_level.txt,sha256=6xckoxU5fjdxvk-5J6uCv2PcPz11wqzqL1DXldzj8ZQ,8
22
+ arbiter_dev-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ arbiter = arbiter.__main__:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Reuben Bowlby, Daniel Matha / HUMMBL LLC
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.
@@ -0,0 +1 @@
1
+ arbiter