Add Nexa BaZi naming contract checker

This commit is contained in:
zfusx
2026-06-03 22:24:07 +08:00
parent 926acb0fe8
commit cd10c418cf
7 changed files with 515 additions and 5 deletions
+1
View File
@@ -2,3 +2,4 @@ __pycache__/
*.py[cod]
.DS_Store
*.pdf
nexa_bazi-main/
+63 -1
View File
@@ -10,6 +10,8 @@ The skill is simplified-Chinese-first:
The source PDF is not included in this repository.
This repository also includes a Nexa BaZi contract checker for programmers. It catches mixed API fields, JSON schema names, prompt variables, and module keys such as `Day_Master`, `DayMaster_Stem`, `Dominant_Deity`, and `Primary_Favorite_Element`.
## Install
Clone the repository and copy it into Codex skills:
@@ -44,7 +46,13 @@ Use $bazi-translation to translate these terms into approved English and code na
Use $bazi-translation to review API field names in packages/bazi.
```
## Use The Checker
For Nexa:
```text
Use $bazi-translation to review Nexa BaZi API fields, prompt variables, and module keys in nexa_bazi-main.
```
## Use The Generic Checker
Run the local checker against files or directories:
@@ -72,6 +80,47 @@ src/chart.ts:42: dayOwner -> dayMaster
line: const dayOwner = chart.dayOwner;
```
## Use The Nexa Contract Checker
Run the Nexa-specific checker against code, JSON, prompt files, or docs:
```bash
python ~/.codex/skills/bazi-translation/scripts/check_nexa_bazi_contract.py <path>
```
Migration planning mode:
```bash
python ~/.codex/skills/bazi-translation/scripts/check_nexa_bazi_contract.py <path> --no-fail
```
CI-style blocking mode, reporting only hard errors:
```bash
python ~/.codex/skills/bazi-translation/scripts/check_nexa_bazi_contract.py <path> --min-severity error
```
List loaded Nexa rules:
```bash
python ~/.codex/skills/bazi-translation/scripts/check_nexa_bazi_contract.py --list-rules
```
Example output:
```text
5 八字详解/5.7 命理符号/2.命理符号-算法与判定规则.md:14: DayMaster_Stem -> day_master_stem [error]
concept: 日主天干
reason: mixed case 不适合接口契约
line: DayMaster_Stem: "辛"
```
Severity handling:
- `error`: do not add in new code; rename or add a compatibility migration.
- `warn`: likely wrong for new BaZi fields; check whether it is already published.
- `review`: not automatically unsafe, but it may show cross-system leakage or a published key that needs migration planning.
## Core Naming Rules
- Use `dayMaster`, not `dayOwner` or `dayLord`.
@@ -81,11 +130,24 @@ src/chart.ts:42: dayOwner -> dayMaster
- Use `usefulGod`, not `useGod`.
- Use `favorableElement` in code by default; keep `Favourable Element` for book-style display text.
- Keep `Useful God` separate from `Favourable Element`.
- For Nexa JSON/API/prompt fields, use `snake_case`: `day_master`, `day_master_stem`, `dominant_ten_god`, `primary_favorite_element`.
- Do not use `deity` for Ten Gods in interfaces. Use `ten_god`.
- Do not use astrology terms such as `astral_event` for BaZi branch relationships or daily BaZi events.
## Nexa Contract Files
- `references/nexa-bazi-contract.md`: canonical Nexa BaZi API/JSON/prompt naming contract.
- `references/nexa-naming-rules.md`: practical naming rules and migration guidance.
- `references/nexa-forbidden-names.md`: deterministic bad-name table used by the checker.
## Files
- `SKILL.md`: Codex skill trigger and workflow.
- `references/terms.md`: simplified-Chinese-first dictionary.
- `references/naming-rules.md`: review rules and anti-patterns.
- `references/nexa-bazi-contract.md`: Nexa BaZi interface and prompt-variable naming contract.
- `references/nexa-naming-rules.md`: Nexa-specific naming guidance.
- `references/nexa-forbidden-names.md`: Nexa forbidden/legacy name table.
- `references/pending-terms.md`: staging area for terms needing domain review.
- `scripts/check_bazi_names.py`: source-code naming checker.
- `scripts/check_nexa_bazi_contract.py`: Nexa BaZi API/JSON/prompt naming checker.
+18 -4
View File
@@ -1,13 +1,13 @@
---
name: bazi-translation
description: Simplified-Chinese-first BaZi/Four Pillars terminology dictionary and code naming review workflow. Use when translating Chinese BaZi, Four Pillars, Chinese metaphysics, destiny analysis, Heavenly Stem/Earthly Branch, Ten Gods, Five Factors, luck cycle, or favorable-element terms into English; naming code identifiers, classes, files, API fields, database columns, UI labels, or documentation; or reviewing code for inconsistent or literal translations such as dayOwner, skyStem, groundBranch, fortuneColumn, useGod, or mixed BaZi vocabulary.
description: Simplified-Chinese-first BaZi/Four Pillars terminology dictionary and deterministic code/interface naming review workflow, including a Nexa BaZi system contract. Use when translating Chinese BaZi, Four Pillars, Chinese metaphysics, destiny analysis, Heavenly Stem/Earthly Branch, Ten Gods, Five Factors, luck cycle, or favorable-element terms into English; naming code identifiers, classes, files, API fields, database columns, JSON schema fields, prompt variables, UI labels, or documentation; or reviewing code for inconsistent or literal translations such as dayOwner, skyStem, groundBranch, fortuneColumn, useGod, Day_Master, Dominant_Deity, Primary_Favorite_Element, or mixed BaZi vocabulary.
---
# BaZi Translation
## Overview
Use this skill to keep BaZi terminology readable and consistent across code, UI labels, comments, docs, schemas, and tests.
Use this skill to keep BaZi terminology readable and consistent across code, UI labels, comments, docs, schemas, prompt variables, and tests.
The source of truth is simplified Chinese first. Use the approved English display term for user-facing text, and use the approved ASCII code name for identifiers unless an existing project convention is stronger.
@@ -23,8 +23,16 @@ python ~/.codex/skills/bazi-translation/scripts/check_bazi_names.py <path>
Use `--no-fail` for exploratory review and `--list-approved` to print approved code names.
4. Report naming findings as concrete replacements: current name, approved name, reason, and file/line when available.
5. When a concept is not in `terms.md`, do not invent a final translation. Add or propose a row in `references/pending-terms.md` with context and a provisional English/code name.
4. For Nexa BaZi code, APIs, JSON schemas, prompt variables, and module keys, read `references/nexa-bazi-contract.md`, `references/nexa-naming-rules.md`, and `references/nexa-forbidden-names.md`, then run:
```bash
python ~/.codex/skills/bazi-translation/scripts/check_nexa_bazi_contract.py <path>
```
Use `--no-fail` during migration planning and `--min-severity error` for CI-style blocking checks.
5. Report naming findings as concrete replacements: current name, approved name, reason, severity, and file/line when available.
6. When a concept is not in `terms.md` or the Nexa contract, do not invent a final translation. Add or propose a row in `references/pending-terms.md` with context and a provisional English/code name.
## Standards
@@ -35,10 +43,16 @@ Use `--no-fail` for exploratory review and `--list-approved` to print approved c
- Keep Chinese, English display text, and code names separate. Do not force one field to serve all audiences.
- Do not conflate `用神` (`Useful God`, advanced concept) with `喜用元素` or `有利元素` (`Favourable Element`, beginner balancing element).
- Do not translate classical terms word by word when the approved table has a domain term.
- For programmer-facing Nexa contracts, prefer deterministic rules over vector/semantic matching. Vector search may help discover candidates, but it must not decide final names.
- In Nexa JSON/API fields, prefer `snake_case`; reserve PascalCase for type names and camelCase for language-local code when the host language expects it.
## Resources
- `references/terms.md`: approved simplified-Chinese-first dictionary.
- `references/naming-rules.md`: code naming rules, anti-patterns, and review checklist.
- `references/nexa-bazi-contract.md`: Nexa BaZi interface and prompt-variable naming contract.
- `references/nexa-naming-rules.md`: Nexa-specific naming rules for APIs, JSON, module keys, and prompt files.
- `references/nexa-forbidden-names.md`: deterministic legacy/bad-name table used by the Nexa checker.
- `references/pending-terms.md`: staging area for terms needing domain review.
- `scripts/check_bazi_names.py`: scans source files for forbidden or suspicious BaZi translations and suggests approved names.
- `scripts/check_nexa_bazi_contract.py`: scans Nexa code/docs for mixed interface names and suggests contract names.
+70
View File
@@ -0,0 +1,70 @@
# Nexa 八字命名契约
本文件是 Nexa 八字系统的程序员命名契约。目标不是翻译给用户看,而是让 API、JSON、prompt 变量、模块 key、测试数据和文档中的八字命理词汇保持一致。
## 适用范围
- Provider / BFF / frontend API 字段。
- JSON schema、测试 fixture、调试数据。
- Prompt 变量、Context Vault、fallback schema。
- 模块 key、SSE path、registry key。
- 代码中的函数、变量、类名和枚举名。
## 分层原则
| 层级 | 命名风格 | 示例 | 说明 |
|---|---|---|---|
| API / JSON 字段 | `snake_case` | `day_master`, `ten_god_label` | 对外和跨语言最稳定 |
| 模块 key / registry key | `snake_case` | `daily_overview`, `monthly_luck_tag` | 已发布 key 需要迁移策略 |
| Python 字段/变量 | `snake_case` | `day_master_stem` | 跟 Python 社区一致 |
| JS/TS 局部变量 | `camelCase` | `dayMasterStem` | 仅限局部代码,不用于 JSON contract |
| Type / Class / Enum | `PascalCase` | `HeavenlyStem`, `EarthlyBranch`, `TenGod` | 类型名 |
| UI 中文 | 简体中文 | `日主`, `十神`, `流日` | 面向用户 |
| 英文显示名 | 标准术语 | `Day Master`, `Ten Gods`, `Annual Luck` | 面向英文 UI/文档 |
## Nexa 推荐字段
| 中文概念 | 推荐 API/JSON 字段 | Type/Enum | 说明 |
|---|---|---|---|
| 八字命盘 | `bazi_chart` | `BaziChart` | 完整排盘结构 |
| 四柱 | `pillars` | `Pillars` | 包含 year/month/day/hour |
| 年柱 | `year_pillar` | `Pillar` | 不要写 `year_column` |
| 月柱 | `month_pillar` | `Pillar` | 不要写 `month_column` |
| 日柱 | `day_pillar` | `Pillar` | 不要写 `day_column` |
| 时柱 | `hour_pillar` | `Pillar` | 不要写 `time_column` |
| 日主 | `day_master` | `DayMaster` | 可表示完整日主,如 `辛金` |
| 日主天干 | `day_master_stem` | `HeavenlyStem` | 只表示天干,如 `辛` |
| 天干 | `heavenly_stem` | `HeavenlyStem` | JSON 字段用 snake_case |
| 地支 | `earthly_branch` | `EarthlyBranch` | JSON 字段用 snake_case |
| 藏干 | `hidden_stems` | `HiddenStem` | list 字段用复数 |
| 十神 | `ten_god` | `TenGod` | 单个十神 |
| 十神 ID | `ten_god_label` | `TenGodLabel` | 若已上线为数字 ID,可保留但要写清映射 |
| 主导十神 | `dominant_ten_god` | `TenGod` | 不要写 `dominant_deity` |
| 五行 | `element` / `five_elements` | `FiveElement` | 单个用 `element`,集合用 `five_elements` |
| 五行强度 | `element_strengths` | `ElementStrengths` | map: wood/fire/earth/metal/water |
| 喜用元素 | `favorite_element` | `FiveElement` | Nexa 内部可用 favorite;若对齐通用词表可映射为 favorable |
| 第一喜用元素 | `primary_favorite_element` | `FiveElement` | 不要写 `Primary_Favorite_Element` |
| 第二喜用元素 | `secondary_favorite_element` | `FiveElement` | 不要写 `Secondary_Favorite_Element` |
| 用神 | `useful_god` | `UsefulGod` | 高阶概念,不等同喜用元素 |
| 大运 | `luck_pillar` | `LuckPillar` | 十年运柱 |
| 流年 | `annual_luck` | `AnnualLuck` | 避免和本命年柱混淆 |
| 流月 | `monthly_luck` | `MonthlyLuck` | 月运 |
| 流日 | `daily_luck` | `DailyLuck` | 日运 |
| 流日干支 | `flow_day_ganzhi` | `StemBranch` | 若已上线可保留;新字段建议 `daily_stem_branch` |
| 合冲刑害破 | `branch_relationship` | `BranchRelationship` | 不要混用 astrology/astral 命名 |
| 合盘 | `bazi_synastry` | `BaziSynastry` | 与占星 synastry 区分时加 `bazi_` 前缀 |
## 已发布字段处理
已发布 API 或前端依赖字段不能无迁移直接改名。处理顺序:
1. 标记为 `compat`:旧字段继续读取。
2. 新增推荐字段:例如同时返回 `day_master_stem`
3. 在文档中声明旧字段 deprecated。
4. 前端/BFF 迁移完成后,再删除旧字段。
审查时不要只说“改名”。要说明影响面:API、fixture、prompt、前端 path、SSE key、数据库列、测试断言。
## 不做向量判断
本契约用于程序员纠错,必须可重复、可解释、可进入 CI。不要用向量相似度决定字段是否违规。向量搜索最多用于发现候选混名,最终仍必须落回本契约或 `nexa-forbidden-names.md`
+38
View File
@@ -0,0 +1,38 @@
# Nexa 禁用/兼容命名表
本表供 `scripts/check_nexa_bazi_contract.py` 读取。`error` 表示新代码不应出现;`warn` 表示需要迁移或人工确认;`review` 表示可能是已发布兼容字段,改动前必须查影响面。
| severity | current | preferred | concept | reason |
|---|---|---|---|---|
| error | Day_Master | day_master | 日主 | Nexa JSON/API/prompt 字段使用 snake_case |
| error | DayMaster | day_master | 日主 | JSON/API 字段不要用 PascalCase 或拼接式 camel |
| error | DayMaster_Stem | day_master_stem | 日主天干 | mixed case 不适合接口契约 |
| error | dayMasterStem | day_master_stem | 日主天干 | JSON/API 字段使用 snake_caseJS 局部变量除外 |
| error | Primary_Favorite_Element | primary_favorite_element | 第一喜用元素 | mixed case 不适合接口契约 |
| error | Secondary_Favorite_Element | secondary_favorite_element | 第二喜用元素 | mixed case 不适合接口契约 |
| error | Favorite_Element | favorite_element | 喜用元素 | mixed case 不适合接口契约 |
| error | Element_Strengths | element_strengths | 五行强度 | mixed case 不适合接口契约 |
| error | Dominant_Deity | dominant_ten_god | 主导十神 | 十神不是 deity |
| error | dominant_deity | dominant_ten_god | 主导十神 | 十神不是 deity |
| error | deity | ten_god | 十神 | 接口字段不要用 deity 表示十神 |
| error | useGod | useful_god | 用神 | 不要把 Useful God 写成动词短语 |
| error | usefulGod | useful_god | 用神 | Nexa JSON/API 字段使用 snake_case |
| error | primary_useful_god | primary_favorite_element | 喜用元素 | 喜用元素不等同用神 |
| error | dayOwner | day_master | 日主 | 日主标准术语是 Day Master |
| error | dayLord | day_master | 日主 | 日主标准术语是 Day Master |
| error | skyStem | heavenly_stem | 天干 | 标准术语是 Heavenly Stem |
| error | groundBranch | earthly_branch | 地支 | 标准术语是 Earthly Branch |
| error | fortuneColumn | luck_pillar | 大运 | 大运是 Luck Pillar,不是 column |
| error | fortune_column | luck_pillar | 大运 | 大运是 Luck Pillar,不是 column |
| error | big_luck | luck_pillar | 大运 | 新接口使用 luck_pillar |
| error | annual_pillar | annual_luck | 流年 | 避免和本命 year_pillar 混淆 |
| error | monthly_pillar | monthly_luck | 流月 | 避免和本命 month_pillar 混淆 |
| error | daily_pillar | daily_luck | 流日 | 避免和本命 day_pillar 混淆 |
| warn | flowing_year | annual_luck | 流年 | 直译味过重,优先 annual_luck |
| warn | flowing_month | monthly_luck | 流月 | 直译味过重,优先 monthly_luck |
| warn | flowing_day | daily_luck | 流日 | 若表示干支请用 daily_stem_branch |
| warn | daily_fortune_insights | daily_luck_insights | 日运洞察模块 | 新八字模块 key 优先用 luck;已发布则兼容迁移 |
| review | key_astral_event | key_bazi_event | 今日关键八字事件 | 八字模块避免 astral;若已发布需迁移 |
| review | astral_event | bazi_event | 八字事件 | astral 属占星语境,八字事实链不要混用 |
| review | provider_visual_tags | provider_bazi_tags | Provider 标签 | 若标签全是八字来源,名称应体现 bazi |
| review | provider_visual_tag_detail | provider_bazi_tag_detail | Provider 标签详情 | 若标签全是八字来源,名称应体现 bazi |
+78
View File
@@ -0,0 +1,78 @@
# Nexa 八字程序员命名规则
## 最高优先级
1. JSON/API 字段使用 `snake_case`
2. 八字体系字段不得混入占星术语,除非该模块确实属于占星产品线。
3. 十神不要翻译成 `deity``god``star` 作为接口字段。对外字段用 `ten_god`,用户文案可写中文十神名。
4. `favorite_element` 是 Nexa 喜用元素字段;`useful_god` 是用神字段。两者不要互换。
5. 已上线字段必须走兼容迁移,不做无提示破坏性重命名。
## 推荐命名
| 类别 | 推荐 | 避免 | 说明 |
|---|---|---|---|
| 日主 | `day_master` | `Day_Master`, `dayOwner`, `self_lord` | API/JSON 用 snake_case |
| 日主天干 | `day_master_stem` | `DayMaster_Stem`, `dayMasterStem` in JSON | JSON 禁止 mixed case |
| 主导十神 | `dominant_ten_god` | `Dominant_Deity`, `dominant_star` | 十神不是 deity |
| 喜用元素 | `primary_favorite_element` | `Primary_Favorite_Element`, `primary_useful_god` | 喜用元素不等同用神 |
| 五行强度 | `element_strengths` | `Element_Strengths`, `energy_strengths` | 明确是五行强度 |
| 大运 | `luck_pillar` | `fortune_column`, `big_luck` | 八字术语是 Luck Pillar |
| 流年 | `annual_luck` | `flowing_year`, `annual_pillar` | 避免和本命年柱混淆 |
| 流日 | `daily_luck` | `flow_day` when used as event concept | 若表示干支可写 `daily_stem_branch` |
| 刑冲合害 | `branch_relationship` | `astral_event` | 八字地支关系不是占星事件 |
## 模块 key
模块 key 是前后端契约,不是标题文案。命名要表达数据职责:
| 当前/候选 | 建议 | 备注 |
|---|---|---|
| `daily_overview` | 保留 | 已清晰,非术语问题 |
| `monthly_luck_tag` | 保留 | 已上线可保留 |
| `daily_luck_mantra` | 保留 | 产品文案 key,可保留 |
| `daily_fortune_insights` | `daily_luck_insights` | 新增模块建议用 luck;已上线则兼容迁移 |
| `key_astral_event` | `key_bazi_event``key_energy_event` | 八字首页避免 astral |
| `home_image_module` | 保留或改为业务中性名 | 不属于术语违规 |
## Prompt 变量
Prompt 变量也属于接口契约,不应出现临时写法:
```yaml
destiny_symbols_context:
birth_date: "1999-09-09"
day_master_stem: "辛"
primary_favorite_element: "金"
secondary_favorite_element: "水"
element_strengths:
wood: 10
fire: 35
earth: 12
metal: 22
water: 8
```
不要写:
```yaml
DayMaster_Stem: "辛"
Primary_Favorite_Element: "金"
Dominant_Deity: "七杀"
```
## 审查输出
输出必须具体到替换建议:
```text
path/file.md:14 DayMaster_Stem -> day_master_stem [error]
原因:Nexa JSON/prompt contract 使用 snake_case;日主天干字段为 day_master_stem。
```
如果是已发布字段,输出迁移提醒:
```text
key_astral_event -> key_bazi_event [review]
原因:八字模块不应使用 astral;若前端已依赖,先新增新 key 并保留旧 key 兼容。
```
+247
View File
@@ -0,0 +1,247 @@
#!/usr/bin/env python3
"""Scan Nexa BaZi files for mixed interface and prompt-variable names."""
from __future__ import annotations
import argparse
import os
import re
import sys
from dataclasses import dataclass
from pathlib import Path
SKILL_DIR = Path(__file__).resolve().parents[1]
DEFAULT_RULES = SKILL_DIR / "references" / "nexa-forbidden-names.md"
SEVERITY_ORDER = {"review": 0, "warn": 1, "error": 2}
EXCLUDE_DIRS = {
".git",
".hg",
".svn",
".idea",
".vscode",
"__pycache__",
"node_modules",
"vendor",
"dist",
"build",
"coverage",
".next",
".nuxt",
".venv",
"venv",
}
TEXT_EXTENSIONS = {
".c",
".cc",
".cpp",
".cs",
".css",
".csv",
".go",
".h",
".hpp",
".html",
".java",
".js",
".json",
".jsx",
".kt",
".lua",
".md",
".mjs",
".php",
".py",
".rb",
".rs",
".scss",
".sh",
".sql",
".svelte",
".swift",
".toml",
".ts",
".tsx",
".txt",
".vue",
".xml",
".yaml",
".yml",
}
@dataclass(frozen=True)
class Rule:
severity: str
current: str
preferred: str
concept: str
reason: str
@dataclass(frozen=True)
class Issue:
path: Path
line_no: int
line: str
rule: Rule
def split_md_row(line: str) -> list[str]:
stripped = line.strip()
if not stripped.startswith("|") or not stripped.endswith("|"):
return []
return [cell.strip().strip("`").strip() for cell in stripped.strip("|").split("|")]
def parse_rules(path: Path) -> list[Rule]:
header: list[str] | None = None
rules: list[Rule] = []
for raw_line in path.read_text(encoding="utf-8").splitlines():
cells = split_md_row(raw_line)
if not cells:
continue
if {"severity", "current", "preferred"}.issubset(set(cells)):
header = cells
continue
if cells and all(set(cell) <= {"-"} for cell in cells):
continue
if not header or len(cells) < len(header):
continue
row = dict(zip(header, cells))
severity = row.get("severity", "").lower()
if severity not in SEVERITY_ORDER:
continue
current = row.get("current", "").strip()
preferred = row.get("preferred", "").strip()
if not current or not preferred:
continue
rules.append(
Rule(
severity=severity,
current=current,
preferred=preferred,
concept=row.get("concept", ""),
reason=row.get("reason", ""),
)
)
return rules
def iter_files(paths: list[Path]) -> list[Path]:
found: list[Path] = []
for path in paths:
if not path.exists():
print(f"warning: path does not exist: {path}", file=sys.stderr)
continue
if path.is_file():
if is_text_candidate(path):
found.append(path)
continue
for root, dirs, files in os.walk(path):
dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]
for name in files:
candidate = Path(root) / name
if is_text_candidate(candidate):
found.append(candidate)
return found
def is_text_candidate(path: Path) -> bool:
return path.suffix.lower() in TEXT_EXTENSIONS
def identifier_pattern(name: str) -> re.Pattern[str] | None:
if not re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", name):
return None
return re.compile(r"(?<![A-Za-z0-9_])" + re.escape(name) + r"(?![A-Za-z0-9_])")
def line_has_rule(line: str, rule: Rule) -> bool:
pattern = identifier_pattern(rule.current)
if pattern:
return bool(pattern.search(line))
return rule.current in line
def scan(paths: list[Path], rules: list[Rule], min_severity: str) -> list[Issue]:
min_rank = SEVERITY_ORDER[min_severity]
active_rules = [rule for rule in rules if SEVERITY_ORDER[rule.severity] >= min_rank]
issues: list[Issue] = []
for path in iter_files(paths):
try:
text = path.read_text(encoding="utf-8")
except UnicodeDecodeError:
try:
text = path.read_text(encoding="utf-8-sig")
except UnicodeDecodeError:
continue
for line_no, line in enumerate(text.splitlines(), start=1):
for rule in active_rules:
if line_has_rule(line, rule):
issues.append(Issue(path=path, line_no=line_no, line=line.strip(), rule=rule))
return issues
def print_rules(rules: list[Rule]) -> None:
for rule in rules:
print(f"{rule.severity}\t{rule.current}\t{rule.preferred}\t{rule.concept}")
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("paths", nargs="*", type=Path, help="files or directories to scan")
parser.add_argument("--rules", type=Path, default=DEFAULT_RULES, help="path to nexa-forbidden-names.md")
parser.add_argument(
"--min-severity",
choices=sorted(SEVERITY_ORDER, key=SEVERITY_ORDER.get),
default="review",
help="minimum severity to report",
)
parser.add_argument("--list-rules", action="store_true", help="print loaded rules and exit")
parser.add_argument("--no-fail", action="store_true", help="always exit 0")
args = parser.parse_args()
if not args.rules.exists():
print(f"rules file not found: {args.rules}", file=sys.stderr)
return 2
rules = parse_rules(args.rules)
if args.list_rules:
print_rules(rules)
return 0
if not args.paths:
parser.error("provide at least one file or directory")
issues = scan(args.paths, rules, args.min_severity)
if not issues:
print("No Nexa BaZi contract naming issues found.")
return 0
for issue in issues:
rule = issue.rule
print(f"{issue.path}:{issue.line_no}: {rule.current} -> {rule.preferred} [{rule.severity}]")
print(f" concept: {rule.concept}")
print(f" reason: {rule.reason}")
print(f" line: {issue.line}")
counts: dict[str, int] = {"error": 0, "warn": 0, "review": 0}
for issue in issues:
counts[issue.rule.severity] += 1
print(f"\n{len(issues)} issue(s) found: {counts['error']} error, {counts['warn']} warn, {counts['review']} review.")
blocking = any(issue.rule.severity in {"error", "warn"} for issue in issues)
return 0 if args.no_fail or not blocking else 1
if __name__ == "__main__":
raise SystemExit(main())