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
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,57 @@
## Context

- `hooks/env_check.py::detect_linter_config`(M8 后):`for lang_id, lang_def in REGISTRY.items(): files = lang_def.get("linter_config_files") or []` → 存在性过滤 → `{lang: [files]}`。**填错无害**(不存在的文件名永远不命中),这是本 change 敢于「保守估计 + 宽清单」的依据。
- 现状覆盖:java / rust / typescript / python 四条(M8 迁移自旧硬编码常量)。
- 关键区分(M8 design 遗留):`requiresConfig` **不等于** linter 配置——java 的 `requiresConfig` 是 `pom.xml` 等项目文件(gate 判定「项目是否接入」用),**不是** linter 配置文件;直接把 requiresConfig 拷进 linter_config_files 会造成误报(SessionStart 会说「linter 配置:pom.xml」)。只有 requiresConfig 本身就是 linter 配置的条目(yaml 的 `.yamllint*`、markdown 的 `.markdownlint*`、scala 的 `.scalafmt.conf`、erlang 的 `elvis.config*`、protobuf 的 `buf.yaml`、vue/svelte/astro 的 eslint 系)可直接复制。

## Decisions

### 保守清单(只填确定存在的标准配置名)

按语言分组(~30 条):

- go: `.golangci.yml`, `.golangci.yaml`
- shell: `.shellcheckrc`
- dockerfile: `.hadolint.yaml`, `.hadolint.yml`
- yaml: `.yamllint`, `.yamllint.yaml`, `.yamllint.yml`(requiresConfig 即 linter 配置,直接复制)
- markdown: `.markdownlint-cli2.*` / `.markdownlint.*` 九件套(同上复制)
- vue / svelte / astro: eslint 系(requiresConfig 直接复制)
- ruby: `.rubocop.yml`, `.rubocop.yaml`
- php: `phpstan.neon`, `phpstan.neon.dist`, `psalm.xml`
- swift: `.swiftlint.yml`, `.swiftlint.yaml`
- kotlin: `detekt.yml`, `detekt.yaml`
- dart: `analysis_options.yaml`
- elixir: `.credo.exs`
- c / cpp / objc / cuda: `.clang-tidy`, `.clang-format`
- css: `.stylelintrc`, `.stylelintrc.json`, `.stylelintrc.yml`, `stylelint.config.js`
- html: `.htmlhintrc`
- terraform: `.tflint.hcl`, `tflint.hcl`
- sql: `.sqlfluff`
- haskell: `.hlint.yaml`, `.hlint.yml`
- ocaml: `.ocamlformat`
- perl: `.perlcriticrc`
- clojure: `.clj-kondo/config.edn`
- powershell: `PSScriptAnalyzerSettings.psd1`
- crystal: `.ameba.yml`
- lua: `.luacheckrc`
- r: `.lintr`
- ansible: `.ansible-lint`
- solidity: `.solhint.json`, `.solhintrc`
- scala: `.scalafmt.conf`(requiresConfig 复制)
- erlang: `elvis.config`, `elvis.config.js`(requiresConfig 复制)
- protobuf: `buf.yaml`, `buf.gen.yaml`, `buf.work.yaml`(requiresConfig 复制)

**明确不填**(低置信或无标准配置名):graphql、toml、zig、nim、julia、pascal、liquid、luau、nix、groovy、cfml、fsharp、vbnet、csharp、elm、objc→已填 clang、cfml 等——留空数组是诚实状态。

### 守护测试

`test_linter_config_files_coverage`:stable/beta 中非空 `linter_config_files` 数量 ≥ 25(当前应 ~34)。防止未来语言注册表重构静默清空填充。

### 不动 requiresConfig 语义

两条字段语义分离是 M8 的设计决定,本 change 遵守:requiresConfig 服务 gate 判定,linter_config_files 服务 SessionStart 盘点。

## Risks / Trade-offs

- 个别配置名猜错 → 该文件不存在 → 盘点不报 → 无副作用。
- 个别语言存在更流行的配置名(如 graphql 的 `.graphqlrc`)→ 未填 → 盘点少报一项 → 未来按需补,本 change 不冒进。
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## Why

M8(v0.6.5)把 `hooks/env_check.py` 的 linter 配置盘点改为从 `scripts/languages.json` 的 `linter_config_files` 字段读取,但当时只迁移了 java/rust/typescript/python 四条历史记录——54 条 stable/beta 语言中仍有约 30 条填着默认空数组,SessionStart 钩子对它们的项目依旧报告「未配置任何 linter」,即使项目里躺着 `.golangci.yml`、`.rubocop.yml`、`.shellcheckrc` 等真实配置。这就是评审遗留的「detect_linter_config 推全局」。

## What Changes

- `scripts/languages.json`:为约 30 条 stable/beta 语言填充 `linter_config_files`(**保守清单**:只填确定存在的业界标准配置文件名,如 `.golangci.yml` / `.shellcheckrc` / `.rubocop.yml` / `.swiftlint.yml` / `analysis_options.yaml` / `.credo.exs` / `.clang-tidy` / `.stylelintrc*` / `.shellcheckrc` 等)。填错无副作用——`detect_linter_config` 只报告**实际存在**的文件,永不误报;填漏只影响盘点广度,不影响正确性。
- `tests/test_validate_languages_json.py` 新增 1 条守护:stable/beta 语言中非空 `linter_config_files` 的数量 ≥ 25(防止未来重构把填充结果清空)。
- 不改 `hooks/env_check.py`(M8 已 registry 驱动);不改 `scripts/languages.json` 其他字段;不改 `docs/LANGUAGES.md` 生成逻辑(生成器不读该字段,已实测)。

## Capabilities

### New Capabilities

None.

### Modified Capabilities

- `registry-driven-config`: 新增 Requirement——已知配置文件名的 stable/beta 语言 SHALL 填充该字段,SessionStart 盘点覆盖面由数据而非代码决定。

## Impact

约 30 条语言 JSON 条目各加 1-4 个字符串;1 条测试;spec delta 1 条 Requirement + 2 Scenario。零运行时行为变化(除 SessionStart 报告更全)。
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## ADDED Requirements

### Requirement: Known linter config filenames SHALL be populated for stable/beta languages

For each stable/beta language whose linter has well-known project-level configuration filenames, `scripts/languages.json` SHALL populate that entry's `linter_config_files` with those filenames. The population SHALL be conservative: only filenames that genuinely exist as that linter's configuration. Entries whose linter has no standard config filename (or where the filename is uncertain) MAY keep the empty list. `hooks/env_check.py` SHALL continue to report only files that actually exist, so an over-guessed filename never produces a false report.

#### Scenario: A Go project carries .golangci.yml

- **WHEN** the SessionStart hook runs in a repository whose root contains `.golangci.yml`
- **THEN** `detect_linter_config` reports `{"go": [".golangci.yml"]}` because `go.linter_config_files` includes it

#### Scenario: A guessed filename does not exist in the project

- **WHEN** a language lists a config filename that the current project does not contain
- **THEN** the hook reports nothing for that language/file pair — existence filtering prevents false positives

### Requirement: Linter config coverage SHALL be guarded against regression

`tests/test_validate_languages_json.py` SHALL assert that at least 25 stable/beta languages have a non-empty `linter_config_files` list, so a registry refactor cannot silently wipe the populated coverage.

#### Scenario: A refactor drops the populated fields

- **WHEN** a future edit removes `linter_config_files` from most stable/beta entries
- **THEN** the coverage test fails with the current populated count
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
## 1. Data fill

- [x] 1.1 Apply the conservative per-language map from design.md to `scripts/languages.json` (~30 stable/beta entries).
- [x] 1.2 `python3 scripts/validate_languages_json.py` — exit 0 (type rule covers the new values).

## 2. Coverage guard test

- [x] 2.1 Add `test_linter_config_files_coverage` (≥25 stable/beta with non-empty list) to `tests/test_validate_languages_json.py`.

## 3. Validation

- [x] 3.1 `python3 -m unittest discover -s tests -p 'test_*.py' -v` — all pass.
- [x] 3.2 `python3 tests/run_all.py` — 141 / 0 / 0.
- [x] 3.3 `git diff --check` — clean; `openspec validate --all --strict` — pass.

## 4. Release

- [x] 4.1 bump patch → 0.6.8; in-repo marketplace pins → v0.6.8; feat PR → merge; release PR → merge; tag v0.6.8; market repo catalog + README row → 0.6.8.
- [x] 4.2 `openspec archive fill-linter-config-files`.
23 changes: 23 additions & 0 deletions openspec/specs/registry-driven-config/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,26 @@ Every language entry in `scripts/languages.json` SHALL have a `linter_config_fil
- **WHEN** the project's root contains `ruff.toml` but no other linter configs
- **THEN** `env_check.detect_linter_config` returns `{"python": ["ruff.toml"]}` (or equivalent from the registry) and excludes all other languages

### Requirement: Known linter config filenames SHALL be populated for stable/beta languages

For each stable/beta language whose linter has well-known project-level configuration filenames, `scripts/languages.json` SHALL populate that entry's `linter_config_files` with those filenames. The population SHALL be conservative: only filenames that genuinely exist as that linter's configuration. Entries whose linter has no standard config filename (or where the filename is uncertain) MAY keep the empty list. `hooks/env_check.py` SHALL continue to report only files that actually exist, so an over-guessed filename never produces a false report.

#### Scenario: A Go project carries .golangci.yml

- **WHEN** the SessionStart hook runs in a repository whose root contains `.golangci.yml`
- **THEN** `detect_linter_config` reports `{"go": [".golangci.yml"]}` because `go.linter_config_files` includes it

#### Scenario: A guessed filename does not exist in the project

- **WHEN** a language lists a config filename that the current project does not contain
- **THEN** the hook reports nothing for that language/file pair — existence filtering prevents false positives

### Requirement: Linter config coverage SHALL be guarded against regression

`tests/test_validate_languages_json.py` SHALL assert that at least 25 stable/beta languages have a non-empty `linter_config_files` list, so a registry refactor cannot silently wipe the populated coverage.

#### Scenario: A refactor drops the populated fields

- **WHEN** a future edit removes `linter_config_files` from most stable/beta entries
- **THEN** the coverage test fails with the current populated count

Loading
Loading