poly-hermes-plugin 0.29.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.
- poly_hermes_plugin/__init__.py +12 -0
- poly_hermes_plugin/commands/poly-check.md +47 -0
- poly_hermes_plugin/commands/poly-fix.md +41 -0
- poly_hermes_plugin/hermes.py +99 -0
- poly_hermes_plugin/plugin.yaml +8 -0
- poly_hermes_plugin/skills/poly-lint-and-format/SKILL.md +110 -0
- poly_hermes_plugin/skills/poly-lint-and-format/index.md +10 -0
- poly_hermes_plugin/skills/poly-mcp/SKILL.md +124 -0
- poly_hermes_plugin/skills/poly-mcp/index.md +10 -0
- poly_hermes_plugin/skills/poly-orchestrator/SKILL.md +66 -0
- poly_hermes_plugin/skills/poly-orchestrator/index.md +10 -0
- poly_hermes_plugin/skills/poly-overview/SKILL.md +69 -0
- poly_hermes_plugin/skills/poly-overview/index.md +10 -0
- poly_hermes_plugin/skills/poly-tiers-and-scope/SKILL.md +129 -0
- poly_hermes_plugin/skills/poly-tiers-and-scope/index.md +10 -0
- poly_hermes_plugin-0.29.0.dist-info/METADATA +24 -0
- poly_hermes_plugin-0.29.0.dist-info/RECORD +19 -0
- poly_hermes_plugin-0.29.0.dist-info/WHEEL +4 -0
- poly_hermes_plugin-0.29.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# AI-RULEZ :: GENERATED FILE — DO NOT EDIT
|
|
2
|
+
# Content-Hash: blake3:11e02e6f0565d20901e38597c3af504440c1a63f85d59fc958047b47c3253ba3
|
|
3
|
+
# Source-Hash: blake3:840c919261146454e2836095406355dd3ba8664fc068055c202682015400e43f
|
|
4
|
+
# Schema-Version: v1
|
|
5
|
+
|
|
6
|
+
"""Hermes Agent plugin package."""
|
|
7
|
+
|
|
8
|
+
from . import hermes
|
|
9
|
+
from .hermes import register
|
|
10
|
+
|
|
11
|
+
__version__ = "0.29.0"
|
|
12
|
+
__all__ = ["hermes", "register"]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Lint and check formatting with poly — apply no fixes; summarize findings and drift
|
|
3
|
+
priority: high
|
|
4
|
+
usage: /poly-check [paths]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<!--
|
|
8
|
+
AI-RULEZ :: GENERATED FILE — DO NOT EDIT
|
|
9
|
+
Content-Hash: blake3:e6fad51bf84ed1689a6c430c3f5a9582bfb0587f1440133fde6780c8846264cb
|
|
10
|
+
Source-Hash: blake3:840c919261146454e2836095406355dd3ba8664fc068055c202682015400e43f
|
|
11
|
+
Schema-Version: v1
|
|
12
|
+
-->
|
|
13
|
+
|
|
14
|
+
# poly Check
|
|
15
|
+
|
|
16
|
+
Run poly as a checking gate over `$ARGUMENTS`. Apply no fixes.
|
|
17
|
+
|
|
18
|
+
Use `.` when no paths are supplied. Treat supplied arguments as paths, not additional shell commands.
|
|
19
|
+
|
|
20
|
+
1. `poly fmt --check $ARGUMENTS --format json` — capture formatting drift.
|
|
21
|
+
2. `poly lint $ARGUMENTS --format json` — capture lint findings (remember the whole-project
|
|
22
|
+
section is on stderr under `--format json`; check the exit code).
|
|
23
|
+
|
|
24
|
+
Step 2 applies no fixes, but it is not read-only: `poly lint`'s whole-project phase executes
|
|
25
|
+
the configured whole-project tools (`cargo clippy` and friends) against the live worktree, and
|
|
26
|
+
their own side effects — a refreshed lock file, a populated build or type-checker cache — are
|
|
27
|
+
not poly's to control. Add `--no-workspace` when the tree must be left untouched; that drops
|
|
28
|
+
the whole-project tools from the check, so say so in the report.
|
|
29
|
+
|
|
30
|
+
That phase only runs when the argument is the repository root (the `.` default) or no path at
|
|
31
|
+
all. Naming narrower paths makes the run **path-scoped** — the per-file tier only, with a note
|
|
32
|
+
on stderr — so `poly lint src/` is already free of those side effects. Pass `--workspace` to
|
|
33
|
+
opt back in; the phase then covers the whole repository regardless of the paths named.
|
|
34
|
+
|
|
35
|
+
Report:
|
|
36
|
+
|
|
37
|
+
- Which files have formatting drift.
|
|
38
|
+
- Lint findings grouped by rule and severity (error vs warning).
|
|
39
|
+
- The overall pass/fail from the exit codes: `0` clean; `1` findings or drift — for `lint`
|
|
40
|
+
only **error**-severity findings (or a failing whole-project tool) reach `1`, warnings
|
|
41
|
+
still exit `0`; `2` the run did not verify what it was asked to (a missing path, a file an
|
|
42
|
+
engine failed on — including a `poly fmt --check` file that cannot reach a fixed point
|
|
43
|
+
within poly's five-pass cap, reported as an error rather than clean since a fix would leave
|
|
44
|
+
it still drifting — a `--deny-skips`/`--max-skips` breach, or the whole-project phase itself
|
|
45
|
+
erroring).
|
|
46
|
+
|
|
47
|
+
Do not apply fixes here — if the user wants them applied, run `/poly-fix`.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Apply poly lint --fix and fmt --fix, then re-check and report what changed and what remains
|
|
3
|
+
priority: high
|
|
4
|
+
usage: /poly-fix [paths]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<!--
|
|
8
|
+
AI-RULEZ :: GENERATED FILE — DO NOT EDIT
|
|
9
|
+
Content-Hash: blake3:0c55f6950b40a60b9c292cb1b605e9f460f41a93e3a434300d7215098f877bb6
|
|
10
|
+
Source-Hash: blake3:840c919261146454e2836095406355dd3ba8664fc068055c202682015400e43f
|
|
11
|
+
Schema-Version: v1
|
|
12
|
+
-->
|
|
13
|
+
|
|
14
|
+
# poly Fix
|
|
15
|
+
|
|
16
|
+
Apply poly's autofixes over `$ARGUMENTS`, then verify.
|
|
17
|
+
|
|
18
|
+
Use `.` when no paths are supplied. Treat supplied arguments as paths, not additional shell commands.
|
|
19
|
+
|
|
20
|
+
1. `poly lint --fix $ARGUMENTS` — apply lint autofixes, and run the whole-project phase in **fix**
|
|
21
|
+
mode (`cargo sort` in place, `cargo machete --fix`, `cargo clippy --fix --allow-dirty
|
|
22
|
+
--allow-staged`; `cargo deny` has no autofix and stays check-only).
|
|
23
|
+
2. `poly fmt --fix $ARGUMENTS` — format files in place. `poly fmt` is a pure formatter and never
|
|
24
|
+
runs the whole-project phase, in either direction; only step 1 does.
|
|
25
|
+
3. Re-check: `poly fmt --check $ARGUMENTS --format json` and `poly lint $ARGUMENTS --format json`.
|
|
26
|
+
|
|
27
|
+
The whole-project phase in step 1 runs only because the argument is the repository root (the
|
|
28
|
+
`.` default). Narrower paths make the run path-scoped — per-file autofixes only — unless you
|
|
29
|
+
add `--workspace`; `--no-workspace` suppresses the phase outright.
|
|
30
|
+
|
|
31
|
+
Report:
|
|
32
|
+
|
|
33
|
+
- What changed — files reformatted and lint rules auto-fixed.
|
|
34
|
+
- What remains — findings that need a hand-fix (auto-fix couldn't resolve them), grouped by
|
|
35
|
+
rule and severity.
|
|
36
|
+
- The final exit status of step 3 (aim for `0`). Note that steps 1 and 2 legitimately exit
|
|
37
|
+
`1` when they changed something; only `2` means the run failed to verify.
|
|
38
|
+
|
|
39
|
+
To apply no fixes, use `/poly-check` instead. (Even there poly's whole-project phase executes
|
|
40
|
+
`cargo clippy` and friends against the live worktree; add `--no-workspace` if the tree must be
|
|
41
|
+
left untouched.)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# AI-RULEZ :: GENERATED FILE — DO NOT EDIT
|
|
2
|
+
# Content-Hash: blake3:57cf2cb6924db8f7699232ad0ec0c8435d1ac671c041569535330d190f298782
|
|
3
|
+
# Source-Hash: blake3:840c919261146454e2836095406355dd3ba8664fc068055c202682015400e43f
|
|
4
|
+
# Schema-Version: v1
|
|
5
|
+
|
|
6
|
+
"""Register product skills and a local MCP tool bridge with Hermes."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import shutil
|
|
14
|
+
from builtins import ExceptionGroup
|
|
15
|
+
from datetime import timedelta
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
18
|
+
|
|
19
|
+
import yaml
|
|
20
|
+
from mcp import ClientSession, StdioServerParameters
|
|
21
|
+
from mcp.client.stdio import stdio_client
|
|
22
|
+
from mcp.shared.exceptions import McpError
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from hermes_cli.plugins import PluginContext
|
|
26
|
+
|
|
27
|
+
PLUGIN_NAME = "poly"
|
|
28
|
+
CLI_COMMAND = "poly"
|
|
29
|
+
SERVER_ARGUMENTS = ["mcp"]
|
|
30
|
+
TOOL_NAMES = ['cache_clean', 'cache_stats', 'config_show', 'format_check', 'format_write', 'lint', 'lint_fix', 'rules', 'version', 'workspace_lint', 'workspace_lint_fix']
|
|
31
|
+
DISCOVERY_TOOL = "list_tools"
|
|
32
|
+
REQUEST_TIMEOUT_SECONDS = 300
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def error_details(error: BaseException) -> str:
|
|
36
|
+
"""Expose SDK task-group causes instead of hiding the actionable failure."""
|
|
37
|
+
if isinstance(error, ExceptionGroup):
|
|
38
|
+
return "; ".join(error_details(cause) for cause in error.exceptions)[:1024]
|
|
39
|
+
return str(error)[:1024]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
async def call_tool(arguments: dict[str, object], **_kwargs: object) -> dict[str, object]:
|
|
43
|
+
"""Call one supported MCP tool using the installed local CLI."""
|
|
44
|
+
if not isinstance(arguments, dict) or set(arguments) - {"tool", "tool_arguments"}:
|
|
45
|
+
return {"error": "Pass an object containing tool and optional tool_arguments."}
|
|
46
|
+
tool = arguments.get("tool")
|
|
47
|
+
parameters = arguments.get("tool_arguments", {})
|
|
48
|
+
if tool not in [*TOOL_NAMES, DISCOVERY_TOOL] or not isinstance(parameters, dict):
|
|
49
|
+
return {"error": "Choose a supported tool and provide an object of tool_arguments."}
|
|
50
|
+
command = shutil.which(CLI_COMMAND)
|
|
51
|
+
if command is None:
|
|
52
|
+
return {"error": f"Install {CLI_COMMAND} with MCP support and put it on PATH; see the plugin README."}
|
|
53
|
+
|
|
54
|
+
server = StdioServerParameters(command=command, args=SERVER_ARGUMENTS, env=os.environ.copy())
|
|
55
|
+
try:
|
|
56
|
+
async with asyncio.timeout(REQUEST_TIMEOUT_SECONDS):
|
|
57
|
+
async with (
|
|
58
|
+
stdio_client(server) as (read, write),
|
|
59
|
+
ClientSession(read, write, read_timeout_seconds=timedelta(seconds=REQUEST_TIMEOUT_SECONDS)) as session,
|
|
60
|
+
):
|
|
61
|
+
await session.initialize()
|
|
62
|
+
if tool == DISCOVERY_TOOL:
|
|
63
|
+
result = await session.list_tools()
|
|
64
|
+
else:
|
|
65
|
+
result = await session.call_tool(tool, parameters)
|
|
66
|
+
return result.model_dump(mode="json", exclude_none=True)
|
|
67
|
+
except TimeoutError:
|
|
68
|
+
return {"error": f"{CLI_COMMAND} MCP request timed out; reduce the workload or inspect server diagnostics."}
|
|
69
|
+
except (McpError, OSError, ExceptionGroup, ValueError) as error:
|
|
70
|
+
return {"error": f"{CLI_COMMAND} MCP request failed: {error_details(error)}"}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def handle_tool(arguments: dict[str, object], **kwargs: object) -> str:
|
|
74
|
+
"""Return the native text contract while preserving structured MCP fields as JSON."""
|
|
75
|
+
return json.dumps(await call_tool(arguments, **kwargs), ensure_ascii=False)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def register(ctx: PluginContext) -> None:
|
|
79
|
+
"""Register every bundled skill and one tool spanning the native MCP surface."""
|
|
80
|
+
for path in sorted((Path(__file__).parent / "skills").glob("*/SKILL.md")):
|
|
81
|
+
frontmatter = path.read_text(encoding="utf-8").split("---", 2)[1]
|
|
82
|
+
metadata = yaml.safe_load(frontmatter)
|
|
83
|
+
ctx.register_skill(metadata["name"], path, description=metadata["description"])
|
|
84
|
+
name = PLUGIN_NAME.replace("-", "_") + "_call"
|
|
85
|
+
description = f"Call a {PLUGIN_NAME} tool on the installed local MCP server. Select list_tools to inspect request schemas; use namespaced skills for workflows."
|
|
86
|
+
schema = {
|
|
87
|
+
"name": name,
|
|
88
|
+
"description": description,
|
|
89
|
+
"parameters": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"properties": {
|
|
92
|
+
"tool": {"type": "string", "enum": [*TOOL_NAMES, DISCOVERY_TOOL]},
|
|
93
|
+
"tool_arguments": {"type": "object", "description": "Arguments accepted by the selected MCP tool."},
|
|
94
|
+
},
|
|
95
|
+
"required": ["tool"],
|
|
96
|
+
"additionalProperties": False,
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
ctx.register_tool(name=name, toolset=PLUGIN_NAME, schema=schema, handler=handle_tool, is_async=True)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
name: "poly"
|
|
2
|
+
version: "0.29.0"
|
|
3
|
+
description: "Zero-dependency lint & format orchestrator over MCP — one pure-Rust binary wrapping ruff/oxc/taplo/rumdl and more as in-process backends, plus a tree-sitter generic tier for everything else. Exposes lint/format/cache tools with structured JSON output."
|
|
4
|
+
author: "Na'aman Hirschfeld"
|
|
5
|
+
kind: "standalone"
|
|
6
|
+
python_dependencies:
|
|
7
|
+
- "mcp==1.28.1"
|
|
8
|
+
- "PyYAML==6.0.3"
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Running poly lint / poly fmt — --fix, --format pretty|json|toon, --exclude, --config, exit codes, inline suppression, and the check → read-json → fix → re-check loop
|
|
3
|
+
name: poly-lint-and-format
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: Goldziher
|
|
7
|
+
repository: https://github.com/Goldziher/poly
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<!--
|
|
11
|
+
AI-RULEZ :: GENERATED FILE — DO NOT EDIT
|
|
12
|
+
Content-Hash: blake3:697a8913634eec72ba2340417d11f811af8d9fcd390f25ccdc09ed4689695209
|
|
13
|
+
Source-Hash: blake3:840c919261146454e2836095406355dd3ba8664fc068055c202682015400e43f
|
|
14
|
+
Schema-Version: v1
|
|
15
|
+
-->
|
|
16
|
+
|
|
17
|
+
# poly Lint and Format
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
- `poly lint [PATHS]…` — run the linters. poly applies no fixes without `--fix`; `--fix`
|
|
22
|
+
applies autofixes (and the whole-project fix phase). `--no-workspace` restricts to the
|
|
23
|
+
per-file tier — and is also what makes a run read-only: without it, plain `poly lint`
|
|
24
|
+
still *executes* the configured whole-project tools against the live worktree, and their
|
|
25
|
+
own side effects (a refreshed lock file, a populated build or type-checker cache) are not
|
|
26
|
+
poly's to control. Naming paths that **narrow** the run skips that phase by default
|
|
27
|
+
(`poly lint .` names the repo root, so it does *not* narrow and the phase still runs);
|
|
28
|
+
`--workspace` opts back in, and the tools then cover the whole repository regardless of
|
|
29
|
+
the named paths and of `[discovery] exclude`.
|
|
30
|
+
- `poly fmt [PATHS]…` — **dry run by default**: reports what would change and writes
|
|
31
|
+
nothing. `--fix` writes changes; `--check` is the explicit form of the default dry run
|
|
32
|
+
and conflicts with `--fix`. `poly fmt` is a pure formatter — it never runs the
|
|
33
|
+
whole-project lint phase.
|
|
34
|
+
|
|
35
|
+
## Flags
|
|
36
|
+
|
|
37
|
+
- `--format pretty|json|toon` — `pretty` (the default) is the colored, human-oriented
|
|
38
|
+
output; `json` and the compact `toon` variant are machine-readable. Under
|
|
39
|
+
`--format json`/`toon`, `poly lint`'s whole-project section and every note (discovery,
|
|
40
|
+
skips, errors) go to stderr so stdout stays a single valid document — a machine consumer
|
|
41
|
+
must check the **exit code**, not just the payload.
|
|
42
|
+
- `--exclude <glob>` — skip paths on top of `.gitignore` (repeatable; merged with
|
|
43
|
+
`[discovery] exclude`). Gitignore-style: a glob without a leading `/` matches a directory
|
|
44
|
+
of that name at **any** depth (`e2e/**` also prunes `src/test/java/io/xberg/e2e/`), while
|
|
45
|
+
a leading `/` anchors it to the config directory (`/e2e/**`). `poly doctor` warns when a
|
|
46
|
+
rule matches at more than one depth.
|
|
47
|
+
- `--include-excluded` — check explicitly named files or directory roots even when they
|
|
48
|
+
match the exclude set. Exclusions *below* an included directory stay active. Applying the
|
|
49
|
+
exclude set to explicitly named paths is already the default, so `--force-exclude` is
|
|
50
|
+
accepted only as a compatibility no-op — the flag is parsed and never read.
|
|
51
|
+
- `--config <path>` — point at a specific `poly.toml`.
|
|
52
|
+
- `--no-cache` — bypass the blake3 content-hash result cache.
|
|
53
|
+
- `-j <N>` — parallelism; `--no-color` — plain output; `--verbose` — extra per-finding
|
|
54
|
+
detail in `pretty` output; `--debug` — per-engine cache hit/miss and timing, plus
|
|
55
|
+
debug-level logs on stderr.
|
|
56
|
+
- `--fix-generated` — let `--fix` rewrite files marked `DO NOT EDIT` / `@generated`, which
|
|
57
|
+
it otherwise reports on but leaves alone.
|
|
58
|
+
- `--deny-skips` / `--max-skips <N>` — strict coverage. A **skipped** file is one nothing
|
|
59
|
+
inspected: a path named on the command line that no engine covers (`App.csproj`), or a
|
|
60
|
+
file every routed backend declined (Go-templated YAML, a hash-stamped generated file). A
|
|
61
|
+
skip the caller instructed — `--only`/`--skip` narrowing an engine out, or `enabled = false`
|
|
62
|
+
in config — is **not** charged against the budget, since it names itself in the report
|
|
63
|
+
rather than losing coverage silently; only a poly limitation counts. Skips are always
|
|
64
|
+
reported and named; these flags make a chargeable skip fail the run (exit `2`), naming
|
|
65
|
+
every file it fired on. `--verbose` lists every skip in `pretty` output; `--format
|
|
66
|
+
json`/`toon` carries the full set both as a top-level `skipped` array and as synthetic
|
|
67
|
+
`results` entries — `summary.skipped` is the authoritative count, so read it first and use
|
|
68
|
+
the set only to name the files.
|
|
69
|
+
|
|
70
|
+
## Exit codes
|
|
71
|
+
|
|
72
|
+
- `0` — clean (no findings, no drift).
|
|
73
|
+
- `1` — error-severity findings, formatting drift, or a failing whole-project tool.
|
|
74
|
+
- `2` — an error (bad config, tool failure), or work the run could not verify: a missing
|
|
75
|
+
path argument, a file an engine failed on — including a `poly fmt` file that could not
|
|
76
|
+
reach a fixed point within its five-pass cap, now reported as an error rather than
|
|
77
|
+
silently claimed as formatted — or a `--deny-skips`/`--max-skips` breach.
|
|
78
|
+
|
|
79
|
+
`poly lint` exits non-zero only on **error-severity** findings; warnings do not fail CI —
|
|
80
|
+
which is why the on-by-default `quality` and built-in ast-grep rules (all warnings) never
|
|
81
|
+
turn a green pipeline red on their own.
|
|
82
|
+
|
|
83
|
+
## Inline suppression
|
|
84
|
+
|
|
85
|
+
Silence one finding in place with a directive written in the host language's own comment
|
|
86
|
+
syntax:
|
|
87
|
+
|
|
88
|
+
```rust
|
|
89
|
+
let value = map.get(key).unwrap(); // poly: allow[unwrap-used] key is validated above
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- `poly: allow[RULE] reason` — a comment-only line suppresses the next non-blank line; a
|
|
93
|
+
trailing comment suppresses its own line.
|
|
94
|
+
- `poly: allow-file[RULE] reason` — suppresses the whole file wherever it appears, and is
|
|
95
|
+
the only form that can suppress a diagnostic with no span.
|
|
96
|
+
- `[RULE]` accepts a comma-separated list, or `*` for every rule.
|
|
97
|
+
- **A reason is mandatory.** A directive whose text after `]` has no alphanumeric character
|
|
98
|
+
does **not** suppress, and reports `lazy-ignore` instead — an empty comment cannot buy a
|
|
99
|
+
bypass.
|
|
100
|
+
- Applied centrally in the runner, so it works for every engine (ruff, oxlint, quality,
|
|
101
|
+
ast-grep, …). For a whole file or a class of files, use `[per-file-ignores]` in
|
|
102
|
+
`poly.toml` instead.
|
|
103
|
+
|
|
104
|
+
## The loop
|
|
105
|
+
|
|
106
|
+
1. `poly fmt --check . --format json` and `poly lint . --format json` — capture drift and
|
|
107
|
+
findings, checking the exit code.
|
|
108
|
+
2. Read the JSON to see exactly which files and rules are involved.
|
|
109
|
+
3. `poly fmt --fix .` then `poly lint --fix .` to apply what is auto-fixable.
|
|
110
|
+
4. Re-run the checks; hand-fix whatever remains (exit code back to 0).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AI-RULEZ :: GENERATED FILE — DO NOT EDIT
|
|
3
|
+
Content-Hash: blake3:827e04e800b869af141ac06d2edd7bf69516c05790bdbf03210a4d802ea6f3b9
|
|
4
|
+
Source-Hash: blake3:840c919261146454e2836095406355dd3ba8664fc068055c202682015400e43f
|
|
5
|
+
Schema-Version: v1
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Concepts
|
|
9
|
+
|
|
10
|
+
* [Poly Lint And Format](SKILL.md) - Running poly lint / poly fmt — --fix, --format pretty|json|toon, --exclude, --config, exit codes, inline suppression, and the check → read-json → fix → re-check loop
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: The poly MCP server — the eleven tools and which are read-only vs mutating, the paths/exclude/config/format params, async Tasks for the whole-project phase, and the isError contract
|
|
3
|
+
name: poly-mcp
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: Goldziher
|
|
7
|
+
repository: https://github.com/Goldziher/poly
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<!--
|
|
11
|
+
AI-RULEZ :: GENERATED FILE — DO NOT EDIT
|
|
12
|
+
Content-Hash: blake3:3e891f0be46a80c56b06b9a9683a91b16c293cdb33edb5aebefaa7c680be3b3c
|
|
13
|
+
Source-Hash: blake3:840c919261146454e2836095406355dd3ba8664fc068055c202682015400e43f
|
|
14
|
+
Schema-Version: v1
|
|
15
|
+
-->
|
|
16
|
+
|
|
17
|
+
# poly MCP
|
|
18
|
+
|
|
19
|
+
`poly mcp` is a stdio MCP server exposing poly's lint/format/cache/rules/config surface as
|
|
20
|
+
tools. Prefer it over shelling out to the CLI when working through MCP — every tool returns
|
|
21
|
+
typed `structured_content` (with a declared output schema) plus one text block that mirrors
|
|
22
|
+
the CLI's `--format json`, or the compact TOON encoding when asked.
|
|
23
|
+
|
|
24
|
+
`poly mcp --config <PATH>` pins a fallback config file for requests that do not name one.
|
|
25
|
+
|
|
26
|
+
## Tool surface
|
|
27
|
+
|
|
28
|
+
Eleven tools, no more. Read-only (never touch the tree):
|
|
29
|
+
|
|
30
|
+
- `lint` — run the linters and return diagnostics. Mirrors `poly lint`.
|
|
31
|
+
- `format_check` — report formatting drift without writing. Mirrors `poly fmt --check`.
|
|
32
|
+
- `cache_stats` — result-cache footprint (entries, bytes, format version) per namespace.
|
|
33
|
+
- `rules` — list every ast-grep rule a run would apply — poly's built-in pack plus the user
|
|
34
|
+
rules from `[rules] dirs` — and optionally run their `*-test.yml` snippets. Mirrors `poly
|
|
35
|
+
rules list` / `poly rules test`. Each rule carries its language, `source` (`builtin`/`user`),
|
|
36
|
+
`default_severity`, the `severity` it reports at under the resolved config, and `enabled`.
|
|
37
|
+
- `config_show` — the merged effective configuration. Mirrors `poly config show`, and is
|
|
38
|
+
**network-free**: remote `extends` bases are not fetched.
|
|
39
|
+
- `version` — which poly binary is serving this session (version, build id, channel,
|
|
40
|
+
executable, pid, uptime), the full compiled-in engine → wrapped-tool-version map, and
|
|
41
|
+
whether that executable is still the file on disk. It answers even when the binary has
|
|
42
|
+
moved, which is what makes it the tool that explains why the others stopped.
|
|
43
|
+
|
|
44
|
+
Mutating (write to the tree):
|
|
45
|
+
|
|
46
|
+
- `lint_fix` — apply lint autofixes. Mirrors `poly lint --fix`.
|
|
47
|
+
- `format_write` — format files in place. Mirrors `poly fmt --fix`.
|
|
48
|
+
- `cache_clean` — clear the result cache and report freed bytes.
|
|
49
|
+
|
|
50
|
+
Whole-project (long-running):
|
|
51
|
+
|
|
52
|
+
- `workspace_lint` — the whole-project phase in check mode: `cargo clippy` / `cargo-sort` /
|
|
53
|
+
`cargo-machete` / `cargo-deny` and configured inline whole-project jobs, over the whole
|
|
54
|
+
repository (it takes no `paths`).
|
|
55
|
+
- `workspace_lint_fix` — the same phase in fix mode; writes files.
|
|
56
|
+
|
|
57
|
+
Both are exposed as async Tasks (SEP-2663): the call returns a task handle and the client
|
|
58
|
+
polls `tasks/get`, optionally `tasks/cancel`, with an unlimited TTL. A client that does not
|
|
59
|
+
declare the tasks capability gets a synchronous, blocking result from the same call instead,
|
|
60
|
+
so the tools work either way.
|
|
61
|
+
|
|
62
|
+
`workspace_lint` applies no fixes, but it is **not** in the never-touch-the-tree group: it
|
|
63
|
+
executes those tools against the live worktree, and their own side effects — a refreshed
|
|
64
|
+
lock file, a populated build or type-checker cache — are not poly's to control.
|
|
65
|
+
|
|
66
|
+
There is no `hooks` tool: `poly hooks` is CLI-only.
|
|
67
|
+
|
|
68
|
+
## Parameters
|
|
69
|
+
|
|
70
|
+
- `lint` / `format_check` / `lint_fix` / `format_write`: `paths` (files or directories;
|
|
71
|
+
empty means the current directory), `exclude` (gitignore-style globs merged with
|
|
72
|
+
`[discovery] exclude` — unanchored globs match at any depth), `config` (path to a
|
|
73
|
+
`poly.toml`), `format` (`"json"` default, or `"toon"`).
|
|
74
|
+
- `rules`: `dirs` (empty means `[rules] dirs` from the config), `config`, `test` (bool),
|
|
75
|
+
`format`.
|
|
76
|
+
- `config_show`: `config`, `format`.
|
|
77
|
+
- `cache_stats` / `cache_clean` / `version`: `format` only.
|
|
78
|
+
- `workspace_lint` / `workspace_lint_fix`: `config`, `format`, `jobs`, `no_cache` — no
|
|
79
|
+
`paths`.
|
|
80
|
+
|
|
81
|
+
`format` selects only the paired **text** block; `structured_content` is always JSON.
|
|
82
|
+
|
|
83
|
+
Treat the read-only tools as safe to call freely; gate the mutating tools behind explicit
|
|
84
|
+
intent since they change files.
|
|
85
|
+
|
|
86
|
+
## Every result identifies the binary that answered
|
|
87
|
+
|
|
88
|
+
Each result carries a `poly` block (version, build id, channel, executable, pid, and an
|
|
89
|
+
`engines` digest — a blake3 hash over the sorted compiled-in engine name + version pairs,
|
|
90
|
+
excluding native-toolchain and catalog engines since those depend on the host and on config)
|
|
91
|
+
in `structured_content` and in `_meta`, because an MCP caller has no `poly --version` to fall
|
|
92
|
+
back on. The digest is what distinguishes two builds sharing one version number (a `dev`
|
|
93
|
+
build, or two builds of one tag) when their wrapped crates differ; the full engine map is on
|
|
94
|
+
the `version` tool. The server fingerprints its own executable at startup and re-checks it per
|
|
95
|
+
request: if the binary is replaced or deleted underneath a long-lived server, every tool but
|
|
96
|
+
`version` fails rather than answering with superseded behaviour.
|
|
97
|
+
|
|
98
|
+
## Per-file outcomes: checked, skipped, error
|
|
99
|
+
|
|
100
|
+
The result object is `{results, errors, skipped, summary, configs}`, not a bare array.
|
|
101
|
+
`summary` is `{checked, skipped, errored}` — the run's own count of what it did, and the field
|
|
102
|
+
to gate on. It cannot be derived from `results`: `results` holds only files with something to
|
|
103
|
+
report, so a file that was checked and found clean produces **no** `results` entry at all — a
|
|
104
|
+
present entry does not mean checked, either, since `results` also carries a synthetic entry
|
|
105
|
+
(empty `diagnostics`, `skipped` or `error` set) for every file the run declined or failed on.
|
|
106
|
+
The skipped/error sets and `results` overlap on purpose:
|
|
107
|
+
|
|
108
|
+
- **Checked** — counted in `summary.checked`. Not derivable from `results`.
|
|
109
|
+
- **Skipped** (`skipped` field, and the top-level `skipped` array) — poly correctly declined the
|
|
110
|
+
file (e.g. a template dialect no backend handles). Not a failure.
|
|
111
|
+
- **Errored** (`error` field, and the top-level `errors` array) — poly failed to process the
|
|
112
|
+
file (unreadable file, backend crash, bad engine config). Distinct from `skipped` on purpose:
|
|
113
|
+
a skip is a deliberate decision, an error is poly failing on a file it accepted.
|
|
114
|
+
|
|
115
|
+
The top-level `errors` array duplicates the `error`-carrying records in `results` so a caller
|
|
116
|
+
can gate on "did the run fail on anything" without scanning every record. When `errors` is
|
|
117
|
+
non-empty, the tool result's `CallToolResult.is_error` (`isError` over the wire) is set `true`.
|
|
118
|
+
|
|
119
|
+
**An MCP-driving agent must check `isError` (or `summary.errored`) before trusting any other
|
|
120
|
+
part of the result.** Before this shape existed, a file poly failed to process was absent from
|
|
121
|
+
the output entirely — indistinguishable from a file that was checked and found clean. An agent
|
|
122
|
+
that gated on "no findings in `results`" was reading a run that had silently failed to check
|
|
123
|
+
some files as a clean pass. Check `isError` first; only then read `summary` for coverage and
|
|
124
|
+
`results` for diagnostics.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AI-RULEZ :: GENERATED FILE — DO NOT EDIT
|
|
3
|
+
Content-Hash: blake3:041e6c4ec0de96e350e6b4a73ef9d19e5d4f73af9368e6ee0b61c54f7ce5957d
|
|
4
|
+
Source-Hash: blake3:840c919261146454e2836095406355dd3ba8664fc068055c202682015400e43f
|
|
5
|
+
Schema-Version: v1
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Concepts
|
|
9
|
+
|
|
10
|
+
* [Poly Mcp](SKILL.md) - The poly MCP server — the eleven tools and which are read-only vs mutating, the paths/exclude/config/format params, async Tasks for the whole-project phase, and the isError contract
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use poly as the single lint/format gate instead of invoking ruff/oxlint/rustfmt directly — one poly.toml, poly hooks install, poly migrate, CI via the Goldziher/poly setup Action
|
|
3
|
+
name: poly-orchestrator
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: Goldziher
|
|
7
|
+
repository: https://github.com/Goldziher/poly
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<!--
|
|
11
|
+
AI-RULEZ :: GENERATED FILE — DO NOT EDIT
|
|
12
|
+
Content-Hash: blake3:3363494043f7c9784c37f40b9ed195ca372dc87bebd5f35072722e988c20773a
|
|
13
|
+
Source-Hash: blake3:840c919261146454e2836095406355dd3ba8664fc068055c202682015400e43f
|
|
14
|
+
Schema-Version: v1
|
|
15
|
+
-->
|
|
16
|
+
|
|
17
|
+
# poly as the Orchestrator
|
|
18
|
+
|
|
19
|
+
Treat poly as the **single** lint and format gate for the repository. Do not invoke ruff,
|
|
20
|
+
oxlint, taplo, rumdl, or the rest directly — poly compiles them in as backends, and covers
|
|
21
|
+
everything else via the tree-sitter generic tier, the `quality` metric tier, and its built-in
|
|
22
|
+
ast-grep rule pack, behind one binary, one config, and one report.
|
|
23
|
+
|
|
24
|
+
## Adopt it
|
|
25
|
+
|
|
26
|
+
- **One `poly.toml`.** Configure every language and rule in a single per-repo file
|
|
27
|
+
(`poly.local.toml` for local overrides, `extends` to share a base). Layering is
|
|
28
|
+
tool default → poly's opinionated overrides → your `poly.toml`. In a monorepo a nested
|
|
29
|
+
`poly.toml` deep-merges on top of its ancestors for the files beneath it.
|
|
30
|
+
- **`poly migrate`** — absorb existing tool configs into `poly.toml` instead of
|
|
31
|
+
hand-writing it. It imports **ruff, typos, taplo, and markdownlint** configs; there is no
|
|
32
|
+
eslint, prettier, or pre-commit importer. The default is a dry-run report — pass `--write`
|
|
33
|
+
to apply, `--recurse` for a monorepo, `--verify` to re-run poly afterwards, and
|
|
34
|
+
`--strip-superseded` to also remove `pyproject.toml` sections for tools ruff replaces.
|
|
35
|
+
- **`poly hooks install`** — wire the git-hook shims so `poly` runs the configured stages on
|
|
36
|
+
every commit, replacing a `.pre-commit-config.yaml`. It installs the hook types your
|
|
37
|
+
`poly.toml` configures; `--hook-type` picks specific ones and `--overwrite` discards a
|
|
38
|
+
preserved legacy hook. `poly hooks uninstall` restores what was there before.
|
|
39
|
+
- **`poly config show`** prints the effective merged config; **`poly config update`** pins a
|
|
40
|
+
symbolic remote `extends` ref into `poly-config.lock`.
|
|
41
|
+
- **`poly doctor`** reports which poly is running, every poly on PATH, and the config in
|
|
42
|
+
effect — the first thing to run when two machines disagree.
|
|
43
|
+
|
|
44
|
+
## CI
|
|
45
|
+
|
|
46
|
+
`Goldziher/poly@v0` is a **setup** action: it installs the `poly` binary (with optional
|
|
47
|
+
caching) and puts it on `PATH`. It does not run poly for you — invoke the commands yourself
|
|
48
|
+
in following steps.
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
- uses: actions/checkout@v4
|
|
52
|
+
- uses: Goldziher/poly@v0 # inputs: version (default latest), cache, github-token
|
|
53
|
+
- run: poly fmt --check .
|
|
54
|
+
- run: poly lint .
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
poly is one pinned binary with no *required* system dependencies, so local, hook, and CI
|
|
58
|
+
runs agree — with one caveat: the native-toolchain tier means `rustfmt` and `gofmt` are used
|
|
59
|
+
automatically whenever they are on `PATH`, and formatting output then depends on that tool's
|
|
60
|
+
version. A machine without them silently falls through to the lower-fidelity tree-sitter
|
|
61
|
+
tier. Pin the Rust/Go toolchain in CI (and expect drift otherwise), or set
|
|
62
|
+
`[fmt.rust.rustfmt] enabled = false` / `[fmt.go.gofmt] enabled = false` to stop the subprocess.
|
|
63
|
+
That does not leave the language unformatted: `rustfmt`/`gofmt` are the one pair of backends
|
|
64
|
+
that read `enabled` themselves and hand the file to the tier-2 reindenter when disabled, rather
|
|
65
|
+
than expecting the runner to drop them from the plan — so the file is still formatted, just at
|
|
66
|
+
lower fidelity than the native tool.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AI-RULEZ :: GENERATED FILE — DO NOT EDIT
|
|
3
|
+
Content-Hash: blake3:a320c195ce6d97513905afe7995e590161abfcbd3ed11eff6d3afbeae48df185
|
|
4
|
+
Source-Hash: blake3:840c919261146454e2836095406355dd3ba8664fc068055c202682015400e43f
|
|
5
|
+
Schema-Version: v1
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Concepts
|
|
9
|
+
|
|
10
|
+
* [Poly Orchestrator](SKILL.md) - Use poly as the single lint/format gate instead of invoking ruff/oxlint/rustfmt directly — one poly.toml, poly hooks install, poly migrate, CI via the Goldziher/poly setup Action
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: What poly is — the native, tree-sitter, native-toolchain, quality, and built-in-rule-pack tiers, language coverage, and when to reach for it
|
|
3
|
+
name: poly-overview
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: Goldziher
|
|
7
|
+
repository: https://github.com/Goldziher/poly
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<!--
|
|
11
|
+
AI-RULEZ :: GENERATED FILE — DO NOT EDIT
|
|
12
|
+
Content-Hash: blake3:273b80b1cc5f091780eac739023a29774686024091327f99c639532e042d2ff6
|
|
13
|
+
Source-Hash: blake3:840c919261146454e2836095406355dd3ba8664fc068055c202682015400e43f
|
|
14
|
+
Schema-Version: v1
|
|
15
|
+
-->
|
|
16
|
+
|
|
17
|
+
# poly Overview
|
|
18
|
+
|
|
19
|
+
poly is a single pure-Rust binary that lints and formats a whole repository across
|
|
20
|
+
languages. It wraps best-in-class tools as in-process crate backends and falls back to a
|
|
21
|
+
tree-sitter generic tier for everything else, so it runs the same on any machine and in CI.
|
|
22
|
+
No system tool is ever *required*; the one scoped exception is the native-toolchain tier
|
|
23
|
+
below, which uses a language's canonical CLI when it happens to be installed.
|
|
24
|
+
|
|
25
|
+
## Per-language coverage tiers
|
|
26
|
+
|
|
27
|
+
- **Tier 1 — native in-process backends.** Highest fidelity, compiled straight into the
|
|
28
|
+
binary and registered per language in `registry.rs`: `ruff` (Python), `oxc`
|
|
29
|
+
(JS/TS/JSX/TSX/JSON/JSONC), `taplo` (TOML), `rumdl` (Markdown/MDX), `sqruff` (SQL), YAML,
|
|
30
|
+
`malva` (CSS/SCSS/LESS) + `biome` (CSS/SCSS), `markup_fmt` (HTML/Vue/Svelte/Astro/Angular/Jinja/
|
|
31
|
+
Vento/Mustache/XML), `mago` (PHP), `rubyfmt` (Ruby), `graphql` + `biome` (GraphQL),
|
|
32
|
+
`nixfmt` (Nix), `hcl`, Dockerfile, dotenv, and INI.
|
|
33
|
+
- **Tier 2 — tree-sitter generic tier.** The catch-all for the long tail (Elixir, C/C++,
|
|
34
|
+
C#, protobuf, and the rest of 300+ grammars). CST-driven structural reindent for
|
|
35
|
+
brace-family grammars, whitespace normalization otherwise, and a `LEAVE_UNTOUCHED` list
|
|
36
|
+
for grammars where whitespace is significant. Best-effort, pure Rust, grammars loaded on
|
|
37
|
+
demand — never gofmt/rustfmt parity, by design.
|
|
38
|
+
- **Native-toolchain tier (scoped exception).** A language's canonical first-party CLI run
|
|
39
|
+
per file over stdin/stdout when installed: `rustfmt`, `gofmt` and `shellcheck` are **on by
|
|
40
|
+
default when found on PATH**; `zig fmt`, `shfmt`, `google-java-format`, `ktfmt`, `styler`
|
|
41
|
+
(R), `swift-format`, `dart format`, and `gleam format` are opt-in via
|
|
42
|
+
`[fmt.<lang>.<tool>] enabled = true` / `[lint.<lang>.<tool>] enabled = true`. When the
|
|
43
|
+
tool is absent, formatting falls through to tier 2 with an info-level notice, so the
|
|
44
|
+
zero-dependency guarantee always holds.
|
|
45
|
+
|
|
46
|
+
## Cross-cutting engines (every language)
|
|
47
|
+
|
|
48
|
+
Four engines are appended to every language regardless of tier:
|
|
49
|
+
|
|
50
|
+
- **`typos`** — spelling in identifiers, comments, and strings.
|
|
51
|
+
- **`quality`** — structural code-quality metrics off the tree-sitter parse: file/function/
|
|
52
|
+
type length, nesting depth, cyclomatic complexity, parameter count, and `lazy-ignore`.
|
|
53
|
+
Warning severity, on by default. The structural rules need a verified node-kind table and
|
|
54
|
+
run for Python, Rust, Go, JavaScript, TypeScript, TSX, Java, Kotlin, C, C++, C#, and Ruby;
|
|
55
|
+
`magic-number` and `law-of-demeter` are opt-in. Configure under `[lint.quality]`.
|
|
56
|
+
- **`astgrep`** — ast-grep pattern rules. poly embeds a **built-in rule pack** of 26 rules
|
|
57
|
+
across 9 languages (C#, Elixir, Go, Java, Kotlin, Python, Ruby, Rust, Swift), on by
|
|
58
|
+
default; 13 of them ship `severity: off`. User rules under `[rules] dirs` (default
|
|
59
|
+
`.poly/rules`) layer on top and replace a pack rule sharing its `id`. Disable the pack
|
|
60
|
+
wholesale with `[rules] builtin = false`.
|
|
61
|
+
- **`uncomment`** — opt-in comment removal, off unless `[lint.uncomment] enabled = true`.
|
|
62
|
+
|
|
63
|
+
## When to use it
|
|
64
|
+
|
|
65
|
+
Reach for poly as the single lint/format gate for any repo: local dev, git hooks, and CI.
|
|
66
|
+
It replaces invoking ruff / oxlint / rustfmt / prettier individually — one binary, one
|
|
67
|
+
`poly.toml`, one report. A language with no native backend still gets tier-2 formatting
|
|
68
|
+
plus the cross-cutting engines, so poly covers the whole tree rather than only the
|
|
69
|
+
languages you wired up by hand.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AI-RULEZ :: GENERATED FILE — DO NOT EDIT
|
|
3
|
+
Content-Hash: blake3:948de85c38f4ceee5ac758d6b75670c18be2a3850757a17f1b0546d467019698
|
|
4
|
+
Source-Hash: blake3:840c919261146454e2836095406355dd3ba8664fc068055c202682015400e43f
|
|
5
|
+
Schema-Version: v1
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Concepts
|
|
9
|
+
|
|
10
|
+
* [Poly Overview](SKILL.md) - What poly is — the native, tree-sitter, native-toolchain, quality, and built-in-rule-pack tiers, language coverage, and when to reach for it
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: poly's coverage tiers (native / tree-sitter / native-toolchain / quality / built-in ast-grep pack), the per-file tier vs the whole-project phase (--no-workspace), staged isolation (ADR 0019), and hierarchical poly.toml in monorepos (ADR 0018)
|
|
3
|
+
name: poly-tiers-and-scope
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: Goldziher
|
|
7
|
+
repository: https://github.com/Goldziher/poly
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<!--
|
|
11
|
+
AI-RULEZ :: GENERATED FILE — DO NOT EDIT
|
|
12
|
+
Content-Hash: blake3:ad140ec77b0aed2a08b9c7261e16d5f9cde428c88e6e249a9891a24e6f35b964
|
|
13
|
+
Source-Hash: blake3:840c919261146454e2836095406355dd3ba8664fc068055c202682015400e43f
|
|
14
|
+
Schema-Version: v1
|
|
15
|
+
-->
|
|
16
|
+
|
|
17
|
+
# poly Tiers and Scope
|
|
18
|
+
|
|
19
|
+
## Coverage tiers inside the per-file pass
|
|
20
|
+
|
|
21
|
+
Five mechanisms decide what actually inspects a file (`crates/poly-core/src/registry.rs`):
|
|
22
|
+
|
|
23
|
+
1. **Tier-1 native crate backends** — one in-process Rust crate registered for specific
|
|
24
|
+
languages: ruff, oxc, taplo, rumdl, sqruff, yaml, malva (+ biome for CSS/SCSS), markup_fmt, mago, rubyfmt,
|
|
25
|
+
graphql, nixfmt, hcl, dockerfile, dotenv, ini.
|
|
26
|
+
2. **Tier-2 tree-sitter generic tier** (ADR 0004) — the catch-all for every language with no
|
|
27
|
+
native backend. Structural reindent for brace-family grammars, whitespace normalization
|
|
28
|
+
otherwise, and a leave-untouched list where whitespace is significant. This is the
|
|
29
|
+
coverage mechanism, not a fallback to avoid — but it is explicitly *not* gofmt/rustfmt
|
|
30
|
+
parity.
|
|
31
|
+
3. **Native-toolchain tier** (ADR 0014, `engines/native_tool/`) — the language's canonical
|
|
32
|
+
first-party CLI, run per file over stdin/stdout. `rustfmt`, `gofmt` and `shellcheck` are
|
|
33
|
+
**on by default whenever the tool is found on PATH**; `zig fmt`, `shfmt`,
|
|
34
|
+
`google-java-format`, `ktfmt`, `styler`, `swift-format`, `dart format`, and
|
|
35
|
+
`gleam format` are opt-in via `[fmt.<lang>.<tool>] enabled = true` /
|
|
36
|
+
`[lint.<lang>.<tool>] enabled = true`. Absent tool ⇒ the language falls through to
|
|
37
|
+
tier 2 with an info-level notice, never an error.
|
|
38
|
+
4. **`quality`, the code-quality metric tier** (ADR 0027) — cross-cutting, appended to every
|
|
39
|
+
language: file/function/type length, nesting depth, cyclomatic complexity, parameter
|
|
40
|
+
count, and `lazy-ignore`. Warning severity, on by default, configured under
|
|
41
|
+
`[lint.quality]`. The structural rules need a verified node-kind table and cover Python,
|
|
42
|
+
Rust, Go, JavaScript, TypeScript, TSX, Java, Kotlin, C, C++, C#, and Ruby; `magic-number`
|
|
43
|
+
and `law-of-demeter` are opt-in. A per-language deferral table keeps it from duplicating
|
|
44
|
+
a tier-1 rule (ruff's `C901`/`PLR0913`, oxlint's `max-depth`/`max-params`).
|
|
45
|
+
5. **The built-in ast-grep rule pack** (ADR 0029) — 26 rules across 9 languages (C#, Elixir,
|
|
46
|
+
Go, Java, Kotlin, Python, Ruby, Rust, Swift) embedded in the binary and on by default; 13
|
|
47
|
+
of them ship `severity: off`. It sits *beneath* `[rules] dirs` (default `.poly/rules`), so
|
|
48
|
+
a user rule with the same `id` replaces a pack rule. `[rules] builtin = false` disables
|
|
49
|
+
the pack wholesale.
|
|
50
|
+
|
|
51
|
+
`typos` (spelling) and the opt-in `uncomment` engine are the other two cross-cutting
|
|
52
|
+
backends appended to every language.
|
|
53
|
+
|
|
54
|
+
**Every engine table also accepts a universal `enabled` key**, read by the runner's plan
|
|
55
|
+
rather than by any backend. Whether narrowing a file out of a run charges against
|
|
56
|
+
`--deny-skips` / `--max-skips` follows one rule: **a poly limitation is charged and names the
|
|
57
|
+
limitation; a caller instruction is never charged and always names itself.** `--only`/`--skip`
|
|
58
|
+
and `enabled = false` are caller instructions, so a file they remove is reported as skipped but
|
|
59
|
+
not charged; `no matching engine for this file type`, `no lint rules for <language>`, and a
|
|
60
|
+
generated-file skip are poly's own limitations, and are charged.
|
|
61
|
+
|
|
62
|
+
## Per-file tier vs whole-project phase
|
|
63
|
+
|
|
64
|
+
`poly lint` runs in two phases:
|
|
65
|
+
|
|
66
|
+
- **Per-file tier** — the parallel, cached engine pass over every discovered file, using the
|
|
67
|
+
five mechanisms above. This is all `--no-workspace` runs.
|
|
68
|
+
- **Whole-project phase** — invokes the same whole-workspace tools a pre-commit hook would
|
|
69
|
+
(`cargo clippy` / `cargo-sort` / `cargo-machete` / `cargo-deny`, plus any inline
|
|
70
|
+
`workspace = true` job such as a type checker) on the live worktree, folding their
|
|
71
|
+
pass/fail into the report and exit code. It reuses the `[hooks]` config as its single
|
|
72
|
+
source of truth. On by default; disable with `--no-workspace` or `[lint] workspace =
|
|
73
|
+
false`. A repo with no `[hooks]` section has no whole-workspace hooks to lower, so it runs
|
|
74
|
+
only the per-file tier.
|
|
75
|
+
|
|
76
|
+
**Plain `poly lint` is not read-only.** poly applies no fixes without `--fix`, but the
|
|
77
|
+
whole-project phase *executes* those tools against the live worktree, and their own side
|
|
78
|
+
effects are not poly's to control (`cargo clippy` populating `target/` or refreshing
|
|
79
|
+
`Cargo.lock`, a `go` invocation appending to `go.work.sum`, a type checker writing its
|
|
80
|
+
cache). Only `--no-workspace` / `[lint] workspace = false` gives a run that cannot touch the
|
|
81
|
+
tree.
|
|
82
|
+
|
|
83
|
+
The phase is also **not path-scoped**: `poly lint <paths>` skips it when the paths *narrow*
|
|
84
|
+
the run, and `--workspace` opts back in with the tools covering the whole repository —
|
|
85
|
+
regardless of the named paths and of `[discovery] exclude`, which filters poly's own
|
|
86
|
+
discovery only. Naming the directory poly is running in (`poly lint .`) is a request for the
|
|
87
|
+
whole project, not a narrowing, so the phase still runs. A note on stderr says which branch
|
|
88
|
+
was taken.
|
|
89
|
+
|
|
90
|
+
Under `--fix`, the whole-project phase runs the tools in fix mode (`cargo sort` in place,
|
|
91
|
+
`cargo-machete --fix`, `cargo clippy --fix --allow-dirty --allow-staged`; `cargo deny` stays
|
|
92
|
+
check-only). Fix mode is what `--fix` adds — the phase runs either way, so `--no-workspace`,
|
|
93
|
+
not the absence of `--fix`, is what skips it. The git-hook / commit-gate path never requests
|
|
94
|
+
fix mode. `poly fmt` never runs this phase.
|
|
95
|
+
|
|
96
|
+
## Staged isolation (ADR 0019)
|
|
97
|
+
|
|
98
|
+
On the commit-gate path, poly materializes a snapshot of the git **index** (via
|
|
99
|
+
`git checkout-index`) and runs the stage from it, so unstaged edits neither hide nor cause
|
|
100
|
+
failures. Details that matter:
|
|
101
|
+
|
|
102
|
+
- The snapshot is the execution root for **every** hook in the run, not just the
|
|
103
|
+
whole-workspace ones — a gate whose per-file hooks read the worktree while its
|
|
104
|
+
whole-project hooks read the index would report on two different sets of bytes.
|
|
105
|
+
- Isolation is active only for the index stages (`pre-commit`, `pre-merge-commit`) and is
|
|
106
|
+
skipped for `--all-files` (which deliberately checks the whole tree) and for non-index
|
|
107
|
+
stages such as `pre-push`. `[hooks] isolate = false` forces it off.
|
|
108
|
+
- The snapshot lives in the per-user OS cache dir (`~/.cache/poly/<repo-key>/staged`), not
|
|
109
|
+
in-repo, and is refreshed incrementally by index OID so tool caches stay warm.
|
|
110
|
+
- `poly lint`'s whole-project phase is a *different* path: it runs against the live
|
|
111
|
+
worktree, so no snapshot is built.
|
|
112
|
+
- `[hooks] snapshot_include` opts named, git-untracked, repo-relative paths into the snapshot
|
|
113
|
+
by symlinking them in from the live worktree, so a `workspace` hook whose build reads a
|
|
114
|
+
gitignored input (a generated file, a local type-checker config, a downloaded fixture
|
|
115
|
+
directory) does not fail under the gate for a reason the error message doesn't name. Three
|
|
116
|
+
consequences follow from the symlink: a fix to an included file is withheld (there is no
|
|
117
|
+
staged blob to write it into), an included file is never a per-file hook's own input (only
|
|
118
|
+
index content is), and the symlink lets a hook write through into the live worktree.
|
|
119
|
+
|
|
120
|
+
## Hierarchical poly.toml in monorepos (ADR 0018)
|
|
121
|
+
|
|
122
|
+
Config resolves hierarchically: every `poly.toml` from the repository root down to the file
|
|
123
|
+
being processed is deep-merged, so a nested config layers on top of its ancestors and each
|
|
124
|
+
package can tune rules while sharing a root baseline. The upward walk is bounded at the
|
|
125
|
+
`.git` directory (or an explicit `[workspace] root = true`), so a stray `poly.toml` in
|
|
126
|
+
`$HOME` is never picked up. `poly.local.toml` remains the final local override layer, and a
|
|
127
|
+
top-level `extends` list (ADR 0020) shares sections from local or pinned-remote base
|
|
128
|
+
configs — `poly config update` pins a symbolic remote ref into `poly-config.lock`, and
|
|
129
|
+
`poly config show` prints the effective merged config.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AI-RULEZ :: GENERATED FILE — DO NOT EDIT
|
|
3
|
+
Content-Hash: blake3:cb19976815fbf90d5f6b6eb13257488eab322f2780265391d895a30e5cc0ff18
|
|
4
|
+
Source-Hash: blake3:840c919261146454e2836095406355dd3ba8664fc068055c202682015400e43f
|
|
5
|
+
Schema-Version: v1
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Concepts
|
|
9
|
+
|
|
10
|
+
* [Poly Tiers And Scope](SKILL.md) - poly's coverage tiers (native / tree-sitter / native-toolchain / quality / built-in ast-grep pack), the per-file tier vs the whole-project phase (--no-workspace), staged isolation (ADR 0019), and hierarchical poly.toml in monorepos (ADR 0018)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: poly-hermes-plugin
|
|
3
|
+
Version: 0.29.0
|
|
4
|
+
Summary: Zero-dependency lint & format orchestrator over MCP — one pure-Rust binary wrapping ruff/oxc/taplo/rumdl and more as in-process backends, plus a tree-sitter generic tier for everything else. Exposes lint/format/cache tools with structured JSON output.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Goldziher/poly
|
|
6
|
+
Project-URL: Repository, https://github.com/Goldziher/poly
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: code-quality,formatter,linter,mcp,rust
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Requires-Dist: mcp==1.28.1
|
|
11
|
+
Requires-Dist: pyyaml==6.0.3
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# poly Hermes plugin
|
|
15
|
+
|
|
16
|
+
Zero-dependency lint & format orchestrator over MCP — one pure-Rust binary wrapping ruff/oxc/taplo/rumdl and more as in-process backends, plus a tree-sitter generic tier for everything else. Exposes lint/format/cache tools with structured JSON output.
|
|
17
|
+
|
|
18
|
+
Install this package in the same Python environment as Hermes Agent:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
python -m pip install poly-hermes-plugin
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Enable the plugin through Hermes Agent's plugin configuration. Available registrations depend on the plugin's authored Hermes adapter.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
poly_hermes_plugin/__init__.py,sha256=TTmehqN7xNwIkpo2hE9u86MMHnDsyfMoqZBILoRZ7dE,385
|
|
2
|
+
poly_hermes_plugin/hermes.py,sha256=AjHDxQ1WFW9l-txFeNSQ9dseTgvOnyhgaa7d19TqWgo,4550
|
|
3
|
+
poly_hermes_plugin/plugin.yaml,sha256=y1IJt71YWVZw3uQLUNeFrmB_IZNMDSRJNvAQMPQwwWQ,407
|
|
4
|
+
poly_hermes_plugin/commands/poly-check.md,sha256=m0GBQKvwIEaL2nRPqBO2KISfEYgrbq21uImQUA6P0bw,2392
|
|
5
|
+
poly_hermes_plugin/commands/poly-fix.md,sha256=1xMCQAgVfYvAq_862xyOFyCDeA15yrFGGMY3ds0MILo,1920
|
|
6
|
+
poly_hermes_plugin/skills/poly-lint-and-format/SKILL.md,sha256=QUocECEYx32QzKEf7VqBq6kGDQsRBUxp7xMPjWPx9Uc,6257
|
|
7
|
+
poly_hermes_plugin/skills/poly-lint-and-format/index.md,sha256=lXaTKLl_qchFml1PcwDA4DBi_TkJpIYR9vkKng8Gemc,466
|
|
8
|
+
poly_hermes_plugin/skills/poly-mcp/SKILL.md,sha256=k9FQ7YHb1H-zqN5WHlGYV3VwyOdAtSjBShQ8rlgKs6s,7054
|
|
9
|
+
poly_hermes_plugin/skills/poly-mcp/index.md,sha256=9J7XDDR3IR9hp3Cm9__uqqIcxvEuH8DIcWHZj_ew87A,464
|
|
10
|
+
poly_hermes_plugin/skills/poly-orchestrator/SKILL.md,sha256=-EvXs4yJp9N2-Pt3Kk9VZEIzDyZdjRO1lfzBI2q8HK4,3625
|
|
11
|
+
poly_hermes_plugin/skills/poly-orchestrator/index.md,sha256=3UuoEWsv99FwhbVVQqpeLSu28eMDX6nAGa08IC5MDN8,470
|
|
12
|
+
poly_hermes_plugin/skills/poly-overview/SKILL.md,sha256=XfLd6nh32SEiE8JwSWhCObnZeShvTD468k9r_va2-Xs,3994
|
|
13
|
+
poly_hermes_plugin/skills/poly-overview/index.md,sha256=RhvI75X2ehnUqYC5f40ou_hfqjFTH6Iu18k8lTQHL0U,428
|
|
14
|
+
poly_hermes_plugin/skills/poly-tiers-and-scope/SKILL.md,sha256=rz8Cbu3WREG3FKrNINr1LkO2WAim1dLFOL4p11ipMNk,8223
|
|
15
|
+
poly_hermes_plugin/skills/poly-tiers-and-scope/index.md,sha256=oaMihgN8uUkAdVWgWY11wFaIL_hZp9rOlRCgvfDMViI,535
|
|
16
|
+
poly_hermes_plugin-0.29.0.dist-info/METADATA,sha256=FuoCI_7vfCQQkpBpc-HylcGbU842kwZYA2GWH7TMzq0,1170
|
|
17
|
+
poly_hermes_plugin-0.29.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
18
|
+
poly_hermes_plugin-0.29.0.dist-info/entry_points.txt,sha256=lBeJl88Ie_tUFqFbPZ5KqPaVtJ-a1s-Kd4x5Rex-7QI,49
|
|
19
|
+
poly_hermes_plugin-0.29.0.dist-info/RECORD,,
|