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
8 changes: 4 additions & 4 deletions .agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@
"source": {
"source": "url",
"url": "https://github.com/partme-ai/partme-codeguard-plugin.git",
"ref": "v0.6.0"
"ref": "v0.6.1"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_USE"
},
"category": "Developer Tools",
"version": "0.6.0",
"version": "0.6.1",
"description": "Cross-language code lint enforcement for AI coding assistants (ZCode, Claude Code, Codex CLI, Kimi Code): Java, Rust, TypeScript, Python. PostToolUse hook auto-runs the native linter on every AI-written file and blocks on failure in strict mode.",
"icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.6.0/assets/official-logo.png",
"icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.6.1/assets/official-logo.png",
"interface": {
"displayName": "代码规范守卫",
"shortDescription": "Make AI-written code pass lint on first try",
"logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.6.0/assets/official-logo.png"
"logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.6.1/assets/official-logo.png"
}
}
]
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codeguard",
"version": "0.6.0+codex.20260921",
"version": "0.6.1+codex.20260922",
"description": "Cross-language code lint enforcement for AI coding assistants (ZCode, Claude Code, Codex CLI, Kimi Code): Java, Rust, TypeScript, Python. PostToolUse hook auto-runs the native linter on every AI-written file and blocks on failure in strict mode.",
"author": {
"name": "Full Stack Skills / PartMe.AI",
Expand Down
2 changes: 1 addition & 1 deletion .zcode-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"en": "CodeGuard",
"zh-CN": "代码规范检查"
},
"version": "0.6.0",
"version": "0.6.1",
"description": "Cross-language code lint enforcement for AI coding assistants (ZCode, Claude Code, Codex CLI, Kimi Code): Java, Rust, TypeScript, Python. PostToolUse hook auto-runs the native linter on every AI-written file and blocks on failure in strict mode.",
"description_i18n": {
"en": "Cross-language code lint enforcement. PostToolUse hook auto-runs the language-specific linter on every AI-written file; failed lint blocks further writes when strict_mode is on. Ships ready-to-go .pre-commit-config.yaml templates for Java/Rust/TypeScript/Python.",
Expand Down
2 changes: 1 addition & 1 deletion kimi.plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codeguard",
"version": "0.6.0",
"version": "0.6.1",
"description": "Cross-language code lint enforcement for AI coding assistants (ZCode, Claude Code, Codex CLI, Kimi Code): Java, Rust, TypeScript, Python. PostToolUse hook auto-runs the native linter on every AI-written file and blocks on failure in strict mode.",
"author": {
"name": "Full Stack Skills / PartMe.AI"
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,43 @@
## Context

实测证据(code-review-graph 2026-09-22 build,HEAD `5591125`):
- 社区数 3:scripts-scan(42 节点) / hooks-gate(39 节点) / tests-skill(36 节点)。
- 跨社区边 25,其中 19 条 CALLS 全部指向 `scripts/detect_lang.py` 的同一组函数:
- `ensure_user_path`(4 处,env_check / post_tool_lint / pre_tool_git_guard / user_prompt_validator)
- `detect_languages` / `detect_language`(5 处)
- `find_project_root`(3 处)
- `probe_toolchain`(5 处)
- `project_uses_linter`(5 处)
- `load_user_config`(4 处,含 user_prompt_validator / pre_tool_git_guard / post_tool_lint / env_check)

耦合来源是三职责合一:所有 hook 既要 PATH 补齐(属于进程环境),又要项目配置(属于用户态),又要语言识别(属于注册表)。后续若新增 hook / 修改 PATH 策略 / 改配置 schema 都会让 hooks 全军覆没。

## Decisions

### 三个文件按职责一刀切

- `scripts/paths.py`:仅 `ensure_user_path` + 内部静态目录与 fallback。依赖:std + `Path`。无项目根、无 JSON 解析。
- `scripts/user_config.py`:`load_user_config` / `load_project_overrides` / `get_overrides`。依赖:std + JSON + `Path`。无 subprocess。
- `scripts/detect_lang.py`:保留 `detect_languages` / `detect_language` / `find_project_root` / `probe_toolchain` / `project_uses_linter` / `extract_tool_binaries` / `LANG_COMMANDS` / `_load_registry`。依赖注册表 + subprocess。

### 外部 API 不变

`detect_lang.py` 顶部 `from paths import ensure_user_path` 与 `from user_config import load_user_config, load_project_overrides, get_overrides`,并 `__all__` 暴露全部历史符号。已有 6 处 hooks + 1 处 `fix.py` + 1 处 `run_check.py` + `tests/run_all.py` 全部无需改动。

### 调用方注释化

每个 hook 的 `from detect_lang import ...` 行末尾加注释 `# 实际定义见 scripts/paths.py / user_config.py`,便于未来定位到正确文件。

### 不动锁与注册表

`scripts/languages.json` 与 `skills.lock.json` 不变;拆分是纯结构调整,不影响任何语言识别结果。

### 不下沉到插件本地技能

PATH / 用户配置是 Hook 必需的运行时环境,与「面向用户的技能」无关——下沉到 `skills/` 会让 import 路径变得奇怪且增加发现成本。

## Risks / Trade-offs

- **首次 import 多一层**:`ensure_user_path` 调用点不变,但 Python 解释器多解析一个小文件(<10ms),可忽略。
- **`detect_lang.py` 顶端 re-export 列表易过期**:新增函数时若忘记在 `detect_lang.py` 加 re-export,hook 会立刻 ImportError——这是设计意图(fail-fast),但需在 PR review 时人工检查。
- **测试夹具路径**:`tests/run_all.py` 用 `sys.path.insert(0, str(PLUGIN / "scripts"))`,新文件位置在同一目录,零改动。
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## Why

`scripts/detect_lang.py`(401 行)当前承担三职责合一:① 进程 PATH 补齐(`ensure_user_path` + 内部 helper)、② 用户/项目级配置加载(`load_user_config` / `load_project_overrides` / `get_overrides`)、③ 语言注册表与命令表(其余)。code-review-graph 已记录 hooks↔scripts 高耦合告警(19 条 CALLS 边集中指向 `detect_lang.py`),三职责合一让新增/修改任意一条职责都会触发全部 hooks 的回归面。

## What Changes

- 新增 `scripts/paths.py`:仅含 `ensure_user_path(from_login_shell=False) -> None` 及其内部 helper;零业务依赖。
- 新增 `scripts/user_config.py`:仅含 `load_user_config` / `load_project_overrides` / `get_overrides`;与 hooks/commands 共享。
- `scripts/detect_lang.py` 改为 re-export 上述符号以保持外部 API 不变(`from detect_lang import ensure_user_path, load_user_config` 仍工作),同时删除实现正文。
- 所有 hooks(`env_check.py` / `pre_tool_git_guard.py` / `post_tool_lint.py` / `user_prompt_validator.py` / `stop_summary.py`)继续走 `detect_lang` re-export;显式注释每个 import 来自哪个新文件,便于后续单独演进。

## Capabilities

### New Capabilities

- `language-gate-commands`: Defines the responsibility split between PATH handling, user configuration loading, and language detection command tables.

### Modified Capabilities

None.

## Impact

新增 2 个脚本文件(`scripts/paths.py` / `scripts/user_config.py`,预计合计 ~150 行);修改 `scripts/detect_lang.py`(约 400 行缩到 ~250 行,但保持 API);hooks 无外部行为变化,仅加注释。`scripts/languages.json` 与 OpenSpec 现有 `language-gate-commands` / `language-registry-doc-sync` 两个 spec 不变。code-review-graph 的 hooks↔scripts 跨社区边应从 19 降至 ≤4。
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## ADDED Requirements

### Requirement: Process PATH enrichment SHALL live in scripts/paths.py

The function `ensure_user_path(from_login_shell: bool = False) -> None` SHALL be defined in `scripts/paths.py` and re-exported from `scripts/detect_lang.py` so existing call sites continue to work without modification.

#### Scenario: Hooks import ensure_user_path from detect_lang

- **WHEN** any hook script imports `ensure_user_path` from `scripts/detect_lang`
- **THEN** the import resolves to the symbol defined in `scripts/paths.py` without behavioral change

#### Scenario: Direct import from scripts/paths

- **WHEN** a new script needs to enrich PATH without depending on language detection
- **THEN** it can `from paths import ensure_user_path` with no other dependencies required

### Requirement: User configuration loading SHALL live in scripts/user_config.py

The functions `load_user_config`, `load_project_overrides`, and `get_overrides` SHALL be defined in `scripts/user_config.py` and re-exported from `scripts/detect_lang.py`.

#### Scenario: Hooks import load_user_config from detect_lang

- **WHEN** any hook script imports configuration helpers from `scripts/detect_lang`
- **THEN** the import resolves to the symbol defined in `scripts/user_config.py`

### Requirement: scripts/detect_lang.py SHALL retain language-detection surface only

`scripts/detect_lang.py` SHALL expose only the language-detection and command-table API: `LANG_COMMANDS`, `detect_languages`, `detect_language`, `find_project_root`, `probe_toolchain`, `project_uses_linter`, `extract_tool_binaries`, and the internal `_load_registry`. It SHALL NOT define PATH enrichment or configuration helpers in its own body.

#### Scenario: Refactor introduces a third responsibility

- **WHEN** a new function in `scripts/detect_lang.py` mixes PATH handling, config loading, or other unrelated concerns with language detection
- **THEN** the change SHALL be rejected because it violates the single-responsibility boundary
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## 1. New files

- [ ] 1.1 Create `scripts/paths.py` with `ensure_user_path(from_login_shell=False)` plus its internal static-dir and node-availability helpers, copied verbatim from `scripts/detect_lang.py`.
- [ ] 1.2 Create `scripts/user_config.py` with `load_user_config`, `load_project_overrides`, `get_overrides`, copied verbatim from `scripts/detect_lang.py`.

## 2. detect_lang.py refactor

- [ ] 2.1 Delete the implementation bodies of `ensure_user_path` and configuration helpers from `scripts/detect_lang.py`.
- [ ] 2.2 Replace them with re-exports (`from paths import ensure_user_path`, `from user_config import load_user_config, load_project_overrides, get_overrides`) and add `__all__` enumerating all externally imported symbols (existing + new re-exports).

## 3. Hooks annotation

- [ ] 3.1 In each of `hooks/env_check.py`, `hooks/pre_tool_git_guard.py`, `hooks/post_tool_lint.py`, `hooks/user_prompt_validator.py`, append an inline comment to the existing `from detect_lang import ...` line noting the source file (`paths.py` or `user_config.py`).

## 4. Validation

- [ ] 4.1 Run `python3 tests/run_all.py` — all hook simulation, language-registry, unit, CVE, edges, and field-regression subsets must pass.
- [ ] 4.2 Run `python3 -m unittest discover -s tests -p 'test_*.py' -v` — `test_plugin_manifests.py` and `test_skill_vendor.py` must pass (pre-existing baseline).
- [ ] 4.3 Run `git diff --check` — must be clean.
- [ ] 4.4 Run `openspec validate --all --strict` — must pass (3 pre-existing specs + new `language-gate-commands`).
- [ ] 4.5 Run `ruff check scripts/paths.py scripts/user_config.py scripts/detect_lang.py hooks/` — zero lint issues.

## 5. Graph evidence

- [ ] 5.1 Rebuild `code-review-graph` and verify the cross-community edge count between `hooks-gate` and `scripts-scan` drops to ≤4 (was 19).

## 6. Release

- [ ] 6.1 `node scripts/bump-plugin.mjs codeguard patch` — bump 0.6.0 → 0.6.1.
- [ ] 6.2 Commit plugin repo change, push `main` + tag `v0.6.1`.
- [ ] 6.3 In `full-stack-plugins` market repo, sync catalog `codeguard` to `0.6.1` and regenerate three marketplace manifests; commit and push.
- [ ] 6.4 `openspec archive add-detect-lang-splits` after all tasks above are checked.
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,37 @@
## Context

实测基线(HEAD `5591125`):
- `skills.lock.json` 1 个 source,union `skills` 集合 = 68 个 id
- `plugin-local-skills.json` 当前 `skills` 数组 = 0
- `skills/` 下含 `SKILL.md` 的目录 = 68 个
- 三者相等 → 现状 `68 == len(glob)` 通过

变更目标:让测试在校验 manifest 引用时同时校验三集合一致——任一漂移(lock bump 多/少、plugin-local 新增/移除但仓根漏目录、仓根目录漏登记到 lock)都会被同一断言捕获,错误信号强。

## Decisions

### 期望值从三源交叉推导

```python
lock_union = set().union(*(set(s["skills"]) for s in d["sources"]))
local = set(json.loads(PLUGIN_LOCAL.read_text())["skills"])
on_disk = {p.parent.name for p in ROOT.glob("skills/*/SKILL.md")}
expected = lock_union | local
```

测试断言改为:
- `on_disk == expected`:技能仓根与契约集合相等(防止 vendor 篡改或漏 vendor)。
- 每个 manifest 的 `skills` 字段等于 `{"skills", "./skills/"}` 中之一。

### 测试名去数字

魔法数字 `68` 仅是现状产物;改名为 `test_each_released_manifest_points_to_the_lock_union_plus_local_skills` 体现意图。

### 不引入新测试或新 fixture

变更限制在原测试方法的内部。增加测试会增加失败面(更多用例 = 更容易在 bump 时挂掉),与「去硬编码」的初衷相反。

## Risks / Trade-offs

- **第三方 contributor 看测试时不直观**:但测试名足够描述意图,docstring 注释「三源一致」即可。
- **未来加 manifest 新位置(如 docs/`x.json`)需同步测试**:这是正面的失败信号——任何契约变更都应主动改测试。
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Why

`tests/test_plugin_manifests.py::test_each_released_manifest_points_to_the_68_skill_bundle` 写死 `assertEqual(68, expected)`。当上游技能 snapshot bump(如 `c1468d0` 升至 `codeguard-skills@v0.1.2`)引入或移除技能时,这条断言会让 CI 在「不相关」位置失败,错误信号弱——测试位于 manifest 校验区,但失败源是 lockfile/技能数漂移。需要把硬编码常量改成「从锁+本地插件推导」的动态期望。

## What Changes

- `tests/test_plugin_manifests.py::test_each_released_manifest_points_to_the_68_skill_bundle` 重命名为 `test_each_released_manifest_points_to_the_lock_union_plus_local_skills`,断言改为:期望 = `skills.lock.json` 各 source 的 `skills` 并集 + `plugin-local-skills.json` 的 `skills` 集合 + 仓根 `skills/` 下含 `SKILL.md` 的目录数三者一致。
- 期望值不再硬编码;测试名去掉魔法数字。

## Capabilities

### New Capabilities

- `plugin-manifest-contracts`: Defines the contract that released manifests' skills field resolves to the union of vendored and plugin-local skills.

### Modified Capabilities

None.

## Impact

仅修改 1 个测试用例(~5 行)。不改变 manifest 内容、不改 lock、不改 plugin-local 登记。
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## ADDED Requirements

### Requirement: Released manifest skills bundle SHALL equal the lock+local union

For each released manifest, the test contract SHALL assert that `len(glob("skills/*/SKILL.md")) == |union(skills.lock.json sources[*].skills) ∪ plugin-local-skills.json skills|`. The expectation SHALL NOT be a hardcoded number.

#### Scenario: A new vendored skill lands in the upstream snapshot

- **WHEN** the upstream `codeguard-skills` snapshot is bumped and adds one new skill
- **THEN** the test passes once the plugin repo runs `python3 scripts/vendor/skill_vendor.py update` and the new directory appears under `skills/`

#### Scenario: A plugin-local skill is declared without a directory

- **WHEN** `plugin-local-skills.json` lists a name that has no matching `skills/<name>/SKILL.md`
- **THEN** the test fails with the symmetric-set difference, pointing at the missing directory

#### Scenario: An undeclared directory appears under skills/

- **WHEN** a directory `skills/<x>/SKILL.md` exists but `<x>` is neither in any lock source nor in `plugin-local-skills.json`
- **THEN** the test fails with the same symmetric-set difference, pointing at the orphan directory
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## 1. Test refactor

- [ ] 1.1 In `tests/test_plugin_manifests.py`, rename `test_each_released_manifest_points_to_the_68_skill_bundle` to `test_each_released_manifest_points_to_the_lock_union_plus_local_skills`.
- [ ] 1.2 Replace `self.assertEqual(68, expected)` with the three-source union assertion: `on_disk == lock_union | local`.

## 2. Validation

- [ ] 2.1 Run `python3 -m unittest discover -s tests -p 'test_*.py' -v` — both `test_plugin_manifests.py` and `test_skill_vendor.py` pass.
- [ ] 2.2 Run `git diff --check` — must be clean.
- [ ] 2.3 Run `openspec validate --all --strict` — must pass (3 pre-existing + `language-gate-commands` + new `plugin-manifest-contracts`).

## 3. Release

- [ ] 3.1 Commit as part of the same release commit as HIGH 1 (single `release: v0.6.1` covers both changes).
- [ ] 3.2 After archive of `add-detect-lang-splits`, archive `add-manifest-bundle-dynamic` together with it (single release flow).
32 changes: 32 additions & 0 deletions openspec/specs/language-gate-commands/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,35 @@
- **WHEN** 某语言未声明 `requiresConfig`
- **THEN** 其行为与现状一致,不被本要求改变

### Requirement: Process PATH enrichment SHALL live in scripts/paths.py

The function `ensure_user_path(from_login_shell: bool = False) -> None` SHALL be defined in `scripts/paths.py` and re-exported from `scripts/detect_lang.py` so existing call sites continue to work without modification.

#### Scenario: Hooks import ensure_user_path from detect_lang

- **WHEN** any hook script imports `ensure_user_path` from `scripts/detect_lang`
- **THEN** the import resolves to the symbol defined in `scripts/paths.py` without behavioral change

#### Scenario: Direct import from scripts/paths

- **WHEN** a new script needs to enrich PATH without depending on language detection
- **THEN** it can `from paths import ensure_user_path` with no other dependencies required

### Requirement: User configuration loading SHALL live in scripts/user_config.py

The functions `load_user_config`, `load_project_overrides`, and `get_overrides` SHALL be defined in `scripts/user_config.py` and re-exported from `scripts/detect_lang.py`.

#### Scenario: Hooks import load_user_config from detect_lang

- **WHEN** any hook script imports configuration helpers from `scripts/detect_lang`
- **THEN** the import resolves to the symbol defined in `scripts/user_config.py`

### Requirement: scripts/detect_lang.py SHALL retain language-detection surface only

`scripts/detect_lang.py` SHALL expose only the language-detection and command-table API: `LANG_COMMANDS`, `detect_languages`, `detect_language`, `find_project_root`, `probe_toolchain`, `project_uses_linter`, `extract_tool_binaries`, and the internal `_load_registry`. It SHALL NOT define PATH enrichment or configuration helpers in its own body.

#### Scenario: Refactor introduces a third responsibility

- **WHEN** a new function in `scripts/detect_lang.py` mixes PATH handling, config loading, or other unrelated concerns with language detection
- **THEN** the change SHALL be rejected because it violates the single-responsibility boundary

Loading
Loading