agentseed-mcp 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +178 -0
- package/LICENSE +202 -0
- package/README.ja.md +320 -0
- package/README.md +318 -0
- package/README.zh.md +306 -0
- package/bin/cli.js +37 -0
- package/mcp.json +12 -0
- package/package.json +30 -0
- package/plugin.json +22 -0
- package/server/.agentseed/verification-log.jsonl +2 -0
- package/server/__pycache__/guard_cli.cpython-313.pyc +0 -0
- package/server/__pycache__/guard_engine.cpython-313.pyc +0 -0
- package/server/__pycache__/test_cli.cpython-313-pytest-9.1.1.pyc +0 -0
- package/server/__pycache__/test_cli.cpython-313.pyc +0 -0
- package/server/__pycache__/test_features.cpython-313-pytest-9.1.1.pyc +0 -0
- package/server/__pycache__/test_features.cpython-313.pyc +0 -0
- package/server/__pycache__/test_guard.cpython-313-pytest-9.1.1.pyc +0 -0
- package/server/__pycache__/test_guard.cpython-313.pyc +0 -0
- package/server/__pycache__/test_hook.cpython-313-pytest-9.1.1.pyc +0 -0
- package/server/__pycache__/test_hook.cpython-313.pyc +0 -0
- package/server/__pycache__/test_manifests.cpython-313-pytest-9.1.1.pyc +0 -0
- package/server/__pycache__/test_manifests.cpython-313.pyc +0 -0
- package/server/__pycache__/test_server.cpython-313-pytest-9.1.1.pyc +0 -0
- package/server/__pycache__/test_server.cpython-313.pyc +0 -0
- package/server/engine/__init__.py +64 -0
- package/server/engine/__pycache__/__init__.cpython-313.pyc +0 -0
- package/server/engine/__pycache__/audit.cpython-313.pyc +0 -0
- package/server/engine/__pycache__/config.cpython-313.pyc +0 -0
- package/server/engine/__pycache__/hallucination.cpython-313.pyc +0 -0
- package/server/engine/__pycache__/imports.cpython-313.pyc +0 -0
- package/server/engine/__pycache__/plugin.cpython-313.pyc +0 -0
- package/server/engine/__pycache__/sandbox.cpython-313.pyc +0 -0
- package/server/engine/__pycache__/schema.cpython-313.pyc +0 -0
- package/server/engine/__pycache__/symbols.cpython-313.pyc +0 -0
- package/server/engine/__pycache__/version.cpython-313.pyc +0 -0
- package/server/engine/audit.py +84 -0
- package/server/engine/config.py +131 -0
- package/server/engine/hallucination.py +254 -0
- package/server/engine/imports.py +136 -0
- package/server/engine/plugin.py +367 -0
- package/server/engine/sandbox.py +287 -0
- package/server/engine/schema.py +193 -0
- package/server/engine/symbols.py +984 -0
- package/server/engine/version.py +17 -0
- package/server/guard_cli.py +455 -0
- package/server/guard_engine.py +111 -0
- package/server/guard_hook.py +404 -0
- package/server/guard_server.py +472 -0
- package/server/requirements.txt +7 -0
- package/server/test_cli.py +132 -0
- package/server/test_features.py +426 -0
- package/server/test_guard.py +828 -0
- package/server/test_hook.py +331 -0
- package/server/test_manifests.py +70 -0
- package/server/test_server.py +247 -0
- package/skills/verify-before-code/SKILL.ja.md +116 -0
- package/skills/verify-before-code/SKILL.md +140 -0
- package/skills/verify-before-code/SKILL.zh.md +117 -0
- package/skills/verify-before-code/references/DEFAULT-NORMS.md +52 -0
- package/skills/verify-before-code/references/HALLUCINATION-PATTERNS.ja.md +121 -0
- package/skills/verify-before-code/references/HALLUCINATION-PATTERNS.md +166 -0
- package/skills/verify-before-code/references/HALLUCINATION-PATTERNS.zh.md +145 -0
- package/skills/verify-before-code/references/PROMPT-POOL.ja.md +248 -0
- package/skills/verify-before-code/references/PROMPT-POOL.md +282 -0
- package/skills/verify-before-code/references/PROMPT-POOL.zh.md +252 -0
- package/skills/verify-before-code/references/SDD-CONTRACT.ja.md +61 -0
- package/skills/verify-before-code/references/SDD-CONTRACT.md +66 -0
- package/skills/verify-before-code/references/SDD-CONTRACT.zh.md +58 -0
- package/skills/verify-before-code/references/VENDOR-SOLUTIONS.ja.md +62 -0
- package/skills/verify-before-code/references/VENDOR-SOLUTIONS.md +62 -0
- package/skills/verify-before-code/references/VENDOR-SOLUTIONS.zh.md +54 -0
- package/skills/verify-before-code/references/VERIFICATION-CHECKLIST.ja.md +68 -0
- package/skills/verify-before-code/references/VERIFICATION-CHECKLIST.md +73 -0
- package/skills/verify-before-code/references/VERIFICATION-CHECKLIST.zh.md +68 -0
- package/skills/verify-before-code/scripts/check.ps1 +52 -0
- package/skills/verify-before-code/scripts/check.sh +44 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""AgentSeed JSON Schema validator.
|
|
2
|
+
|
|
3
|
+
Primary path: the industry-standard ``jsonschema`` library (Draft 2020-12
|
|
4
|
+
and friends) — full keyword coverage. Fallback path: a zero-dependency
|
|
5
|
+
subset validator so the plugin still runs in bare environments (the
|
|
6
|
+
``verify-before-code`` skill only relies on subset keywords).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from jsonschema import Draft202012Validator as _Validator
|
|
15
|
+
from jsonschema.exceptions import ValidationError as _ValidationError
|
|
16
|
+
from jsonschema.validators import validator_for as _validator_for
|
|
17
|
+
|
|
18
|
+
_HAS_JSONSCHEMA = True
|
|
19
|
+
except ImportError: # pragma: no cover - exercised via the bare CI job
|
|
20
|
+
_HAS_JSONSCHEMA = False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Defensive ReDoS bound: schema-supplied regexes are compiled by whichever
|
|
24
|
+
# validator runs; cap their length before compilation anywhere in the tree.
|
|
25
|
+
_MAX_PATTERN_LENGTH = 256
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _oversized_pattern(schema: dict) -> str | None:
|
|
29
|
+
stack = [schema]
|
|
30
|
+
while stack:
|
|
31
|
+
node = stack.pop()
|
|
32
|
+
if isinstance(node, dict):
|
|
33
|
+
pattern = node.get("pattern")
|
|
34
|
+
if isinstance(pattern, str) and len(pattern) > _MAX_PATTERN_LENGTH:
|
|
35
|
+
return pattern
|
|
36
|
+
stack.extend(node.values())
|
|
37
|
+
elif isinstance(node, list):
|
|
38
|
+
stack.extend(node)
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def schema_validate(instance, schema: dict) -> dict:
|
|
43
|
+
"""Validate an instance against a JSON Schema.
|
|
44
|
+
|
|
45
|
+
Uses ``jsonschema`` when installed (all Draft 2020-12 keywords, boolean
|
|
46
|
+
schemas); otherwise falls back to the built-in subset validator
|
|
47
|
+
(type incl. type arrays / enum / const / minLength / maxLength /
|
|
48
|
+
pattern / minItems / maxItems / items / properties / required /
|
|
49
|
+
additionalProperties).
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
{"valid": bool, "errors": [human-readable messages],
|
|
53
|
+
"validator": "jsonschema" | "builtin-subset"}
|
|
54
|
+
"""
|
|
55
|
+
if not isinstance(schema, dict):
|
|
56
|
+
return {
|
|
57
|
+
"valid": False,
|
|
58
|
+
"errors": ["schema must be a JSON object"],
|
|
59
|
+
"validator": "builtin-subset" if not _HAS_JSONSCHEMA else "jsonschema",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
bad_pattern = _oversized_pattern(schema)
|
|
63
|
+
if bad_pattern is not None:
|
|
64
|
+
return {
|
|
65
|
+
"valid": False,
|
|
66
|
+
"errors": [
|
|
67
|
+
f"schema pattern rejected: longer than {_MAX_PATTERN_LENGTH} "
|
|
68
|
+
"chars (defensive ReDoS bound)"
|
|
69
|
+
],
|
|
70
|
+
"validator": "builtin-subset" if not _HAS_JSONSCHEMA else "jsonschema",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if _HAS_JSONSCHEMA:
|
|
74
|
+
try:
|
|
75
|
+
cls = _validator_for(schema, default=_Validator)
|
|
76
|
+
validator = cls(schema)
|
|
77
|
+
errors = sorted(validator.iter_errors(instance), key=lambda e: e.path)
|
|
78
|
+
return {
|
|
79
|
+
"valid": not errors,
|
|
80
|
+
"errors": [e.message for e in errors],
|
|
81
|
+
"validator": "jsonschema",
|
|
82
|
+
}
|
|
83
|
+
except _ValidationError as exc:
|
|
84
|
+
return {"valid": False, "errors": [exc.message], "validator": "jsonschema"}
|
|
85
|
+
except Exception: # noqa: BLE001
|
|
86
|
+
# Draft 2020-12 rejects some valid draft-07 schemas (e.g. tuple
|
|
87
|
+
# "items" arrays) — sometimes as SchemaError, sometimes as raw
|
|
88
|
+
# AttributeErrors mid-walk depending on library version. Degrade
|
|
89
|
+
# to the builtin subset (which understands those keywords) instead
|
|
90
|
+
# of reporting an opaque crash.
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
errors: list[str] = []
|
|
94
|
+
try:
|
|
95
|
+
_validate_subset(instance, schema, "$", errors)
|
|
96
|
+
except Exception as exc: # noqa: BLE001
|
|
97
|
+
return {
|
|
98
|
+
"valid": False,
|
|
99
|
+
"errors": [f"validation crashed: {exc}"],
|
|
100
|
+
"validator": "builtin-subset",
|
|
101
|
+
}
|
|
102
|
+
return {"valid": len(errors) == 0, "errors": errors, "validator": "builtin-subset"}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# Zero-dependency fallback (subset)
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _json_equal(a, b) -> bool:
|
|
111
|
+
"""JSON-Schema equality: booleans never equal numbers (True != 1)."""
|
|
112
|
+
if isinstance(a, bool) or isinstance(b, bool):
|
|
113
|
+
return isinstance(a, bool) and isinstance(b, bool) and a is b
|
|
114
|
+
return a == b
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _schema_type_ok(value, expected: str) -> bool:
|
|
118
|
+
if expected == "object":
|
|
119
|
+
return isinstance(value, dict)
|
|
120
|
+
if expected == "array":
|
|
121
|
+
return isinstance(value, list)
|
|
122
|
+
if expected == "string":
|
|
123
|
+
return isinstance(value, str)
|
|
124
|
+
if expected == "integer":
|
|
125
|
+
return isinstance(value, int) and not isinstance(value, bool)
|
|
126
|
+
if expected == "number":
|
|
127
|
+
return isinstance(value, (int, float)) and not isinstance(value, bool)
|
|
128
|
+
if expected == "boolean":
|
|
129
|
+
return isinstance(value, bool)
|
|
130
|
+
if expected == "null":
|
|
131
|
+
return value is None
|
|
132
|
+
return True
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _validate_subset(instance, schema: dict, path: str, errors: list[str]) -> None:
|
|
136
|
+
type_spec = schema.get("type")
|
|
137
|
+
if type_spec is not None:
|
|
138
|
+
if isinstance(type_spec, list):
|
|
139
|
+
if not any(_schema_type_ok(instance, t) for t in type_spec):
|
|
140
|
+
errors.append(
|
|
141
|
+
f"{path}: expected type in {type_spec}, got {type(instance).__name__}"
|
|
142
|
+
)
|
|
143
|
+
return
|
|
144
|
+
elif not _schema_type_ok(instance, type_spec):
|
|
145
|
+
errors.append(f"{path}: expected type {type_spec}, got {type(instance).__name__}")
|
|
146
|
+
return
|
|
147
|
+
if "enum" in schema and not any(_json_equal(instance, v) for v in schema["enum"]):
|
|
148
|
+
errors.append(f"{path}: value not in enum {schema['enum']}")
|
|
149
|
+
if "const" in schema and not _json_equal(instance, schema["const"]):
|
|
150
|
+
errors.append(f"{path}: expected const {schema['const']!r}")
|
|
151
|
+
if isinstance(instance, str):
|
|
152
|
+
if "minLength" in schema and len(instance) < schema["minLength"]:
|
|
153
|
+
errors.append(f"{path}: shorter than minLength {schema['minLength']}")
|
|
154
|
+
if "maxLength" in schema and len(instance) > schema["maxLength"]:
|
|
155
|
+
errors.append(f"{path}: longer than maxLength {schema['maxLength']}")
|
|
156
|
+
if "pattern" in schema and not re.search(schema["pattern"], instance):
|
|
157
|
+
errors.append(f"{path}: does not match pattern {schema['pattern']}")
|
|
158
|
+
if isinstance(instance, list):
|
|
159
|
+
if "minItems" in schema and len(instance) < schema["minItems"]:
|
|
160
|
+
errors.append(f"{path}: fewer than minItems {schema['minItems']}")
|
|
161
|
+
if "maxItems" in schema and len(instance) > schema["maxItems"]:
|
|
162
|
+
errors.append(f"{path}: more than maxItems {schema['maxItems']}")
|
|
163
|
+
items = schema.get("items")
|
|
164
|
+
if isinstance(items, list):
|
|
165
|
+
# draft-07 tuple form: positional schemas; extras governed by
|
|
166
|
+
# additionalItems (default: allowed).
|
|
167
|
+
for idx, sub in enumerate(items):
|
|
168
|
+
if idx < len(instance):
|
|
169
|
+
_validate_subset(instance[idx], sub, f"{path}[{idx}]", errors)
|
|
170
|
+
extra = len(instance) - len(items)
|
|
171
|
+
if extra > 0:
|
|
172
|
+
addl = schema.get("additionalItems")
|
|
173
|
+
if addl is False:
|
|
174
|
+
errors.append(f"{path}: {extra} additional item(s) not allowed")
|
|
175
|
+
elif isinstance(addl, dict):
|
|
176
|
+
for j in range(len(items), len(instance)):
|
|
177
|
+
_validate_subset(instance[j], addl, f"{path}[{j}]", errors)
|
|
178
|
+
elif "items" in schema:
|
|
179
|
+
for idx, item in enumerate(instance):
|
|
180
|
+
_validate_subset(item, schema["items"], f"{path}[{idx}]", errors)
|
|
181
|
+
if isinstance(instance, dict):
|
|
182
|
+
if "properties" in schema:
|
|
183
|
+
for prop, subschema in schema["properties"].items():
|
|
184
|
+
if prop in instance:
|
|
185
|
+
_validate_subset(instance[prop], subschema, f"{path}.{prop}", errors)
|
|
186
|
+
for req in schema.get("required", []):
|
|
187
|
+
if req not in instance:
|
|
188
|
+
errors.append(f"{path}: missing required property '{req}'")
|
|
189
|
+
if schema.get("additionalProperties") is False:
|
|
190
|
+
allowed = schema.get("properties", {})
|
|
191
|
+
for key in instance:
|
|
192
|
+
if key not in allowed:
|
|
193
|
+
errors.append(f"{path}: unexpected property '{key}'")
|