its-magic 0.1.3-0 → 0.1.3-2
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/installer.ps1 +8 -0
- package/installer.py +8 -0
- package/installer.sh +1 -0
- package/package.json +1 -1
- package/scripts/check_intake_template_parity.py +62 -0
- package/template/.cursor/agents/curator.mdc +1 -0
- package/template/.cursor/agents/po.mdc +1 -0
- package/template/.cursor/agents/release.mdc +1 -0
- package/template/.cursor/commands/auto.md +90 -0
- package/template/.cursor/commands/execute.md +26 -0
- package/template/.cursor/commands/refresh-context.md +32 -0
- package/template/.cursor/commands/sovereign-critic.md +104 -0
- package/template/.cursor/model-catalog.local.example.cursor-only.json +9 -0
- package/template/.cursor/model-catalog.local.example.json +9 -0
- package/template/.cursor/model-catalog.local.example.level-1-easy.json +9 -0
- package/template/.cursor/model-catalog.local.example.level-2-complex.json +9 -0
- package/template/.cursor/model-catalog.local.example.level-3-mega.json +9 -0
- package/template/.cursor/model-catalog.local.example.level-4-super.json +9 -0
- package/template/.cursor/model-catalog.local.example.role-based-balanced.json +18 -0
- package/template/.cursor/model-catalog.local.example.role-based-highend.json +18 -0
- package/template/.cursor/rules/sovereign-role-manifest.mdc.example +27 -0
- package/template/.cursor/scratchpad.local.example.md +55 -0
- package/template/.cursor/scratchpad.md +203 -0
- package/template/.cursor/sovereign-role-manifest.yaml.example +53 -0
- package/template/decisions/DEC-0104.md +279 -0
- package/template/decisions/DEC-0105.md +231 -0
- package/template/decisions/DEC-0107.md +246 -0
- package/template/docs/engineering/architecture.md +78 -0
- package/template/docs/engineering/auto-orchestration-reference.md +7 -0
- package/template/docs/engineering/context/installer-owned-paths.manifest +8 -0
- package/template/docs/engineering/reason_codes.md +411 -0
- package/template/docs/engineering/runbook.md +1119 -0
- package/template/docs/engineering/sovereign-memory/.gitkeep +0 -0
- package/template/docs/engineering/sovereign-memory/retrospectives/.gitkeep +0 -0
- package/template/handoffs/sovereign_decisions/.gitkeep +0 -0
- package/template/handoffs/sovereign_deferrals/.gitkeep +0 -0
- package/template/handoffs/sovereign_role_reviews.jsonl +0 -0
- package/template/scripts/__pycache__/decision_ledger_lib.cpython-312.pyc +0 -0
- package/template/scripts/check_intake_template_parity.py +181 -0
- package/template/scripts/decision_ledger_lib.py +732 -0
- package/template/scripts/ledger_validate.py +153 -0
- package/template/scripts/model_tier_lib.py +680 -0
- package/template/scripts/model_tier_validate.py +368 -0
- package/template/scripts/parallel_dev_arbiter.py +923 -0
- package/template/scripts/release_trigger_adapters.py +843 -0
- package/template/scripts/self_healing_deploy_lib.py +463 -0
- package/template/scripts/self_healing_deploy_validate.py +78 -0
- package/template/scripts/sovereign_convergence_lib.py +994 -0
- package/template/scripts/sovereign_convergence_validate.py +206 -0
- package/template/scripts/sovereign_critic_lib.py +629 -0
- package/template/scripts/sovereign_critic_validate.py +131 -0
- package/template/scripts/sovereign_loop_lib.py +828 -0
- package/template/scripts/sovereign_loop_validate.py +122 -0
- package/template/scripts/sovereign_memory_lib.py +869 -0
- package/template/scripts/sovereign_memory_validate.py +153 -0
- package/template/scripts/sovereign_role_manifest_lib.py +547 -0
- package/template/scripts/sovereign_role_manifest_validate.py +105 -0
- package/template/tests/us0108_contract_test.py +207 -0
- package/template/tests/us0109_contract_test.py +209 -0
- package/template/tests/us0109_us0110_compose_test.py +34 -0
- package/template/tests/us0111_contract_test.py +345 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Model tier validator CLI (US-0101 / DEC-0086; US-0102 / DEC-0087).
|
|
4
|
+
|
|
5
|
+
Validates:
|
|
6
|
+
- Tier enum values (cheap|balanced|strong)
|
|
7
|
+
- Catalog schema (v1 + v2)
|
|
8
|
+
- Direct MODEL_<PHASE> slug keys
|
|
9
|
+
- Phase key spelling (canonical phase IDs)
|
|
10
|
+
- Forbidden vendor slugs in template agents
|
|
11
|
+
|
|
12
|
+
Exit codes:
|
|
13
|
+
- 0: All validations passed
|
|
14
|
+
- 1: Validation failed (see stderr for details)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import re
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Dict, List, Tuple
|
|
23
|
+
|
|
24
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
25
|
+
from model_tier_lib import (
|
|
26
|
+
CANONICAL_PHASE_IDS,
|
|
27
|
+
CATALOG_ROLE_KEYS,
|
|
28
|
+
DEFAULT_PHASE_TIER_MATRIX,
|
|
29
|
+
PRECEDENCE_CHAIN_STEPS,
|
|
30
|
+
ReasonCode,
|
|
31
|
+
Tier,
|
|
32
|
+
catalog_validation_reason_code,
|
|
33
|
+
phase_to_model_key,
|
|
34
|
+
resolve_model_for_phase,
|
|
35
|
+
validate_catalog_schema,
|
|
36
|
+
validate_direct_slug,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Reason codes used for fail-closed reporting (DEC-0086 §3 + DEC-0087 §8):
|
|
40
|
+
# - MODEL_TIER_INVALID: unknown tier value
|
|
41
|
+
# - MODEL_CATALOG_INVALID: malformed catalog JSON (v1)
|
|
42
|
+
# - MODEL_SLUG_UNKNOWN: tier key missing from catalog
|
|
43
|
+
# - MODEL_RESOLVE_FALLBACK: catalog lookup failed, using fallback
|
|
44
|
+
# - MODEL_OVERRIDE_SLUG_UNKNOWN: direct slug validation failure
|
|
45
|
+
# - MODEL_ROLE_SLUG_UNKNOWN: role catalog lookup miss
|
|
46
|
+
# - MODEL_CATALOG_SCHEMA_V2_INVALID: v2 schema validation failure
|
|
47
|
+
REASON_CODES = list(ReasonCode)
|
|
48
|
+
|
|
49
|
+
FORBIDDEN_SLUG_PATTERNS = [
|
|
50
|
+
r"composer-",
|
|
51
|
+
r"claude-",
|
|
52
|
+
r"gpt-",
|
|
53
|
+
r"opus-",
|
|
54
|
+
r"glm-",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
CANONICAL_PHASE_ID_SET = set(CANONICAL_PHASE_IDS)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def validate_tier_enum(tier_value: str) -> Tuple[bool, str]:
|
|
61
|
+
"""Validate tier enum value."""
|
|
62
|
+
try:
|
|
63
|
+
Tier(tier_value)
|
|
64
|
+
return True, ""
|
|
65
|
+
except ValueError:
|
|
66
|
+
return False, f"Invalid tier value: {tier_value} (expected: cheap|balanced|strong)"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def validate_phase_key(phase: str) -> Tuple[bool, str]:
|
|
70
|
+
"""Validate phase key spelling."""
|
|
71
|
+
if phase not in CANONICAL_PHASE_ID_SET:
|
|
72
|
+
return False, (
|
|
73
|
+
f"Unknown phase ID: {phase} "
|
|
74
|
+
f"(canonical: {', '.join(sorted(CANONICAL_PHASE_ID_SET))})"
|
|
75
|
+
)
|
|
76
|
+
return True, ""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def check_forbidden_slugs_in_file(file_path: Path) -> List[str]:
|
|
80
|
+
"""Check for forbidden vendor slugs in a file."""
|
|
81
|
+
violations = []
|
|
82
|
+
if not file_path.exists():
|
|
83
|
+
return violations
|
|
84
|
+
|
|
85
|
+
content = file_path.read_text(encoding="utf-8")
|
|
86
|
+
lines = content.split("\n")
|
|
87
|
+
|
|
88
|
+
for line_num, line in enumerate(lines, start=1):
|
|
89
|
+
for pattern in FORBIDDEN_SLUG_PATTERNS:
|
|
90
|
+
if re.search(pattern, line, re.IGNORECASE):
|
|
91
|
+
violations.append(
|
|
92
|
+
f"{file_path}:{line_num}: forbidden slug pattern '{pattern}' found: {line.strip()}"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return violations
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def check_template_agents(repo_root: Path) -> List[str]:
|
|
99
|
+
"""Check template/.cursor/agents/*.mdc for forbidden slugs."""
|
|
100
|
+
violations = []
|
|
101
|
+
agents_dir = repo_root / "template" / ".cursor" / "agents"
|
|
102
|
+
|
|
103
|
+
if not agents_dir.exists():
|
|
104
|
+
return violations
|
|
105
|
+
|
|
106
|
+
for agent_file in agents_dir.glob("*.mdc"):
|
|
107
|
+
violations.extend(check_forbidden_slugs_in_file(agent_file))
|
|
108
|
+
|
|
109
|
+
return violations
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def parse_scratchpad_keys(scratchpad_path: Path) -> Dict[str, str]:
|
|
113
|
+
"""Parse key=value lines from scratchpad (skip comments)."""
|
|
114
|
+
result: Dict[str, str] = {}
|
|
115
|
+
if not scratchpad_path.exists():
|
|
116
|
+
return result
|
|
117
|
+
for line in scratchpad_path.read_text(encoding="utf-8").split("\n"):
|
|
118
|
+
stripped = line.strip()
|
|
119
|
+
if not stripped or stripped.startswith("#"):
|
|
120
|
+
continue
|
|
121
|
+
if "=" in stripped:
|
|
122
|
+
key, value = stripped.split("=", 1)
|
|
123
|
+
result[key.strip()] = value.strip()
|
|
124
|
+
return result
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def validate_catalog(catalog_path: Path) -> Tuple[bool, List[str], ReasonCode]:
|
|
128
|
+
"""Validate catalog schema and return list of errors."""
|
|
129
|
+
errors: List[str] = []
|
|
130
|
+
|
|
131
|
+
if not catalog_path.exists():
|
|
132
|
+
errors.append(f"Catalog file not found: {catalog_path}")
|
|
133
|
+
return False, errors, ReasonCode.MODEL_CATALOG_INVALID
|
|
134
|
+
|
|
135
|
+
is_valid, error_msg = validate_catalog_schema(catalog_path)
|
|
136
|
+
if not is_valid:
|
|
137
|
+
schema_version = None
|
|
138
|
+
try:
|
|
139
|
+
with open(catalog_path, "r", encoding="utf-8") as f:
|
|
140
|
+
data = json.load(f)
|
|
141
|
+
schema_version = data.get("schema_version")
|
|
142
|
+
except (json.JSONDecodeError, OSError):
|
|
143
|
+
pass
|
|
144
|
+
code = catalog_validation_reason_code(
|
|
145
|
+
error_msg,
|
|
146
|
+
{"schema_version": schema_version} if schema_version else None,
|
|
147
|
+
)
|
|
148
|
+
errors.append(error_msg or "Catalog validation failed")
|
|
149
|
+
return False, errors, code
|
|
150
|
+
|
|
151
|
+
with open(catalog_path, "r", encoding="utf-8") as f:
|
|
152
|
+
catalog = json.load(f)
|
|
153
|
+
|
|
154
|
+
for tier_name, slug in catalog["tiers"].items():
|
|
155
|
+
if not slug.strip():
|
|
156
|
+
errors.append(f"Tier '{tier_name}' has empty slug")
|
|
157
|
+
|
|
158
|
+
if catalog.get("schema_version") == 2 and "roles" in catalog:
|
|
159
|
+
for role_name in CATALOG_ROLE_KEYS:
|
|
160
|
+
if role_name not in catalog["roles"]:
|
|
161
|
+
errors.append(f"Missing role key: {role_name}")
|
|
162
|
+
elif not catalog["roles"][role_name].strip():
|
|
163
|
+
errors.append(f"Role '{role_name}' has empty slug")
|
|
164
|
+
|
|
165
|
+
code = (
|
|
166
|
+
ReasonCode.MODEL_CATALOG_SCHEMA_V2_INVALID
|
|
167
|
+
if catalog.get("schema_version") == 2
|
|
168
|
+
else ReasonCode.MODEL_CATALOG_INVALID
|
|
169
|
+
)
|
|
170
|
+
return len(errors) == 0, errors, code
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def validate_scratchpad_tiers(scratchpad_path: Path) -> Tuple[bool, List[str]]:
|
|
174
|
+
"""Validate MODEL_TIER_* keys in scratchpad file."""
|
|
175
|
+
errors: List[str] = []
|
|
176
|
+
|
|
177
|
+
if not scratchpad_path.exists():
|
|
178
|
+
return True, errors
|
|
179
|
+
|
|
180
|
+
content = scratchpad_path.read_text(encoding="utf-8")
|
|
181
|
+
|
|
182
|
+
for line in content.split("\n"):
|
|
183
|
+
line = line.strip()
|
|
184
|
+
if line.startswith("MODEL_TIER_") and "=" in line:
|
|
185
|
+
key, value = line.split("=", 1)
|
|
186
|
+
value = value.strip()
|
|
187
|
+
|
|
188
|
+
if key.startswith("#"):
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
if value and not value.startswith("<"):
|
|
192
|
+
is_valid, error = validate_tier_enum(value)
|
|
193
|
+
if not is_valid:
|
|
194
|
+
errors.append(f"{key}={value}: {error}")
|
|
195
|
+
|
|
196
|
+
return len(errors) == 0, errors
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def validate_scratchpad_direct_slugs(scratchpad_path: Path, catalog: dict | None) -> Tuple[bool, List[str]]:
|
|
200
|
+
"""Validate MODEL_<PHASE> direct override keys."""
|
|
201
|
+
errors: List[str] = []
|
|
202
|
+
pad = parse_scratchpad_keys(scratchpad_path)
|
|
203
|
+
model_resolve = pad.get("MODEL_RESOLVE", "alias_only")
|
|
204
|
+
|
|
205
|
+
for phase_id in CANONICAL_PHASE_ID_SET:
|
|
206
|
+
key = phase_to_model_key(phase_id)
|
|
207
|
+
if key in pad and pad[key].strip() and not pad[key].startswith("<"):
|
|
208
|
+
slug = pad[key].strip()
|
|
209
|
+
is_valid, error = validate_direct_slug(slug, model_resolve, catalog)
|
|
210
|
+
if not is_valid:
|
|
211
|
+
errors.append(f"{key}={slug}: {error} [{ReasonCode.MODEL_OVERRIDE_SLUG_UNKNOWN.value}]")
|
|
212
|
+
|
|
213
|
+
return len(errors) == 0, errors
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def run_precedence_self_test() -> List[str]:
|
|
217
|
+
"""Precedence self-test hook (DEC-0087)."""
|
|
218
|
+
errors: List[str] = []
|
|
219
|
+
|
|
220
|
+
if len(PRECEDENCE_CHAIN_STEPS) != 5:
|
|
221
|
+
errors.append("PRECEDENCE_CHAIN_STEPS must have exactly 5 steps")
|
|
222
|
+
|
|
223
|
+
# Step 1 wins over tier
|
|
224
|
+
pad = {"MODEL_EXECUTE": "<test-slug>", "MODEL_TIER_EXECUTE": "cheap", "MODEL_RESOLVE": "alias_only"}
|
|
225
|
+
result = resolve_model_for_phase("execute", pad)
|
|
226
|
+
if not result.success or result.slug != "<test-slug>":
|
|
227
|
+
errors.append("Precedence self-test: MODEL_EXECUTE should win step 1")
|
|
228
|
+
|
|
229
|
+
# Tier-only backward compat: execute → strong → omit alias
|
|
230
|
+
result = resolve_model_for_phase("execute", {"MODEL_RESOLVE": "alias_only"})
|
|
231
|
+
if not result.success or result.tier != Tier.STRONG or result.alias is not None:
|
|
232
|
+
errors.append("Precedence self-test: tier-only execute should resolve to strong/omit")
|
|
233
|
+
|
|
234
|
+
return errors
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def main():
|
|
238
|
+
parser = argparse.ArgumentParser(
|
|
239
|
+
description="Model tier validator (US-0101 / US-0102)",
|
|
240
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
241
|
+
epilog="""
|
|
242
|
+
Examples:
|
|
243
|
+
python scripts/model_tier_validate.py --repo .
|
|
244
|
+
python scripts/model_tier_validate.py --catalog .cursor/model-catalog.local.json
|
|
245
|
+
python scripts/model_tier_validate.py --check-template-agents
|
|
246
|
+
python scripts/model_tier_validate.py --enforce
|
|
247
|
+
""",
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
parser.add_argument("--repo", type=Path, help="Repository root (default: current directory)")
|
|
251
|
+
parser.add_argument("--catalog", type=Path, help="Path to local catalog")
|
|
252
|
+
parser.add_argument("--scratchpad", type=Path, help="Path to scratchpad file")
|
|
253
|
+
parser.add_argument("--check-template-agents", action="store_true", help="Check template agents for forbidden slugs")
|
|
254
|
+
parser.add_argument("--self-test", action="store_true", help="Run self-test (validate library contract)")
|
|
255
|
+
parser.add_argument("--enforce", action="store_true", help="Exit non-zero on any fail-closed code")
|
|
256
|
+
|
|
257
|
+
args = parser.parse_args()
|
|
258
|
+
|
|
259
|
+
repo_root = (args.repo or Path.cwd()).resolve()
|
|
260
|
+
all_errors: List[str] = []
|
|
261
|
+
|
|
262
|
+
if args.self_test:
|
|
263
|
+
print("[SELF-TEST] Validating model_tier_lib contract...")
|
|
264
|
+
|
|
265
|
+
for tier in Tier:
|
|
266
|
+
is_valid, error = validate_tier_enum(tier.value)
|
|
267
|
+
if not is_valid:
|
|
268
|
+
all_errors.append(f"Self-test failed: {error}")
|
|
269
|
+
|
|
270
|
+
for phase in CANONICAL_PHASE_ID_SET:
|
|
271
|
+
is_valid, error = validate_phase_key(phase)
|
|
272
|
+
if not is_valid:
|
|
273
|
+
all_errors.append(f"Self-test failed: {error}")
|
|
274
|
+
|
|
275
|
+
test_content = "model: composer-1"
|
|
276
|
+
for pattern in FORBIDDEN_SLUG_PATTERNS:
|
|
277
|
+
if not re.search(pattern, test_content, re.IGNORECASE):
|
|
278
|
+
all_errors.append(f"Self-test failed: pattern '{pattern}' not matching test content")
|
|
279
|
+
|
|
280
|
+
all_errors.extend(run_precedence_self_test())
|
|
281
|
+
|
|
282
|
+
for code in (
|
|
283
|
+
ReasonCode.MODEL_OVERRIDE_SLUG_UNKNOWN,
|
|
284
|
+
ReasonCode.MODEL_ROLE_SLUG_UNKNOWN,
|
|
285
|
+
ReasonCode.MODEL_CATALOG_SCHEMA_V2_INVALID,
|
|
286
|
+
):
|
|
287
|
+
if code not in ReasonCode:
|
|
288
|
+
all_errors.append(f"Self-test failed: missing reason code {code.value}")
|
|
289
|
+
|
|
290
|
+
if all_errors:
|
|
291
|
+
print("[SELF_TEST_FAILED]")
|
|
292
|
+
for error in all_errors:
|
|
293
|
+
print(f" {error}", file=sys.stderr)
|
|
294
|
+
sys.exit(1)
|
|
295
|
+
else:
|
|
296
|
+
print("[DEV_ENVIRONMENT_SELF_TEST_OK]")
|
|
297
|
+
sys.exit(0)
|
|
298
|
+
|
|
299
|
+
catalog_dict = None
|
|
300
|
+
|
|
301
|
+
if args.catalog:
|
|
302
|
+
print(f"[CATALOG] Validating {args.catalog}...")
|
|
303
|
+
is_valid, errors, code = validate_catalog(args.catalog)
|
|
304
|
+
if not is_valid:
|
|
305
|
+
all_errors.extend(f"[{code.value}] {e}" for e in errors)
|
|
306
|
+
print(f"[CATALOG_INVALID] {args.catalog}", file=sys.stderr)
|
|
307
|
+
for error in errors:
|
|
308
|
+
print(f" [{code.value}] {error}", file=sys.stderr)
|
|
309
|
+
else:
|
|
310
|
+
with open(args.catalog, "r", encoding="utf-8") as f:
|
|
311
|
+
catalog_dict = json.load(f)
|
|
312
|
+
|
|
313
|
+
if args.scratchpad:
|
|
314
|
+
print(f"[SCRATCHPAD] Validating {args.scratchpad}...")
|
|
315
|
+
is_valid, errors = validate_scratchpad_tiers(args.scratchpad)
|
|
316
|
+
if not is_valid:
|
|
317
|
+
all_errors.extend(errors)
|
|
318
|
+
is_valid, errors = validate_scratchpad_direct_slugs(args.scratchpad, catalog_dict)
|
|
319
|
+
if not is_valid:
|
|
320
|
+
all_errors.extend(errors)
|
|
321
|
+
|
|
322
|
+
if args.check_template_agents:
|
|
323
|
+
print(f"[TEMPLATE] Checking {repo_root / 'template' / '.cursor' / 'agents'}...")
|
|
324
|
+
violations = check_template_agents(repo_root)
|
|
325
|
+
if violations:
|
|
326
|
+
all_errors.extend(violations)
|
|
327
|
+
|
|
328
|
+
if not args.catalog and not args.scratchpad and not args.check_template_agents:
|
|
329
|
+
print(f"[REPO] Validating {repo_root}...")
|
|
330
|
+
|
|
331
|
+
for example_name in (
|
|
332
|
+
"model-catalog.local.example.json",
|
|
333
|
+
"model-catalog.local.example.role-based-balanced.json",
|
|
334
|
+
"model-catalog.local.example.role-based-highend.json",
|
|
335
|
+
):
|
|
336
|
+
catalog_example = repo_root / ".cursor" / example_name
|
|
337
|
+
if catalog_example.exists():
|
|
338
|
+
print(f"[CATALOG] Validating {catalog_example}...")
|
|
339
|
+
is_valid, errors, code = validate_catalog(catalog_example)
|
|
340
|
+
if not is_valid:
|
|
341
|
+
all_errors.extend(f"[{code.value}] {e}" for e in errors)
|
|
342
|
+
|
|
343
|
+
scratchpad = repo_root / ".cursor" / "scratchpad.md"
|
|
344
|
+
if scratchpad.exists():
|
|
345
|
+
print(f"[SCRATCHPAD] Validating {scratchpad}...")
|
|
346
|
+
is_valid, errors = validate_scratchpad_tiers(scratchpad)
|
|
347
|
+
if not is_valid:
|
|
348
|
+
all_errors.extend(errors)
|
|
349
|
+
|
|
350
|
+
print(f"[TEMPLATE] Checking {repo_root / 'template' / '.cursor' / 'agents'}...")
|
|
351
|
+
violations = check_template_agents(repo_root)
|
|
352
|
+
if violations:
|
|
353
|
+
all_errors.extend(violations)
|
|
354
|
+
|
|
355
|
+
all_errors.extend(run_precedence_self_test())
|
|
356
|
+
|
|
357
|
+
if all_errors:
|
|
358
|
+
print(f"\n[MODEL_TIER_VALIDATION_FAILED] {len(all_errors)} error(s)", file=sys.stderr)
|
|
359
|
+
for error in all_errors:
|
|
360
|
+
print(f" {error}", file=sys.stderr)
|
|
361
|
+
sys.exit(1 if args.enforce or True else 0)
|
|
362
|
+
else:
|
|
363
|
+
print("\n[MODEL_TIER_VALIDATION_OK]")
|
|
364
|
+
sys.exit(0)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
if __name__ == "__main__":
|
|
368
|
+
main()
|