Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/skills-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ jobs:
run: python3 scripts/vendor/skill_vendor.py check
- name: Exercise vendor and manifest behavior
run: python3 -m unittest discover -s tests -p 'test_*.py' -v
- name: Validate languages.json schema
run: python3 scripts/validate_languages_json.py
- name: Run plugin regression suite
run: python3 tests/run_all.py
- name: Reject direct edits to externally managed skills
Expand Down
8 changes: 4 additions & 4 deletions docs/LANGUAGES.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# partme-codeguard-plugin 支持的语言

> 覆盖 **55 种编程语言**(注册表 57 条,含 Dockerfile/Ansible 等文件类型条目)。所有语言均可被 `detect_lang` 识别;其中 **53 条**已接入 linter 强制门禁(Stable / Beta),其余列入路线图(Planned,钩子检测到后安全跳过)。
> 覆盖 **55 种编程语言**(注册表 57 条,含 Dockerfile/Ansible 等文件类型条目)。所有语言均可被 `detect_lang` 识别;其中 **54 条**已接入 linter 强制门禁(Stable / Beta),其余列入路线图(Planned,钩子检测到后安全跳过)。
>
> 本文档由 `scripts/languages.json` 注册表自动生成(`scripts/gen_language_docs.py`);新增/调整语言请改注册表后重新生成。


## Stable(默认强制,V0.1 起)(53 种)
## Stable(默认强制,V0.1 起)(54 种)

| 语言 | 扩展名 | Lint 命令 | Format 命令 | 安装说明 |
|---|---|---|---|---|
Expand Down Expand Up @@ -62,15 +62,15 @@
| Erlang | `.erl` `.hrl` | `elvis rock` | `erlfmt` | rebar3 plugins / escript |
| Liquid (Shopify) | `.liquid` | `theme-check .` | — | gem install theme-check |
| CUDA | `.cu` `.cuh` | `clang-tidy --quiet {file}` | `clang-format -i {file}` | CUDA Toolkit + clangd |
| Ansible | (文件名匹配) | `ansible-lint` | `ansible-lint --fix` | playbook 检查由 ansible-lint 承接(yaml 通道);语义级检查待集成 |

## Planned(已识别,linter 在路线图上)(4 种)
## Planned(已识别,linter 在路线图上)(3 种)

| 语言 | 扩展名 | 计划 Linter | 目标版本 |
|---|---|---|---|
| COBOL | `.cbl` `.cob` `.cpy` | 无独立 CLI linter(依赖平台 IDE 诊断) | V0.5 |
| ArkTS (HarmonyOS) | `.ets` | 无独立 CLI linter(依赖平台 IDE 诊断) | V0.5 |
| Metal | `.metal` | 无独立 CLI linter(依赖平台 IDE 诊断) | V0.5 |
| Ansible | — | playbook 检查由 ansible-lint 承接(yaml 通道);语义级检查待集成 | V0.3 |

## 检测机制

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-22
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
## Context

现状(v0.6.3):
- `scripts/languages.json` 顶层结构 `{version, description, status_levels, languages[]}`;status_levels 三档 stable/beta/planned。
- 单条语言字段:id, name, extensions, markers, status, since, lint, format, install_hint, requiresConfig(部分可选)。
- 下游消费:
- `scripts/detect_lang.py::_load_registry` + `EXT_LANG_MAP` / `FILE_LANG_MAP` / `PROJECT_MARKERS` / `LANG_COMMANDS` 构建(line 60-122)。
- `scripts/gen_language_docs.py` → `docs/LANGUAGES.md`。
- `tests/run_all.py::test_languages` 子集断言 stable/beta ≥50 + by_id/get(key) smoke。
- 已存在错误模式:
- `tests/test_distribution_safety.py::test_no_rickroll_surface_is_published` 间接证明 languages.json 漏字段会让安全测试挂。
- 当前 57 个语言全为 stable 或 planned(无 beta),但 status_levels 列出 beta 是历史遗物。

## Decisions

### 校验器独立 CLI(不嵌入 detect_lang)

校验逻辑不放在 `detect_lang.py`——会污染运行时加载路径。独立脚本 `scripts/validate_languages_json.py`,被 CI 直接调、被单元测试直接调。

### 校验规则集(11 条)

每条失败都打印 `ERROR: <id-or-'all'>.<field-or-path>: <message>`,并在脚本末尾汇总「N errors」:

1. **顶层结构**:`version` 存在且为正整数;`languages` 数组非空;`status_levels` 至少包含 stable/beta/planned。
2. **id 唯一性**:所有 languages[i].id 两两不同。
3. **status 合法**:status ∈ status_levels。
4. **extensions 唯一性**:跨语言 extensions 不互相覆盖(`.py` 只能被一个 stable/beta 占用)。
5. **markers 唯一性**:跨语言 markers 不互相覆盖。
6. **lint / format 一致性**:若 status ∈ {stable, beta},lint 或 format 至少有一个非空;planned 允许全空。
7. **requiresConfig 字段类型**:list of str;glob 模式允许(`*`, `?`, `[`)。
8. **id 命名约定**:kebab-case(小写 + `-`)。
9. **since 字段**:存在;与 status_levels 对应——stable/beta 应有 since,planned 允许为空(但当前均为 V0.1 +)。
11. **extensions 必须以 `.` 开头**。
12. **lint/ format 命令非空列表**:每元素为非空字符串。

### 输出格式

```
INFO: languages.json contains 57 entries (53 stable, 0 beta, 4 planned)
INFO: 53 stable/beta languages (>= 50 ✓)
INFO: 11 schema rules passed
ERROR: all:extensions: marker ' pom.xml' duplicated across java/kotlin/scala (current stable/beta owners differ)
... (N errors)
```

退出码:errors=0 → 0;errors>0 → 1;--help/参数错误 → 2。

### 测试矩阵(unittest)

- `test_clean_registry_passes`:当前 `scripts/languages.json` 通过校验(baseline)。
- `test_duplicate_id_fails`:复制某条 id → 期望 1 条 ERROR。
- `test_invalid_status_fails`:status="draft" → 期望 1 条 ERROR。
- `test_extension_collision_fails`:手造一个 stable/beta 语言 `extensions=["rs"]`(撞 rust)→ 期望 1 条 ERROR。
- `test_marker_collision_fails`:类似。
- `test_planned_with_lint_fails`:status="planned" + lint 非空 → 期望 1 条 ERROR。
- `test_stable_with_no_lint_or_format_fails`:status="stable" 且 lint/format 都为 null/[] → 期望 1 条 ERROR。
- `test_requiresConfig_must_be_list_of_str`:类型错误 → 期望 1 条 ERROR。
- `test_extensions_must_start_with_dot`:".py" 改成 "py" → 期望 1 条 ERROR。
- `test_id_must_be_kebab_case`:id="Ruby_on_Rails" → 期望 1 条 ERROR。

### CI 入口

`skills-check.yml` 增加:
```yaml
- name: languages.json schema check
run: python3 scripts/validate_languages_json.py
- name: languages.json schema unit tests
run: python3 -m unittest tests.test_validate_languages_json -v
```

与现有 `python3 -m unittest discover -s tests -p 'test_*.py' -v` 不重复(仍跑总发现测试)。

## Risks / Trade-offs

- **校验器本身需要测试覆盖**——`tests/test_validate_languages_json.py` 是必须项;CI 单独跑一条确保它没有自我指涉。
- **当前注册表可能有隐藏违规**——test_clean_registry_passes 是真实基线;如果发现历史遗留违规,需在 change 里同步修正并标注原因。
- **extensions/markers 冲突判定的语义**:`planned` 语言的扩展名不参与冲突检测(仅 stable/beta 是「已部署」)——避免给未来新增留过严约束。
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## Why

`scripts/languages.json` 是 codeguard 的语言注册表(57 条),由 `detect_lang.py`、`hooks/__protocol__.md` 指向的 `gen_language_docs.py`、`tests/run_all.py::test_languages` 三处下游同时消费。MEDIUM #5 评审指出:每条语言字段是手工维护的(id / name / extensions / markers / status / since / lint / format / install_hint / requiresConfig),任一字段漏写、状态值非法、id 重复、extensions 互相冲突,错误信号极弱——`tests/run_all.py::test_languages` 仅断言「stable/beta ≥50」,捕获不到字段完整性或一致性。需要一个集中校验脚本,作为 PR-time 的硬门禁。

## What Changes

- 新增 `scripts/validate_languages_json.py`:纯函数校验器,零网络、零依赖;接受 `--path`(默认 `scripts/languages.json`);退出码 0=通过 / 1=失败 / 2=参数错误;输出逐行 `level: id.field: message` 文本格式。
- 新增 `tests/test_validate_languages_json.py`:unittest,覆盖正例(当前 json)与反例(id 重复、status 非法、extensions 冲突、required 字段缺失、requiresConfig 指向不存在的注册项)。
- CI(`skills-check.yml`)增加一行 `python3 scripts/validate_languages_json.py` 与 `python3 -m unittest tests.test_validate_languages_json` 的执行(如同现有 `run_all.py`)。
- 不修改 `languages.json` 内容;不修改下游消费方(detect_lang / gen_language_docs / run_all.py)。

## Capabilities

### New Capabilities

- `languages-registry-contract`: Defines the schema and consistency rules for `scripts/languages.json`.

### Modified Capabilities

None.

## Impact

新增 1 个校验脚本(~80 行)+ 1 个测试文件(~80 行);CI 工作流加 2 行。总行数净增 < 200。
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## ADDED Requirements

### Requirement: languages.json SHALL be validated by scripts/validate_languages_json.py

A standalone validator SHALL live at `scripts/validate_languages_json.py`. It SHALL exit 0 on success and exit 1 when one or more schema or consistency rules fail. It SHALL accept `--path PATH` (default `scripts/languages.json`) and `--help`, with exit code 2 for argument errors.

#### Scenario: A clean registry passes

- **WHEN** `scripts/validate_languages_json.py` runs against the current `scripts/languages.json`
- **THEN** exit code is 0 and the output reports zero errors

#### Scenario: A duplicate id is rejected

- **WHEN** two language entries share the same `id`
- **THEN** the validator exits 1 and prints one `ERROR` line identifying the duplicated id

### Requirement: The validator SHALL enforce 11 schema and consistency rules

The validator SHALL check, at minimum:
1. top-level structure (`version`, `languages`, `status_levels` present)
2. id uniqueness across languages
3. status ∈ `status_levels`
4. extensions uniqueness across stable/beta languages
5. markers uniqueness across stable/beta languages
6. stable/beta require at least one non-empty `lint` or `format`; planned allows all-empty
7. `requiresConfig` is a list of strings (globs allowed)
8. id follows kebab-case
9. `since` present for stable/beta
10. extensions entries start with `.`
11. `lint` and `format` entries are non-empty strings when present

#### Scenario: A planned language registers a linter

- **WHEN** a `status: "planned"` entry has a non-empty `lint` field
- **THEN** the validator exits 1 with `ERROR` identifying the entry

### Requirement: languages.json validation SHALL run in CI

The `skills-check.yml` workflow SHALL run the validator and its unit tests on every push to `main` and on every PR.

#### Scenario: A PR introduces a duplicate id

- **WHEN** a contributor adds an entry with an existing `id`
- **THEN** `skills-check.yml` reports the validator failure and blocks merge via required status check
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## 1. Validator

- [ ] 1.1 Create `scripts/validate_languages_json.py` with the 11-rule set per design.md.
- [ ] 1.2 `python3 scripts/validate_languages_json.py` on current registry must exit 0 (baseline).
- [ ] 1.3 Document CLI flags + exit codes in the script's top docstring.

## 2. Unit tests

- [ ] 2.1 Create `tests/test_validate_languages_json.py` with the 10-test matrix per design.md.
- [ ] 2.2 All tests must pass with the current registry as the baseline fixture.

## 3. CI wiring

- [ ] 3.1 Edit `.github/workflows/skills-check.yml` to add two new steps (validator + unit tests) after the existing unittest discovery.

## 4. Validation

- [ ] 4.1 `python3 -m unittest discover -s tests -p 'test_*.py' -v` — all pass (existing + new).
- [ ] 4.2 `python3 tests/run_all.py` — full 141 / 0 / 0 must hold.
- [ ] 4.3 `git diff --check` — clean.
- [ ] 4.4 `openspec validate --all --strict` — pass (5 pre-existing + new `languages-registry-contract`).
- [ ] 4.5 `ruff check scripts/validate_languages_json.py tests/test_validate_languages_json.py` — zero new errors.

## 5. Release

- [ ] 5.1 `node scripts/bump-plugin.mjs codeguard patch` — bump 0.6.3 → 0.6.4.
- [ ] 5.2 Fix in-repo `.agents/plugins/marketplace.json` ref/icon to `v0.6.4`.
- [ ] 5.3 Open PR, wait for vendor-check, merge.
- [ ] 5.4 Push tag `v0.6.4`.
- [ ] 5.5 Sync market repo catalog + README codeguard row to `0.6.4` and push.
- [ ] 5.6 `openspec archive add-languages-schema-validator` after all tasks above are checked.
48 changes: 48 additions & 0 deletions openspec/specs/languages-registry-contract/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# languages-registry-contract Specification

## Purpose
TBD - created by archiving change add-languages-schema-validator. Update Purpose after archive.
## Requirements
### Requirement: languages.json SHALL be validated by scripts/validate_languages_json.py

A standalone validator SHALL live at `scripts/validate_languages_json.py`. It SHALL exit 0 on success and exit 1 when one or more schema or consistency rules fail. It SHALL accept `--path PATH` (default `scripts/languages.json`) and `--help`, with exit code 2 for argument errors.

#### Scenario: A clean registry passes

- **WHEN** `scripts/validate_languages_json.py` runs against the current `scripts/languages.json`
- **THEN** exit code is 0 and the output reports zero errors

#### Scenario: A duplicate id is rejected

- **WHEN** two language entries share the same `id`
- **THEN** the validator exits 1 and prints one `ERROR` line identifying the duplicated id

### Requirement: The validator SHALL enforce 11 schema and consistency rules

The validator SHALL check, at minimum:
1. top-level structure (`version`, `languages`, `status_levels` present)
2. id uniqueness across languages
3. status ∈ `status_levels`
4. extensions uniqueness across stable/beta languages
5. markers uniqueness across stable/beta languages
6. stable/beta require at least one non-empty `lint` or `format`; planned allows all-empty
7. `requiresConfig` is a list of strings (globs allowed)
8. id follows kebab-case
9. `since` present for stable/beta
10. extensions entries start with `.`
11. `lint` and `format` entries are non-empty strings when present

#### Scenario: A planned language registers a linter

- **WHEN** a `status: "planned"` entry has a non-empty `lint` field
- **THEN** the validator exits 1 with `ERROR` identifying the entry

### Requirement: languages.json validation SHALL run in CI

The `skills-check.yml` workflow SHALL run the validator and its unit tests on every push to `main` and on every PR.

#### Scenario: A PR introduces a duplicate id

- **WHEN** a contributor adds an entry with an existing `id`
- **THEN** `skills-check.yml` reports the validator failure and blocks merge via required status check

2 changes: 1 addition & 1 deletion scripts/languages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1458,7 +1458,7 @@
"ansible.cfg",
"playbook.yml"
],
"status": "planned",
"status": "stable",
"since": "V0.3",
"lint": [
"ansible-lint"
Expand Down
Loading
Loading