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.14.15"
"ref": "v0.15.0"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_USE"
},
"category": "Developer Tools",
"version": "0.14.15",
"version": "0.15.0",
"description": "Evidence-backed code checks and Git content gates for AI assistants, with Maven/Gradle module impact analysis. Save hooks provide feedback; unverified checks are explicit.",
"icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.14.15/assets/official-logo.png",
"icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.15.0/assets/official-logo.png",
"interface": {
"displayName": "代码规范守卫",
"shortDescription": "Trustworthy code checks and Java impact analysis",
"logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.14.15/assets/official-logo.png"
"logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.15.0/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.14.15+codex.20260923",
"version": "0.15.0+codex.20260923",
"description": "Evidence-backed code checks and Git content gates for AI assistants, with Maven/Gradle module impact analysis. Save hooks provide feedback; unverified checks are explicit.",
"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.14.15",
"version": "0.15.0",
"description": "Evidence-backed code checks and Git content gates for AI assistants, with Maven/Gradle module impact analysis. Save hooks provide feedback; unverified checks are explicit.",
"description_i18n": {
"en": "Evidence-backed code checks and Git content gates for AI assistants, with Maven/Gradle module impact analysis. Save hooks provide feedback; unverified checks are explicit.",
Expand Down
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ git add -A && git commit -m "release: <插件id> <版本>" && git push

### 硬性禁令

- **`.` 开头的目录与文件默认忽略**(检查/保存/发现/全量扫描面):不扫描、不检查、不报告;
两个例外照常生效——入库安全检查照拦密钥模式(`.env`/`*.pem` 等),linter 配置发现
(`requiresConfig`/`linter_config_files`)照常匹配点文件
- 禁止改代码不 bump 版本(「小版本也要发」)
- 禁止手改 catalog.json 的 version 以外的生成产物、或手改三份市场清单——
它们只能由 `scripts/bump-plugin.mjs` 与 `plugins/scripts/sync-marketplaces.mjs` 生成
Expand Down
1 change: 1 addition & 0 deletions docs/current-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ flowchart TD
Entry -. 会话与去重 .-> State[storage / hook_state / cache]
```

检查作用域默认忽略项目根下任一以 `.` 开头的目录与文件(PostToolUse 保存面、delta 门禁面、语言发现面与全量扫描的 ruff/`find` 型通道;项目根本身位于点前缀父目录下不构成命中);入库安全检查与 linter 配置发现不受此影响——密钥模式照拦,点前缀配置文件照常判定接入。
`execution` 只记录进程证据,不把非零退出自动认定为代码违规;stdout/stderr 合计按默认 16 MiB 捕获预算并发读取,超限终止执行、保留预算内诊断且返回 `output_limit`/UNVERIFIED,不把片段当成完整日志。`verdict`、CVE 与 Dockerfile 报告解析决定 PASS、FAIL 或 UNVERIFIED;入口仅按各自协议呈现和聚合退出码。Git 提交检查使用 index 或预测暂存快照,推送检查使用 HEAD;保存钩子只给反馈。计划、未执行和未验证不能宣传为通过。
Git 准确快照复用统一执行器的原始字节模式:路径列表默认最多捕获 16 MiB,`cat-file --batch-check` 按对象数分配响应预算;通过大小响应确认对象总量不超过 256 MiB 后,`--batch` 才按总 blob 大小和逐项头部预算读取。stdout/stderr 合计超限时及时终止 Git,抛出 `SnapshotError`,不交付截断内容。预测暂存的工作树覆盖层只列举一次,并以同一集合决定 `changed` 和物化内容;最多 20,000 项、单文件 32 MiB、实际复制总量 256 MiB,以 64 KiB 块流式读取。打开时使用非阻塞与不跟随末级链接的可用平台标志,避免类型检查后变成 FIFO 时卡住。特殊文件、读取期间变化和超限不交付临时树,文件↔目录转换按父路径先处理。随后严格解析 index/HEAD 对象列表的 NUL 帧、模式、ID、类型/阶段和唯一文件名,并与独立路径列举核对;`cat-file` 两阶段响应再逐项核对 ID、blob 类型、大小与边界,按 SHA-1/SHA-256 blob 对象格式复算内容哈希。缺项、截断、尾部脏数据或同长度内容替换均不交付临时树,而是明确标记 Git UNVERIFIED。原始 index 不在观察过程中改写。该校验不是恶意 Git 进程沙箱或完整 Git/Shell 模拟。
`language_check` 将 `PlanExecution` 的实际命令依序映射为应用层有界 `execution_trace`(检查、修复、复检阶段);`check_application` 将其进一步压成 MCP 安全元数据,不回显原始 argv、环境覆盖或捕获输出。MCP `auto_fix` 以 `fingerprint.file_content` 对仓内改动文件在修复前、formatter 后、复检后分别采集有总量预算的流式身份,仓外路径、符号链接或读取故障不启动 formatter;顶层 `fixed` 只归因于 formatter 且复检后仍保留的变化。公开 `fix_results` 将命令成功 `formatter_succeeded` 与逐项修复确认 `fixed` 分离;只有唯一成功执行的 formatter 且内容变化可验证时才把全局变化归给该项,多 formatter 执行不猜测归属。后续身份不可采集、formatter 失败后仍有内容变化或检查器改写目标文件时,保留实际执行证据、整体标为 UNVERIFIED,不把未知副作用声称为已确认修复。`fix_results` 只公开状态与安全元数据;formatter stderr 尾部写入私有 `.codeguard-fix.log` 后只公开路径,日志不可用时不退回公开原文。失败日志保留各已执行检查在捕获预算内的输出;输出超限时日志只含片段,不能称为完整诊断,且可能含敏感文本,应排除出版本控制。项目日志和门禁截断日志共用 `storage.write_private_text` 的私有原子落盘;预置日志文件链接不被跟随,默认 `out` 目录为链接或不可写时不返回虚假日志路径,也不覆盖检查结论。终止命令仍决定既有状态、退出码和旧字段;未运行的计划命令不进入证据。
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.14.15",
"version": "0.15.0",
"description": "Evidence-backed code checks and Git content gates for AI assistants, with Maven/Gradle module impact analysis. Save hooks provide feedback; unverified checks are explicit.",
"author": {
"name": "Full Stack Skills / PartMe.AI"
Expand Down
32 changes: 32 additions & 0 deletions openspec/changes/2026-09-23-dot-prefix-default-skip/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 2026-09-23-dot-prefix-default-skip

## Why

用户指令(2026-09-23):codeguard 要默认忽略 `.` 开头的目录和文件,且这套硬性约束必须**既在代码中也在提示词中**。

实测背景:插件仓发版时 `.agents/plugins/marketplace.json` 与 `.codex-plugin/plugin.json` 被提交安全检查判成「不应入库」阻断发版(入库面已由 `0f284ee` 修复);但**检查/保存/发现/全量扫描**四类面仍会把点前缀路径当检查对象——宿主工具目录(`.cursor/`、`.claude/`、`.mimosa/` 快照等新点目录不在 `FULL_SCAN_EXCLUDES` 枚举内)、点前缀配置文件(`.eslintrc.js`)会被 lint 报与仓库内容无关的问题,且枚举永远追不上新宿主目录。

## What Changes

- 单一谓词 `path_policy.is_dot_prefixed(path, root)`:相对项目根任一路径段 `.` 开头(`.`/`..` 段除外)即点前缀。
- 检查面四通道接入默认忽略:PostToolUse 保存面、提交门禁 delta 面(`changed_files`)、全量扫描的 ruff 与 `find` 型 gate 注入。
- 语言发现面(`detect_languages`)不再计入点前缀文件。
- **两个例外面不受影响**:入库安全检查照拦密钥模式(`.env`/`*.pem`/`.DS_Store`),linter 配置发现(`requiresConfig`/`linter_config_files`)照常匹配点文件。
- 提示词面声明约束:SessionStart「codeguard 项目记忆」上下文与 `AGENTS.md` 硬性禁令写明该规则,并以测试锚定。

## Capabilities

### New Capabilities

- `scan-scope-policy`:检查作用域的点前缀默认忽略规则、例外面边界与提示词声明要求。

### Modified Capabilities

无。既有 `gate-trigger-policy`、`verdict-integrity` 等行为契约不变。

## Impact

- `scripts/codeguard/path_policy.py`(新谓词)、`scripts/scope.py`(delta 过滤 + ruff/find 注入)、`scripts/codeguard/save_application.py`、`scripts/codeguard/discovery.py`、`scripts/codeguard/startup_application.py`(提示词)。
- `AGENTS.md`、`docs/current-architecture.md`(提示词/文档面)。
- 行为变更:检查面默认忽略点前缀路径——此前会被检查的 `.eslintrc.js`、`.github/` 下文件等不再进入检查面;入库安全与配置发现行为不变。`0f284ee` 的入库面行为一并纳入规格固化。
- 不改动受管技能内容、不改 `FULL_SCAN_EXCLUDES` 枚举本体。
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# scan-scope-policy:检查作用域与点前缀默认忽略

## Purpose

定义 codeguard 检查作用域的点前缀默认忽略规则:哪些路径面默认不检查、哪些例外面必须照常生效,以及该约束在提示词面的声明要求,使「宿主工具目录与配置文件不再产生与仓库内容无关的结论」成为可测试契约。

## ADDED Requirements

### Requirement: Check and save faces MUST skip dot-prefixed paths by default

相对项目根,任一路径段以 `.` 开头(`.`、`..` 段除外)的目录与文件为点前缀路径,默认忽略:不扫描、不检查、不报告。适用于 PostToolUse 保存面、提交门禁 delta 面、语言发现面,以及全量扫描的 ruff 与 `find` 型 gate 通道。项目根本身位于点前缀父目录下(如 `~/.config/proj/`)不构成点前缀命中。

#### Scenario: PostToolUse skips dot-prefixed files

- **WHEN** AI 保存 `.cursor/rules.py` 或 `.eslintrc.js`
- **THEN** 保存面静默跳过,不触发 lint、不产生告警

#### Scenario: Delta gate face excludes dot-prefixed paths

- **WHEN** 本次提交同时改动 `.github/workflows/ci.yml` 与 `src/main.py`
- **THEN** 门禁检查面只含 `src/main.py`

#### Scenario: Language discovery ignores dot-prefixed files

- **WHEN** 项目根仅有 `.eslintrc.js` 与 `main.py`
- **THEN** 发现面只计入 python,不因 `.eslintrc.js` 计入 javascript

#### Scenario: Full-scan channels exclude dot-prefixed subtrees

- **WHEN** 全量扫描执行 ruff 或 `find` 型 gate
- **THEN** ruff 命令带点前缀排除参数,`find` 表达式注入 `-not -path '*/.*'`,`.agents/` 等子树不产生结论

#### Scenario: Project under a dot-prefixed parent is not skipped

- **WHEN** 项目根为 `~/.config/proj/` 且检查 `main.py`
- **THEN** 不因父目录点前缀跳过,正常检查

### Requirement: Commit safety face MUST NOT be weakened by dot-prefix skipping

点前缀默认忽略只作用于检查面。入库安全检查独立收集拟入库路径:密钥/凭据类文件模式(`.env`、`*.pem`、`.DS_Store` 等)无论点前缀与否照常拦截;点前缀目录(宿主插件清单与第一方配置)照常可入库。

#### Scenario: Secret files remain blocked

- **WHEN** 拟提交 `.env` 或 `id_rsa`
- **THEN** 入库安全检查照常给出违规与修复指令

#### Scenario: Host manifest dirs remain committable

- **WHEN** 拟提交 `.agents/plugins/marketplace.json`
- **THEN** 不因目录点前缀被判「不应入库」

### Requirement: Config discovery MUST keep matching dot-prefixed config files

linter 配置发现(`requiresConfig`、`linter_config_files` 项目级匹配)不受点前缀忽略影响:点前缀配置文件照常使语言判定为已接入。

#### Scenario: requiresConfig matches dot files

- **WHEN** 项目根存在 `.markdownlint-cli2.jsonc` 且 markdown 声明 `requiresConfig` 含该文件名
- **THEN** markdown 判定为已接入,进入正常检查流程

### Requirement: Prompt surfaces MUST state the constraint

点前缀默认忽略规则 MUST 出现在提示词面:SessionStart「codeguard 项目记忆」上下文与 `AGENTS.md` 硬性禁令,且须同时声明两个例外面(入库安全照拦、配置发现照常),并有测试锚定提示词包含该声明。

#### Scenario: SessionStart context states the rule

- **WHEN** SessionStart 生成项目记忆文本
- **THEN** 文本含默认忽略声明及例外说明

#### Scenario: AGENTS.md hard rules state the rule

- **WHEN** 读取 `AGENTS.md` 硬性禁令
- **THEN** 含点前缀默认忽略条目
34 changes: 34 additions & 0 deletions openspec/changes/2026-09-23-dot-prefix-default-skip/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Tasks: 2026-09-23-dot-prefix-default-skip

## 1. 单一谓词

- [x] 1.1 `path_policy.is_dot_prefixed(path, root)`:相对 root 判段,`.`/`..` 段豁免;root 外或无法相对化时返回 False(多检查不漏检查)
- [x] 1.2 路径分隔符统一 `/`(沿用 `is_build_artifact` 的 Windows 兼容写法,不用 `lstrip('./')`)

## 2. 检查面四通道接入

- [x] 2.1 `scope.changed_files` 返回前过滤点前缀路径(delta 门禁面 + push 面同时生效)
- [x] 2.2 `scope._inject_find_excludes` 注入 `-not -path '*/.*'`(幂等,OR 组括号前提不变)
- [x] 2.3 `scope.scope_cmd` ruff 分支 full_excludes 时追加 `--exclude '.*'` 与 `--exclude '**/.*'`
- [x] 2.4 `save_application.should_skip` 点前缀文件静默跳过(root 由 `find_project_root` 兜底推导)

## 3. 发现面

- [x] 3.1 `discovery.detect_languages` 的 include 不计入点前缀文件

## 4. 例外面回归锚定(不许被忽略吞掉)

- [x] 4.1 入库安全:`.env`/`*.pem` 照拦;`.agents/`/`.codex-plugin/` 等点目录照常可入库(固化 `0f284ee`)
- [x] 4.2 配置发现:`requiresConfig` 匹配 `.markdownlint-cli2.jsonc` 等点文件仍判已接入

## 5. 提示词面

- [x] 5.1 `startup_application` 项目记忆文本声明默认忽略规则与两个例外
- [x] 5.2 `AGENTS.md` 硬性禁令补该条
- [x] 5.3 `docs/current-architecture.md` 行为段落补一句

## 6. 测试与发版

- [x] 6.1 `tests/test_dot_prefix_default_skip.py` 锚定 1–5 全部场景(含「项目根本身在点目录下不误伤」陷阱)
- [x] 6.2 全量回归(run_all + unittest discover)通过
- [x] 6.3 按仓规 bump minor、市场仓同步、PR/CI 合并、两仓推送
2 changes: 1 addition & 1 deletion scripts/check_architecture.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"codeguard.registry_schema": {"__future__", "re"},
"codeguard.registry": {"__future__", "json", "pathlib", "typing", "codeguard.registry_schema"},
"codeguard.config": {"__future__", "json", "re", "pathlib", "codeguard.registry"},
"codeguard.discovery": {"__future__", "fnmatch", "re", "pathlib", "codeguard.config", "codeguard.registry"},
"codeguard.discovery": {"__future__", "fnmatch", "re", "pathlib", "codeguard.config", "codeguard.path_policy", "codeguard.registry"},
"codeguard.toolchain": {"__future__", "pathlib", "threading", "codeguard.execution"},
"codeguard.language_check": {"__future__", "pathlib", "codeguard.config", "codeguard.discovery",
"codeguard.execution", "codeguard.models", "codeguard.planning",
Expand Down
3 changes: 3 additions & 0 deletions scripts/codeguard/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pathlib import Path

from .config import get_overrides
from .path_policy import is_dot_prefixed
from .registry import EXT_LANG_MAP, FILE_LANG_MAP, PROJECT_MARKERS


Expand Down Expand Up @@ -74,6 +75,8 @@ def detect_languages(project_root: str | Path) -> list[str]:
ext_map = {**EXT_LANG_MAP, **overrides.get("extensions", {})}

def include(file: Path) -> None:
if is_dot_prefixed(file, project_root):
return # 点前缀默认忽略(scan-scope-policy):.eslintrc.js 不计入语言
if file.is_file() and not _excluded(file, exclude):
language = ext_map.get(file.suffix.lower()) or FILE_LANG_MAP.get(file.name)
if language:
Expand Down
31 changes: 31 additions & 0 deletions scripts/codeguard/path_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,29 @@ def is_build_artifact(path: str | Path) -> bool:
return any(seg in FULL_SCAN_EXCLUDES for seg in parts)


def is_dot_prefixed(path: str | Path, root: str | Path | None = None) -> bool:
"""路径相对 root 的任一段以 `.` 开头(`.`/`..` 段除外)→ 点前缀,默认忽略。

检查面的单一谓词(scan-scope-policy spec):PostToolUse 保存面、delta 门禁面、
全量扫描与语言发现都用它跳过点前缀目录与文件(.cursor/、.claude/、
.eslintrc.js 等——枚举追不上新宿主目录,谓词才是硬约束)。
例外面由调用方保证:入库安全检查(check_paths)与配置发现不走本谓词。

root 语义:path 相对 root 判段——项目根本身位于点前缀父目录(~/.config/proj/)
不算命中(实测陷阱);root 缺省或 path 不在 root 下时退化为全路径判段。
不能用 lstrip("./")(会把 `.tox` 的点剥掉,同 is_build_artifact 的踩点)。
"""
p = Path(path)
if root is not None:
try:
p = p.resolve().relative_to(Path(root).resolve())
except (ValueError, OSError):
pass # root 外或无法解析:全路径判段,宁可多忽略也不错扫宿主目录
parts = [seg for seg in str(p).replace("\\", "/").split("/")
if seg not in ("", ".", "..")]
return any(seg.startswith(".") for seg in parts)


def check_paths(paths: list[str]) -> list[tuple[str, str, str]]:
"""纯路径策略:只判断给定拟入库路径,不发现文件、不读 Git、不执行修复。"""
violations: list[tuple[str, str, str]] = []
Expand All @@ -80,6 +103,14 @@ def check_paths(paths: list[str]) -> list[tuple[str, str, str]]:
# (实测误伤,且打断依赖它的 skills-check CI)。
if seg == "vendor" and idx != 0:
continue
# 点前缀目录默认忽略(2026-09-23):.agents/.codex-plugin/.zcode/
# .github/.claude/ 等是宿主插件清单与第一方配置,必须可入库——
# 裸段匹配曾把插件仓的 marketplace.json/plugin.json 判成"不应入库"
# (实测阻断发版)。扫描面本就不扫这些目录(FULL_SCAN_EXCLUDES),
# 这里只放开"入库面"的目录拦截;密钥类**文件**模式不受影响
# (.env、*.pem 等仍按 GUARD_EXCLUDE_FILES 拦截)。
if seg.startswith("."):
continue
hit_dir = seg
break
if hit_dir:
Expand Down
6 changes: 4 additions & 2 deletions scripts/codeguard/save_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
from dataclasses import dataclass, field
from pathlib import Path

from scope import is_build_artifact
from scope import is_build_artifact, is_dot_prefixed

from . import hook_state
from .discovery import detect_language, project_uses_linter
from .discovery import detect_language, find_project_root, project_uses_linter
from .execution import execute
from .fingerprint import check_identity
from .hook_state import codeguard_home
Expand Down Expand Up @@ -73,6 +73,8 @@ def run(cmd: list[str], cwd: Path, timeout: int = 300) -> tuple[int, str, str]:
def should_skip(file_path: str, languages: list[str]) -> tuple[bool, str]:
if not file_path or is_build_artifact(file_path):
return True, ""
if is_dot_prefixed(file_path, find_project_root(file_path)):
return True, ""
lang = detect_language(file_path)
if not lang:
return True, ""
Expand Down
Loading
Loading