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.
Files changed (76) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/LICENSE +202 -0
  3. package/README.ja.md +320 -0
  4. package/README.md +318 -0
  5. package/README.zh.md +306 -0
  6. package/bin/cli.js +37 -0
  7. package/mcp.json +12 -0
  8. package/package.json +30 -0
  9. package/plugin.json +22 -0
  10. package/server/.agentseed/verification-log.jsonl +2 -0
  11. package/server/__pycache__/guard_cli.cpython-313.pyc +0 -0
  12. package/server/__pycache__/guard_engine.cpython-313.pyc +0 -0
  13. package/server/__pycache__/test_cli.cpython-313-pytest-9.1.1.pyc +0 -0
  14. package/server/__pycache__/test_cli.cpython-313.pyc +0 -0
  15. package/server/__pycache__/test_features.cpython-313-pytest-9.1.1.pyc +0 -0
  16. package/server/__pycache__/test_features.cpython-313.pyc +0 -0
  17. package/server/__pycache__/test_guard.cpython-313-pytest-9.1.1.pyc +0 -0
  18. package/server/__pycache__/test_guard.cpython-313.pyc +0 -0
  19. package/server/__pycache__/test_hook.cpython-313-pytest-9.1.1.pyc +0 -0
  20. package/server/__pycache__/test_hook.cpython-313.pyc +0 -0
  21. package/server/__pycache__/test_manifests.cpython-313-pytest-9.1.1.pyc +0 -0
  22. package/server/__pycache__/test_manifests.cpython-313.pyc +0 -0
  23. package/server/__pycache__/test_server.cpython-313-pytest-9.1.1.pyc +0 -0
  24. package/server/__pycache__/test_server.cpython-313.pyc +0 -0
  25. package/server/engine/__init__.py +64 -0
  26. package/server/engine/__pycache__/__init__.cpython-313.pyc +0 -0
  27. package/server/engine/__pycache__/audit.cpython-313.pyc +0 -0
  28. package/server/engine/__pycache__/config.cpython-313.pyc +0 -0
  29. package/server/engine/__pycache__/hallucination.cpython-313.pyc +0 -0
  30. package/server/engine/__pycache__/imports.cpython-313.pyc +0 -0
  31. package/server/engine/__pycache__/plugin.cpython-313.pyc +0 -0
  32. package/server/engine/__pycache__/sandbox.cpython-313.pyc +0 -0
  33. package/server/engine/__pycache__/schema.cpython-313.pyc +0 -0
  34. package/server/engine/__pycache__/symbols.cpython-313.pyc +0 -0
  35. package/server/engine/__pycache__/version.cpython-313.pyc +0 -0
  36. package/server/engine/audit.py +84 -0
  37. package/server/engine/config.py +131 -0
  38. package/server/engine/hallucination.py +254 -0
  39. package/server/engine/imports.py +136 -0
  40. package/server/engine/plugin.py +367 -0
  41. package/server/engine/sandbox.py +287 -0
  42. package/server/engine/schema.py +193 -0
  43. package/server/engine/symbols.py +984 -0
  44. package/server/engine/version.py +17 -0
  45. package/server/guard_cli.py +455 -0
  46. package/server/guard_engine.py +111 -0
  47. package/server/guard_hook.py +404 -0
  48. package/server/guard_server.py +472 -0
  49. package/server/requirements.txt +7 -0
  50. package/server/test_cli.py +132 -0
  51. package/server/test_features.py +426 -0
  52. package/server/test_guard.py +828 -0
  53. package/server/test_hook.py +331 -0
  54. package/server/test_manifests.py +70 -0
  55. package/server/test_server.py +247 -0
  56. package/skills/verify-before-code/SKILL.ja.md +116 -0
  57. package/skills/verify-before-code/SKILL.md +140 -0
  58. package/skills/verify-before-code/SKILL.zh.md +117 -0
  59. package/skills/verify-before-code/references/DEFAULT-NORMS.md +52 -0
  60. package/skills/verify-before-code/references/HALLUCINATION-PATTERNS.ja.md +121 -0
  61. package/skills/verify-before-code/references/HALLUCINATION-PATTERNS.md +166 -0
  62. package/skills/verify-before-code/references/HALLUCINATION-PATTERNS.zh.md +145 -0
  63. package/skills/verify-before-code/references/PROMPT-POOL.ja.md +248 -0
  64. package/skills/verify-before-code/references/PROMPT-POOL.md +282 -0
  65. package/skills/verify-before-code/references/PROMPT-POOL.zh.md +252 -0
  66. package/skills/verify-before-code/references/SDD-CONTRACT.ja.md +61 -0
  67. package/skills/verify-before-code/references/SDD-CONTRACT.md +66 -0
  68. package/skills/verify-before-code/references/SDD-CONTRACT.zh.md +58 -0
  69. package/skills/verify-before-code/references/VENDOR-SOLUTIONS.ja.md +62 -0
  70. package/skills/verify-before-code/references/VENDOR-SOLUTIONS.md +62 -0
  71. package/skills/verify-before-code/references/VENDOR-SOLUTIONS.zh.md +54 -0
  72. package/skills/verify-before-code/references/VERIFICATION-CHECKLIST.ja.md +68 -0
  73. package/skills/verify-before-code/references/VERIFICATION-CHECKLIST.md +73 -0
  74. package/skills/verify-before-code/references/VERIFICATION-CHECKLIST.zh.md +68 -0
  75. package/skills/verify-before-code/scripts/check.ps1 +52 -0
  76. 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}'")