diff --git a/openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/.openspec.yaml b/openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/.openspec.yaml similarity index 100% rename from openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/.openspec.yaml rename to openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/.openspec.yaml diff --git a/openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/README.md b/openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/README.md similarity index 100% rename from openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/README.md rename to openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/README.md diff --git a/openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/design.md b/openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/design.md similarity index 100% rename from openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/design.md rename to openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/design.md diff --git a/openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/proposal.md b/openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/proposal.md similarity index 94% rename from openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/proposal.md rename to openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/proposal.md index 6d4c12e..45b3b95 100644 --- a/openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/proposal.md +++ b/openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/proposal.md @@ -27,5 +27,5 @@ None. - `hooks/user_prompt_validator.py`:触发判定与语言选择逻辑改动;外部行为变化仅在误触发场景(更少误触发为正向变化)。 - `scripts/run_check.py`:新增官方 SDK 依赖(`mcp` 包需写入 `requirements.txt` 或类似清单),新增 `out/.codeguard-last.log` 落盘逻辑,删除占位 `mcp_main` 分支。 -- 新建根 `requirements.txt` 声明 `mcp>=1.0`(本仓当前没有任何 requirements 文件;`mcp` 1.26.0 已在开发环境安装,CI 需显式安装)。 +- 新建根 `requirements.txt` 声明 `mcp>=1.0,<2`(2.x 更改了装饰器 API;本仓此前无 requirements 文件,CI 显式安装)。 - 不修改 vendored 技能(codeguard-* 走 vendor 流程);不修改 marketplace 清单。 diff --git a/openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/specs/gate-trigger-policy/spec.md b/openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/specs/gate-trigger-policy/spec.md similarity index 100% rename from openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/specs/gate-trigger-policy/spec.md rename to openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/specs/gate-trigger-policy/spec.md diff --git a/openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/specs/mcp-tool-server/spec.md b/openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/specs/mcp-tool-server/spec.md similarity index 100% rename from openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/specs/mcp-tool-server/spec.md rename to openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/specs/mcp-tool-server/spec.md diff --git a/openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/tasks.md b/openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/tasks.md similarity index 83% rename from openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/tasks.md rename to openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/tasks.md index 8516706..0288201 100644 --- a/openspec/changes/2026-09-22-fix-gate-trigger-and-mcp/tasks.md +++ b/openspec/changes/archive/2026-09-22-fix-gate-trigger-and-mcp/tasks.md @@ -42,7 +42,7 @@ ## 7. Release -- [ ] 7.1 `node scripts/bump-plugin.mjs codeguard minor` (0.6.6 → 0.7.0; MCP server is a new feature). -- [ ] 7.2 Sync marketplace manifest + hub catalog. -- [ ] 7.3 Push, confirm CI green, tag v0.7.0, GitHub Release. -- [ ] 7.4 `openspec archive 2026-09-22-fix-gate-trigger-and-mcp`. +- [x] 7.1 Bump minor 0.6.8 → 0.7.0 (MCP server is a new feature; worktree depth broke the hub resolver so manifests were completed manually: 4 manifests + marketplace ref/icon/logo pins + README rows). +- [x] 7.2 Sync marketplace manifest + hub catalog (repo pins v0.7.0; hub chain landed as `release: codeguard 0.7.0` + README row alignment). +- [x] 7.3 Push, confirm CI green (PR #21 feature, PR #24 release), tag v0.7.0 at dbc133a, GitHub Release published (README row follow-up at main 0e84186). +- [x] 7.4 `openspec archive 2026-09-22-fix-gate-trigger-and-mcp`(delta specs 已并入 openspec/specs:gate-trigger-policy +3、mcp-tool-server +4)。 diff --git a/openspec/specs/gate-trigger-policy/spec.md b/openspec/specs/gate-trigger-policy/spec.md new file mode 100644 index 0000000..43339c3 --- /dev/null +++ b/openspec/specs/gate-trigger-policy/spec.md @@ -0,0 +1,44 @@ +# gate-trigger-policy Specification + +## Purpose +TBD - created by archiving change 2026-09-22-fix-gate-trigger-and-mcp. Update Purpose after archive. +## Requirements +### Requirement: The UserPromptSubmit hook SHALL run the lint gate only when intent is commit-like +The `UserPromptSubmit` hook MUST evaluate the user's prompt for commit intent using word-boundary matching of trigger words (`commit|push|deploy|发布|部署`) and a separate action-verb check. The hook SHALL exit silently when either (a) no trigger word is present, (b) the only trigger occurrence is followed by `?`, or (c) a trigger word appears in an interrogative sentence identified by `QUESTION_MARKERS` (`?`, `?`, `么`, `吗`, `如何`, `怎么`, `有没有`, `是不是`, `什么是`, `哪些`). + +#### Scenario: User asks a question containing a trigger word + +- **WHEN** the user prompt is `使用过程中的情况` or `Should I commit this?` or `先读项目结构` +- **THEN** the hook exits 0 with empty stdout; no linter runs + +#### Scenario: User issues a commit-like imperative + +- **WHEN** the user prompt is `请帮我 commit` or `commit this now` or `请帮我 push` +- **THEN** the hook enters the gate path and invokes `run_gate()` + +#### Scenario: Trigger word followed by question mark is exempt + +- **WHEN** the user prompt is `commit?` or `要不要 push?` or `should I deploy?` +- **THEN** the hook exits silently regardless of other QUESTION_MARKERS content + +### Requirement: The gate SHALL run only languages named in the user message when they can be identified +When the trigger fires, the hook MUST extract candidate language ids from the user prompt using case-insensitive token matching against `scripts/languages.json` ids. When the intersection is non-empty, the gate SHALL run only that subset. When the intersection is empty, the gate SHALL fall back to the existing `detect_languages()` behavior. + +#### Scenario: User mentions one language explicitly + +- **WHEN** the user prompt is `请帮我 commit 这次 ruff 改动` +- **THEN** the gate runs only Python; other detected languages are skipped for this invocation + +#### Scenario: User mentions no language + +- **WHEN** the user prompt is `请帮我 commit` with no language token +- **THEN** the gate runs every language detected by `detect_languages()` (current behavior preserved) + +### Requirement: Question markers SHALL cover both Chinese and English +`QUESTION_MARKERS` MUST include the ASCII `?` in addition to the existing Chinese markers, so that an English prompt ending with `?` is recognized as a question even without other markers. + +#### Scenario: ASCII question mark without any other marker + +- **WHEN** the user prompt is `Should I push this?` +- **THEN** the hook exits silently because `?` is a recognized question marker + diff --git a/openspec/specs/mcp-tool-server/spec.md b/openspec/specs/mcp-tool-server/spec.md new file mode 100644 index 0000000..a341fce --- /dev/null +++ b/openspec/specs/mcp-tool-server/spec.md @@ -0,0 +1,47 @@ +# mcp-tool-server Specification + +## Purpose +TBD - created by archiving change 2026-09-22-fix-gate-trigger-and-mcp. Update Purpose after archive. +## Requirements +### Requirement: The MCP server SHALL expose three tools over stdio JSON-RPC +`scripts/run_check.py --mcp` MUST start an stdio JSON-RPC server using the official `mcp` Python SDK and register exactly three tools: `check_code_style`, `auto_fix`, and `list_languages`. The server MUST NOT take any other CLI flag in MCP mode. + +#### Scenario: Listing tools at startup + +- **WHEN** a client sends `tools/list` +- **THEN** the response enumerates `check_code_style`, `auto_fix`, `list_languages` with their input schemas + +### Requirement: `check_code_style` SHALL return a structured envelope with full stderr on disk +`check_code_style(path, languages?)` MUST run the configured linters, write the full stderr of any failing invocation to `/.codeguard-last.log` (default `/out/.codeguard-last.log`), and return a JSON envelope with one entry per language: `{language, passed: bool, exit_code: int, stderr_path: str, log_path: str}`. + +#### Scenario: One language fails + +- **WHEN** the path contains a single Python project whose ruff lint fails +- **THEN** the envelope contains one entry with `passed=false`, `exit_code` from ruff, `stderr_path` pointing at the on-disk log, and `log_path` echoing the same path + +#### Scenario: All languages pass + +- **WHEN** every linter exits 0 +- **THEN** every entry has `passed=true`, `exit_code=0`, and the log file is either empty or absent (no stderr written) + +### Requirement: `auto_fix` SHALL run formatters and re-verify +`auto_fix(path, languages?)` MUST invoke the per-language formatter chain (`format` from `scripts/languages.json`) and then re-run `check_code_style` on the same scope before returning. The response envelope MUST contain both `fixed: bool` (whether any file was modified) and the `check_code_style` envelope for the post-fix re-check. + +#### Scenario: Formatter succeeds and lint passes + +- **WHEN** ruff `--fix` removes unused imports and the re-check passes +- **THEN** `fixed=true` and the embedded `check_code_style` envelope is all-pass + +#### Scenario: Formatter succeeds but lint still fails + +- **WHEN** ruff `--fix` ran but a remaining error is non-fixable +- **THEN** `fixed=true` and the embedded `check_code_style` envelope contains at least one failing entry + +### Requirement: `list_languages` SHALL return id + name only +`list_languages()` MUST return a JSON array of `{id, name}` objects from `scripts/languages.json`, omitting the rest of each language record (lint command, format command, install hint, etc.) so internal commands are not exposed as part of the MCP surface. + +#### Scenario: Listing all supported languages + +- **WHEN** the client calls `list_languages` +- **THEN** the response enumerates every entry in `scripts/languages.json` with exactly two fields (`id`, `name`) per record +