From c7f722a468f7cbf27f7edc320bcabd7f0d6db444 Mon Sep 17 00:00:00 2001 From: 1339190177 <1339190177@users.noreply.github.com> Date: Wed, 11 Nov 2026 13:08:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(regress-guard):=20sync=20to=20v2.8.24=20?= =?UTF-8?q?=E2=80=94=20whitelist=20distillation=20+=20doc=20alignment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Distillation switched to an explicit whitelist (root files / dirs / named scripts): new source-tree directories no longer leak into the payload by default (dev caches and session artifacts previously slipped in) - Docs: dual-host (dsh desktop), machine PR-review loop, effect tracking, session identity layer documented; side-effects declaration now declares the optional systemd user timers; generated reference zones refreshed in both READMEs - Commands renamed from colon to hyphen (NTFS-safe); installer Python-native (install.py/uninstall.py) for Windows 10+ - Marketplace entry appended without reformatting upstream style - Payload: 92 files, self-checked (zero leakage / required subset / count band) --- .../regress-guard/.zcode-plugin/plugin.json | 27 + external_plugins/regress-guard/LICENSE | 21 + external_plugins/regress-guard/README.md | 182 ++ external_plugins/regress-guard/README_CN.md | 211 ++ .../regress-guard/benchmarks/README.md | 100 + .../regress-guard/benchmarks/cases.json | 576 +++++ .../regress-guard/commands/regress-bypass.md | 64 + .../commands/regress-characterize.md | 62 + .../regress-guard/commands/regress-evolve.md | 49 + .../regress-guard/commands/regress-finish.md | 152 ++ .../regress-guard/commands/regress-init.md | 189 ++ .../regress-guard/commands/regress-install.md | 60 + .../regress-guard/commands/regress-learn.md | 122 + .../regress-guard/commands/regress-plan.md | 333 +++ .../regress-guard/commands/regress-quick.md | 79 + .../regress-guard/commands/regress-resume.md | 100 + .../regress-guard/commands/regress-stats.md | 77 + .../regress-guard/commands/regress-trace.md | 64 + .../regress-guard/commands/regress-track.md | 47 + .../commands/regress-uninstall.md | 34 + .../regress-guard/commands/regress-update.md | 45 + .../regress-guard/commands/regress-verify.md | 150 ++ .../regress-guard/docs/AGENT-HOOKS.md | 76 + .../regress-guard/docs/PHILOSOPHY.md | 262 +++ .../regress-guard/docs/WORKFLOW.md | 739 ++++++ .../regress-guard/docs/agent-hooks-mapping.md | 57 + .../regress-guard/docs/claim-discipline.md | 79 + .../regress-guard/docs/dsh-support.md | 225 ++ .../docs/feature-graph-design.md | 177 ++ .../regress-guard/docs/pr-auto-review.md | 156 ++ ...71\344\275\215\350\257\204\345\256\241.md" | 141 ++ .../docs/stability-conditions.md | 50 + .../regress-guard/docs/subagent-playbook.md | 125 + ...53\344\273\275\350\257\255\344\271\211.md" | 44 + ...17\347\231\275\346\214\207\345\215\227.md" | 166 ++ .../regress-guard/hooks/hooks.json | 159 ++ .../hooks/scripts/boundary_guard.py | 632 +++++ .../hooks/scripts/compact_notice.py | 38 + .../hooks/scripts/execution_valve.py | 401 ++++ .../regress-guard/hooks/scripts/fail_watch.py | 174 ++ .../regress-guard/hooks/scripts/launcher.js | 145 ++ .../hooks/scripts/lib/advisor_token.py | 82 + .../hooks/scripts/lib/agent_hooks_adapter.py | 233 ++ .../hooks/scripts/lib/cochange_rules.py | 323 +++ .../regress-guard/hooks/scripts/lib/facts.py | 174 ++ .../hooks/scripts/lib/filelock.py | 87 + .../hooks/scripts/lib/git_diff_analyzer.py | 180 ++ .../hooks/scripts/lib/history.py | 847 +++++++ .../hooks/scripts/lib/host_env.py | 173 ++ .../hooks/scripts/lib/journal.py | 235 ++ .../regress-guard/hooks/scripts/lib/lease.py | 135 ++ .../hooks/scripts/lib/manifest_fields.py | 81 + .../hooks/scripts/lib/manifest_parser.py | 443 ++++ .../regress-guard/hooks/scripts/lib/notify.py | 556 +++++ .../hooks/scripts/lib/pending.py | 268 +++ .../hooks/scripts/lib/plan_approve.py | 494 ++++ .../hooks/scripts/lib/rules_ledger.py | 484 ++++ .../hooks/scripts/lib/scan_check.py | 96 + .../hooks/scripts/lib/secret_scan.py | 89 + .../hooks/scripts/lib/sentinel.py | 127 + .../hooks/scripts/lib/session_relay.py | 102 + .../hooks/scripts/lib/test_cache.py | 192 ++ .../hooks/scripts/lib/test_runner.py | 576 +++++ .../hooks/scripts/lib/veto_search.py | 143 ++ .../hooks/scripts/lib/wecom_notify.py | 165 ++ .../hooks/scripts/plan_bridge.py | 481 ++++ .../hooks/scripts/pre_commit_guard.py | 2045 +++++++++++++++++ .../hooks/scripts/prompt_intercept.py | 484 ++++ .../hooks/scripts/read_before_edit_guard.py | 450 ++++ .../hooks/scripts/reflection_check.py | 604 +++++ .../regress-guard/hooks/scripts/risk_watch.py | 292 +++ .../regress-guard/hooks/scripts/self_heal.py | 704 ++++++ .../hooks/scripts/stop_notify.py | 217 ++ external_plugins/regress-guard/install.py | 486 ++++ external_plugins/regress-guard/install.sh | 20 + .../regress-guard/requirements-dev.txt | 11 + .../regress-guard/scripts/cache_experiment.py | 127 + .../regress-guard/scripts/check_docs.py | 287 +++ .../regress-guard/scripts/clean_env_check.sh | 30 + .../regress-guard/scripts/critic_pass.py | 252 ++ .../regress-guard/scripts/health_probe.py | 114 + .../regress-guard/scripts/heldout_gate.py | 451 ++++ .../regress-guard/scripts/install_dogfood.sh | 100 + .../regress-guard/scripts/install_dsh.py | 425 ++++ .../scripts/post_install_check.py | 161 ++ .../scripts/replay_experiment.py | 242 ++ .../regress-guard/templates/adr.md | 37 + .../regress-guard/templates/product-arch.md | 26 + .../templates/product-context.md | 22 + .../templates/regress-dir-readme.md | 34 + .../templates/regress-manifest.md | 251 ++ external_plugins/regress-guard/uninstall.py | 169 ++ marketplace.json | 25 + 93 files changed, 21422 insertions(+) create mode 100644 external_plugins/regress-guard/.zcode-plugin/plugin.json create mode 100644 external_plugins/regress-guard/LICENSE create mode 100644 external_plugins/regress-guard/README.md create mode 100644 external_plugins/regress-guard/README_CN.md create mode 100644 external_plugins/regress-guard/benchmarks/README.md create mode 100644 external_plugins/regress-guard/benchmarks/cases.json create mode 100644 external_plugins/regress-guard/commands/regress-bypass.md create mode 100644 external_plugins/regress-guard/commands/regress-characterize.md create mode 100644 external_plugins/regress-guard/commands/regress-evolve.md create mode 100644 external_plugins/regress-guard/commands/regress-finish.md create mode 100644 external_plugins/regress-guard/commands/regress-init.md create mode 100644 external_plugins/regress-guard/commands/regress-install.md create mode 100644 external_plugins/regress-guard/commands/regress-learn.md create mode 100644 external_plugins/regress-guard/commands/regress-plan.md create mode 100644 external_plugins/regress-guard/commands/regress-quick.md create mode 100644 external_plugins/regress-guard/commands/regress-resume.md create mode 100644 external_plugins/regress-guard/commands/regress-stats.md create mode 100644 external_plugins/regress-guard/commands/regress-trace.md create mode 100644 external_plugins/regress-guard/commands/regress-track.md create mode 100644 external_plugins/regress-guard/commands/regress-uninstall.md create mode 100644 external_plugins/regress-guard/commands/regress-update.md create mode 100644 external_plugins/regress-guard/commands/regress-verify.md create mode 100644 external_plugins/regress-guard/docs/AGENT-HOOKS.md create mode 100644 external_plugins/regress-guard/docs/PHILOSOPHY.md create mode 100644 external_plugins/regress-guard/docs/WORKFLOW.md create mode 100644 external_plugins/regress-guard/docs/agent-hooks-mapping.md create mode 100644 external_plugins/regress-guard/docs/claim-discipline.md create mode 100644 external_plugins/regress-guard/docs/dsh-support.md create mode 100644 external_plugins/regress-guard/docs/feature-graph-design.md create mode 100644 external_plugins/regress-guard/docs/pr-auto-review.md create mode 100644 "external_plugins/regress-guard/docs/reviews/2026-09-08-\345\205\250\346\226\271\344\275\215\350\257\204\345\256\241.md" create mode 100644 external_plugins/regress-guard/docs/stability-conditions.md create mode 100644 external_plugins/regress-guard/docs/subagent-playbook.md create mode 100644 "external_plugins/regress-guard/docs/\344\274\232\350\257\235\350\272\253\344\273\275\350\257\255\344\271\211.md" create mode 100644 "external_plugins/regress-guard/docs/\345\260\217\347\231\275\346\214\207\345\215\227.md" create mode 100644 external_plugins/regress-guard/hooks/hooks.json create mode 100644 external_plugins/regress-guard/hooks/scripts/boundary_guard.py create mode 100644 external_plugins/regress-guard/hooks/scripts/compact_notice.py create mode 100644 external_plugins/regress-guard/hooks/scripts/execution_valve.py create mode 100755 external_plugins/regress-guard/hooks/scripts/fail_watch.py create mode 100644 external_plugins/regress-guard/hooks/scripts/launcher.js create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/advisor_token.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/agent_hooks_adapter.py create mode 100755 external_plugins/regress-guard/hooks/scripts/lib/cochange_rules.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/facts.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/filelock.py create mode 100755 external_plugins/regress-guard/hooks/scripts/lib/git_diff_analyzer.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/history.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/host_env.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/journal.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/lease.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/manifest_fields.py create mode 100755 external_plugins/regress-guard/hooks/scripts/lib/manifest_parser.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/notify.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/pending.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/plan_approve.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/rules_ledger.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/scan_check.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/secret_scan.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/sentinel.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/session_relay.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/test_cache.py create mode 100755 external_plugins/regress-guard/hooks/scripts/lib/test_runner.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/veto_search.py create mode 100644 external_plugins/regress-guard/hooks/scripts/lib/wecom_notify.py create mode 100644 external_plugins/regress-guard/hooks/scripts/plan_bridge.py create mode 100644 external_plugins/regress-guard/hooks/scripts/pre_commit_guard.py create mode 100755 external_plugins/regress-guard/hooks/scripts/prompt_intercept.py create mode 100755 external_plugins/regress-guard/hooks/scripts/read_before_edit_guard.py create mode 100755 external_plugins/regress-guard/hooks/scripts/reflection_check.py create mode 100644 external_plugins/regress-guard/hooks/scripts/risk_watch.py create mode 100755 external_plugins/regress-guard/hooks/scripts/self_heal.py create mode 100644 external_plugins/regress-guard/hooks/scripts/stop_notify.py create mode 100644 external_plugins/regress-guard/install.py create mode 100755 external_plugins/regress-guard/install.sh create mode 100644 external_plugins/regress-guard/requirements-dev.txt create mode 100644 external_plugins/regress-guard/scripts/cache_experiment.py create mode 100755 external_plugins/regress-guard/scripts/check_docs.py create mode 100755 external_plugins/regress-guard/scripts/clean_env_check.sh create mode 100644 external_plugins/regress-guard/scripts/critic_pass.py create mode 100644 external_plugins/regress-guard/scripts/health_probe.py create mode 100644 external_plugins/regress-guard/scripts/heldout_gate.py create mode 100755 external_plugins/regress-guard/scripts/install_dogfood.sh create mode 100644 external_plugins/regress-guard/scripts/install_dsh.py create mode 100644 external_plugins/regress-guard/scripts/post_install_check.py create mode 100644 external_plugins/regress-guard/scripts/replay_experiment.py create mode 100644 external_plugins/regress-guard/templates/adr.md create mode 100644 external_plugins/regress-guard/templates/product-arch.md create mode 100644 external_plugins/regress-guard/templates/product-context.md create mode 100644 external_plugins/regress-guard/templates/regress-dir-readme.md create mode 100644 external_plugins/regress-guard/templates/regress-manifest.md create mode 100644 external_plugins/regress-guard/uninstall.py diff --git a/external_plugins/regress-guard/.zcode-plugin/plugin.json b/external_plugins/regress-guard/.zcode-plugin/plugin.json new file mode 100644 index 00000000..3aa3027e --- /dev/null +++ b/external_plugins/regress-guard/.zcode-plugin/plugin.json @@ -0,0 +1,27 @@ +{ + "name": "regress-guard", + "version": "2.8.24", + "description": "AI-coding governance gate for ZCode: pre-commit test enforcement, manifest-boundary guard, six-layer trust surface, assumption ledger, and FP-calibrated telemetry.", + "author": { + "name": "1339190177", + "url": "https://github.com/1339190177" + }, + "license": "MIT", + "commands": "commands", + "hooks": "hooks", + "description_i18n": { + "en": "AI-coding governance gate for ZCode: pre-commit test enforcement, manifest-boundary guard, six-layer trust surface, assumption ledger, and FP-calibrated telemetry.", + "zh-CN": "AI 辅助开发治理门禁:提交前测试强制(不信 AI 自报的 done)、清单边界事前拦截、六层信任面、假设账本与误报校准遥测。" + }, + "keywords": [ + "governance", + "hooks", + "pre-commit", + "testing", + "regression", + "guardrails", + "spec-driven", + "telemetry" + ], + "category": "developer-tools" +} \ No newline at end of file diff --git a/external_plugins/regress-guard/LICENSE b/external_plugins/regress-guard/LICENSE new file mode 100644 index 00000000..112f86ee --- /dev/null +++ b/external_plugins/regress-guard/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 1339190177 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/external_plugins/regress-guard/README.md b/external_plugins/regress-guard/README.md new file mode 100644 index 00000000..7383cc40 --- /dev/null +++ b/external_plugins/regress-guard/README.md @@ -0,0 +1,182 @@ +# regress-guard + +> AI-coding governance gate for ZCode: **don't trust the agent's "done"** — the gate +> re-runs your tests itself, blocks edits outside the declared change-scope, and +> keeps human-only-calibrated telemetry on every interception. +> +> Design philosophy first: [docs/PHILOSOPHY.md](docs/PHILOSOPHY.md) (the design +> principles and the real incidents behind them — count derived, never +> hand-written). Features age, principles don't. +> Full workflow: [docs/WORKFLOW.md](docs/WORKFLOW.md) · 中文文档:[README_CN.md](README_CN.md) + +## Why + +Agents report "done" confidently and cheaply. This plugin makes "done" a +machine-verified claim, at the only surface that matters in a direct-to-main +workflow: **the commit**. + +- **Completion re-verification (the core)**: a PreToolUse gate intercepts commit + commands and runs the project's own test suite itself — agent self-reports are + never trusted. EARS-style acceptance rows must be checked off with evidence + before `done` is stamped. Same-tree retries hit a content-keyed result cache + (0-second gates). +- **Boundary guard**: edits/writes to files not declared in the active change + manifest are blocked before they happen — the plan's file list *is* the boundary. +- **Blast-radius gate**: commits touching configured high-impact paths (contracts, + shared packages) or spanning ≥2 monorepo units demand an M-tier manifest plus an + explicit `impact_scope` declaration; unconfigured monorepos get a warn-only hint. +- **Assumption ledger**: when reality falsifies a plan's assumption, the + was→reality→evidence record is mandatory. +- **Rule lifecycle (canary for knowledge)**: learned rules enter as *shadow* + (still recalled, 🔬-marked), accumulate fires, and reach *curated* only by + human promotion from an evidence card — or get retired (kept on file) when + they starve past TTL. +- **Six-layer trust surface**: project-level notification channels require a + human-granted trust table + content pinning; HOME/env-injection redirection is + neutralized at the launcher allowlist. Cloned-repo injection surfaces are closed. +- **FP-calibrated telemetry**: every interception is adjudicated later by the + human (useful / false_positive / ignored) — the agent gets no vote on its own + accuracy. Public benchmark: 82 cases / 13 families, see [benchmarks/](benchmarks/). +- **Self-healing**: session start compares the installed copy against the source + repo; version drift is announced, not silently tolerated. +- **Dual-host**: the same guard chain also runs as a native plugin on DeepSeek + dsh desktop (five interception points; web/headless/superagent profiles). +- **Machine PR-review loop (optional)**: a 90-second systemd user sweep + mechanically triages agent PRs — mergeable checks, a conflict self-resolution + engine, and same-cause re-review idempotence keep the loop noise-free. +- **Session identity layer**: manifest leases plus a session relay make + cross-session collaboration safe — a continued conversation that mints a new + platform id no longer trips its own guards. + +## Install + +**Marketplace (recommended)**: install `regress-guard` from the ZCode plugin +marketplace — hooks activate via the host runtime, and the plugin can be +toggled/upgraded natively. (Listing under review upstream — see PR +[zai-org/zcode-plugins#34](https://github.com/zai-org/zcode-plugins/pull/34); +available via the personal-source track below in the meantime.) + +**Personal-source track**: add marketplace source +`github:1339190177/regress-guard` in ZCode settings, then install. + +**Dev track**: clone this repo (`git clone +https://github.com/1339190177/regress-guard.git`), then +`python3 install.py` (Windows: `python install.py`; POSIX may also `bash install.sh`) +(or tell the AI `/regress-install`). Use one track at a time. + +Requirements: ZCode client + Python 3.10+ (the installer checks and refuses +early with an upgrade path — no obscure heredoc crashes). Runtime has +**zero third-party Python dependencies** (`filelock` is optional — absent → +degrades to lock-free atomic write, v1.95.1). To run this repo's own test +suite: `pip install -r requirements-dev.txt` (pytest / anyio / filelock / +pyyaml / pytest-cov). + +**Supported environments** (batch 191): Linux and Windows 10+ are the +CI-verified platforms (Python 3.10 & 3.12 × ubuntu/windows matrix, +`.github/workflows/ci.yml`). Since v2.8.0 all hooks route through +`node launcher.js` (zero `python3`-command dependency — Windows only ships +`python.exe`/`py.exe`), the installer is native Python (`python install.py`), +and stdio/file IO is UTF-8 hardened against cp936. Git for Windows is +recommended (git is a hard prerequisite anyway; its bundled bash also serves +manifest `verify` shell one-liners). macOS is expected to work (only +`fcntl`/`pwd` from stdlib are Unix-specific) but is not CI-verified. Git tags +(`v2.0.0`+ milestones, forward from `v2.1.0`) mark published releases. + +### What your first interception looks like + +After install, hooks fail **open** in projects without `.regress/` (nothing +changes until you `/regress-init`). Once a manifest is active: + +- Editing a file outside the manifest boundary → the edit is **blocked + before it happens**, with three escape routes printed in the message + (extend via track / finish the current task / disable per project). +- Committing → the gate re-runs the project's tests; failures, unchecked + acceptance rows, or leaked secrets block the commit with the reason and + the bypass path. +- A crashing guard **blocks** rather than silently passes (fail-closed, + v1.99) — escape hatches: `/regress-bypass ` or the per-feature + config switches in docs/WORKFLOW.md. + +Note on `RG_*` env switches: hooks registered via `launcher.js` run in a +**sanitized environment** (v1.69) — `RG_*`/`WECOM_*`/`GIT_*` variables set +in your shell do **not** reach those hooks by design. Use config switches +(`.regress/config.json`) or `/regress-bypass` instead. + +Fresh-install smoke test (the machine-runnable stranger funnel): +`bash scripts/install_dogfood.sh` — installs into a throwaway home, runs +the four post-install checks, and exercises boundary-block / valve-block / +gate-pass end to end. + +## Five-minute start + +1. In any project, tell the AI: `/regress-init` +2. Daily flow: state a requirement → the AI produces a plan card → you + **approve / edit / cancel** → it works (with boundary enforcement) → on + commit, the gate re-runs tests and blocks on failure or unchecked + acceptance rows +3. Project state and archaeology live in `.regress/` + +## Side-effects declaration (transparency contract) + +- **Execution**: all hooks are local Python 3.10+ processes (no prebuilt + binaries, no obfuscated source) +- **File writes**: `.regress/` inside projects (manifests/history/ledgers) and + `~/.zcode/regress-guard-hooks` (installed copy) +- **Command execution**: exactly two classes — the gate invoking the project's + own test runner (pytest/jest/maven/gradle/go, auto-detected), and notification + channel commands (**only for human-granted projects**; untrusted configs fall + back to machine level, closing the cloned-repo injection surface) +- **Network**: zero egress by default. Optional interception notifications via + WeCom self-built apps — credentials require human trust-grant and the API base + is pinned to the official domain. No MCP, no telemetry upload, no third-party + analytics +- **Hook surface**: PreToolUse (commit gate / boundary guard / execution valve), + PostToolUse (manifest bridge), UserPromptSubmit (governance context), + SessionStart (self-heal), Stop (end-of-turn notices) +- **Resident timers (optional, v2.6+ ecosystem)**: PR review (pr-gate, 90 s + fallback sweep), effect tracking, stuck-watch liveness, proposal sweep — + systemd **user-level** units, installed only when you explicitly enable the + corresponding ecosystem; disable with `systemctl --user disable --now `, + inspect under `~/.config/systemd/user/`. No system-level units. +- **Escapes**: `/regress-bypass N` gives a time-boxed amnesty (audited); each + feature has an independent env switch + +## Health & verification + +`bash validate.sh` (expect 5/5) · `python3 -m pytest tests/ -q` (expect green) +· post-install smoke check runs automatically with `install.sh` + +## License + +MIT. Third-party: none at runtime (stdlib only). + + + +**实况(由 gen_reference.py 生成,勿手改本区)** + +- 命令:16 个 · hook 注册:11 个事件条目 · 测试函数:1052 个 + +| 命令 | 说明 | +|---|---| +| `/regress-bypass` | 紧急绕过回归卡点(限时 + 审计日志),用于 hotfix 等紧急场景 | +| `/regress-characterize` | 特征测试生成:改无测试老代码前先钉住现状行为(golden master——探针真跑取真值,不猜输出) | +| `/regress-evolve` | 遇到难点时查社区经验→适配项目→沉淀为知识库(不再重复查) | +| `/regress-finish` | 收尾流水线:track 回写 → verify 全证据 → 代谢沉淀 → 状态推进 → 汇总报告(一条命令走完收尾,人只出现在决策点) | +| `/regress-init` | 初始化项目的 .regress/ 数据目录(代码已在全局级安装,这里只建数据) | +| `/regress-install` | 一键安装 regress-guard 到用户级(零配置,装一次全局生效) | +| `/regress-learn` | 分析历史+检测框架规则,输出项目洞察,写入 AGENTS.md | +| `/regress-plan` | 需求→解析→消歧→改动清单。AI 先补全上下文再动手(不问能推断的,只问关键分歧) | +| `/regress-quick` | 快速模式——机器判据达标的小改动专用(合并 plan+track 一步到位) | +| `/regress-resume` | 从 .regress/ 产物层单侧重建工作现场(断点续作,不依赖对话历史) | +| `/regress-stats` | 健康报表:门禁拦截/债务/规律命中/僵尸清单/钩子活性一屏读(观测期仪表盘,只读不改) | +| `/regress-trace` | 查看交付链:需求→会话→事件→提交 的可追溯视图(文本版 Inspector) | +| `/regress-track` | 对比 git diff 发现 F3 并直接回写(AI 完成修改后自动执行,不需用户手动触发) | +| `/regress-uninstall` | 卸载 regress-guard(清理用户级配置 + hook + 命令) | +| `/regress-update` | 检查并更新 regress-guard 到最新版(手动触发;SessionStart 也会自动检测) | +| `/regress-verify` | 跑测试预览结果(提交时 hook 也会自动跑;测试失败 AI 应自行修复重跑) | + + + +## 编号空间注记(批187) + +插件自家开发号 `REGRESS-2026-NNN`(年-序)与接入仓工单号(如 tongtu 旧 `REGRESS-NNN`/新 `REGRESS-YYMMDD-hex4`,2026-11-03 改制 commit 6078f33)是**三个独立编号空间**,互不统一续编。plan_bridge 铸号按清单目录在场格式自适配:年格式续年、新格式按当日+随机 hex 铸、纯旧格式续旧、旧空间在新格式在场后视为冻结。 diff --git a/external_plugins/regress-guard/README_CN.md b/external_plugins/regress-guard/README_CN.md new file mode 100644 index 00000000..f7cb50ee --- /dev/null +++ b/external_plugins/regress-guard/README_CN.md @@ -0,0 +1,211 @@ +# regress-guard + +> AI 辅助开发的测试回归卡点 + 反脆弱控制体(四公理架构,版本见 plugin.json) +> +> **先读思想**:[docs/PHILOSOPHY.md](docs/PHILOSOPHY.md)——设计哲学与它们背后的真实教训(条数由机器派生,不手写)。功能会过时,思想不会。 + +## 安装(三轨) + +**官方市场轨(推荐)**:ZCode 插件市场搜索 regress-guard 一键安装 +(官方市场收录后可用;市场安装即含全部钩子,无需 install.sh)。 + +## 安装(另两轨) + +**插件轨(推荐,实验路径)**:本仓已是 marketplace 就绪形态(根目录 +`marketplace.json` + `hooks/hooks.json` 全 `${ZCODE_PLUGIN_ROOT}` 相对化)。 +在 ZCode 设置 → 插件市场添加个人来源 `github:1339190177/regress-guard`, +安装 regress-guard——钩子经宿主运行时合并自动生效,可随插件开关启停与升级 +(git pin 自带完整性校验)。自检脚本已适配双轨(版本面:.source 戳或已装 plugin.json 任一形态)。 + +**开发轨(现行)**:克隆本仓后 `python3 install.py`(Windows:`python install.py`;POSIX 亦可 `bash install.sh`;或对 AI 说 `/regress-install`) +——脚本拷贝钩子到 `~/.zcode/regress-guard-hooks` 并写用户级注册,附带三查自检。 +两轨只取其一,同装会双份钩子。 + +## 五分钟上手 + +1. **环境**:ZCode 客户端(hooks/commands 机制)+ Python 3.10+ + Node + (支持矩阵·批191 声明:**Linux 与 Windows 10+ 为 CI 实证平台** + (ubuntu/windows × 3.10/3.12 四腿 matrix,见 + `.github/workflows/ci.yml`);v2.8.0 起钩子链全部经 `node launcher.js` + 路由(零 python3 命令依赖——Windows 只装 python.exe/py.exe)、安装器 + Python 原生化(`python install.py`)、cp936 编码根治;建议 Git for + Windows(git 本是硬前置,其自带 bash 亦服务清单 verify 命令);macOS + 预期兼容未实证。运行时零三方依赖;发布物带 git tag(里程碑自 v2.0.0 + 起留痕,远端 tag 自 v2.1.0 前向)) +2. **安装**:`python3 install.py`(Windows:`python install.py`;POSIX + 亦可 `bash install.sh` 薄委托,或对 AI 说 `/regress-install`) +3. **接入项目**:任意项目里对 AI 说 `/regress-init` +4. **日常**:说需求 → AI 出计划卡片 → 你「批准/修改/取消」→ 开工; + 提交时 hook 自动跑测试拦问题;卡住看项目里的 `.regress/README.md` + +- 人类使用手册:[docs/小白指南.md](docs/小白指南.md)——你只需要会说三类话 +- 完整工作流:[docs/WORKFLOW.md](docs/WORKFLOW.md) +- 健康检查:`bash validate.sh`(应 5/5)· 单元测试:`python3 -m pytest tests/ -q`(应全绿) + +## 与 ZCode 原生能力的关系(分层,不重复) + +本插件遵循第一原则"不做 ZCode 已有的,只做增强层"——原生能力是战友不是对手: + +| ZCode 原生 | 本插件的关系 | 分层逻辑 | +|---|---|---| +| 计划模式(EnterPlanMode) | **互补** | 宿主计划模式管**会话权限**(只读探索,批准后放开工具);本插件 planning 状态管**产物生命周期**(清单未批准,边界守卫拦编辑、批准落 approved.at、临行进否决窗)。一个绑会话、一个绑清单,可叠加:计划阶段建议在宿主计划模式下探索 | +| 智能体(Agent 工具) | **复用** | /regress-plan 步骤 3 的 A/B/C 并行分析(需求理解/改动点/影响面)就是直接委派宿主智能体 | +| TodoWrite | **分层** | 会话内待办(易失)vs 清单 manifest(跨会话、门禁绑定) | +| hooks 事件系统 | **复用** | 全部强制力(11 个注册)骑在宿主 PreToolUse/PostToolUse/Stop/SessionStart 事件上——"外挂宪法"的字面含义 | +| skills / MCP | **复用** | second-opinion 技能、advisor MCP;verify 报告可接 document-skills,GUI 回归可接 browser-use | + +## 可选:第二意见顾问生态 + +部分注入文本会建议调用 `mcp__advisor__consult`(外部第二意见工具)——这是**可选生态**: + +- **未安装顾问**:AI 收到降级提示(自行判断并标注「未获第二意见」),全部核心门禁不受影响 +- **一票关闭**:`REGRESS_AUTO_CONSULT=off` +- 设计哲学:顾问有一票否决权、无一票批准权(见 `commands/regress-plan.md` 步骤 5a)—— + 意见只注入文本、永不执行 + +## 误报口径与公开基准 + +误报口径 **human-only**:拦截是否有用由**人事后裁决**(useful / false_positive / +ignored),AI 无投票权——门禁数字不自己给自己打分,比厂商自报口径严。 +本仓测试内容本就公开,[benchmarks/](benchmarks/) 基准集是对既有攻击回放用例的 +策展,**非新披露**。方法论、实测数字与引用格式见 +[benchmarks/README.md](benchmarks/README.md)。 + +## 四公理架构(v1.8:基于脆弱性前置的分布式认知控制论) + +| 公理 | 机制 | 强制力 | +|------|------|--------| +| **一·脆弱性前置** | 清单 `fragile_points` 挂牌(open/locked/flagged);verify 命令拿证据;环境指纹 env.lock.json 漂移检测 | open 状态 **hook 阻断提交** | +| **二·认知物质化** | 决策链写 `.regress/decisions.md`;文件指纹(mtime+size)——读后被外部改过禁止盲改 | 指纹不匹配 **hook 阻断修改** | +| **三·考古持久化** | 失败/风险动作/用户纠正自动埋入 `.regress/journal/`(append-only,随 git 入库);/regress-learn 挖跨会话重复失败 | 自动、无感、不可关闭(REGRESS_JOURNAL=off 除外) | +| **四·建议执行分离** | 执行阀:mkfs/dd if=/force push/DROP/TRUNCATE/chmod -R 777/项目外 rm -rf 需显式令牌;顾问意见只注入文本、永不执行 | 无令牌 **hook 阻断**(令牌 `REGRESS_CONFIRM=YES`) | + +一句话:不要指望变聪明,要指望变死板——灰尘对得上(指纹)、失败刻在石头上(地层)、拆弹要输密码(令牌)、未挂牌的脆弱点不许出门(V 门禁)。 + +## 核心能力 + +| 能力 | 实现方式 | 什么时候触发 | +|------|---------|------------| +| **commit 门禁** | hook 自跑测试,exit 2 阻断 | git commit 时 | +| **F3 追踪** | git diff 对比清单 | /regress-track | +| **先读后改** | hook 阻断(ratio=2)+ 文件指纹复核 | Edit/Write 时 | +| **pre-mortem** | Stop hook 注入提醒 | 改≥3 文件时 | +| **自愈+升级** | SessionStart hook | ZCode 启动时 | +| **需求入口检查** | UserPromptSubmit hook | 用户输入时 | +| **提交观测** | git post-commit 钩子(静默/自禁用/monorepo 感知) | 任何来源的提交,含 IDE/终端 | +| **覆盖率信号** | jest --coverage 自动采集 | 每次门禁测试 | +| **经验注入** | 启动时摘要注入(防重复门) | 项目规律变化时 | +| **执行阀** | PreToolUse(Bash) 令牌制 | 不可逆命令时 | +| **开发边界守卫** | PreToolUse(Edit/Write) 事前拦截越界编辑;track 回写即扩界 | 活跃清单存在时 | +| **计划审批落产物** | planning 清单边界内拦编辑;plan_approve.py 转写 approved.{at,note} + base_head 漂移检查;人类可直接填 approved.at(产物直通) | /regress-plan 步骤 5 | +| **现场重建** | /regress-resume 从 .regress/ 产物层单侧重建工作现场(不依赖对话) | 断点续作时 | +| **受阻一等状态** | plan_approve.py --block 四问落产物(阻塞/已试/为何不能绕/需要人类什么);受阻期间边界守卫拦编辑;--unblock 解阻 | 实施卡住时 | +| **假设失效化石** | 推测被实测证伪→追加 was→reality→evidence + journal add assumption_broken | verify 证伪假设时 | +| **假设账本(软引导)** | 排障任务先检验后动手:假设/检验命令/结果/裁决四列落清单正文;第一嫌疑人=自己最近的 diff;失败即证伪不叠层;宣称跟着证据走(不说"根治"只说"缓解待验证")。病例:REGRESS-005 三轮防御 6+ 轮部署,真凶是自己上一轮的参数 | /regress-plan 步骤 2e · 排障时 | +| **项目禁改区** | config boundary.forbidden 冻结区通配——与任务无关任何状态都拦;逃生口统一 /regress-bypass(赦后记债) | 编辑冻结区时 | +| **赦免权普适化** | bypass_until 与 commit 门禁同源,边界/提交类流程闸统一认;**不可赦名单两条**:执行阀(物理不可逆,逐次令牌)+ 先读后改(防盲改,读文件成本≈10s,赦它省时间只会引雷) | bypass 有效期内 | +| **解析单一来源** | lib/manifest_fields.py 统一 frontmatter 读取与可编辑性判定;validate 架构守卫保证块正则不出 lib | 维护时 | +| **感官证据(AVS)** | sensory 脆弱点问人只收布尔 → human_check 化石;门禁验化石存在性不复跑感官;拓扑新增 oracle 类(测试替身保真度) | 终验/提交时 | +| **顾问预审·临行** | 计划卡片送顾问预审(一票否决权/无一票批准权);预授权+无异议 → --provisional 临行进否决窗,哨兵标 🚀 | /regress-plan 步骤 5a | +| **活跃清单哨兵** | SessionStart 注入进行中任务(id/status/open 数/受阻 need)→ 指路 /regress-resume | 每次会话启动 | +| **老项目自动迁移** | SessionStart self_heal 检测 .regress/ 缺零号 README → 从模板幂等补写(永不覆盖定制版) | 每次会话启动 | +| **失忆读者层** | 清单正文写给断上下文的读者:实施顺序一步一响(可观察里程碑)+ 环境准备 + 报错自救(rescue 字段) | /regress-plan 步骤 4.7 | +| **考古地层** | 探测器自动 append 到 journal/ | 每次失败/风险/纠正 | +| **代谢链(规律账本)** | finish 收尾必过沉淀位(有料沉淀无料跳过);规律记账(再检出=命中);🍂 半年零命中列降级候选提示人工修剪(永不自动删);🦴 命中≥3 建议经人批准用 skill-creator 固化为宿主 skill——地层是脂肪,规律是肌肉,skill 是骨骼 | /regress-finish · /regress-learn | +| **需求判据层(软引导)** | 验收标准节(判据/证据/状态——"根治/更好"类词必须挂判据,非功能底线需求阶段定型)+ 设计备选卡(没有备选=没想过);未门禁化——误用病例出现再议 | /regress-plan 4.8 · verify | +| **产品适定性层(软引导)** | 产品上下文卡(用户/价值观人类填,行业惯例顾问 scout 起草,永不删人类段)+ 草图先行(用户可见功能卡片必备「所见」,否决在草图不在实现后)+ design_rejected 学费化石+ 预审第四问必带搜索(scout 不可用则标注,不以无搜索充数) | /regress-plan 2a·5a · learn | + +## 多宿主与机器生态(v2.4+) + +上表是单会话内的治理链;v2.4 起插件长出第二条腿——**多宿主与机器自治面** +(本节详述中文版定位;英文 README 保持短脸面只列要点): + +| 能力族 | 实现方式 | 自哪个版本 | +|--------|---------|-----------| +| **dsh 双宿主** | DeepSeek dsh 桌面版原生插件轨道(五拦截点:pre/post-execute+session-start+pre-step+turn-stopping);web/headless/superagent 三 profile;桥兼容 `--mode bridge`([docs/dsh-support.md](docs/dsh-support.md)) | v2.4.0(批166)起,v2.6.0 默认原生 | +| **PR 机械评审** | pr_gate:90 秒 systemd 兜底扫描+事件驱动,mergeable 三态、冲突自处理引擎(单槽串行+熔断 agent 接力)、同因重评幂等闸、升级评论附判据教学([docs/pr-auto-review.md](docs/pr-auto-review.md)) | v2.6.x 起,批184/207/214/220 递进 | +| **效果跟踪与服务活性** | effect_track:容器清理/磁盘压力 GC(超阈回收终态无主树,ui-evidence 归档校验)/首过率与新会话摩擦遥测;stuck_watch:卡滞三态+服务活性双信号(lock 目录 mtime OR /proc 精确边界),活→停跃迁一次性告警 | 批208-221 渐进 | +| **会话身份层** | session_relay 会话身份中继(钩子侧盖章/agent 侧读);my_sids 身份集合=env∪新鲜 relay(双 id 工件根治——长对话续接换平台号而 shell env 停旧号不再自拦);清单 lease 租约跨会话协作;双清单并存归因引擎(交集评分+全序决胜) | 批211-218 | +| **Windows 对称与编码** | PowerShell 三方言递归删目标判定(与 POSIX rm -rf 对称——外部用户三轮反馈标本);PYTHONUTF8 进程级+父进程九调用位显式 encoding+AST ratchet 测试(subprocess text 缺 encoding 当场红) | 批195/206 | + +## 命令 + +| 命令 | 作用 | +|------|------| +| `/regress-install` / `uninstall` / `update` | 安装/卸载/升级 | +| `/regress-init` | 初始化项目 .regress/ | +| `/regress-plan` | 需求解析 + 改动清单 | +| `/regress-track` | 发现 F3 + 回写 | +| `/regress-verify` | 跑测试 | +| `/regress-quick` | 快速模式 | +| `/regress-bypass` | 紧急绕过 | +| `/regress-learn` | 分析历史 + 框架规则 → 写入 AGENTS.md | +| `/regress-trace` | 交付链视图(意图→过程→产出) | +| `/regress-resume` | 断点续作:产物层单侧重建现场 | +| `/regress-finish` | 收尾流水线:track→verify→状态推进→报告 | +| `/regress-evolve` | 查社区 → 适配 → 沉淀到知识库 | + +## 三种工作密度 + +| 模式 | 步骤 | 适用 | +|------|------|------| +| full | 说需求→批准卡片→实施→finish→commit(门禁发 done) | 团队/大需求 | +| fast | 改代码→quick→commit | 个人/小改动 | +| bypass | bypass→commit | 紧急 hotfix | + +## 配置 + +`.regress/config.json`: +```json +{"strict": true, "read_before_edit_ratio": 2} +``` + +## 安装架构 + +代码全局装一次(`~/.zcode/`),数据各项目独立(`项目/.regress/`)。 + +详见 [小白指南](docs/小白指南.md) · [WORKFLOW](docs/WORKFLOW.md) + + + +**实况(由 gen_reference.py 生成,勿手改本区)** + +- 命令:16 个 · hook 注册:11 个事件条目 · 测试函数:1052 个 + +| 命令 | 说明 | +|---|---| +| `/regress-bypass` | 紧急绕过回归卡点(限时 + 审计日志),用于 hotfix 等紧急场景 | +| `/regress-characterize` | 特征测试生成:改无测试老代码前先钉住现状行为(golden master——探针真跑取真值,不猜输出) | +| `/regress-evolve` | 遇到难点时查社区经验→适配项目→沉淀为知识库(不再重复查) | +| `/regress-finish` | 收尾流水线:track 回写 → verify 全证据 → 代谢沉淀 → 状态推进 → 汇总报告(一条命令走完收尾,人只出现在决策点) | +| `/regress-init` | 初始化项目的 .regress/ 数据目录(代码已在全局级安装,这里只建数据) | +| `/regress-install` | 一键安装 regress-guard 到用户级(零配置,装一次全局生效) | +| `/regress-learn` | 分析历史+检测框架规则,输出项目洞察,写入 AGENTS.md | +| `/regress-plan` | 需求→解析→消歧→改动清单。AI 先补全上下文再动手(不问能推断的,只问关键分歧) | +| `/regress-quick` | 快速模式——机器判据达标的小改动专用(合并 plan+track 一步到位) | +| `/regress-resume` | 从 .regress/ 产物层单侧重建工作现场(断点续作,不依赖对话历史) | +| `/regress-stats` | 健康报表:门禁拦截/债务/规律命中/僵尸清单/钩子活性一屏读(观测期仪表盘,只读不改) | +| `/regress-trace` | 查看交付链:需求→会话→事件→提交 的可追溯视图(文本版 Inspector) | +| `/regress-track` | 对比 git diff 发现 F3 并直接回写(AI 完成修改后自动执行,不需用户手动触发) | +| `/regress-uninstall` | 卸载 regress-guard(清理用户级配置 + hook + 命令) | +| `/regress-update` | 检查并更新 regress-guard 到最新版(手动触发;SessionStart 也会自动检测) | +| `/regress-verify` | 跑测试预览结果(提交时 hook 也会自动跑;测试失败 AI 应自行修复重跑) | + + + +## 副作用声明(透明度契约) + +- **执行**:全部钩子为本机 Python 3.10+ 进程(无预编译二进制、无混淆源码) +- **写文件**:项目内 `.regress/`(清单/历史/台账/考古地层)与 `~/.zcode/regress-guard-hooks`(安装副本) +- **命令执行**:仅两类——门禁自跑项目测试运行器(pytest/jest/maven/gradle/go 自动探测); + 通知通道命令(**仅人工授信项目可配**,未受信一律回退机器级,克隆仓库注入面封堵) +- **网络**:默认零外发。可选拦截通知(企业微信自建应用)——凭据须人工授信且 API 域 + 钉官方域;无 MCP、无遥测上传、无第三方分析 +- **Hook 面**:PreToolUse(提交门禁/边界守卫/执行阀)、PostToolUse(清单回写桥)、 + UserPromptSubmit(治理上下文)、SessionStart(自愈)、Stop(轮末警示) +- **常驻定时器(可选组件,v2.6+ 生态)**:PR 评审(pr-gate,90 秒兜底扫描)、 + 效果跟踪(effect_track)、服务活性(stuck-watch)、提案扫描(proposal-sweep) + 四个 systemd **用户级**单元——仅在你显式启用对应生态(PR 评审/效果跟踪)时 + 安装,全部 `systemctl --user disable --now <单元>` 可停、`~/.config/systemd/user/` + 可查,不碰系统级 +- **逃生**:`/regress-bypass N` 分钟级限时赦免(留债审计);各特性独立 env 开关 diff --git a/external_plugins/regress-guard/benchmarks/README.md b/external_plugins/regress-guard/benchmarks/README.md new file mode 100644 index 00000000..30fff153 --- /dev/null +++ b/external_plugins/regress-guard/benchmarks/README.md @@ -0,0 +1,100 @@ +# 公开攻击回放基准(benchmarks/cases.json) + +对 regress-guard 既有攻击回放测试的**策展索引**:把散在 `tests/` 里的供应链注入、 +信任旁路、缓存伪造、边界旁路、密钥外发、执行阀、先读后改指纹、提交门禁、 +全貌新鲜度、验收入环、规律召回、待决回流等真实翻车标本,整理为可独立引用的 +基准用例集(当前 82 例 / 13 族)。 + +## 非新披露声明 + +**本集是策展,不是新披露。** 所有被引用的测试内容本来就公开于本仓 `tests/` +目录(攻击回放自 v1.29 起即以测试形态入库);cases.json 只增加一层脱敏摘要 +与稳定 id,不引入任何新的攻击载荷、命令原文或密钥样例形态。摘要粒度停在 +"环境变量注入重定向信任面"这一层,不复述具体变量名、命令与值。 + +## 方法论:human-only 误报口径(为什么比厂商自报严) + +安全工具厂商的基准数字通常由厂商自报——自己拦截、自己判定拦截是否有价值, +既当运动员又当裁判。本插件的口径不同: + +- **拦截是否有用,由人**事后**裁决**:门禁每次拦截经人复核,归入三类—— + `useful`(真拦住了问题)/ `false_positive`(误报)/ `ignored`(人主动忽略) +- **AI 无投票权**:AI(包括本插件的自动化层)不能把自己的拦截记为 useful, + 也不能销掉 false_positive——数字不经被测系统的手 +- 对照面同理:放行路径上的对照用例(如授权后放行、暂存态不误拒)红掉时 + 同样计入问题——误放与误拦都算翻车 + +厂商自报口径下"0 误报"是营销数字;human-only 口径下它是审计结果。 + +## 当前实测数字(截至 2026-09-21) + +| 指标 | 值 | +|---|---| +| 拦截累计裁决 | 33 useful / 0 false_positive / 1 ignored | +| 门禁测试缓存命中 | 4 次(键随树变化,命中即同树全绿) | + +数字来源:真实使用中的人工裁决累计,非基准集跑分。基准集本身是 +"这些拦截能力有测试钉着"的证据链,不是分数发生器。 + +## 如何跑 + +每条用例的 `pytest_selector` 字段是可直接使用的 `-k` 表达式: + +```bash +# 单条(以 TRUST-001 为例) +python3 -m pytest tests/ -k "test_trust_default_denied" + +# 一族(launcher-env 拒列钉族,批215) +python3 -m pytest tests/ -k "test_override_injection_denied or test_toolchain_vars_pass or test_deny_list_pinned_exact" + +# 全族回放:按 cases.json 的 pytest_selector 逐条跑,或直接跑源测试文件 +python3 -m pytest tests/test_notify.py tests/test_test_cache.py tests/test_boundary_guard.py \ + tests/test_secret_scan.py tests/test_launcher_env.py tests/test_execution_valve.py \ + tests/test_read_before_edit.py tests/test_pre_commit_guard.py tests/test_scan_check.py \ + tests/test_history.py tests/test_pending.py -q +``` + +清单自身的健康校验(schema / selector 真实可命中 / 摘要长度上限): + +```bash +python3 -m pytest tests/test_benchmark_manifest.py -q +``` + +## 族一览 + +| family | 覆盖面 | 例数 | +|---|---|---| +| notify-trust | 通知通道供应链信任面(env 注入重定向 / 凭据字段门 / API 域钉住) | 5 | +| notify-pin | 同路径换内容分级防线(敏感面调包 / 人工重授信对照) | 2 | +| launcher-env | 守卫子进程环境白名单(全量剥离 / 全集双向钉 / 大小写归一) | 5 | +| test-cache | 门禁测试缓存完整性(键敏感 / 伪造拒绝 / 生存期 / 防死循环对照) | 6 | +| boundary-guard | 开发边界与 shell 写目标提取(越界 / 工具旁路 / 跨会话 / 误报防御) | 8 | +| secret-scan | 提交密钥扫描(高精度命中 / 私钥块 / 目录豁免边界 / 删除行对照) | 4 | +| execution-valve | 不可逆命令执行阀(毁灭类模式 / 复合命令夹带 / 旗标变形 / 令牌授权对照) | 7 | +| edit-fingerprint | 先读后改与文件指纹守卫(盲改 / 读数阈值 / 外部改动失配 / 重读恢复对照) | 7 | +| commit-gate | 提交门禁本体(间接提交识别 / 清单解析 fail-closed / 验收拦截 / 自审键) | 10 | +| scan-freshness | 全貌新鲜度三态与规则A(缺扫描拦 / 占位防绕 / 三态判定 / 轻量豁免对照) | 9 | +| acceptance-gate | 验收标准入环 EARS 机器化(未勾拦 / 占位拦 / 两行判据 / 宽松计勾对照) | 5 | +| rule-recall | 拦截现场规律召回与有效性度量(TOP-3 召回 / 开关与坏账降级 / 三态归类 / 热力图) | 8 | +| pending-reflow | 待决决策自动回流(过门禁闭环 / 精确锚定 / 多义防护 / 误报口径隔离) | 6 | + +schema 冻结为五字段(增删须同步 `tests/test_benchmark_manifest.py` 与本文档): + +```json +{"id": "TRUST-001", "family": "notify-trust", "attack_summary": "一句话脱敏描述", + "expected": "门禁应然行为(拒/拦/剥离/放行/钉回)", "pytest_selector": "-k 表达式"} +``` + +约束:`attack_summary` <= 80 字符(防载荷复述回潮);`id` 全局唯一且形状稳定 +(大写词-三位序号)。 + +## 引用格式 + +按 id 引用,不引用测试函数名(函数会改名,id 不会): + +> regress-guard 公开基准 TRUST-001(环境变量注入重定向通知信任面): +> 通知通道默认拒 + 生产路径零环境变量影响。 + +批量引用时可引族名:`regress-guard benchmarks: launcher-env 族(5 例)`。 +校验引用是否仍有效:`python3 -m pytest tests/test_benchmark_manifest.py -q` +——任何一条 selector 失配(测试被删/改名)都会红。 diff --git a/external_plugins/regress-guard/benchmarks/cases.json b/external_plugins/regress-guard/benchmarks/cases.json new file mode 100644 index 00000000..6688dafc --- /dev/null +++ b/external_plugins/regress-guard/benchmarks/cases.json @@ -0,0 +1,576 @@ +[ + { + "id": "TRUST-001", + "family": "notify-trust", + "attack_summary": "环境变量三重注入重定向通知信任面,诱导未受信项目通道执行", + "expected": "拒:生产路径零环境变量影响,恶意通道不执行且给出人工信任出口", + "pytest_selector": "test_trust_default_denied" + }, + { + "id": "TRUST-002", + "family": "notify-trust", + "attack_summary": "诱导信任管理子命令代写信任表,绕过人工授信", + "expected": "拒:信任视图只读,任何形态不写表文件", + "pytest_selector": "test_trust_cli_readonly_never_writes" + }, + { + "id": "CRED-001", + "family": "notify-trust", + "attack_summary": "克隆仓库配置覆盖通知凭据字段,劫持凭据外发", + "expected": "剥离:未受信项目凭据字段覆盖回退机器级并告警", + "pytest_selector": "test_wecom_cred_fields_gated" + }, + { + "id": "APIBASE-001", + "family": "notify-trust", + "attack_summary": "环境变量劫持通知推送 API 基域指向恶域", + "expected": "钉回:非白名单域被重置为官方域并告警", + "pytest_selector": "test_api_base_pinned" + }, + { + "id": "HOME-001", + "family": "notify-trust", + "attack_summary": "注入家目录变量重定向机器级通知配置读取路径", + "expected": "剥离:默认路径仍按系统账户数据库派生,注入路径无效", + "pytest_selector": "test_machine_conf_path_home_injection_neutralized" + }, + { + "id": "PIN-001", + "family": "notify-pin", + "attack_summary": "同路径换内容替换通知通道敏感配置(同址调包)", + "expected": "拒:敏感面变更被拦,回退已钉配置并提示人工重授信", + "pytest_selector": "test_pin_swap_sensitive_denied" + }, + { + "id": "PIN-002", + "family": "notify-pin", + "attack_summary": "同址调包后人工已重新授信的对照路径", + "expected": "放行:人工重授信后重钉新配置(授权路径不误拦)", + "pytest_selector": "test_pin_human_retrust_repin" + }, + { + "id": "ENV-001", + "family": "launcher-env", + "attack_summary": "全污染环境(覆盖类/凭据类/自定义变量)传入守卫子进程", + "expected": "剥离:白名单外变量全部不出现在子进程环境", + "pytest_selector": "test_override_injection_denied" + }, + { + "id": "ENV-002", + "family": "launcher-env", + "attack_summary": "混合环境中夹带覆盖类与凭据类变量,试探精确输出", + "expected": "剥离:输出恰等于白名单子集,多留一个即红", + "pytest_selector": "test_toolchain_vars_pass" + }, + { + "id": "ENV-003", + "family": "launcher-env", + "attack_summary": "白名单自身被放宽(增条目漂移)的试探", + "expected": "钉死:白名单全集双向等值,增删条目即红", + "pytest_selector": "test_deny_list_pinned_exact" + }, + { + "id": "ENV-004", + "family": "launcher-env", + "attack_summary": "以小写形态夹带覆盖类与凭据类变量绕过比较", + "expected": "剥离:归一比较命中白名单外小写形态仍被剥离", + "pytest_selector": "test_case_normalized_comparison_keeps_original_key" + }, + { + "id": "ENV-005", + "family": "launcher-env", + "attack_summary": "对照:白名单变量夹带在污染环境中传递", + "expected": "保留:白名单变量原键原值到达,同环境毒变量被剥离", + "pytest_selector": "test_legacy_contract_vars_pass" + }, + { + "id": "CACHE-001", + "family": "test-cache", + "attack_summary": "已跟踪文件内容被篡改后重放旧缓存冒充门禁通过", + "expected": "拒:缓存键随内容变化,旧记录失效", + "pytest_selector": "test_key_sensitive_tracked_edit" + }, + { + "id": "CACHE-002", + "family": "test-cache", + "attack_summary": "新增未跟踪文件后重放旧缓存冒充门禁通过", + "expected": "拒:未跟踪新增同样入键,旧记录失效", + "pytest_selector": "test_key_sensitive_untracked_add" + }, + { + "id": "CACHE-003", + "family": "test-cache", + "attack_summary": "删除已跟踪文件后重放旧缓存冒充门禁通过", + "expected": "拒:删除使键变化,旧记录失效", + "pytest_selector": "test_key_sensitive_tracked_delete" + }, + { + "id": "CACHE-004", + "family": "test-cache", + "attack_summary": "伪造失败结果写入缓存冒充通过", + "expected": "拒:仅通过结果可落账,失败不写入", + "pytest_selector": "test_record_rejects_fail" + }, + { + "id": "CACHE-005", + "family": "test-cache", + "attack_summary": "篡改缓存时间戳使陈旧记录永生", + "expected": "拒:超生存期记录查询失效", + "pytest_selector": "test_lookup_ttl_expiry" + }, + { + "id": "CACHE-006", + "family": "test-cache", + "attack_summary": "对照:门禁自身运行时目录写入不得改键(防死循环)", + "expected": "放行:运行时目录不入键,同树缓存命中", + "pytest_selector": "test_key_ignores_regress_runtime" + }, + { + "id": "BOUND-001", + "family": "boundary-guard", + "attack_summary": "编辑活跃清单边界外文件,绕过任务声明面", + "expected": "拦:越界编辑被阻断并指路回写命令", + "pytest_selector": "test_outside_boundary_blocked" + }, + { + "id": "BOUND-002", + "family": "boundary-guard", + "attack_summary": "待批准计划期内经 shell 删除边界内文件(工具旁路)", + "expected": "拦:计划未批准时 shell 写路径与编辑同等拦截", + "pytest_selector": "test_bash_rm_inside_planning_boundary_blocked" + }, + { + "id": "BOUND-003", + "family": "boundary-guard", + "attack_summary": "经 shell 重定向写边界内文件(工具旁路)", + "expected": "拦:重定向目标被识别为写目标并阻断", + "pytest_selector": "test_bash_redirection_write_blocked" + }, + { + "id": "BOUND-004", + "family": "boundary-guard", + "attack_summary": "经移动/原地编辑/低级块拷贝类命令写边界内文件", + "expected": "拦:多形态写命令均被识别为写目标", + "pytest_selector": "test_bash_mv_sed_dd_blocked" + }, + { + "id": "BOUND-005", + "family": "boundary-guard", + "attack_summary": "他会话活跃清单边界内的文件被本会话编辑(竞态)", + "expected": "拦:跨会话编辑被阻断并给出对焦提示", + "pytest_selector": "test_cross_session_edit_blocked" + }, + { + "id": "BOUND-006", + "family": "boundary-guard", + "attack_summary": "任务受阻后利用既往批准戳短路拦截", + "expected": "拦:受阻拦截不被既往批准戳短路", + "pytest_selector": "test_approved_then_blocked_still_blocks" + }, + { + "id": "BOUND-007", + "family": "boundary-guard", + "attack_summary": "载荷文本中嵌入命令词样字符串,试探写目标误判(防误报)", + "expected": "放行:引号载荷内容不判为写目标,无误拦", + "pytest_selector": "test_dequoted_payload_command_word_not_target" + }, + { + "id": "BOUND-008", + "family": "boundary-guard", + "attack_summary": "文档体内嵌入删除与重定向样文本,试探写目标误判", + "expected": "半拦:文档体不判写目标,真实重定向目标仍收", + "pytest_selector": "test_heredoc_body_not_targets_but_redir_is" + }, + { + "id": "SECRET-001", + "family": "secret-scan", + "attack_summary": "新增行夹带高精度形态云凭据", + "expected": "拦:命中并给出文件与行号", + "pytest_selector": "test_high_precision_hit_and_lineno" + }, + { + "id": "SECRET-002", + "family": "secret-scan", + "attack_summary": "新增行夹带私钥块头", + "expected": "拦:私钥块命中", + "pytest_selector": "test_private_key_block" + }, + { + "id": "SECRET-003", + "family": "secret-scan", + "attack_summary": "把真凭据藏进测试目录试探豁免", + "expected": "拦:高精度模式不豁免测试目录", + "pytest_selector": "test_high_precision_not_exempt_in_tests" + }, + { + "id": "SECRET-004", + "family": "secret-scan", + "attack_summary": "对照:删除行中的历史凭据样文本不产生新增告警", + "expected": "放行:只扫新增行,删除行不误报", + "pytest_selector": "test_deleted_lines_not_scanned" + }, + { + "id": "VALVE-001", + "family": "execution-valve", + "attack_summary": "直呼不可逆模式串试探执行阀盲区(毁盘/强推/删库/递归改权类)", + "expected": "拦:毁灭类模式串命中即阻断并提示令牌出口", + "pytest_selector": "test_catastrophic_patterns_block" + }, + { + "id": "VALVE-002", + "family": "execution-valve", + "attack_summary": "复合命令前段良性后段夹带毁灭步骤,试探只查整串首词", + "expected": "拦:串内每段独立过阀,夹带段照拦", + "pytest_selector": "test_compound_command_caught" + }, + { + "id": "VALVE-003", + "family": "execution-valve", + "attack_summary": "删除旗标变形与多旗标聚合,试探字面匹配绕过", + "expected": "拦:旗标归一解析后变形形态同样命中", + "pytest_selector": "test_flag_variants_rm" + }, + { + "id": "VALVE-004", + "family": "execution-valve", + "attack_summary": "递归强删目标逃出项目根目录,试探作用域放行", + "expected": "拦:项目外路径命中阻断(项目内常规目录类不拦)", + "pytest_selector": "test_rm_rf_outside_project_blocks" + }, + { + "id": "VALVE-005", + "family": "execution-valve", + "attack_summary": "对照:良性命令含项目内递归删与常规推送,试探误拦面", + "expected": "放行:模式不中即放,无令牌也不误拦", + "pytest_selector": "test_benign_commands_pass" + }, + { + "id": "VALVE-006", + "family": "execution-valve", + "attack_summary": "对照:显式令牌前缀携不可逆命令走授权出口", + "expected": "放行:令牌在场即人工确认语义,阀门打开", + "pytest_selector": "test_token_unlocks" + }, + { + "id": "VALVE-007", + "family": "execution-valve", + "attack_summary": "顶层非字典畸形载荷试探解析崩溃反向放行", + "expected": "拦:解析异常走阻断出口,fail-closed 不反向", + "pytest_selector": "test_nondict_payload_blocks" + }, + { + "id": "FPIN-001", + "family": "edit-fingerprint", + "attack_summary": "零读直改试探跳过理解环节(盲改)", + "expected": "拦:读数不足即阻断并提示先读后改", + "pytest_selector": "test_blind_edit_blocked" + }, + { + "id": "FPIN-002", + "family": "edit-fingerprint", + "attack_summary": "读数恰差一次(阈值下一格)试探宽松比较", + "expected": "拦:差一次也算不足,阈值无弹性", + "pytest_selector": "test_partial_read_blocked" + }, + { + "id": "FPIN-003", + "family": "edit-fingerprint", + "attack_summary": "对照:读满阈值后再改(守规矩路径)", + "expected": "放行:读数达标即放(授权路径不误拦)", + "pytest_selector": "test_read_then_edit_allowed" + }, + { + "id": "FPIN-004", + "family": "edit-fingerprint", + "attack_summary": "首轮读数耗尽后改未读过的文件(轮次阶梯爬升)(v1.92.0 判据链重构:累计比例死亡螺旋根治)", + "expected": "拦:新一轮重新计读,未读文件被拦并点名", + "pytest_selector": "test_no_spiral_v1120" + }, + { + "id": "FPIN-005", + "family": "edit-fingerprint", + "attack_summary": "读后被外部修改(他会话/工具格式化)再改,指纹失配", + "expected": "拦:指纹不匹配阻断,防基于过期读取态盲改", + "pytest_selector": "test_fingerprint_blocks_external_change" + }, + { + "id": "FPIN-006", + "family": "edit-fingerprint", + "attack_summary": "对照:被拦后重读刷新指纹再改(恢复路径)", + "expected": "放行:重读后指纹对齐即放", + "pytest_selector": "test_fingerprint_reread_restores" + }, + { + "id": "FPIN-007", + "family": "edit-fingerprint", + "attack_summary": "对照:自己改完未重读继续迭代(防误拦)", + "expected": "放行:自改挂起指纹校验,不拦迭代", + "pytest_selector": "test_self_edit_suspends_fingerprint" + }, + { + "id": "GATE-001", + "family": "commit-gate", + "attack_summary": "经版本工具隐式自动提交,试探命令识别只认显式提交", + "expected": "拦:间接提交形态同样被识别进门禁", + "pytest_selector": "test_commit_via_npm_version_detected" + }, + { + "id": "GATE-002", + "family": "commit-gate", + "attack_summary": "清单夹非法编码字节试探解析异常穿透为放行", + "expected": "拦:解码异常不反向,fail-closed 阻断", + "pytest_selector": "test_non_utf8_manifest_blocks" + }, + { + "id": "GATE-003", + "family": "commit-gate", + "attack_summary": "乱写清单头试探手写兜底拿空字段冒充无清单", + "expected": "拦:字段全失即解析失败阻断,不静默当无清单", + "pytest_selector": "test_garbage_yaml_manifest_blocks" + }, + { + "id": "GATE-004", + "family": "commit-gate", + "attack_summary": "自造清单状态词不在活跃枚举内(语义反转回归锁定)", + "expected": "放行:未登记状态不算活跃,不误卡提交并留痕", + "pytest_selector": "test_invented_status_not_active" + }, + { + "id": "GATE-005", + "family": "commit-gate", + "attack_summary": "测试全绿但清单缺验收标准节,试探以绿测代替验收", + "expected": "拦:中档清单缺验收节不许盖章", + "pytest_selector": "test_acceptance_missing_blocks" + }, + { + "id": "GATE-006", + "family": "commit-gate", + "attack_summary": "判据行勾选了但缺验证命令(假勾)", + "expected": "拦:勾与验命令双全才算完成判据", + "pytest_selector": "test_acceptance_mark_without_verify_still_blocks" + }, + { + "id": "GATE-007", + "family": "commit-gate", + "attack_summary": "对照:判据全勾验命令齐全且测试绿(合规盖章)", + "expected": "放行:过验收即盖章终态并落通过账", + "pytest_selector": "test_acceptance_all_checked_stamps_done" + }, + { + "id": "GATE-008", + "family": "commit-gate", + "attack_summary": "实际变更夹带清单外文件而计划外自审键缺失", + "expected": "半拦:键缺先拦,补键声明后转放行", + "pytest_selector": "test_self_review_planned_outside_key_required" + }, + { + "id": "GATE-009", + "family": "commit-gate", + "attack_summary": "变更夹带调试残留而调试残留自审键缺失", + "expected": "半拦:键缺先拦,逐处确认声明后转放行", + "pytest_selector": "test_self_review_debug_residue_key_required" + }, + { + "id": "GATE-010", + "family": "commit-gate", + "attack_summary": "对照:测试目录内的调试模式是测试常态(防误触)", + "expected": "放行:测试路径豁免不触发自审键", + "pytest_selector": "test_debug_pattern_in_tests_not_triggered" + }, + { + "id": "SCAN-001", + "family": "scan-freshness", + "attack_summary": "无产品架构卡片的仓库试探新鲜度判定崩溃或误判", + "expected": "判 absent:提示先建卡或声明纯库,不误判新鲜", + "pytest_selector": "test_absent_when_no_card" + }, + { + "id": "SCAN-002", + "family": "scan-freshness", + "attack_summary": "对照:卡片随代码同步提交后的即时新鲜态", + "expected": "判 fresh:同步即新鲜,不误报重扫", + "pytest_selector": "test_fresh_right_after_card" + }, + { + "id": "SCAN-003", + "family": "scan-freshness", + "attack_summary": "结构性新增模块文件后拿旧卡片冒充全貌仍新鲜", + "expected": "判 stale:结构性增删点名文件并要求重扫", + "pytest_selector": "test_stale_on_structural_add" + }, + { + "id": "SCAN-004", + "family": "scan-freshness", + "attack_summary": "无结构变更但代码提交堆量,试探活动量代理失明", + "expected": "判 stale:代码提交数过阈即老化触发重扫", + "pytest_selector": "test_stale_on_commit_volume" + }, + { + "id": "SCAN-005", + "family": "scan-freshness", + "attack_summary": "对照:仅新增测试与文档类元数据文件", + "expected": "判 fresh:元数据不算结构变更,不误判 stale", + "pytest_selector": "test_not_stale_on_metadata_only" + }, + { + "id": "SCAN-006", + "family": "scan-freshness", + "attack_summary": "中档清单缺全貌扫描三行,以绿测试探跳过理解产物", + "expected": "拦:规则A 命中即阻断并点名全貌产物要求", + "pytest_selector": "test_ml_missing_scan_blocks" + }, + { + "id": "SCAN-007", + "family": "scan-freshness", + "attack_summary": "全貌扫描行填模板占位值冒充已填,试探防绕校验", + "expected": "拦:占位值不算填过,规则A 照拦", + "pytest_selector": "test_scan_placeholder_rejected" + }, + { + "id": "SCAN-008", + "family": "scan-freshness", + "attack_summary": "对照:全貌三行齐备的中档清单正常提交", + "expected": "放行:规则A 齐备即不因全貌拦(合规路径不误拦)", + "pytest_selector": "test_ml_with_scan_passes_rule_a" + }, + { + "id": "SCAN-009", + "family": "scan-freshness", + "attack_summary": "对照:轻量档清单不带全貌仪式直接提交", + "expected": "放行:规则A 仅约束中重档,轻量档豁免", + "pytest_selector": "test_s_tier_exempt_from_rule_a" + }, + { + "id": "ACC-001", + "family": "acceptance-gate", + "attack_summary": "验收判据行无勾选标记,试探以未验判据盖章", + "expected": "拦:验收未勾不许盖章并点名未勾行数", + "pytest_selector": "test_acceptance_open_blocks" + }, + { + "id": "ACC-002", + "family": "acceptance-gate", + "attack_summary": "验收判据填模板占位值冒充已写", + "expected": "拦:占位行视同未写,验收照拦", + "pytest_selector": "test_acceptance_placeholder_blocks" + }, + { + "id": "ACC-003", + "family": "acceptance-gate", + "attack_summary": "判据拆两行书写,试探行级解析漏看续行勾选", + "expected": "拦:续行合并成逻辑行再判,只报真实未勾行", + "pytest_selector": "test_acceptance_two_line_bullet_joined" + }, + { + "id": "ACC-004", + "family": "acceptance-gate", + "attack_summary": "对照:勾选标记后拖验证输出文字(非行尾形态)", + "expected": "放行:任意位置计勾,真实验过不误拦", + "pytest_selector": "test_acceptance_mark_anywhere_counts" + }, + { + "id": "ACC-005", + "family": "acceptance-gate", + "attack_summary": "对照:轻量档清单无验收节直接提交", + "expected": "放行:验收环仅约束中重档,轻量档豁免", + "pytest_selector": "test_acceptance_exempt_s_tier" + }, + { + "id": "RECALL-001", + "family": "rule-recall", + "attack_summary": "重犯同类拦截时试探现场不给历史规律提示", + "expected": "召回:拦截输出附规律段封顶三条,召回事件落账", + "pytest_selector": "test_block_recall_shows_history_rules" + }, + { + "id": "RECALL-002", + "family": "rule-recall", + "attack_summary": "关闭召回开关试探门禁依赖增强链路而失拦", + "expected": "降级:召回可一键关,门禁本体照拦不受拖累", + "pytest_selector": "test_block_recall_off_switch" + }, + { + "id": "RECALL-003", + "family": "rule-recall", + "attack_summary": "未接线的拦截原因试探强挂召回段制造噪声", + "expected": "静默:选择性接线,未挂载拦截点不召回", + "pytest_selector": "test_block_recall_not_wired_reason" + }, + { + "id": "RECALL-004", + "family": "rule-recall", + "attack_summary": "规律账本被写坏试探召回链路异常拖垮门禁", + "expected": "降级:坏账本静默跳过,拦截照常不反向", + "pytest_selector": "test_block_recall_corrupt_ledger_degrades" + }, + { + "id": "RECALL-005", + "family": "rule-recall", + "attack_summary": "召回后事件序列混杂,试探有效性三态误判", + "expected": "分态:干净/影子/未决按事件序列精确归类", + "pytest_selector": "test_recall_clean_shadow_pending" + }, + { + "id": "RECALL-006", + "family": "rule-recall", + "attack_summary": "他会话他清单拦截事件混入试探召回误判影子态", + "expected": "隔离:跨清单事件不污染本清单三态判定", + "pytest_selector": "test_recall_cross_manifest_events_ignored" + }, + { + "id": "RECALL-007", + "family": "rule-recall", + "attack_summary": "拦截历史杂乱堆积,试探热力图排序聚合失真", + "expected": "聚合:按原因频次降序,含清单数与最近时间", + "pytest_selector": "test_block_heatmap_ranking" + }, + { + "id": "RECALL-008", + "family": "rule-recall", + "attack_summary": "新拦截原因混入老原因,试探新增趋势失明", + "expected": "标注:近期首现原因标记为新键", + "pytest_selector": "test_heatmap_newkey_mark" + }, + { + "id": "REFLOW-001", + "family": "pending-reflow", + "attack_summary": "同清单过门禁后旧拦截待决滞留,试探闭环失灵", + "expected": "回流:同锚点未决拦截自动闭环,异锚点不动", + "pytest_selector": "test_auto_reflow_pending_on_gate_pass" + }, + { + "id": "REFLOW-002", + "family": "pending-reflow", + "attack_summary": "旧格式待决按标题兜底,试探相近编号误回流", + "expected": "精确:全字命中才回流,词边界防短编号吃长编号", + "pytest_selector": "test_resolve_by_ref_exact_and_legacy_fallback" + }, + { + "id": "REFLOW-003", + "family": "pending-reflow", + "attack_summary": "多条待决标题同含目标编号,试探盲回流了结", + "expected": "不动:多义不兜底,宁挂起不误决", + "pytest_selector": "test_resolve_by_ref_no_ambig_no_hit" + }, + { + "id": "REFLOW-004", + "family": "pending-reflow", + "attack_summary": "决策推送缺结构化锚点,试探回流链路失联", + "expected": "携带:推送落锚点字段,回流有数据前提", + "pytest_selector": "test_notify_source_id_flows_into_ref" + }, + { + "id": "REFLOW-005", + "family": "pending-reflow", + "attack_summary": "已裁决拦截再发或锚点超窗,试探刷屏或该推不推", + "expected": "对称:裁决重置窗口可重推,超窗锚点照样重推", + "pytest_selector": "test_blocked_repush_after_resolve_or_expiry" + }, + { + "id": "REFLOW-006", + "family": "pending-reflow", + "attack_summary": "自动闭环量混入人工裁决,试探误报率口径污染", + "expected": "单列:自动回流单独计数,不入误报率分母", + "pytest_selector": "test_resolved_outcome_single_bucket" + } +] \ No newline at end of file diff --git a/external_plugins/regress-guard/commands/regress-bypass.md b/external_plugins/regress-guard/commands/regress-bypass.md new file mode 100644 index 00000000..faf2df5e --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-bypass.md @@ -0,0 +1,64 @@ +--- +description: 紧急绕过回归卡点(限时 + 审计日志),用于 hotfix 等紧急场景 +argument-hint: +allowed-tools: Read, Write, Edit, Bash +--- + +# /regress-bypass — 紧急绕过 + +**仅用于紧急 hotfix 场景。** 限时开启 bypass 模式,期间 commit 不被 hook 阻断,但所有绕过行为记入审计日志。 + +## 使用场景 + +- 生产环境紧急 hotfix +- 测试环境快速验证(非正式提交) +- 演示/原型阶段 + +## 使用方式 + +参数 `$1` 是绕过时长(分钟),默认 10 分钟: + +``` +/regress-bypass 10 # 绕过 10 分钟 +/regress-bypass 30 # 绕过 30 分钟 +``` + +## 执行 + +在 `.regress/config.json` 中写入 `bypass_until` 字段: + +```bash +# 计算到期时间 +EXPIRES=$(python3 -c " +from datetime import datetime, timedelta +mins = ${1:-10} +print((datetime.now() + timedelta(minutes=mins)).isoformat()) +") +``` + +更新 `.regress/config.json`: +```json +{ "strict": true, "bypass_until": "" } +``` + +## 审计 + +每次 bypass 期间的 commit,hook 会写入 `.regress/bypass.log`: +``` +2026-08-11T16:30:00+08:00 | bypass commit by user | +``` + +## 到期自动恢复 + +bypass 到期后,hook 自动清除 `bypass_until`,恢复严格模式。无需手动关闭。 + +## 安全提示 + +``` +⚠️ bypass 已开启( 到期) + +此期间的 commit 不会做回归校验,但会记入审计日志。 +到期后自动恢复严格模式。 + +请确保事后补跑 /regress-verify 验证这些改动。 +``` diff --git a/external_plugins/regress-guard/commands/regress-characterize.md b/external_plugins/regress-guard/commands/regress-characterize.md new file mode 100644 index 00000000..a923464d --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-characterize.md @@ -0,0 +1,62 @@ +--- +description: 特征测试生成:改无测试老代码前先钉住现状行为(golden master——探针真跑取真值,不猜输出) +argument-hint: <文件或目录> +allowed-tools: Read, Write, Edit, Bash, Grep +--- + +# /regress-characterize — 特征测试生成(v1.45) + +治的病:无测试的老代码不敢改——改了不知道坏了什么。特征测试 +(characterization test)**不判断对错,只钉住现状**:把当前行为变成可执行 +断言;之后的修改若改变被钉行为,测试立即红——修改的意外副作用当场现形。 + +## 判据(何时该跑) + +- 活跃清单的 F 项命中无测试覆盖的老文件(plan 脆弱点拓扑的产出) +- 或人类明说"要改这块老代码,先钉住" + +## 流程(五步 · 证据律:跑真代码取真值,不猜) + +### 1. 枚举入口 + +Read 目标文件,列出**公开**函数/类方法(含签名与明显副作用)。跳过并记入 +报告"不可钉":私有(`_` 前缀)、需外部服务/网络/数据库的、明显非确定的 +(随机/时间依赖——除非可注入种子)。 + +### 2. 探针真跑 + +写临时探针脚本(/tmp 下,不进项目),对每个入口用**三类输入**各调一次: + +- 典型值(从调用方/文档/常识推断) +- 空或零值(`""`/`0`/`None`/`[]`,按签名选) +- 边界极端值(超长/负数/嵌套深,按语义选) + +**必须真跑**(Bash 执行探针),把实际返回值/异常落 JSON:浮点记 `repr()`, +异常只记类型不记消息(消息 wording 会漂),副作用可观察的记观察结果。 + +### 3. 生成测试 + +按探针 JSON 生成 `tests/test_characterized_.py`: + +- 每入口至少 3 断言(对应三类输入) +- **断言的是探针捕获的真值,不是 AI 以为的值**——这是 golden master 的命 +- 浮点用 `pytest.approx`;异常用 `pytest.raises(类型)` +- 文件头注释两行:特征测试=钉现状非判对错;行为**有意**改变时更新断言 + 并注明清单 id(区分意图性变更与回归) + +### 4. 跑绿 + +pytest 新文件全绿 = 行为已钉。红 = 探针与生成不一致(自己修到绿;通常是 +非确定依赖——修不了就标"不可钉"并落脆弱点,**诚实优于假钉**)。 + +### 5. 回写 + +- 有活跃清单 → `actual_changes` 记 F3(tests 文件),脆弱点拓扑加一条: + `kind: machine`「特征钉住 <入口数> 入口」verify=`pytest <新文件> -q` +- 无清单 → 提示下次走 /regress-quick(tests 文件也是改动,要留痕收编) + +## 边界 + +- 不 mock 外部服务(mock 出的行为不是现状,钉了也是假钉) +- 不追求覆盖率数字,追求**"要改的路径被钉"**——目标文件的目标入口,不是全仓 +- 与 adapter 的关系:这是项目内单测,走项目自己的 runner(门禁会复验) diff --git a/external_plugins/regress-guard/commands/regress-evolve.md b/external_plugins/regress-guard/commands/regress-evolve.md new file mode 100644 index 00000000..ce83caf8 --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-evolve.md @@ -0,0 +1,49 @@ +--- +description: 遇到难点时查社区经验→适配项目→沉淀为知识库(不再重复查) +argument-hint: <遇到的问题描述> +allowed-tools: Read, Write, Edit, Bash, WebSearch, WebFetch, Grep +--- + +# /regress-evolve — 查攻略·适配·沉淀 + +遇到不熟悉的难题,查社区最佳实践,**适配当前项目**(不盲目照搬),沉淀到知识库。下次同样的问题直接读知识库,不再查。 + +参数 `$ARGUMENTS` 是遇到的问题描述。 + +## 流程 + +### 1. 先查知识库 +读 `.regress/knowledge-base.json`。如果已有匹配的 → 直接用,不查社区, +**并给该条记一笔**(v1.24 代谢链:`hits` +1、`last_hit` 今天——命中是留存的证据, +半年零命中的条下次提示复验,知识库也要新陈代谢)。 + +### 2. 查社区(WebSearch) +搜索关键词,读 2-3 个高赞结果,提取社区公认的解法 + 坑 + 适用条件。 + +### 3. 适配当前项目 +**社区方案再好也要适合当前情况。** 对每个方案问: +- 本项目技术栈匹配? +- 改动量可接受? +- 和已有代码冲突? +不完全适配 → 怎么调整? + +### 4. 沉淀 +写入 `.regress/knowledge-base.json`: +```json +{ + "question": "遇到的问题", + "answer": "社区的解法", + "adaptation": "针对本项目的调整", + "verified": true/false, + "captured_at": "首次沉淀日期", + "hits": 0, + "last_hit": "" +} +``` +同时把适配后的经验写入项目 AGENTS.md。 + +## 自主决策 +- 能从代码推断解决 → 不查社区 +- 知识库里已有 → 直接用 +- 查到后 → 必须适配,不照搬 +- 同一问题已沉淀 → 不再查 diff --git a/external_plugins/regress-guard/commands/regress-finish.md b/external_plugins/regress-guard/commands/regress-finish.md new file mode 100644 index 00000000..91f086fa --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-finish.md @@ -0,0 +1,152 @@ +--- +description: 收尾流水线:track 回写 → verify 全证据 → 代谢沉淀 → 状态推进 → 汇总报告(一条命令走完收尾,人只出现在决策点) +argument-hint: [manifest-id] +allowed-tools: Read, Write, Edit, Bash, Grep +--- + +# /regress-finish — 收尾流水线 + +实施完成后的一条命令收尾:**track → verify → 代谢沉淀 → 提交就绪**。 +流程段之间没有人肉粘合剂——中间不需要人,人只出现在两个决策点 +(残留 open/flagged 的处置、提交本身)。 + +找最新 `status != done` 的清单(或用 `$ARGUMENTS` 指定 id),依次执行: + +## 步骤 1:track(F3 回写 = 扩界留痕) + +按 /regress-track 的完整规则执行:`git diff` + 未跟踪文件对比清单, +F3 直接回写 `actual_changes`(这是开发边界的扩界出口,留痕)。 + +## 步骤 1.5:自审 diff(v1.41:触发表激活,防空转) + +**通读 `git diff`(staged+unstaged),三类各扫一遍**——改错自己写的 diff +只有自己通读才能发现。键按触发表激活(不适用=键不出现,「无」=查过没有): + +- **计划外**(触发:actual_changes 非空):F3 回写的每个计划外文件——是有意 + 扩界还是顺手带上?值=逐文件一句结论或「无」 +- **调试残留**(触发:diff 命中 print/console.log/debugger/pdb 且非 tests/): + 是断言所需的输出还是忘删的探针?值=条目或「无」 +- **顺手重构**(无机器触发,纪律位):计划内文件里夹的函数重命名/移动—— + 发现了记进报告(拆批或明示),不静默混入 + +落产物:清单 frontmatter `self_review:` 下对应键。**提交门禁会拦** +(reason=finish_missing):触发的键缺失过不了 commit——防"三类全填无"的 +形式化由触发表承担(不触发不用填,触发了必须看过)。 + +## 步骤 2:verify(全证据) + +按 /regress-verify 的完整规则执行: + +- 跑项目测试——失败自己修再跑;**修复 3 次仍败或需要输入 → 先分流再受阻**: + 知识型阻塞(用法/取舍/报错不懂)先带四问去 consult,顾问能解则继续(标注采纳情况); + 环境/权限型或顾问不能解 → `plan_approve.py <清单> --block --reason --need` + (tried 里写明顾问意见),把 need 转达人类,流程到此暂停 + (受阻是合法停止,不是 finish 失败) +- 逐条脆弱点:带 `rescue:` 的失败**先自救再重试**;通过 → `locked`; + sensory 类 → 问人一个布尔问题,`human_check` 化石落档后转 locked(verify 写 + `human_check:`,门禁验化石存在性不复跑感官); + **验收标准表逐条拿证据(v1.25)——"根治/完成"必须能指回判据,指不回去不许说**; + 实测证伪假设 → 「假设失效记录」+ 地层化石;显式不处理 → `flagged`(写明知悉原因) +- env.lock.json 漂移检测(漂移了要在报告里标红) + +## 步骤 2.5:DoD 自检 + 弹性打磨(v1.35:跑通≠完成) + +先自检后打磨——**有缺口才打磨,没缺口不强求一轮仪式**(防僵硬,顾问修正): + +1. **DoD 自检**:对照清单「DoD 状态完备性」表(UI 功能)逐状态问"真渲染看过吗"; + 对照「广度矩阵」伴侣清单逐项问"声明与实现一致吗"。全部有证据 → 直进步骤 3 +2. **弹性打磨**(自检见缺口才启动):对缺的状态/粗糙点补一轮——文案、对齐、 + 加载反馈、空态引导、交互一致性(frontend-design / ui-ux-pro-max Skill + 在此适用);补完的证据回填 DoD 表 +3. **缺口不清零的出口**:补不了的转 `flagged`(写明为什么本期不做), + 并把缺口写进模块卡片「已知缺口」栏——缺口要么修、要么挂牌、要么进卡片 + 待办,三选一,不许蒸发 + +## 步骤 3:状态推进(机器判据,不由感觉) + +- **全部脆弱点 locked/flagged + 测试绿** → 无需手动改 status:直接提交。 + commit 门禁自跑测试复验,通过时自动写 `status: done` + `test_verified_by: hook` + ——done 是门禁发的,不是自称的 +- **有残留 open** → 这是人的决策点,两条路摆给用户: + ① 列出每条 open,说明锁死还差什么证据;② 显式 flagged(写明知悉原因) + 用户不表态不继续——摆完即推送:`notify.py . finish_open --title "🏁 收尾待决 " --body ""` + +## 步骤 4:代谢沉淀位(v1.24:任务结束必过,有料沉淀无料跳过) + +任务收尾即代谢入口——规律不靠人想起来才沉淀(病例:v1.23 排障纪律靠人类推动才固化): + +```bash +python3 "<插件路径>/hooks/scripts/lib/journal.py" . digest +``` + +- **digest 非空 / 本次有 rescue 回填 / top_f3 有新共变** → 执行 /regress-learn 的沉淀 + 流程(规律入 AGENTS.md 标记块 + `rules_ledger.py . record` 记账),汇报新增规律条数 +- **机器级分支(v1.31)**:沉淀里若有机器味事实(服务器拓扑/通道/环境坑/跨项目工具坑)→ + `python3 "<插件路径>/hooks/scripts/lib/facts.py" record "<标题>" "<一句话+验证命令>" [域]` + (同键重录自动刷新日期;落 ~/.zcode/skills/machine-facts/,跨项目可借,人类无感) +- **模块卡片回写(v1.35 起;v1.40 升机器位)**:本任务动了用户可见能力/模块结构 → 更新 + `.regress/product-arch.md` 对应卡(完成度提档/能力增行/缺口增删/依赖变化; + 顺手校对卡上的验证路径还走得通吗);重大选型若发生在实施中 → 补 ADR。 + 纯内部零结构变化 → 报告一行「卡片无变化」。 + **提交门禁会拦**(v1.40 规则B):staged 有结构性增删/改名而卡片未随同 staged → + block(病例:028 新增桥脚本卡片 8 天未回写无人拦);纯脚手架确不需进卡 → + 清单 scan 加 `card_sync: false` 并写明理由 +- **验收行勾选(v1.55 升机器位)**:M/L 清单「## 验收标准」每行验证过后行尾加 + `✅`——done 盖章时门禁校验全勾(缺节/未勾/占位/判据缺(验:命令)都拦); + 纸面验收不是反馈,勾过的才算数 +- **顾问采纳落账(v1.49)**:本批评审带顾问意见 → 落一条 advisor_adoption + 事件(journal add advisor_adoption,adoption ∈ adopted/partial/rejected)—— + 与回复中的标注义务同源(回复里写「已咨询第二意见:采纳」,台账里也要有同条), + 采纳率在 /regress-stats 第 10 项可查 +- 全空 → 报告一行「无可沉淀,代谢跳过」——不硬凑 +- 顺带跑 `rules_ledger.py . health`:命中 ≥3 的稳定规律输出「建议固化 skill」卡片 + (🦴 经人批准后用 skill-creator 固化——**自动固化的错误经验会以技能的形式高速复发**) + +## 步骤 5:产物干净检查(v1.27:学费随分支走) + +```bash +git status --porcelain .regress 2>/dev/null +``` + +- 非空 → 汇报「.regress 有 项未提交——清单/地层/决策是学费,随分支走才不丢 + (worktree 场景:remove 前未合并 = 蒸发)」 +- 命令失败(非 git 项目)→ 报告一行「产物层无版本保护(非 git),地层只活在 + 单份工作区」,后续不重复提醒 +- 双仓库项目(.regress 与代码分属两仓,如 dogfood 工作区)→ 在 .regress 所属仓库执行 + +## 步骤 6:汇总报告 + +``` +🏁 收尾完成 · REGRESS- + 改动:F1/F2(计划内)+ F3 × (已回写留痕) + 脆弱点: locked / flagged / 0 open + 测试:/ 通过<+覆盖率> + 债务: 笔 bypass 未补回归(>0 要提示还债) + 环境:<与 env.lock 一致 或 ⚠️已漂移:项> + +可以提交(门禁复验后自动标 done)。 +``` + +**离场召回(v1.31.1)**:汇总输出后推送 done 事件——离场人类靠它知道"回来收货": + +```bash +python3 "<插件路径>/hooks/scripts/lib/notify.py" . done --title "🏁 完成 :<需求一句话>" --body "|测试 /|债务 |<残留或干净>" +``` + +(一条/任务,频率天然低;干净收尾与 finish_open 二选一触发,不双响。 +标题只写任务名——【项目名】前缀与 🕐 时间由 notify 层自动注入,v1.31.2; +正文首位放任务内容简介(requirement 原文,不另行改写——离场人类回看时知道 +"这任务是干啥的",不用回来问),v1.31.2 补。) + +## 自主决策 + +长任务心跳(P1#13 接线):授权自主推进的任务,每完成一个阶段边界(单个 F 落地/ +半小时过半)推送一次——人不在屏前也看得见进度,治"两小时黑箱": + +```bash +python3 "<插件路径>/hooks/scripts/lib/notify.py" . progress --title "⏳ 清单id·阶段n" --body "本阶段一句:动了什么/下一阶段" +``` + +- 清单已是 done/completed → 报告"已收尾",只输出债务/漂移检查 +- 测试全绿但用户不在场 → 报告就绪状态并停,不代答 open 的处置 +- 小改动收不了尾(无清单)→ 提示下次走 /regress-quick 或先 /regress-plan diff --git a/external_plugins/regress-guard/commands/regress-init.md b/external_plugins/regress-guard/commands/regress-init.md new file mode 100644 index 00000000..a66eb083 --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-init.md @@ -0,0 +1,189 @@ +--- +description: 初始化项目的 .regress/ 数据目录(代码已在全局级安装,这里只建数据) +argument-hint: [--force] +allowed-tools: Read, Write, Edit, Bash +--- + +# /regress-init — 初始化项目 + +在项目根目录创建 `.regress/`(**只是数据目录,不是代码**)。代码已在全局级 `~/.zcode/` 安装。 + +## 步骤 1:冲突检测 + +先检查项目里是否有重复的 regress-guard 代码(会导致和全局级冲突): + +```bash +# 检查项目级是否有重复 command/hook 副本(skills 已于 v1.95.1 退役) +ls regress-guard/hooks/ 2>/dev/null +ls .zcode/commands/regress* 2>/dev/null +``` + +如果发现重复 → **警告并询问是否删除项目级副本**: +``` +⚠️ 检测到项目级有 regress-guard 的 command/hook 副本: + +全局级已安装(~/.zcode/commands/ 与 ~/.zcode/regress-guard-hooks/),项目级副本会导致: + - 用到旧版本 + - 更新不同步 + - ZCode 优先级混乱 + +建议删除项目级副本(数据不受影响)。 +``` + +## 步骤 2:检查已有 + +```bash +ls .regress/config.json 2>/dev/null +``` +若已存在且无 `--force` → 停止,提示已初始化。 + +## 步骤 3:创建数据目录 + +```bash +mkdir -p .regress/manifests .regress/journal +``` + +写入 `.regress/config.json`: +```json +{ "strict": true, "read_before_edit_ratio": 2 } +``` + +创建 `.regress/README.md`(**零号入口**——写给失忆的读者:打开仓库第一眼 +就知道从哪读起、"做完"由什么定义。内容与 `templates/regress-dir-readme.md` 同源, +复制后可按项目微调;self_heal 会在老项目里自动补这个文件,永不覆盖已定制版本): + +```bash +cp "<插件路径>/templates/regress-dir-readme.md" .regress/README.md +``` + +创建 `.regress/product-context.md`(v1.26 产品上下文卡——「资深·懂行业·贴近用户」 +那部分知识的住所;用户/价值观段人类填,行业惯例段可由顾问带搜索起草。 +缺失才补、永不覆盖): + +```bash +cp "<插件路径>/templates/product-context.md" .regress/product-context.md +``` + +创建 `.regress/product-arch.md`(v1.35 模块卡片——产品存量与架构连接点同卡: +每模块一卡 = 完成度|能力|依赖方向|契约|缺口|验证路径。AI 扫代码起草初版 +(每个可辨认的模块一张卡),人类校对;缺失才补、永不覆盖): + +```bash +cp "<插件路径>/templates/product-arch.md" .regress/product-arch.md +``` + +创建 `.regress/adr/` 目录(v1.35 架构决策记录——重大选型必落 +`adr/NNNN-短标题.md`,模板 `templates/adr.md`;decisions.md 记"决定", +ADR 记"为什么这么定"的完整备选与红线): + +```bash +mkdir -p .regress/adr +``` + +创建决策日志 `.regress/decisions.md`(公理二:认知物质化——决策链刻在文件里, +新会话/新人不靠记忆,读文件): + +```markdown +# 决策日志(append-only,最新在最上) + + +``` + +采集环境指纹 `.regress/env.lock.json`(公理一:谁帮我设的 CUDA?锁住当下环境, +漂移可检测): + +```bash +python3 - <<'EOF' +import json, subprocess, sys, platform +def cap(cmd): + try: return subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=10).stdout.strip() + except Exception: return "unavailable" +lock = { + "captured_at": __import__("datetime").datetime.now().isoformat(), + "python": sys.version.split()[0], + "node": cap("node --version"), + "git": cap("git --version").split()[-1], + "os": f"{platform.system()} {platform.release()}", +} +open(".regress/env.lock.json", "w").write(json.dumps(lock, indent=2, ensure_ascii=False)) +print(json.dumps(lock, ensure_ascii=False)) +EOF +``` + +## 步骤 4:安装 git 提交观测钩子(补全证据链) + +ZCode 门卫只能看到 ZCode 发起的提交;IDE/终端直接 commit 时不可见。 +装一个原生 git `post-commit` 钩子记录**所有来源**的提交(静默/自禁用/不阻断/monorepo 向上找 .regress)。 + +**已知边界(v1.41 注记)**:钩子装在 init 所在仓库的 `.git/hooks/`——嵌套仓 +拓扑(.regress 在外仓、代码在内仓)时代码提交走内仓,钩子观测不到 +(评审病例:commit_observed 0 条+.expect-commit 残留)。单仓项目机制完整。 + +项目是 git 仓库且 `.git/hooks/post-commit` 不存在时新装(husky 等外来钩子则跳过并提示); +**自升级(批147)**:已有钩子若是本插件旧版观测钩子(体含 `commit_observed` 事件名但缺 `"source"` 字段——gated 提交会被 outofband 聚合误计带外)则覆写为 v2。外来钩子不含该事件名,不会误碰: + +```bash +if git rev-parse --git-dir >/dev/null 2>&1; then + if [ ! -f .git/hooks/post-commit ] \ + || { grep -q 'commit_observed' .git/hooks/post-commit 2>/dev/null \ + && ! grep -q '"source"' .git/hooks/post-commit 2>/dev/null; }; then +cat > .git/hooks/post-commit << 'RGHOOK' +#!/bin/sh +# regress-guard post-commit observer v2 (source-aware) +D="$(git rev-parse --show-toplevel 2>/dev/null)" +RG="" +while [ -n "$D" ] && [ "$D" != "/" ]; do + [ -d "$D/.regress" ] && RG="$D/.regress" && break + D="$(dirname "$D")" +done +[ -n "$RG" ] || exit 0 +SRC="git-hook" +EXP="$RG/.expect-commit" +if [ -f "$EXP" ]; then + if [ -n "$(find "$EXP" -mmin -5 2>/dev/null)" ]; then + SRC="zcode-$(sed -n 's/^kind=//p' "$EXP" | head -1)" + fi + rm -f "$EXP" 2>/dev/null +fi +SHA="$(git rev-parse --short HEAD 2>/dev/null)" +SUBJ="$(git log -1 --format=%s 2>/dev/null | head -c 80 | sed 's/\\/\\\\/g; s/"/\\"/g')" +printf '{"timestamp":"%s","event":"commit_observed","manifest_id":"","commit_sha":"%s","subject":"%s","source":"%s"}\n' \ + "$(date -Iseconds)" "$SHA" "$SUBJ" "$SRC" >> "$RG/history.jsonl" 2>/dev/null +exit 0 +RGHOOK +chmod +x .git/hooks/post-commit + fi +fi +``` + +## 步骤 5:输出 + +``` +✅ regress-guard 已为本项目初始化 + .regress/config.json — 项目配置 + .regress/manifests/ — 回归清单目录 + .regress/journal/ — 考古地层(失败/风险/纠正化石,append-only) + .regress/decisions.md — 决策日志(决策链物质化) + .regress/env.lock.json — 环境指纹(漂移检测基线) + .git/hooks/post-commit — 提交观测(记录所有来源的提交) + + 代码在全局级(~/.zcode/),所有项目共享。 + 这个项目只存自己的数据(清单/历史/配置)。 + +建议把 .regress/(含 journal/)纳入 git——历史厚度决定系统生存韧性。 + +下一步:/regress-plan <需求描述> +(老项目首次大提交若被 F3 拦截,直接跑 /regress-track 一次性回填全部文件) +``` + +## 架构说明 + +``` +全局级 ~/.zcode/ → 代码(skill/command/hook),装一次 +项目级 项目/.regress/ → 数据(清单/历史/配置),每个项目独立 +``` + +不要在项目里放 regress-guard 代码——会导致和全局级冲突。 diff --git a/external_plugins/regress-guard/commands/regress-install.md b/external_plugins/regress-guard/commands/regress-install.md new file mode 100644 index 00000000..ba7b0ea3 --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-install.md @@ -0,0 +1,60 @@ +--- +description: 一键安装 regress-guard 到用户级(零配置,装一次全局生效) +allowed-tools: Bash, Read, Write +--- + +# /regress-install — 一键安装 + +用户说"安装 regress-guard"或"/regress-install"时触发。自动把框架安装到用户级(`~/.zcode/`),装一次全局生效,所有项目都能用。 + +## 执行 + +找到插件源目录(通常在当前工作区或已知路径),运行安装脚本。**批191 起安装器是 +Python(install.py),Windows 10+ 原生可用**;`.sh` 是 POSIX 薄委托等价入口。 + +```bash +# 尝试找到 install.py(bash 块在 Windows 宿主的 Git Bash 下同可跑; +# 纯 cmd/PowerShell 环境由 AI 等价翻译:dir 探测 + python install.py) +PLUGIN_DIR="" + +# 常见位置 +for candidate in \ + "${CLAUDE_PROJECT_DIR:-$(pwd)}/regress-guard" \ + "${HOME}/.zcode/workspace/default/regress-guard" \ + "$(pwd)/regress-guard"; do + if [ -f "${candidate}/install.py" ]; then + PLUGIN_DIR="${candidate}" + break + fi +done + +if [ -z "${PLUGIN_DIR}" ]; then + echo "找不到 regress-guard 安装脚本。请指定插件目录。" + exit 1 +fi + +python3 "${PLUGIN_DIR}/install.py" +``` + +## 安装后 + +安装脚本会输出成功信息。然后告诉用户: + +``` +✅ regress-guard 已安装! + +现在可以在任意项目里用了。对 AI 说: + /regress-init ← 在项目中初始化回归工作流 +``` + +## 如果已安装 + +install.py 是幂等的——重复运行只会更新,不会重复添加。所以直接跑就行。 + +## 卸载 + +用户想卸载时,运行: +```bash +python3 "${PLUGIN_DIR}/uninstall.py" +``` +或对 AI 说 `/regress-uninstall`。 diff --git a/external_plugins/regress-guard/commands/regress-learn.md b/external_plugins/regress-guard/commands/regress-learn.md new file mode 100644 index 00000000..62a3fd8f --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-learn.md @@ -0,0 +1,122 @@ +--- +description: 分析历史+检测框架规则,输出项目洞察,写入 AGENTS.md +allowed-tools: Read, Write, Edit, Bash +--- + +# /regress-learn — 学习项目规律 + +分析历史 + 框架 co-change 规则,输出洞察,**写入 AGENTS.md**。 + +## 执行 + +```bash +# 历史分析 +python3 "<插件路径>/hooks/scripts/lib/history.py" .regress summary + +# 框架 co-change 规则 +python3 "<插件路径>/hooks/scripts/lib/cochange_rules.py" . + +# 考古地层挖掘(公理三:跨会话重复失败 = 稳定经验) +python3 "<插件路径>/hooks/scripts/lib/journal.py" . digest +``` + +## 考古地层(.regress/journal/events.jsonl) + +工具失败、风险动作、用户纠正三类化石自动埋入地层(随 git 入库,重启不丢)。 +`journal.py digest` 输出**跨会话出现 ≥2 次**的重复失败签名(单会话高频是重试噪声, +跨会话重复才是稳定经验)。 + +digest 不为空时,对每条重复签名: +1. `journal.py . raw` 找到该签名的历次现场(时间/会话/上下文) +2. **三问归因(v1.46,GEPA 借思想)**——把"模型错了"翻译成"规则该改": + - **① 错误定位**:哪个推理步骤出的错(读哪份文件/哪个假设/哪次调用)? + - **② 规则归因**:AGENTS 契约或命令文档的**哪一条**导致或未能阻止这个错误? + (归因不到具体条款=这不是规则问题,是知识缺口——走 facts/machine-facts 而非改契约) + - **③ 改进建议**:那一条该怎么改(定向改写,不是"更小心"式泛泛建议)? +3. 写入下方项目规律块,格式:`- 「<签名>」反复失败 ×N(N 个会话):定位 <步骤>,归因条款 <契约第几条/命令某节>,改法 <定向建议>` +4. **同步记账(v1.24 代谢链)**: + ```bash + python3 "<插件路径>/hooks/scripts/lib/rules_ledger.py" . record --sig "<签名>" --occurrences + ``` + 首次沉淀 hits=1;同签名再检出(下次 learn 又见到它)= 命中一次—— + 命中是固化的证据,没记过账的规律块只会膨胀(实测:真实项目 3 周 18 节)。 + 新沉淀若输出「⚠️ 近重复候选」(v1.57 源头去重)→ 是同一规律的改写就改用 + `supersede --old <旧> --new <新>` 链接版本,别让账本双胞胎膨胀 + **生命周期(v1.102.0 批150)**:新规律默认 **shadow**(照常召回/计 fires, + 差别只在策展簿记)。哨兵轮跑 `case` 看证据卡(fires/服役天数/召回开火); + `promote --sig` 人裁晋升 curated;超 TTL(60 天,config + rules_ledger.shadow_ttl_days 可调)零火候的会被 `sweep` 饿死退役(留档 + 可复活)——知识堆积饿死与误守 junk 两条路都封了 +5. **匹配型证据只用于排序(GEPA 6.3 防线)**:规律"恰好命中"(任务顺利只是因为 + 没踩到坑)只作排序参考,不单独构成沉淀/固化依据——否则"改得越少→匹配越多 + →分越高"会把规律带向保守;沉淀要的是失败现场的归因,不是顺利时的巧合 + +**产品否决同样可挖(v1.26)**:digest 之外查 `design_rejected` 类化石—— +跨任务重复的产品否决(行业惯例/用户习惯/该简化)升格为产品规律,写入 +`.regress/product-context.md` 的「设计否决记录」段,稳定后晋级「行业惯例」段。 + +## 纠正聚类(v1.94.0,122:纠正化石从只埋不挖到进 learn) + +digest 自本版起纳入 `user_correction`(kind 字段区分,签名形如 +`correction:<摘录前16字>`)。**同主题纠正跨会话 ≥2 次 = 用户反复纠正同一类 +误解**——比工具失败更稀缺的信号("自信地错"没有失败信号,纠正词是唯一探测器): + +1. 对 digest 里 kind=user_correction 的条目,`journal.py . raw` 回放历次纠正原文 +2. 三问归因照走,但①错误定位优先问:**AI 当时自认为对的是什么?**(纠正击中的 + 是假设而非步骤——写清被击碎的假设比写清步骤值钱) +3. `how=not_correction` 的 disposition 化石 = 已判 FP 的纠正,归因时剔除 +4. 写入项目规律块的格式:`- 用户反复纠正「<主题>」×N:被击碎的假设 ,修正 ` + +另外顺手看 `pending_corrections`(`journal.py . raw` 尾部的 +correction_disposition 高水位之后仍有的 user_correction)——长期挂起的 +未处置纠正是纪律漏洞的直接证据,值得点名。 + +## 如果 total_commits == 0 +告诉用户:"暂无历史数据。用了几次后再来。" + +## 如果 tech_debt > 0 +``` +💳 技术债务:{debt} 笔 bypass 未补回归 +``` + +## 如果 outside_gate_commits > 0 +``` +📤 {n} 次提交未走门禁(IDE/终端直提或历史回填)——门禁采用率的诚实镜子 +``` + +## 如果 avg_coverage_pct 非 null +``` +📊 平均测试覆盖率 {n}%(jest 项目自动采集) +``` + +## 如果 top_f3_files 不为空 → 写入 AGENTS.md +```markdown + +## 项目规律(/regress-learn 自动发现) +- 改 src/auth/ 时,src/utils/validator.js 通常需要同步改(出现 5 次) + +``` + +## 报错自救合流(v1.15) + +清单里预先写的 rescue(报错→自救命令)与地层学出的失败根因是同一回路的两端: + +- digest 里的签名,若在活跃/历史清单的 `rescue:` 或「报错自救」表里已有对策 → + 把「报错关键词 → 自救命令」写进上面的项目规律块(跨任务的 FAQ) +- 只有失败没有对策 → 标注"待补自救",下次踩同坑时先回填清单再修 + +## 规律健康(v1.24:记账的另一半是衰变) + +```bash +python3 "<插件路径>/hooks/scripts/lib/rules_ledger.py" . health +``` + +- 🦴 **固化候选**(hits ≥3):稳定规律建议固化为宿主 skill——输出卡片**等人批准**, + 批准后用 skill-creator 造(地层是脂肪,规律是肌肉,skill 是骨骼) +- 🍂 **降级候选**(>180 天零命中):提示人工修剪——**本工具永不自动删 AGENTS.md** + (人类文件红线);只进不出的"知识库"是熵增库 +- 空 digest + 空账本 → 告诉用户"暂无可沉淀规律",不硬凑 + +## 自主决策 +- 发现规律 → 直接写入项目 AGENTS.md(不问用户) +- 数据太少(<3 次提交)→ 告诉用户多用几次再来 diff --git a/external_plugins/regress-guard/commands/regress-plan.md b/external_plugins/regress-guard/commands/regress-plan.md new file mode 100644 index 00000000..cbde2a64 --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-plan.md @@ -0,0 +1,333 @@ +--- +description: 需求→解析→消歧→改动清单。AI 先补全上下文再动手(不问能推断的,只问关键分歧) +argument-hint: <需求描述> +allowed-tools: Read, Write, Edit, Bash, Grep +--- + +# /regress-plan — 需求解析 + 改动分析 + +参数 `$ARGUMENTS` 是需求描述。为空则提示用法。 + +> **与宿主计划模式叠加**:可在 EnterPlanMode 下进行——原生管会话权限(只读容器), +> 本命令管产物生命周期(清单=批准后契约,值班到 done)。批准双落:宿主手势退出 +> + plan_approve 落 approved.at;v1.39 起宿主 ExitPlanMode 批准由 plan_bridge +> 钩子自动转录/盖章(钩子未生效时 AI 补转录,幂等)。小改动走 quick(判据见 0.5)。 + +## 步骤 0.5:规模判定(v1.37:full 的重仪式按档触发,不再一刀全量) + +对着需求与初判改动面打档(判据与 /regress-quick 同源,越级可审计): + +- **S 档**(≤3 文件·纯内部·无环境变更)→ 本该走 quick;用户执意 full 时 + 只强制:F 列表+边界+**至少 1 条脆弱点**+验收一行——4.7/4.9/4.10 留骨架跳过 + (批153 起验收一行有门禁 warn 位:S 档盖章零判据行会收到 hint 留痕,不拦) +- **M 档**(用户可见功能)→ 全流程必走 4.9 广度矩阵 + DoD 状态完备性 +- **L 档**(跨模块/结构/新依赖)→ M 全量 + 4.10 架构影响 + ADR 判定 + +塌方曲线的教训(评审 2026-023:011-020 十份连续 0 脆弱点 0 假设): +重仪式在速度压力下被绕过而非被遵守——分档让"轻"合法留痕,而不是伪装"重"。 + +## 步骤 0:自动初始化(如果无 .regress/) + +检测到无 `.regress/config.json` → 直接创建(不问)。 + +## 步骤 1:幂等检查 + +已有活跃清单匹配同一需求 → 跳过重复分析,直接输出已有清单。 + +## 步骤 2:需求解析 + +**先理解需求要做什么,再谈改哪些文件。** + +### 2a:上下文补全(AI 自己做,不问人)+ 全貌新鲜度(v1.40) + +**用户可见的产品需求,先读 `.regress/product-context.md`**(产品上下文卡 v1.26); +卡缺 → 步骤 5 卡片一次问全并建卡。 + +**再读模块卡片并验新鲜度**(全貌层 v1.40:看不全就对不准——歧义检测扫的是 +需求文本的二义性,扫不出需求与代码现状的错配): + +```bash +python3 "<插件路径>/hooks/scripts/lib/scan_check.py" . +``` + +- `fresh` → 继续 +- `stale` → **重扫受影响模块卡再继续**(AI 扫:卡片补上期间的新能力/缺口; + 治的是"计划建立在过期认知上") +- `absent` → M/L 档先建卡(init 产品层)或明确声明纯库项目无产品面; + 结构性提交时门禁会给无卡警示 + +从代码/配置/文档中**推断**缺失的上下文: + +例:"保存设置+下发全部成功才是200" → AI 从代码读出:接口 PUT …/settings +(Controller)、下发走设备控制平台(service)、现行为保存即 200、协议 MQTT。 +能推断→直接用;推断不了→记"未知"。 + +**前召回(v1.61,2a 收尾时顺手一条)**:清单落盘后跑 +`python3 "<插件路径>/hooks/scripts/lib/rules_ledger.py" . match --from-manifest <清单>` +——从清单派生查询(文件+脆弱点+卡名)召回历史规律。开工前看老规律, +比拦截时才看见早一整段(召回是提示不是行动,采纳前对照本次现场)。 + +### 2b:歧义检测(识别二义性) + +检查多种合理理解。例:"下发全部成功才是200"——A 所有设备成功才 200; +B 下发动作执行完即 200;C 200=请求被接受(现状)。 + +### 2c:消歧决策(自决 vs 确认) + +对每个歧义点判断: + +能从代码推断→自己定并标注依据;不能→只问这一个点;改变交付性质 +(如"失败回滚吗")→必须问。 + +**只问"无法自决且影响交付物"的歧义。其他自己定。** + +### 2d:输出解析结果 + +格式:📋 需求解析——理解(一段)/依据(从代码推断,逐条)/我做的假设 +(编号)/需要你确认的(只 1 个问题)。 + +### 2e:排障分流(需求是修 bug/排障时必走,v1.23) + +**先检验后动手——瓶颈不是权限,是顺序。** +(病例 REGRESS-005:真凶是自己上一轮的参数;全文 PHILOSOPHY §4) +三条纪律(先审自己的 diff / 假设落账·先跑检验后写修复 / 旧解释不复用)的完整表述 +见清单模板「假设账本」节(单一来源)——本步骤的职责是**分流**:修 bug 类需求必走, +账本裁决通过才进改动分析(步骤 3),修的是被证据指认的根因,不是防御层。 + +## 步骤 3:并行分析(使用子智能体) + +对复杂需求(涉及 >3 个文件),用 **Agent 工具委派子智能体并行分析**。 + +> ⚠️ **子代理只委派只读分析类任务**——子代理不过 PreToolUse 拦截链(实测见 +> WORKFLOW「智能体通道定界」);写文件/改代码/跑不可逆命令必须主智能体执行。 + +### 子智能体 A:需求理解 +Agent(prompt="分析需求的理解与歧义:代码推断→检测歧义→能推自决不能则列。输出:理解+假设+待确认") + +### 子智能体 B:改动点识别 +Agent(prompt="分析改动点:Grep 源码→判断类型→查现有测试(无则标 characterization_needed)。输出:改动清单") + +### 子智能体 C:影响范围评估 +Agent(prompt="评估影响范围:Grep 引用方+model 广影响。输出:影响矩阵(文件→被谁引用→是否需回归)") + +**简单需求(≤3 文件)不委派子智能体**:自己读 3 个文件分析;类型词表 +method-logic / signature / model / new-file / config。结果合并后进入步骤 4。 + +## 步骤 4:生成清单(含理解字段 + 脆弱点拓扑) + +```yaml +--- +id: REGRESS-<序号> +requirement: "<原始需求>" +understood_intent: "" +assumptions: # AI 自己做的假设(2d 产出) + - "下发失败不回滚" +confirmed: [] # 人类确认过的(初始空) +ambiguities_resolved: # point / ai_choice / basis 三键 + - point: "下发模式" + ai_choice: "同步等待所有设备" + basis: "DeviceControlService.dispatch() 同步调用" +status: planning # 待人类批准(拦编辑)→ plan_approve.py 转写后 in-progress +base_head: "" # 创建时基线——批准时校验漂移 +approved: # 批准落产物:转写器填,或人类直接填 at(产物直通) + at: "" + note: "" +planned_changes: [...] # 全量字段见 templates/regress-manifest.md + # file=提交所在仓的相对路径(嵌套仓=子仓相对,别带 regress-guard/ 前缀) +fragile_points: # 公理一:穷举关键脆弱点(字段见模板) + # verify 须带 cd <仓目录> && 前缀(复验 cwd=工作区根) + - id: V1 + kind: env + description: "CUDA 由 module 加载,重启后丢失" + verify: "python3 -c 'import torch;print(torch.cuda.is_available())'" + status: open +--- +``` + +### 步骤 4.5:脆弱点拓扑(公理一,生成清单时必做) + +**"正确"是瞬态,"锁死脆弱点"才是产出。** 对着改动清单逐类问: + +| 类别 | 问自己 | 典型例子 | +|------|--------|----------| +| env | 谁帮我设的?换台机器还在吗? | CUDA_HOME、NODE_OPTIONS、locale | +| dependency | 哪个库会报错/静默变行为? | flash_attn 编译、语义化版本漂移 | +| resource | 会被抢吗?重启后还在吗? | PID 复用、端口占用、GPU 显存 | +| data | 丢了能恢复吗? | 无迁移的 schema 变更、删跑数据 | +| concurrency | 两个进程同时写会怎样? | 状态文件竞争、缓存击穿 | +| oracle | **测试替身漏掉了什么断言?** | 模拟器不校验帧 length、mock 不查 Redis key 前缀——单测假阴性之源(实测:17/17 绿照样漏 bug 都死在这) | +| sensory | 终验靠人的感官吗? | 对讲声音清晰、画面卡顿——问人只收通过/不通过,verify 写 `human_check:`(门禁验化石存在性,不复跑感官) | +| product | **用户可见的状态都看过吗?**(v1.35 DoD) | 空态/错误态/加载态——真渲染逐个看(browser-use 截图),代码审查发现不了空态尴尬;UI 功能必列 | +| arch | **依赖方向被破坏了吗?**(v1.35) | A 不得 import B、数据流只进不出——ADR 红线写成 grep 类可执行命令当 verify(结构稳定模块适用;结构剧变期人工评审挂牌) | + +- **热点共变(v1.20)**:AGENTS.md 规律/历史 top_f3 有共变对→自动带草案 + (标来源);**不许塞 `echo ok` 式空点**(挂牌制靠稀缺性保值) +- verify 必须是可执行命令(exit code 是证据,"已确认"四个字不是);能当场跑→ + locked;跑不了→open;明知不处理→flagged(写明知悉原因) +- **open 挡提交**——这是设计不是故障 + +### 步骤 4.6:开发边界(计划→物理拦截) + +清单 frontmatter 加 `boundary.include`(目录通配)——**这是省 token 和提质量的关键机制**: +无边界时 AI 漫游改无关文件(实证:一天 6B 消耗、半个月完不成任务、复检几十轮); +边界把行动空间收窄到任务本身(<1B/天、多任务/天、自动复检即达标)。 + +- include = 改动目录 + 配套测试目录;**跨层之问**:跨三层的大杂烩清单是 + 边界失守前兆,该拆就拆 +- hook 在 Edit/Write **发生前**拦截边界外文件;扩界唯一出口=/regress-track + (回写留痕);项目级关闭:config 设 "boundary_enforced": false + +### 步骤 4.7:失忆读者层(v1.15:写给断上下文的读者) + +清单 body 三段(骨架在模板):①**环境准备**——新环境/依赖/特殊启动必写, +给全命令("别点 IDE 的 Run"要写明);纯内部改动留骨架。②**实施顺序**—— +planned_changes 排成编号步骤,每步挂可观察里程碑("单测绿"/"控制台打印 X"), +先基石后骨架后联动,**禁止一口气写完再调**——里程碑是断点续作的"从哪继续"。 +③**报错自救**——已知脆弱点配 rescue(失败先敲什么);踩坑后回填。 + +### 步骤 4.8:设计备选(v1.25:没有备选的设计不是设计) + +方案唯一往往不是想清楚了,是没想过(病例:v1.23 框定偏差被用户纠正——卡片有备选, +人类 30 秒可纠偏)。落产物:清单「需求理解」记一行 +**设计取舍**:`<被否方案>——否决因:<一句理由>`。 + +### 步骤 4.9:广度矩阵(v1.35:治"做完才发现少一半") + +用户可见功能必走,落清单「广度矩阵」节(骨架在模板)。三问: + +1. **角色 × 任务**:谁(哪些角色)拿它做什么(jobs-to-be-done)? + **每行配验证路径**——用户故事反推一条可操作走查语句(顾问补强: + 没有路径的矩阵是条款清单,不是产品思考) +2. **伴侣功能清单**(UI 类 10 项逐个过:设置/错误态/空态/加载态/撤销与二次 + 确认/权限/移动端/数据导出/i18n/无障碍)——**写"不做"也算答,留空=没想过**。 + AI 的默认行为是只做字面需求:没人逼它枚举伴侣,它就永远漏 +3. **竞品对照**(有竞品时):人家有什么我们没有?差距动作 = 补(进本期/下期) + 或不补(写一句为什么不补) + +已装机的 brainstorming(superpowers)/ office-hours Skill 适用于本步骤的 +探索期——知识层在场,流程层以本步骤为准。 + +### 步骤 4.10:架构影响与 ADR(v1.35:选型理由不随对话蒸发) + +先读 `.regress/product-arch.md` 模块卡片(卡不存在 → /regress-init 补骨架), +跨模块/结构性改动必答清单「架构影响」四问(动哪些模块/依赖方向变化/ +新增耦合/触发 ADR 否);单文件内部改动一句"无结构性影响"了事。 + +**何时必须落 ADR**(`.regress/adr/NNNN-标题.md`,模板在 templates/adr.md): +引入新依赖 / 新服务 / 改数据流 / 破模块边界 / 推翻旧 ADR。 +ADR 的「红线」节写成可执行 verify 命令 → 进清单 fragile_points kind=arch +(机器强制);清单 frontmatter `adr:` 填引用。小改动免仪式(防僵硬)。 + +## 步骤 5:计划审批(人类的一寸——方向错误的最后低价纠偏点) + +### 5a:顾问预审(先于卡片输出) + +**步 0·被拒批缓冲(v1.91.1/109,稳定性条件 5 补法)**:预审前先跑—— + +```bash +python3 "<插件路径>/hooks/scripts/lib/veto_search.py" . "机制 关键词" +``` + +命中(近 90 天否决/砍批/反对化石)→ 预审问句**必须**含「与已否决方案有何差异」, +清单 planned 注已否决对照。命中不是禁止(新证据下重提合法),是强制对照—— +砍掉的方案换皮重提是稳定性条件表点名的失败模式。 + +输出卡片前送顾问独立预审(真实 consult,audit 可查不得代笔)。四问:方向性 +风险/遗漏脆弱点/边界合理性/产品适定。**产品适定必带 mode=scout**(无来源的 +行业断言只是另一个自信的模型;问题通用化不含内部数据;scout 不可用则标注 +「未获带来源的行业预审」)。不对称原则:它说反惯例可信(否决权), +说没问题不可信——它不知道你们的用户。 + +判据层追加三问(EARS 一致性,M/L 档清单预审时照单检查): + +1. **判据行三件齐吗**:When 条件/则 结果/验 命令——缺一即不完整判据 +2. **When 条件可观察吗**:外部可观察的事件/状态,不是内心态或"正确地" +3. **验命令当场可复跑吗**:不需要人肉准备环境 + +动机:Kiro SMT 形式化分析的轻量替代路线——不追重基建,用既有顾问管道做 +判据层形式审查(行业对比 2026-09-21 行动项)。 + +自驳一问(M/L 档预审追加,v1.88.3/102——Anthropic security-guidance 反驳 +分类学借抄;Q2/Q3 依赖 diff 可见性,移批前自查,见 WORKFLOW 自迭代协议): + +0. **把自己当攻击者**:这个计划最会被怎么绕过/腐化?谁受益于绕过它 + (包括未来的自己——疲惫/赶工时的自己也是攻击面)? + +埋预审化石: + +```bash +python3 "<插件路径>/hooks/scripts/lib/journal.py" . add plan_advisor_review \ + '{"manifest_id":"","verdict":"no_objection|objection|unavailable","summary":"<要点>"}' +``` + +**不对称原则:顾问有一票否决权,没有一票批准权。** + +- 有方向性异议 → 卡片附「⚠️ 顾问异议:<要点>」,保持 planning 等人——**即使任务已预授权** +- 无异议 + 已预授权 → 卡片标注后临行(伪全自动): + ```bash + python3 "<插件路径>/hooks/scripts/lib/plan_approve.py" <清单> --provisional \ + --advisor "<一句话预审结论>" + ``` + 临行进入否决窗(直到 done):人类随时 `--cancel` 否决,或给修改意见完善计划重新预审 +- 无异议 + 未预授权 → 卡片附「✓ 顾问预审无方向性异议」,仍等人类批准词 +- 顾问不可用 → 卡片标注「未获预审」,按原流程走(预审是增强不是依赖) + +清单以 `status: planning` 创建后,**停下输出计划卡片**(30 秒可扫完),不实施: + +(选号纪律,v1.56:原生桥路径的编号分配已挂文件锁防并发撞号;**AI 手写清单不经锁**—— +落盘前先 `ls .regress/manifests/` 取当前最大号再 +1,别凭记忆写号) + +``` +📋 计划(等待批准 · REGRESS-<序号>) + 理解:<一句话,含边界(做什么/不做什么)> + 改动点:F1 <文件·类型> / F2 … + 边界: + 脆弱点:V1 <描述·验证命令·状态> … + 备选:<被否方案 + 一句否决因>(4.8) + 所见:<界面示意/三步交互流/API 示例,用户可见功能必备> + 广度:<角色×任务几行+伴侣清单做/不做统计,UI 类必填;纯内部改动写"不适用">(4.9) + 架构:<动哪些模块/新增耦合/ADR 触发否——跨模块必填,单文件写"无结构性影响">(4.10) + 测试:<要求> / 假设:<我做的假设> + 验收: + 行业: + 歧义(如有):<只列无法自决且影响交付的> +``` + +通知:`notify.py . plan_approval --title "📋 待批准 " --body "<一句>" --ref <清单id>` +(--ref 进待决台账,批准时自动回流)。然后明确请求批准,三选一: +(批152 起批准/临行转写时 plan_approve 自动补发带 **EARS 判据段** 的 +plan_approval 回执推送——M/L 判据单独凸显给人,此处手动推送仍可先行) + +- **「批准 / 开始 / ok」** → 运行权威转写器(一次完成全部落产物动作): + ```bash + python3 "<插件路径>/hooks/scripts/lib/plan_approve.py" .regress/manifests/<清单>.md --note "对话批准" + ``` + 它会:status→in-progress、填 `approved.{at,note}`、**漂移检查**(base_head ≠ + 当前 HEAD → 警示旧基线,差异报人类确认后继续)、`plan_approved` 入地层 +- **修改意见** → 对话完善计划、更新清单(保持 `planning`,每次细化自动入地层 + `plan_refined`),再次输出卡片请求批准。**产品理由的否决**(行业/习惯/该简化) + → 埋学费化石(跨任务重复经 learn 升格为产品规律): + ```bash + python3 "<插件路径>/hooks/scripts/lib/journal.py" . add design_rejected \ + '{"reason_class":"行业惯例|用户习惯|该简化|其他","note":"<一句>"}' + ``` +- **「取消」** → `plan_approve.py <清单> --cancel`(status: cancelled 归档不实施, + `plan_cancelled` 入地层) + +规则(浓缩):批准词必须来自人类(AI 不得代填 approved.at,地层让一切批准 +可审计);人类直填 approved.at=产物直通,AI 补跑转写对齐(不覆盖时间戳); +原始需求已预批准("直接做")→ 展示卡片标注后直接实施;S 档小改动走 quick; +待批准期间边界内编辑被拦(planning 机器强制),断点续作 /regress-resume。 + +## 步骤 6:实施 + +批准后进入开发。**长任务心跳(P1#13)**:每完成一个阶段边界推 progress +(notify.py . progress,标题「⏳ 清单id·阶段n」正文一句)—— +授权自主推进期间人不看屏也知道走到哪。 + +批准后进入开发。实施期间边界守卫/文件指纹/漂移检测/意图复述照常工作; +完成走 /regress-track → commit 门禁(自跑测试+脆弱点复验)。 + +实施中卡住(环境/权限/修复 3 次仍败/需人类决策)→ 别硬磨: +`plan_approve.py <清单> --block --reason "..." --need "..."`——四问落产物, +受阻期间拦编辑(合法停止),need 转达人类,解阻 --unblock。 diff --git a/external_plugins/regress-guard/commands/regress-quick.md b/external_plugins/regress-guard/commands/regress-quick.md new file mode 100644 index 00000000..0002fd59 --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-quick.md @@ -0,0 +1,79 @@ +--- +description: 快速模式——机器判据达标的小改动专用(合并 plan+track 一步到位) +argument-hint: <需求描述> +allowed-tools: Read, Write, Edit, Bash, Grep +--- + +# /regress-quick — 快速模式 + +合并 `/regress-plan` 和 `/regress-track` 为一步:**基于 git diff 直接生成清单** +(不分 planned/actual,全部算实际改动)。清单标 `mode: quick`。 + +## 与 full 模式的分界(v1.37:机器判据,不再靠"个人/小改动"感觉) + +**四条全过才许 quick**(任一不过 → 走 /regress-plan full): + +1. 改动 ≤3 个文件(git diff 统计,含新增) +2. 无 new-file 以外的结构类型(model 签名变更/跨模块调用链 → full) +3. 纯内部改动(不新增用户可见行为——新功能/UI/接口语义变化 → full, + 用户可见必过广度矩阵与 DoD,那是 full 的领土) +4. 无环境/依赖变更(requirements/package/build/配置文件 → full) + +| | full 模式 | quick 模式 | +|---|---------|----------| +| 流程 | plan→开发→track→verify | 开发→quick→commit | +| F3 追踪 | ✅ 区分计划内/外 | ❌ 不区分(全是 actual) | +| 脆弱点 | 拓扑穷举 | **至少 1 条**(0 条=没想过,不是没有风险) | +| runner 缺失 | 阻断 | **豁免**(warn 放行留痕——门禁认 mode: quick) | +| 广度/架构/ADR | 按触发条件 | 永不(quick 定义即不触发) | + +## 流程 + +参数 `$ARGUMENTS` 是需求描述。 + +### 步骤 1:判据检查(四条) + +```bash +git diff --name-only HEAD; git ls-files --others --exclude-standard +``` +统计文件数、看类型、找用户可见面、找环境文件。**判据不过就在此说 +"这单走 full",转 /regress-plan**——塌方曲线的病根是重仪式被绕过, +不是仪式被正确豁免;机器判据让豁免留痕、越级可审计。 + +### 步骤 2:用户先改好代码 + +quick 模式假设代码**已经改完了**。如果还没改,提示用户先改代码。 + +### 步骤 3:生成清单(全部记为 actual_changes) + +```yaml +--- +id: REGRESS-<序号> +requirement: "<需求>" +status: in-progress +mode: quick +planned_changes: [] +actual_changes: + - id: F1 + file: "<文件>" + type: <类型> + reason: "quick 模式:基于 git diff 生成" + tests_required: [unit] +fragile_points: # 至少 1 条(哪怕就是"无测试覆盖,人工验证过 X") + - id: V1 + kind: oracle + description: "<这条改动最怕什么>" + verify: "<可执行命令>" + status: locked +--- +``` + +### 步骤 4:提示用户 + +``` +⚡ 快速清单已生成(mode: quick·判据四条全过) + F1: src/auth/login.js (method-logic) + V1: <脆弱点一行> + rollback: git revert 即回滚(v1.41 全档必填——触及迁移/外部状态时写真路径) +现在可以直接 git commit。hook 自动跑测试验证;无 runner 时 warn 放行(quick 豁免)。 +``` diff --git a/external_plugins/regress-guard/commands/regress-resume.md b/external_plugins/regress-guard/commands/regress-resume.md new file mode 100644 index 00000000..1f36307f --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-resume.md @@ -0,0 +1,100 @@ +--- +description: 从 .regress/ 产物层单侧重建工作现场(断点续作,不依赖对话历史) +allowed-tools: Read, Bash, Grep +--- + +# /regress-resume — 现场重建(产物自足性) + +对话会压缩、会丢上下文;产物层(.regress/)不会。本命令**只从产物重建现场**, +不依赖任何对话历史——这是「以产物为中心」的验收标准: +清单 + 地层 + 决策三样在,工作现场就在。 + +## 执行 + +### 1. git 现状 + +```bash +git status --short && git log --oneline -5 +``` + +非 git 项目跳过。有未提交改动 → 列出文件(对照清单判断是半成品还是漂移)。 + +### 2. 活跃清单逐份读取 + +`.regress/manifests/*.md` 中 status ∈ planning / in-progress / verifying / **blocked** 的,逐份提取: + +- id / requirement / status / created_at +- **blocked 的清单置顶**:读 `blocked` 四问(reason/tried/unsafe_why/need)—— + 这是需要人类处理的事,恢复现场第一件事就是把它亮出来 +- 脆弱点:open / locked / flagged 各几个(**open = 禁提交**,优先处理) +- **实施顺序对齐**:清单正文有「实施顺序(一步一响)」表时,对照 git diff 与 + 已 locked 的脆弱点推断当前走到第几步——下一步从**第一个未达里程碑的步骤**继续 +- **老格式回填**:清单正文缺「环境准备/实施顺序/报错自救」段(v1.15 前创建)→ + 提示按新模板回填(写给失忆的读者标准),回填本身就是恢复现场的一部分; + `.regress/` 里没有 README.md → 按模板同步补上零号入口 +- 边界 boundary.include +- approved.at(空 = 还没批,提醒人类批准) +- actual_changes 与 planned_changes 的差(F3 回写了几条) + +### 3. 决策考古(方向记忆) + +- 读 `.regress/decisions.md` **尾部 20 行**:最近的方向纠正、否决过的方案 + (否决过的方案不重新提出——那是已经交过学费的岔路) +- 跨会话慢性失败: + +```bash +python3 "<插件路径>/hooks/scripts/lib/journal.py" . digest +``` + +digest 非空 → 每条签名查 `journal.py . raw` 找历次现场,归纳根因模式。 + +- 近期关键化石(假设失效 / 受阻 / 解阻——"为什么"层的叙事): + +```bash +python3 "<插件路径>/hooks/scripts/lib/journal.py" . raw | grep -E 'assumption_broken|task_blocked|task_unblocked' | tail -5 +``` + +### 4. 历史指标 + +```bash +python3 "<插件路径>/hooks/scripts/lib/history.py" .regress summary +``` + +- tech_debt > 0 → 有 bypass 未补回归,提醒还债 +- outside_gate_commits > 0 → 门禁采用率的诚实镜子 + +### 5. 知识库 + +读 `.regress/knowledge-base.json`:条目数 + 最近 3 条的问题关键词 +(上次查过的攻略直接用,不再查)。 + +## 输出格式(现场重建简报) + +``` +🔁 现场重建 · + +📋 REGRESS-():<需求一句话> + 脆弱点: open / locked / flagged + 边界: + 批准: + 改动: 计划 / 已回写 + +📜 最近决策: +⛏️ 慢性失败: +💳 债务: 笔 bypass 未补回归 + +下一步(按 status): + blocked → 🛑 最优先:把 blocked.need 转达给人类(受阻等待人类输入) + planning → 重新输出计划卡片请人类批准(lib/plan_approve.py 转写) + in-progress→ 对齐实施顺序表,从第一个未达里程碑的步骤继续;脆弱点未锁死的先锁 + verifying → 跑 /regress-verify 收尾 +``` + +多份活跃清单 → 逐份输出卡片(按 created_at 排序),下一步只列最旧的一份。 + +## 自主决策 + +- 无活跃清单 → 读 `.regress/history.jsonl` 最后几条事件,报告"上个任务已收尾", + 并列出 decisions.md 要点供开新任务参考 +- 无 `.regress/` → "本项目未接入 regress-guard,/regress-init 初始化" +- 信息缺失(字段空/文件不存在)→ 跳过该项并在简报中注明,不猜 diff --git a/external_plugins/regress-guard/commands/regress-stats.md b/external_plugins/regress-guard/commands/regress-stats.md new file mode 100644 index 00000000..02f6fe90 --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-stats.md @@ -0,0 +1,77 @@ +--- +description: 健康报表:门禁拦截/债务/规律命中/僵尸清单/钩子活性一屏读(观测期仪表盘,只读不改) +allowed-tools: Read, Bash +--- + +# /regress-stats — 健康报表 + +一屏读完治理实况。**只读**——本命令不改任何状态,全部数据来自既有产物。 +观测期仪表盘(病例:v1.26.1 审计时手工统计部署差异,本命令是那次需求现场的工具化)。 + +## 执行(六个既有 CLI,零新采集) + +```bash +# 1. 提交观测:通过/绕门禁/覆盖率/技术债 +python3 "<插件路径>/hooks/scripts/lib/history.py" .regress summary + +# 2. 考古地层:跨会话重复失败(稳定经验候选) +python3 "<插件路径>/hooks/scripts/lib/journal.py" . digest + +# 3. 规律账本:固化候选 🦴 / 降级候选 🍂 +python3 "<插件路径>/hooks/scripts/lib/rules_ledger.py" . health + +# 4. 钩子活性(链外看门狗;P1#14 双路径:用户级装机在 regress-guard-docs,插件模式在插件根) +{ python3 ~/.zcode/regress-guard-docs/check_docs.py 2>/dev/null || python3 "<插件路径>/scripts/check_docs.py"; } 2>&1 | grep -E "config.file.invalid|空 matcher" || echo "钩子链健康" + +# 5. 机器事实卡:条数/最老/陈旧(机制存活判据:长期零新增=死重可退场) +python3 "<插件路径>/hooks/scripts/lib/facts.py" health + +# 6. 僵尸清单:planning/verifying 搁置 >30 天(哨兵口径) +grep -l "status: planning\|status: verifying" .regress/manifests/*.md 2>/dev/null | while read f; do + age=$(( ( $(date +%s) - $(stat -c %Y "$f") ) / 86400 )) + [ $age -gt 30 ] && echo "⏰ $(basename $f) 已搁置 ${age} 天" +done; true + +# 7. 推送闭环(v1.34):送达率/待决未决/误报率——告警从广播到可度量可校准 +python3 "<插件路径>/hooks/scripts/lib/notify.py" stats + +# 8. 待决裁决:人类说「有用/误报/忽略」后由 agent 记账(对号,见推送正文〔待决#N〕) +python3 "<插件路径>/hooks/scripts/lib/pending.py" list --pending + +# 9. 块消息有效性(v1.47 影子采集,GEPA 评分环):拦截消息即提示词—— +# 同清单同原因反复被拦=消息没教会 agent;🚨 无效候选(≥3 且跨会话/跨 7 天, +# 分母恒带)值得改写消息文案;⚠️ repeat 是告警级观察 +python3 "<插件路径>/hooks/scripts/lib/history.py" .regress nudge + +# 10. 顾问采纳率(v1.49,给裁判装评分器):adopted+partial 计入采纳; +# 低采纳率=顾问意见不合用,校准咨询姿势(finish 代谢位落 advisor_adoption 事件) +python3 "<插件路径>/hooks/scripts/lib/journal.py" . adoption + +# 11. 召回有效性(v1.58,事件序代理·弱证据只排序):rule_recall 后同清单 +# 直接放行=干净解决;间有拦截=带干预解决(单列不加总);未决。扩召回接线看这里 +python3 "<插件路径>/hooks/scripts/lib/history.py" .regress recall + +# 12. 拦截热力图(v1.62):commit_blocked 按 reason 聚合频次——高频原因 +# 就是下一个该接召回的拦截点(v1.54 只挂3点,扩点看这里的数据) +python3 "<插件路径>/hooks/scripts/lib/history.py" .regress heatmap +``` + +## 输出格式 + +``` +📊 regress-guard 健康报表 · <项目名> + 门禁: 过 / 绕行(IDE 直提)/ 债 笔 + 地层: 事件 / 跨会话重复失败 个签名 + 规律: 条(🦴 固化候选 / 🍂 降级候选 ) + 事实卡: 条机器事实 / 最老 天 / 🍂 条未刷新 + 钩子:健康 | ⚠️ <看门狗告警> + 僵尸:<无 | ⏰ 清单×n> +``` + +## 解读要点 + +- **绕行率持续高** → 门禁采用率问题(IDE/终端直提),不是测试问题——先解决提交习惯 +- **债不还** → 赦免权闭环失效的前兆(PHILOSOPHY §11) +- **规律只涨不落** → 该跑 learn 并修剪降级候选(熵增警戒) +- **事实卡长期零新增** → 机制死重,摘掉 finish 的机器级分支即退场(机制也要代谢) +- 全部为零 → 项目刚开始或钩子长期静默——用第 4 项确认链活性 diff --git a/external_plugins/regress-guard/commands/regress-trace.md b/external_plugins/regress-guard/commands/regress-trace.md new file mode 100644 index 00000000..68bece08 --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-trace.md @@ -0,0 +1,64 @@ +--- +description: 查看交付链:需求→会话→事件→提交 的可追溯视图(文本版 Inspector) +argument-hint: [manifest-id] +allowed-tools: Read, Bash +--- + +# /regress-trace — 交付链追溯 + +把 `.regress/history.jsonl` 按 **意图→过程→产出** 组织成可读的交付链(借鉴 Harness Inspector 的证据链模型,文本版实现)。 + +## 执行 + +```bash +python3 "<插件路径>/hooks/scripts/lib/history.py" .regress trace +# run 因果链过滤(v2.0.0 批162):只列带该 run_id 的事件——自迭代 run/ +# 哨兵轮次一屏看全(清单带 run: 字段时事件才带 run_id) +python3 "<插件路径>/hooks/scripts/lib/history.py" .regress trace +``` + +## 输出示例 + +``` +📌 REGRESS-001 (2026-08-12T17:20) + └─ 会话 sess_3744109b + 🚫 17:20 commit_blocked untracked_files → src/utils/validator.js + 🔍 17:21 f3_discovered + ✅ 17:22 commit_passed (jest) @a3f8c2d1 + 📦 产出: 1 次放行提交 + +📌 REGRESS-002 (2026-08-13T11:32) + └─ 会话 sess_7f9299c9 + ⚠️ 11:32 error + ✅ 11:32 commit_passed (none) @b7e1f0a9 + 📦 产出: 1 次放行提交 +``` + +## 怎么读 + +| 元素 | 含义 | +|------|------| +| 📌 manifest | 意图锚点——哪个需求 | +| └─ 会话 | 过程锚点——哪次 ZCode 会话 | +| ✅/🚫/⚡/❌ | 事件:放行/阻断/bypass/测试失败 | +| @sha | 产出锚点——基于哪个提交 | + +## 用途 + +- **检查交付**:一个需求经历了什么(多少阻断、是否 bypass、最后有没有产出) +- **发现异常**:一个需求挂了很多会话但 0 产出 → 卡住了 +- **审计 bypass**:⚡ 事件有到期时间,可对照是否补了回归 + +## 哨兵视图(v1.34 多会话) + +trace 看历史,哨兵看**现在**——多会话并行时任何单个会话都看不见别人的 +活跃清单(冲突只在集成点现形)。先跑: + +```bash +python3 "<插件路径>/hooks/scripts/lib/sentinel.py" . +``` + +一屏看清:活跃清单 × 会话归属(本会话/他会话/共享无戳)× 悬停时长 +(in-progress >1 天 ⚠️、blocked 🛑 在等人类),以及**多会话声明了同一 +文件**的重叠清单——提交前先对焦,比提交后回滚便宜。门禁的跨会话拦截 +消息会指路到这里。 diff --git a/external_plugins/regress-guard/commands/regress-track.md b/external_plugins/regress-guard/commands/regress-track.md new file mode 100644 index 00000000..e434c125 --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-track.md @@ -0,0 +1,47 @@ +--- +description: 对比 git diff 发现 F3 并直接回写(AI 完成修改后自动执行,不需用户手动触发) +argument-hint: [manifest-id] +allowed-tools: Read, Write, Edit, Bash, Grep +--- + +# /regress-track — 发现 F3 并回写 + +**AI 完成代码修改后应自动执行此命令,不等用户手动触发。** + +对比 `git diff` 与清单,找出 F3(计划外改动),**直接回写**,汇报结果。 + +## 执行(不等确认) + +```bash +git diff --name-only HEAD 2>/dev/null +git ls-files --others --exclude-standard 2>/dev/null +``` + +1. 找最新 `status != done` 的清单 +2. 对比 `planned_changes`,找出不在清单中的文件 = F3 +3. **直接回写**到 `actual_changes`——**这也是开发边界的扩界出口**:被 boundary_guard + 拦下的文件,回写 actual_changes 后边界随之扩展(扩界必须经过这一步 = 留痕) +4. 为每个 F3 分析影响范围(`grep -rl` 找引用方) +5. 输出汇报 + +## 自主决策(不问用户) + +- F3 只改注释/格式 → 忽略 +- F3 改了逻辑 → 回写 + 提示补测试 +- F3 是新文件 → 回写 + 提示补测试 + +## 过滤规则(自动忽略) + +`.regress/`、`AGENTS.md`、锁文件、纯 `.md`、`*.test.*` + +## 输出 + +``` +🔍 改动审计完成 + ✅ F1, F2(计划内) + ⚠️ F3: src/utils/validator.ts → 已回写 + +继续提交。 +``` + +无 F3 时:`✅ 无计划外改动,可以提交。` diff --git a/external_plugins/regress-guard/commands/regress-uninstall.md b/external_plugins/regress-guard/commands/regress-uninstall.md new file mode 100644 index 00000000..602162a8 --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-uninstall.md @@ -0,0 +1,34 @@ +--- +description: 卸载 regress-guard(清理用户级配置 + hook + 命令) +allowed-tools: Bash +--- + +# /regress-uninstall — 卸载 + +清理 regress-guard 的所有用户级配置。项目的 `.regress/` 目录保留(历史数据不删)。 + +## 执行 + +找到卸载脚本并运行(**批191 起卸载器是 Python,Windows 10+ 原生可用**): + +```bash +# 尝试找到 uninstall.py +for candidate in \ + "${HOME}/.zcode/workspace/default/regress-guard" \ + "${CLAUDE_PROJECT_DIR:-$(pwd)}/regress-guard"; do + if [ -f "${candidate}/uninstall.py" ]; then + python3 "${candidate}/uninstall.py" + exit 0 + fi +done +echo "找不到 uninstall.py" +``` + +## 清理内容 + +- 删除 `~/.zcode/commands/` 下的 16 个命令 +- 删除 `~/.zcode/regress-guard-hooks/` 目录 +- 从 `config.json` 移除全部 regress-guard hook(统一过滤,零死钩子残留) +- 从 `AGENTS.md` 移除回归契约块 + +**安全**:不会删除用户的其他 skill/命令/配置。项目的 `.regress/` 保留。 diff --git a/external_plugins/regress-guard/commands/regress-update.md b/external_plugins/regress-guard/commands/regress-update.md new file mode 100644 index 00000000..b8e90c82 --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-update.md @@ -0,0 +1,45 @@ +--- +description: 检查并更新 regress-guard 到最新版(手动触发;SessionStart 也会自动检测) +allowed-tools: Bash +--- + +# /regress-update — 更新 + +手动检查源目录是否有新版本,有则升级。 + +**通常不需要手动跑**——每次 ZCode 启动时 self_heal 会自动检测版本差异,源目录更新了会静默升级。本命令用于立即触发更新(不等下次启动)。 + +## 执行 + +```bash +python3 "<安装路径>/regress-guard-hooks/lib/self_heal.py" +``` + +如果有更新: +``` +✅ 自动升级 v0.5.0 → v0.6.0(15 个文件已更新) +``` + +如果已是最新: +``` +✅ 已是最新版 v0.6.0 +``` + +## 如何更新源目录 + +如果你是开发者,修改了 `~/.zcode/workspace/default/regress-guard/` 下的代码: +1. 改完后 bump `plugin.json` 的 version(如 0.5.0 → 0.6.0) +2. 所有已安装的 ZCode 下次启动时自动升级(self_heal 检测到版本差异) +3. 或在其他项目里跑 `/regress-update` 立即生效 + +## 升级机制原理 + +``` +源目录 plugin.json: version=0.6.0 +安装目录 .source: source_version=0.5.0 + ↓ self_heal 检测到 + 0.6.0 > 0.5.0 + ↓ 自动全量覆盖 + commands/hooks/lib 全部更新 + .source 的 source_version 更新为 0.6.0 +``` diff --git a/external_plugins/regress-guard/commands/regress-verify.md b/external_plugins/regress-guard/commands/regress-verify.md new file mode 100644 index 00000000..36a2bc04 --- /dev/null +++ b/external_plugins/regress-guard/commands/regress-verify.md @@ -0,0 +1,150 @@ +--- +description: 跑测试预览结果(提交时 hook 也会自动跑;测试失败 AI 应自行修复重跑) +argument-hint: [manifest-id] +allowed-tools: Read, Write, Edit, Bash +--- + +# /regress-verify — 测试验证 + +**提交时 hook 会自动跑测试。本命令用于提交前预览。** + +如果测试失败,**AI 应自行分析失败原因、修复代码、重跑测试**——不要把错误甩给用户。 + +## 自主决策流程 + +``` +跑测试 + ├─ 全通过 → 汇报"测试通过,可以提交" + ├─ 有失败 → 自己分析失败原因 + │ ├─ 是自己的代码 bug → 自己修,重跑 + │ ├─ 是测试本身写错 → 自己修测试,重跑 + │ └─ 是环境/依赖问题 → 修复环境,重跑 + │ └─ 修了 3 次还失败 → 才找用户,附上完整错误 + └─ 无测试运行器 → 告知用户需要装什么,不问"要不要装" +``` + +## 执行 + +直接用 Bash 调项目测试运行器: +- **Node.js**: `npx jest --json --silent` +- **Python**: `python3 -m pytest -q --tb=line` +- **Java**: `mvn test -q` +- **Go**: `go test ./...` + +## 验收标准(v1.25:需求侧的 done 定义) + +清单正文「验收标准」表逐条拿证据(判据 → 证据列的 verify 命令 / `human_check:`), +通过在表内标 `locked`。**汇报里的"根治/完成/更好"必须能指回这里某一条判据**, +指不回去的词不许说(病例:REGRESS-005"根治"×3,每次证据等级零)。 +非功能底线(延迟/安全/兼容)若发现缺席——报告里标红"验收标准缺非功能判据", +补上再继续(缺席的底线会在设计后期反噬,jitter buffer 病例)。 + +## DoD 状态完备性(v1.35:跑通≠完成) + +用户可见功能(清单「DoD 状态完备性」节非空)逐状态**真渲染看过**才算过: + +- 正常态/空态/错误态/加载态逐个用 browser-use(web-gui-tester)打开看一遍, + 截图为证;看不了渲染的(CLI/API)用等价证据(空输入请求、错误注入响应) +- **代码审查发现不了空态尴尬**——"我读了代码,没有空态问题"不是证据, + 截图/human_check 才是 +- 状态证据落脆弱点 kind=product(open 挡提交的机器强制与脆弱点同轨); + 需要人眼裁决美感的走 sensory(`human_check:`,只收通过/不通过) +- 伴侣功能清单里写了"不做"的项不复检——决策已留痕,不翻旧账 + +## 脆弱点证据(公理一:证据律) + +活跃清单 frontmatter 里有 `fragile_points` 时,测试之外还要逐条拿证据: + +1. 逐条执行 `verify` 命令(必须非交互、有 exit code) +2. 通过 → 回写该条 `status: locked`(证据 = 命令 + exit 0,不是"我觉得没问题") +3. 失败 → **先查该条的 `rescue:` 字段和正文「报错自救」表**(为这个错准备的自救命令, + 跑完再重试 verify);仍失败保持 `open`,失败输出贴进报告;自己能修就修完重跑, + 修好后把自救命令**回填** rescue(下次不再踩同一个坑) +4. 显式不处理 → `status: flagged`,description 追加知悉原因 +5. **结果与清单推测不符(假设被证伪)** → 别只改 description 了事: + 清单正文「假设失效记录」表追加 was→reality→evidence,并埋地层化石—— + ```bash + python3 "<插件路径>/hooks/scripts/lib/journal.py" . add assumption_broken \ + '{"manifest_id":"","vid":"V?","was":"<原假设>","reality":"<实际>","evidence":"<命令>"}' + ``` + 失效过程(为什么错、错在哪)比修正后的结论更有价值——它是下次推测的校准器 +6. **sensory 类脆弱点(人的感官是唯一判据,v1.19)**:终验时问人**一个布尔问题** + (通过/不通过,禁止开放描述)→ 结果落化石 → 转 locked(verify 写 `human_check:`): + ```bash + python3 "<插件路径>/hooks/scripts/lib/journal.py" . add human_check \ + '{"manifest_id":"","vid":"V8","question":"对讲声音清晰无断续?","result":"pass"}' + ``` + 门禁不复跑感官——机器验证"人确认过"这个**事实的存在**(AVS 公理三:人只是传感器, + 传感器读数入档即证据);提问前先推送通知:`notify.py . sensory --title "🎤 感官终验 " --body "<问题>"` + +**open 状态的清单提交时会被 hook 阻断**——别绕过它,去拿证据或挂牌。 + +## 排障纪律(v1.23:失败即证伪,不叠层) + +修复部署后 oracle(测试/真机/用户)仍报同样的问题 → **假设被证伪,不是防御不够**: + +- 回清单「假设账本」新增一行(假设/检验命令/结果/裁决=证伪),重新取证—— + 下一层防御必须由**新证据**指认,禁止"再叠一层说不定就好了" + (病例:REGRESS-005 三层防御烧 6+ 轮部署循环,真凶是自己上一轮的参数) +- **宣称跟着证据走**:根因被证据指认、oracle 复验通过之前,只说 + 「缓解了 X,待验证」,**不说「根治」**(病例:同会话"根治"宣言发了三次, + 每次证据等级为零) + +## 真机/联调阶段纪律(用户令:"你自己弄!成功后再叫人类!") + +发起、连接、抓包、日志核对**全部自主完成**;仅感官终验(声音/画面/手感)时叫人, +成功后通知。禁止让人类"帮忙看下日志"、"试下输个特殊字符"——人是传感器不是备用断言机。 + +## 启动预检(v1.19:环境自检 fail-fast) + +环境类脆弱点优先落为 preflight 脚本/健康检查(探测 + 断言 + 不符即**拒绝启动**), +而不是 README 人读指令("请确保 Redis 已启动"是被禁止的形态)。预检标准不止"端口活着"—— +**跨服务契约往返断言**:ticket 写→读一致、流端口握手可完成(多租户前缀这类静默篡改 +只有往返能抓到,实测教训:4001 票据 bug 绿灯存活)。verify 时环境脆弱点 = 跑预检脚本拿 exit code。 + +## 受阻出口(v1.14;v1.18 加知识型分流) + +修复 3 次仍失败、或需要输入才能继续 → **先分流再受阻**: + +1. **知识型阻塞**(API 用法/方案取舍/报错不理解)→ 先带四问上下文去 consult: + 顾问能解 → 实施其建议继续(意见标注采纳/部分采纳/不采纳);仍不能解 → 下一步 +2. **环境/权限型阻塞**(端口不通/凭据缺失/共享环境)→ 顾问无能为力,直接受阻: + +```bash +python3 "<插件路径>/hooks/scripts/lib/plan_approve.py" <清单> --block \ + --reason "卡在哪" --tried "已试什么(含顾问意见)" --unsafe "为何不能绕" --need "需要人类什么" +``` + +受阻期间边界守卫拦编辑(合法停止);把 need 报告给人类;解阻后 `--unblock` 恢复。 + +## 环境指纹漂移检测(公理一:谁帮我设的 CUDA?) + +`.regress/env.lock.json` 存在时,重新采集当前环境并与锁文件对比: + +```bash +python3 -c "import sys;print('python', sys.version.split()[0])" +node --version 2>/dev/null; git --version | cut -d' ' -f3; uname -r +``` + +python / node / git / 内核任一与锁文件不一致 → 在报告里标红"环境已漂移", +并列出漂移项——上次验证过的结论,换了环境不作数。 + +## 输出 + +``` +✅ 测试通过 9/9,可以提交。 +``` + +或(自行修复后): +``` +⚠️ 测试初次失败,已修复 1 个问题,重跑通过 9/9。 + 修复:src/math.js divide() 除零未处理 +``` + +或(修不了才找用户): +``` +❌ 测试失败,我尝试修复 3 次仍未通过。 + 失败用例:"should connect to database" + 原因:需要真实的数据库连接,我无法自行修复。 + 需要你确认:是 mock 掉还是用测试数据库? +``` diff --git a/external_plugins/regress-guard/docs/AGENT-HOOKS.md b/external_plugins/regress-guard/docs/AGENT-HOOKS.md new file mode 100644 index 00000000..db05c4ab --- /dev/null +++ b/external_plugins/regress-guard/docs/AGENT-HOOKS.md @@ -0,0 +1,76 @@ +# AGENT-HOOKS 多宿主适配层(批158,v1.107.0) + +把 regress-guard 的提交门禁暴露为 [AGENT-HOOKS-0.1](https://github.com/responsibleai/agent-hooks) +interceptor——任何实现该契约的宿主(LangGraph / OpenAI Agents SDK / Microsoft +Agent Framework / Semantic Kernel / LlamaIndex / CrewAI / Claude Agent SDK / +裸参考宿主)都能安装。**旗舰场景:AI 谎报 done 直提 main——pre_tool_call +拦截点上的提交门禁。** + +## 安装(宿主侧,Python SDK 形态) + +```python +from agent_hooks import kind # 宿主侧 SDK(拦截器本体零依赖) +import sys; sys.path.insert(0, "/hooks/scripts/lib") +from agent_hooks_adapter import RegressGuardInterceptor + +# 宿主注册(config 透传构造参) +INTERCEPTORS = [ + kind("dev.regress-guard.commit_gate", + lambda config, ctx: RegressGuardInterceptor(**config)), +] +# 中间件安装按各宿主文档:middleware=[create_agent_hooks_middleware(INTERCEPTORS)] +``` + +构造参:`project_dir`(门禁治理的项目根,缺省=宿主进程 cwd)、 +`guard_path`(缺省=适配器旁的门禁脚本)、`timeout`(秒,缺省 600)。 + +## 能力子集声明(spec §13.2 精神——如实声明,未做的不写) + +**覆盖**:仅 `pre_tool_call` 拦截点上的提交门禁子集——检测 tool_call 是否 +git 提交类命令(含复合 add+commit 形态),是则调用 regress-guard 完整门禁 +(测试自跑+清单状态机+归因+计数对账),结果转译为 allow/deny verdict。 + +**不覆盖**:其余七个拦截点(agent_startup/input/pre_model_call/ +post_model_call/post_tool_call/output/agent_shutdown——全部直接 allow); +transform(我们不重写命令);approval 接缝(deny 不带 approval 块=不可 +升降,宿主侧 approval seam 未使用);streaming 向量(AH-CTK-110+)。 + +**on_error 姿态**:拦截器自身 fail-closed——门禁子进程崩溃/超时/非零退出 +一律 deny(reason 前缀 `regress_guard:`,绝不冒用宿主合成词 `host_error:`)。 +宿主侧另有 §6.3 兜底(拦截器异常时宿主合成 deny)——双层对齐。 + +**identity provider**:不使用 approval 接缝,无 content-derived identity。 + +**规范钉版**:AGENT-HOOKS-**0.1** 语义(context core §4.1 / tool_call +§4.2 / Verdict §5 / 信封 §5.5)。上游升版改字段名时适配器会错位—— +失效形态=fail-closed(宿主按非法 verdict 合成 deny),不会静默放行。 + +## 三档路径(顾问裁:反转白名单制——不确定一律放慢) + +- **A 档**:工具名 ∈ 已知非 shell 白名单(Read/Grep/Glob 等读侧)且无命令 + 键 → 零开销 allow。 +- **B 档**:args 命中命令类键(command/cmd/bash/script/…)→ 进程内跑门禁 + 同源提交检测,非提交类 → allow(零子进程)。 +- **C 档**:未知形态(新框架新工具名/陌生键)→ 子进程慢路径**让门禁自判** + ——宁可多付一次快退出的子进程开销,不冒漏拦提交的险。 + +**缺命令策略**:抽不到任何命令字符串 → 显式 allow(无命令即无提交)。 + +## 隐私律 + +evidence 只存元数据(runner/adapter 版本/门禁文件 sha256/路径档位/exit +code/耗时/stderr 摘要),**不存原始命令**(可能含密钥);canonical JSON +主动截到 spec 上限 10240 字节内。 + +## ZCode 宿主注意(双门禁) + +ZCode 已有原生钩子链跑同一门禁——在 ZCode 上再装本适配器会**双重门禁** +(同一次提交拦两次)。本适配器面向**其他七个框架**的宿主;ZCode 用户用 +原生钩子链即可。 + +## 未做与后续观察位 + +- 官方 47 场景 CTK 全套运行(conformance/ 目录)——批 2 候选,先证明适配器 + 有真实宿主使用者再投入。 +- 真实宿主接线实测(任一框架)——「一次适配通吃」目前是契约理论上限, + 无宿主实测背书。 diff --git a/external_plugins/regress-guard/docs/PHILOSOPHY.md b/external_plugins/regress-guard/docs/PHILOSOPHY.md new file mode 100644 index 00000000..2cdac65d --- /dev/null +++ b/external_plugins/regress-guard/docs/PHILOSOPHY.md @@ -0,0 +1,262 @@ +# regress-guard 的设计思想 + +> 这不是一个测试工具,是一套关于**如何与 AI 协作而不被它烧掉**的治理哲学。 +> 功能会过时,思想不会。本文按依赖顺序展开,每一条都来自真实的教训, +> 每一条都在代码里有机器强制的对应物——**没有 hook 兜底的思想只是愿望**。 +> (v1.108 起本句不再写律条计数——手写计数必漂移(「十三条」标本:写下时 +> 就已过时),节数正确性由 § 编号连续性锁测试派生保证。) + +--- + +## 0. 起源:信任链靠证据,不靠声明 + +AI 改代码会偷偷改坏,而"改坏了"往往没有失败信号——测试碰巧绿、日志碰巧静默。 +本项目的全部机制回应同一个问题:**当执行者自己就是不确定性来源时,信任从哪来?** + +答案是一句话,也是本项目的第一性原理: + +> **不要指望它变聪明,要指望它变死板。** +> 灰尘对得上(文件指纹)、失败刻在石头上(考古地层)、 +> 拆弹要输密码(执行阀)、未挂牌的脆弱点不许出门(V 门禁)。 + +## 1. 四公理:基于脆弱性前置的分布式认知控制论 + +**公理一·脆弱性前置**——"正确"是瞬态,"锁死脆弱点"才是产出。每个任务开工前穷举致败 +脆弱点(环境/依赖/资源/数据/并发/**测试判据**/**感官终验**),逐个挂牌: +`open`(禁提交)→ `locked`(verify 命令实测通过)或 `flagged`(带病挂牌,写明知悉原因)。 +**未列出的脆弱点才是真正的未知风险。** + +**公理二·认知物质化**——人脑和会话上下文都是易失存储。决策写 `decisions.md` +(含否决过的方案),文件改动前比对指纹,一切"知道"都刻进文件。 + +**公理三·考古持久化**——失败/风险/用户纠正自动 append 进地层(journal,随 git 入库)。 +单会话高频失败是重试噪声,**跨会话重复失败才是稳定经验**——地层让未来会话像考古学家 +一样挖掘规律,而不是重新踩一遍坑。 + +**公理四·建议执行分离**——不可逆动作(mkfs/dd/force push/DROP)需要显式令牌; +顾问意见只注入文本、**永不执行**。建议的权力上限就是被听见。 + +## 2. 检测 ≠ 拦截 + +事后提醒是"追认":token 已经烧了,熵已经生了。边界必须在动作发生**前**物理拦截。 + +实测数据:不加边界的 agent 一天烧 6B token、半个月完不成一个任务、人工复检几十轮; +加上"边界内才许编辑"的事前拦截后,降到每天 1B 以内、多任务/天、自动复检即达标。 +**约束不是拖慢,是提速**——漫游才是最贵的。 + +## 3. 证据律:done 由门禁发,不由人自称 + +`locked` 的意思是"verify 命令**此刻**能过",不是"曾经能过"——提交门禁会机器复跑 +每条 locked 的验证命令,自封不算数。测试全过时由门禁写入 `done`,而不是开发者自己宣布。 + +> **没有证据的状态,被视为不存在。** + +## 4. 取证先于结论:诊断的证据律 + +§3 管 done 的嘴,这一条管诊断的脑:**代码要测试兜底,结论要证据兜底—— +同一条律,两个域。** + +病例(REGRESS-005,对讲音频排障):同一个会话里,所有突破都来自两三条命令的取证 +(Send-Q=0 定位幽灵会话、日志实测 50fps 推翻"前端 81fps 超发"的定罪),所有事故都 +来自未经取证的动手(读错 ss 列序→守卫误杀活链致设备死锁;假设"同帧连发"→去重层 +实测只拦到 1 帧)。最大的一次误诊已写出整套规格要推给前端,被用户两个字扭转—— +**"看日志"**。而日志全程在 AI 手边:瓶颈不是权限、不是知识,是顺序。 + +四条纪律: + +1. **先审自己的 diff**——症状出现在某次修复之后,第一嫌疑人就是那次修复; + 向外搜索(前端/固件/网络)之前,先看自己最近改了什么(该病例真凶是 + AI 自己上一轮写的缓冲参数) +2. **假设先检验后动手**——每个假设配一条当场能跑的检验命令,跑通才写修复; + 写不出检验命令的不是假设,是猜测 +3. **失败即证伪**——修复后问题还在,说明假设错了,不是防御不够; + 正确反应是重新取证,不是叠下一层防御(叠层须出示新证据) +4. **宣称跟着证据走**——根因被证据指认、复验通过之前,只说"缓解,待验证", + 不说"根治"(该病例"根治"宣言发了三次,每次证据等级为零) + +还有一条隐含的:**曾经为真的解释,换症状后必须重测**——上次灵过的解释 +复用起来最顺口,也最容易掩盖新病因。 + +产物对应物是清单里的「假设账本」(假设/检验命令/结果/裁决四列):清单审计 +"改了什么",管住手;账本审计"信了什么",管住脑。 + +## 5. 以产物为中心:写给失忆的自己 + +对话会压缩、会丢失;产物层(清单/地层/决策)不会。写作标准一句话钉死: +**写给失忆的自己**——断上下文的新会话、新同事、初级程序员,是同一种读者。 +文档不能只有 What 和 Why,必须有 **How-to-step-by-step**(一步一响,每步一个可观察 +里程碑)和 **What-if-broken**(报错→自救命令)。断点续作只凭产物层重建现场, +不依赖任何人的记忆。 + +## 6. 受阻是一等状态 + +卡住时最危险的不是停下来,是**硬磨或绕过**。受阻是合法的、被机器尊重的状态: +四问落产物(阻塞在哪/已试什么/为何不能绕/需要人类什么),受阻期间边界守卫拦下 +一切编辑,直到解阻。求助不是失败,**无声地绕过才是**。 + +## 7. 人类的一寸 + +人只出现在三个时刻,且每次介入都被压缩为最便宜的形态: + +- **意图授权**:30 秒扫计划卡片,批准/否决/修改——方向错误的最后低价纠偏点 +- **物理传感器**:给权限、给凭据、贴一条实测输出 +- **感官终验**:声音清晰吗?只收通过/不通过,读数入档即证据 + +流程段之间**没有人肉粘合剂**。机器无法读心,只能把理解亮出来供人扫视。 + +## 8. 不对称原则:顾问有一票否决权,没有一票批准权 + +外部第二意见(AI 顾问)的价值边界:它说"有问题"较可信(假阳性代价=多等一次人), +它说"没问题"不可信(顾问也会自信地错)。所以预审异议必然挡下任务, +而预审通过只允许"临行"(provisional)——且临行的执行授权来自人类的**事前预授权**, +永不来自顾问。人类从"每个任务批一次"升级为"定授权规则 + 否决窗"。 +**顾问是安全网,不是签字人。** + +## 9. 验证主权与 oracle 保真度 + +自动化验证的最大暗坑不是覆盖率,是**判据(oracle)的保真度**: +模拟器不校验帧长度、mock 不查 key 前缀——单测 17/17 全绿照样漏掉真 bug。 +所以脆弱点拓扑里"测试替身漏掉了什么断言"单列一类(oracle),与"终验靠人的感官吗" +(sensory)并列。人的感官读数(通过/不通过)落化石入证据链—— +**人只是传感器,传感器读数入档即证据**;机器验证"人确认过"这个事实的存在, +不复跑感官。 + +## 10. 工程宪法 + +约束要分权,否则执法者变成第二个立法者(形式指标强制之日就是指标失真之时): + +> **法归人类**(意图授权 + 预审否决),**果归观测**(技术果入地层、业务果归 APM、 +> 感官归人耳),**行归门禁**(复验执法),**赦归应急**(流程闸统一限时 bypass, +> 赦后记债),**阀归令牌**(不可逆动作物理不可赦),**容归规划**(容量边界 +> 不是赦免出来的,是规划出来的)。 + +一句话版本:**形归 lint,果归地层,行归门禁**——做执法者和实测仪, +不做第二个立法者。每个新门禁出生四问:拦什么、证据是什么、逃生口在哪、 +副作用重试路径做什么(幂等键在哪、崩溃恢复怎么对账——§17,v1.108 加宽)。 + +## 11. 债与失效的哲学 + +- **赦后还债**:紧急 bypass 记账,测试通过的提交才还上一笔——赦免权不闭环就会 + 淤积成永久性架构腐化。 +- **假设失效过程比结论值钱**:推测被实测推翻时,不悄悄改写文档,追加 + was→reality→evidence 的失效记录——失效过程是下次推测的校准器。 +- **否决过的方案别重走**:decisions.md 里的否决项是已经交过学费的岔路。 + +## 12. 简约与定界 + +- **复用不重建**:lint/CI/观测各有成熟领土,本项目只做它们不做的事(过程证据)。 +- **契约长度冻结**:每加一条规则先问删哪一条——太长的规则会被摘要, + 被摘要的规则等于没写。 +- **拒绝清单也是资产**:每个"不做什么"的决定连同理由记档,防止未来重新发明 + 已被否决的复杂度。 +- **"全部/所有"是审计词,不是修辞词**——写"全部"之前必须盘点全集。 + +--- + +## 13. 维护准则:文档的证据律(v1.22) + +文档腐烂的根因:文档是"对系统的声明",而声明长期没有验证通道。本项目的对策三条: + +- **写决定,不写状态**:带版本号的史实句("v1.12 起计划须批准")永远为真; + 裸现在时的描述("计划须批准")随时会假——写史实是零成本抗腐烂。 +- **叙事手写,事实派生**:reference 型事实(命令表/计数/清单)由 + `gen_reference.py` 生成并校验,手写只允许在生成区之外——没有手写数字, + 就没有可绕过的检查(Goodhart 面消失)。 +- **规则由病例驱动**:每条文档断言必须注释它抓过的真实病例,无病例不立规则; + 半年零命中的规则标废弃——检查器自己也要有衰变管理。 + +机检的诚实边界:doctest 只能保证已写的一致,**漏写永远是人的问题**—— +省略型腐烂(示例漏新行为、状态机缺态)的最后一道证据律,永远是照着文档走的真人。 + +## 14. 代谢与衰变:带证据律的自改进 + +行业证明了"用得越多越强"的方向(任务后自写 skill 的 agent),也暴露了它的病: +学错没人拦,错得越快。本项目的代谢链做同一件事,但放进证据律: + +> **地层是脂肪(原始病例),规律是肌肉(跨会话≥2 的沉淀),skill 是骨骼 +> (反复命中后经人批准固化)。** + +- **沉淀有闸门**:单会话高频是重试噪声,跨会话重复才是经验(公理三的老规则)。 +- **命中要记账**:规律带 captured_at / hits,再检出即命中一次——没有记账的 + 规律块只会膨胀(实测:真实项目 3 周 18 节)。 +- **衰变是义务**:半年零命中的规律列降级候选,提示人工修剪——检查器有死词表, + 规律也要有新陈代谢;只进不出的"知识库"是熵增库。 +- **成骨要人批**:命中≥3 的稳定规律建议固化为宿主 skill,批准权在人—— + 自动固化的错误经验会以技能的形式高速复发。 + +一句话:**学得快不如学得对;学得对还要忘得掉。** + +## 15. 需求的尽头:判据与备选 + +执行层的尽头是门禁,需求层的尽头是**可检验的判据**。 + +- **验收判据前置**:需求阶段就写下"做到什么算完"(布尔判据/可观察行为), + 非功能底线(延迟/安全/兼容)在这里定型——它们在需求阶段缺席,设计就会朝 + 反方向跑(病例:jitter buffer 回滚——"延迟不可牺牲"这条产品价值观在两轮 + 过度设计后才抵达)。"根治/更好/优化"这类词必须挂在某条判据上,指不回去 + 就不许说(同会话"根治"宣称三次,每次证据等级零)。 +- **没有备选的设计不是设计**:动手前至少否决一条备选路径——方案唯一往往 + 不是想清楚了,是没想过。备选和否决因上卡片:人类 30 秒扫的不是"你选了 + 什么",而是"你是否知道还有别的路"。 + +与 §3 的关系:§3 是代码的证据律(done 由门禁发),这一条是需求的证据律—— +**done 的定义先于 done 的实现**。与脆弱点的分层:验收是需求侧(功能对不对), +脆弱点是风险侧(周边会不会坏),两个都 locked 才是真 done。 + +## 16. 产品适定性:知识可外包,价值观不可 + +AI 的设计常常产品幼稚——不是不会写,是不懂行业、不懂你的用户。这个缺口 +三分,两分在人类,一分可外包: + +- **行业惯例可外包给带搜索的顾问**(scout)——无来源的行业断言只是另一个 + 自信的模型;实测 scout 与裸模型的差距是质不是量(来源/诚实边界/判断清单)。 + 但不对称依旧:它说"反惯例"可信(否决权),说"没问题"不可信——它不知道 + 你们的用户是谁。 +- **用户语境与产品价值观不可外包**——只有人类知道"延迟不可牺牲""误报远好于 + 漏报"。住所是产品上下文卡:人类填价值观,顾问起草行业段,AI 永不删人类写的段。 +- **判断在草图**——产品适定性没有机器 oracle,唯一判据是人;多次设计的根因 + 是反馈点在实现之后。草图先行把否决从"实现后"挪到"草图后",一轮返工从 + 天级降到分钟级。学费要记账:产品理由的设计否决埋 design_rejected 化石, + 跨任务重复的升格为产品规律。 + +一句话:**行业知识借外脑,用户价值观问本人,判断在草图,学费记在账。** + +## 17. 副作用幂等律:计算可重试,副作用不可重试 + +> v1.108(批160)。来源:pero 项目研究借形(2026-10-29——AI4S 平台的执行 +> 不变量「断线恢复按原 request ID 对账:计算可重试、外部副作用不可重试」); +> 本仓四个自有病例早就撞过同一条律,此条把它命名并接入出生四问。 + +重试是钩子环境的常态:宿主重投事件、哨兵轮次日跑、门禁拦后重试提交。 +纯计算重跑一万次只是浪费,副作用重跑一次就是污染——重复推送制造 alert +fatigue、重复记账让误报率失真、重复埋化石让考古地层掺假。所以: + +> **每条副作用路径必带幂等键;崩溃恢复先按幂等键对账,对上了才许重发。** + +三类副作用面的对应物: +- **推送**:blocked 的「项目+ref+原因指纹」30 分钟合并窗(v1.38);固化候选 + 推送的候选集合指纹(v1.108)——同键未决窗口内折叠,不重发不重记账 +- **写盘**:清单走幂等键(session+plan_hash)+ 编号分配临界区(v1.56); + 产物层原子替换(tmp+os.replace)与 file_lock 临界区(锁缺席平台降级 + 无锁原子写,v1.95.1 语义不变) +- **埋化石**:plan_approve 的 _already_journaled 守卫;plan_bridge 幂等 + 重入的守卫性补写(v1.108)——清单写成功而 journal 写崩的窗口,在下次 + 重入时自愈闭合 + +病例垫底:①done×375 假事件(轮末提醒冒充 done,v1.38 拆事件根治) +②同清单 6 分钟 6 连推(v1.38 合并窗由来)③批154 python 写盘四次 +「打印成功而盘上未持久化」(写后独立读回验证的由来)④固化候选每次 +health 运行都新增待决+推送(v1.108 实修病灶,本条落地时一并闭)。 + +与 §10 的关系:出生三问由此加宽为出生四问(第四问见 §10 尾句); +与 §3 的关系:§3 管 done 的嘴,本条管重试的手——同一个动作做第二遍 +之前,先证明第一遍没做成。 + +## 收束 + +本项目的全部机制,都是这一句话的注脚: + +> **把"相信"换成"验证",把"记住"换成"刻录",把"下次小心"换成"下次拦截"。** + +AI 不会因为我们的信任而变可靠,但流程可以因为死板而变可靠—— +而死板,恰恰是机器最擅长的事。 diff --git a/external_plugins/regress-guard/docs/WORKFLOW.md b/external_plugins/regress-guard/docs/WORKFLOW.md new file mode 100644 index 00000000..86e46ab5 --- /dev/null +++ b/external_plugins/regress-guard/docs/WORKFLOW.md @@ -0,0 +1,739 @@ +# 工作流 + +> 自动触发为主,人类只在决策点。命令无需手动召唤——说需求即可(v1.12 起计划 +> 待人类批准,v1.17 起收尾有流水线)。 + +## Full 模式(团队/大需求) + +``` +说需求(自然语言,无需敲命令) + ↓ UserPromptSubmit 钩子提示 AI 走 plan 流程(含 /regress-init 自动兜底) +/regress-plan 逻辑 → 清单(F1/F2 + 脆弱点拓扑 + 边界 + 实施顺序 + 验收标准/设计取舍/用户所见草图;产品需求读产品上下文卡;修 bug 类先过假设账本),status: planning + ↓ (顾问预审=方向四问(方向/脆弱点/边界/产品适定——产品适定必带搜索)+判据三问(判据三件齐/When 可观察/验命令可复跑);有方向性异议必等人) +📋 计划卡片 → 人类 30 秒:批准 / 修改 / 取消 ← 人类决策点 ① + ↓ plan_approve.py:status→in-progress + approved.at 落产物 + ↓ (预授权任务无异议可 --provisional 临行,进否决窗) +[AI 开发] ← 边界守卫事前拦截越界编辑;受阻则四问落产物 + ↓ (人类决策点 ②:给 need 所需的输入/权限) +/regress-finish → track 回写 F3 → verify 全证据(rescue 自救/感官问人收布尔)→ 代谢沉淀 → 产物干净检查(产品否决已埋 design_rejected 化石) + ↓ (人类决策点 ③:感官终验 human_check) +git commit ← 门禁自跑测试 + locked 复验,通过自动写 done +``` + +**长任务进度心跳(v1.33 企业级)**:授权自决策任务预计 >30 分钟时,每个阶段边界 +(F 项完成/测试转绿/进入下一段)推一条 `progress`——离场人类看到的是"⏳ 进度: +F1 完成,进入 F2",而不是两小时黑箱后一声终响: + +```bash +python3 "<插件路径>/hooks/scripts/lib/notify.py" . progress --title "⏳ 进度 :<阶段一句话>" --body "<下一步是什么>" +``` + +**企业级推送覆盖(v1.33)**:决策类=plan_approval/blocked/sensory/finish_open/🦴固化候选; +进程类=done(收尾)/progress(心跳)/💬每轮回复;元层=链外看门狗 +(systemd timer 每小时体检治理链,死亡时独立报警腿:deploy/watchdog.sh); +通道故障=全通道失败时本机声音兜底 + 发送台账(~/.zcode/wecom-send.log)可审计。 + +## 智能体通道定界(v1.28.1,实测) + +ZCode 智能体(Agent 工具)的子代理**不受 PreToolUse 治理**(边界/先读后改/执行阀 +均不覆盖——2026-09-03 对照实验实锤;业界同构:Claude Code #34692 早期同样形态, +后以"钩子在子代理内触发+载荷带 agent 身份"收口——我们的钩子是 config 级, +宿主跟进后覆盖自动延伸)。纪律:**子代理只做只读分析,写操作归主智能体**。 + +## 旁路矩阵(v1.29:状态改变通道 × 治理覆盖) + +| 通道 | 覆盖 | 依据 | +|---|---|---| +| 主智能体 Edit/Write/ApplyPatch | ✅ 拦截(活体实证) | planning 阻断活体首证 | +| 主智能体 Bash 写目标(rm/mv/cp/tee/truncate/touch/sed -i/dd of= 及 >、>> 重定向) | ✅ v1.29 收口(高置信形态;2> 与 /dev/null 豁免防误拦) | 病例:rm 删边界内文件零拦截;37 项单测 | +| 主智能体 Bash 不可逆命令 | ✅ 执行阀(活体实证) | dd 字样拦截首证 | +| 子代理工具调用 | ⚠️ 定界(宿主领土,见上) | 对照实验实锤 | +| MCP 工具写文件 / 浏览器改外部状态 | ⚠️ 未测定界 | 待病例 | +| 无活跃清单时的任意编辑 | ✅ fail-open(设计语义) | 无边界信息不锁人 | + +## git worktree:独立任务舱(v1.27,实测于临时仓) + +`git worktree add` 出的每个工作目录是一套独立治理(2026-09-03 四项实测): +无 `.regress` 的 worktree 守卫放行(fail-open);自建 `.regress` 后哨兵/边界/ +清单自成一体,主仓清单互不可见——base_head 漂移也不会被其他舱的提交污染。 +产物层随分支走,**merge 即知识回流**;注意三点: + +- worktree 清理前先确认 `.regress` 变更已随分支提交(finish 步骤 5 会检查—— + 未合并就 remove = 学费蒸发) +- journal 同位置追加在两个舱都写过时 merge 必冲突:两边都保留即可 +- 边界交叉(两个舱都含同一文件)留给 git merge——冲突检测是 git 的领土 + +## 多会话同仓(v1.34 会话作用域) + +worktree 是**物理隔离**;不开 worktree、多会话直接并行同一仓库时 +(2026-09-07 五标本:门禁被他人清单挡/git add -A 扫走他人暂存/清单号 +竞争/版本碰撞/活体编辑竞态),治理按**会话归属**切分: + +- **身份**:钩子进程带会话 env(Bash 工具进程不带)。UserPromptSubmit + 每轮把它写进会话中继(last-writer-wins,原子写);plan_approve 批准/ + 临行/受阻时给清单盖 `session:` 戳——中继优先,env 兜底 +- **门禁**:mine=无戳或戳==本会话;env 缺失时全部视为 mine(老行为)。 + 他人清单只在本提交 staged 撞其清单文件时拦(跨会话冲突=集成态检查), + 否则放行+警示——他人清单不再挡你的提交 +- **边界**:编辑撞他会话活跃清单边界 → 拦+对焦提示(冲突只在集成点 + 现形,编辑时对焦比提交后回滚便宜);无戳清单保持共享语义 +- **哨兵视图**:`python3 hooks/scripts/lib/sentinel.py`——活跃清单 × + 会话归属 × 悬停时长 × 多会话文件重叠,提交前扫一眼 +- **已知边界**:中继错归属一拍时,错向是"自己的清单被当他人的" + (拦截可见可修),不是静默放行;清单号竞争/他人 git add -A 扫暂存 + 仍靠 git 自身与事后审计(F3 检查以我的清单为基准,他人文件天然不在 + 我清单内→拦,方向正确) + +### 任务租约(v1.109 批161:死会话占边界的到期出口) + +多会话守卫的死穴:会话死了,它的清单还在占边界(人工重盖戳是唯一出口)。 +v1.109 起批准/临行/受阻转写时自动盖 `lease: {holder, expires_at, input_hash}` +(域逻辑单一来源 lib/lease.py;holder 与 session 戳同源): + +- **过期 = 收回跨会话保护**:门禁 mine/others 分桶归 mine + 边界守卫 foreign + 拦截失效 + 哨兵标 ⏳ 可收回——**不改 status 不处置清单**(清单不过期公理) +- **input_hash = 输入钉**:边界文件内容快照;哨兵重算不符标 📥 输入已漂移 + ——接管者先对账再动手(非盲接管) +- **格式坏 = 过期**(bypass 先例);**无 lease 块 = 旧语义逐位不变** +- TTL 缺省 72h,config `lease.ttl_hours` 覆写;**重盖戳 = 接管/续租** + (接管者重跑转写即成新 holder);心跳自动续租=观察位未实现 + +并行子代理开发的移交界面见 [subagent-playbook.md](subagent-playbook.md) + +## 原生计划模式桥(v1.39:批准点对齐) + +ZCode 原生计划模式的批准由 plan_bridge 钩子自动转录成治理清单——两条批准 +通道,产物层只有一份: + +``` +ExitPlanMode(计划原文) ──批准──→ PostToolUse → 转录+盖章原子完成 + ──拒绝──→ PostToolUseFailure → 零残留 + design_rejected 化石 +``` + +- **幂等键** = session + plan_hash:重复事件 no-op;计划修订整档重写(落 plan_refined) +- **双轨合一**:同 session 已有 /regress-plan 的 planning 清单 → 直接盖章它 +- **顾问预审豁免有据**:原生批准时人刚逐字读完计划=意图裁决(顾问否决权 + 为保护不在场者,人在环即最高裁决) +- 边界从计划文本尽力提取(cap 12),提取不到留空——F3/track 扩界留痕兜底 +- 双重防御:成功事件内含拒绝语义(tool_response 特征)也按拒绝处理(拒绝 + 载荷语义未证,failure 事件与 response 特征双保险) +- 注册漂移:脚本在而 config.json 未注册时 self_heal 警示(不自动改用户配置), + 修复出口=重跑 install.sh;钩子未生效时 AI 按契约条款 1 补转录(幂等无害) +- **回执(v1.52 试验位 → v1.79 转正默认开)**:转录成功向对话注入一行 + additionalContext(agent 和人都看得见桥在工作)——PostToolUse 契约已经 + ZCode 3.14.0 开源源码证实(hookEventName 严格匹配/唯一 stdout JSON); + `RG_PLAN_BRIDGE_RECEIPT=off/0/false` 逃生 + +## 推送闭环(v1.34:广播 → 可度量可校准) + +决策型推送(plan_approval/blocked/sensory/finish_open)送出即落 +`~/.zcode/regress-pending.jsonl` 待决台账(正文带〔待决#N〕);人类回 +「有用/误报/忽略」,agent 记 `pending.py resolve `—— +误报率是推送策略的校准指标(防 alert fatigue:噪音推送腐蚀信任, +与「该推没推」是对称的病)。观察仪表盘: + +```bash +python3 hooks/scripts/lib/notify.py stats # 送达率 + event 分布 + 待决/误报率 +``` + +发送台账(~/.zcode/wecom-send.log)v1.34 起带 event= 维度;轮末提醒 v1.38 +起是独立 `chat` 事件(曾冒充 done——台账 done×375 几乎全是轮末提醒,真 done +仅 3 次)。blocked 推送按「项目+清单 ref+拦截原因指纹」合并(v1.38):同键 +未决 30 分钟窗口内折叠不重发、不重记账,只追加旁路行进 stats(治了多少轰炸 +要看得见)——病例:另一项目同清单 6 分钟 6 连推,主动制造 alert fatigue。 + +**幂等律(v1.108 批160,PHILOSOPHY §17)**:推送/写盘/埋化石三类副作用必带 +幂等键,崩溃恢复先按幂等键对账后重发。v1.108 三处病灶实修:固化候选推送按 +候选集合指纹(sig 排序哈希)走显式合并键(rules_ledger health 每跑必新增 +待决的重复推送闭——notify 合并分支从仅 blocked 扩为「blocked+显式 +coalesce_key」);plan_bridge 幂等重入守卫性补写 plan_approved 化石(清单写 +成功而 journal 写崩的窗口,重入自愈);_save_fpr / test_cache.record / +rules_ledger._save 三处原子性/锁硬化(tmp+os.replace / file_lock,v1.95.1 +锁缺席降级语义不变)。 + +## Fast 模式(个人/小改动) + +``` +[先改好代码] + ↓ +/regress-quick <需求> → 基于 diff 生成清单(全 actual) + ↓ +git commit ← 门禁跑测试,通过放行 +``` + +## 测试结果缓存(v1.85:同树重试不重跑全量) + +门禁跑全量前先查 `.regress/test-cache.jsonl`:键=测试目录所在 git 仓的 +组合哈希(HEAD+status porcelain+diff 补丁+未跟踪内容,`.regress/` 排除)。 +TTL 4h 内同键有通过记录 → 跳过全量(stderr ♻️ 复用 + `commit_passed` 事件 +带 `cached:true`/`cache_key`);真跑通过才落账(只缓存通过,上限 50 条)。 + +立场(顾问裁):**优化位,非安全边界**——命中大声可审计;伪造缓存者本有 +更廉价的 `/regress-bypass`。关闭:环境变量 `RG_TEST_CACHE=off` 或 +`.regress/config.json` 的 `test_cache.enabled: false`(ttl_minutes/max_entries +同节可调)。验收环(EARS 勾验)不受缓存影响——它读清单,不读树。 + +## 特征测试(v1.45:改无测试老代码前先钉住现状) + +`/regress-characterize <文件>` 五步:枚举公开入口(私有/外部服务依赖标"不可钉") +→ **探针真跑**三类输入(典型/空值/边界)落 JSON → 生成断言**真值**的 golden +master pytest(浮点 approx/异常只记类型)→ 跑绿=行为已钉 → 回写 F3+脆弱点 +`kind: machine`。证据律沿用:断言探针捕获的真值,不猜 AI 以为的值—— +不 mock 外部服务(mock 出的行为不是现状,钉了也是假钉)。 + +## Bypass 模式(紧急 hotfix) + +``` +/regress-bypass 10 → 10 分钟赦免窗口(边界/门禁统一放行,赦后记债) + ↓ +git commit ← 放行(审计日志 + 技术债记账,测试通过的提交才还债) + ↓ +(到期自动恢复严格模式;逾期未还会被哨兵点名) +``` + +## 信任链 + +hook 在 commit 时**自己跑测试**(自动探测 jest/pytest/maven/go test): +- **测试通过** → 写入 `test_verified_by: hook` + `status: done` → 放行 +- **测试失败** → 阻断,列出失败用例 +- **无测试运行器** → 回退到检查清单 status(降级信任) +- **locked 脆弱点** → 门禁复验 verify 命令(证据律:locked = 此刻能过); + sensory 类验 human_check 化石存在性,不复跑感官 + +## 状态机(v1.20) + +``` +planning ──批准/临行──→ in-progress ──→ verifying ──门禁测试过──→ done + │ │ ↑ + │取消 受阻 └─解阻 + ↓ ↓ + cancelled ←──────────────(临行任务否决窗内可取消;正式批准的任务走受阻/完成) +``` + +`done` 由门禁写入(测试通过时),不由 AI 手动标记。blocked 期间边界守卫拦编辑。 + +## 责任矩阵(谁做什么) + +| 角色 | 职责 | 触发频率 | +|---|---|---| +| **人类(ZCode 外)** | 物理动作(权限/凭据/真机)、感官感知(听/看)、意图理解(批卡前的 30 秒) | 按需 | +| **人类(ZCode 内,必做)** | ①说需求 ②批准/修改/取消卡片 ③答 flagged 疑问 ④转达受阻输入 ⑤感官终验一个词 | 每任务 4-5 句 | +| **人类(ZCode 内,罕见)** | `/regress-install`(新机一次)、`/regress-bypass`(紧急)、否决临行("停") | 一次性/紧急 | +| **AI(可被对话引导)** | 需求解析/脆弱点拓扑/清单与卡片/写码修测/越界找路/知识型先问顾问/finish 收尾/提交 | 全程 | +| **hook(铁律层)** | 边界拦截/执行阀/门禁自跑测试/证据律复验/哨兵/地层采集/自愈升级/老项目迁移 | 全程无感 | + +人类**不需要**做的:说"提交"(AI 自提)、init/plan/track/verify/finish/resume(自动或哨兵指路后 AI 自跑)。 + +三原则:物理世界的动作只能人做;意图的裁决只能人做;其余默认自动且人可随时介入。 + +## 自迭代协议(v1.43:两次实战后固化——批2/10 自迭代 run) + +自迭代≠自批准(同模型审自己=相关错误整队穿透);它的形态是**临行机制的成批运用**: + +- **.regress 写入防丢纪律(批154,四次实证)**:写清单/journal 族文件优先走 + Edit 工具或 `cat >>`(append 通道零丢失记录);用 python 读改写时**必须 + 写后独立读回验证**(`grep -c 关键字`)——python heredoc 曾四次打印成功 + 而盘上未持久化(并发竞态嫌疑面=会话事件钩子对清单族的读改写窗口, + 纯机制已排除;tests/test_write_integrity.py 钉住环境级再现)。 +- **授权句式**:`自迭代,N 批`(有界预授权)或 `自迭代,按照你的思想来`(默认 1 批+ + 检点回报——首跑校准用)。可加目标与停止条件。 +- **否决窗**:整个 run 一个大否决窗——人类随时说"停";任一批被顾问拦或受阻即停。 +- **批内全治理环不豁免**:清单→顾问预审(队列级 consult 一次覆盖+批级引用化石, + 新决策点才追加咨询)→临行→实现→门禁→发布→done 推送→落账(**每条裁决附 + 解释边界一行:「本裁决不许被引用来证明 X」——防裁决被越界引用**,v1.88.3/102 + better-harness 借抄;纪律位,实践两批稳后议升必填)。 +- **批前自驳自查两问**(v1.88.3/102——diff 可见后、提交前跑;预审期只能填猜测 + 故不进 5a):①计划里哪些约束是模型可读软约束,各自的硬通道(exit 2/机器位) + 在哪?没有硬通道的记观察位;②哪个假设被证伪整个计划作废?有没有更早发现 + 的量测位?跳过两次即升 gate 硬通道(升格触发器与批 100 同款)。 +- **检点节奏**:首跑 1 批即检点;多批 run 在中点做方向检点(复利目标漂移——顾问 + 每次只看一份计划,没人查"N 批加起来还在服务原目标吗"——是人检点的唯一职责)。 +- **任务来源**:业界对比优先级清单 / decisions 排队项 / 观察项,AI 自选自报。 +- **停止条件**:N 批完成 / 顾问拦 / 受阻 / 队列价值耗尽(宁缺毋滥——恒零键删除 + 原则同样适用于批)。 +- 每批一条 done 推送(离场人类的进度信号);run 结束出综合检点报告。 +- **run 制已验证三形态**(1 批校准制 / 10 批有界制 / 3 批密集制 / 10 批混合制 + 含研究批):研究批(结论落 decisions + 后续批号)是合法产物形态——宁缺毋滥 + 的意思不是硬凑代码,是"没代码结论也是交付"。run3(v1.56→1.64)另证两条 + 操作纪律:FP verify 须选 15s 内的快子集(全量门禁套件超 5.6 窗口);被门禁 + 拦的是整条工具调用——同命令行里的 git add 不会执行,提交前必须 git status + 核验暂存完整性。 + +### 自迭代轮收尾检查 + +自迭代 run 每批收尾(含研究批)过一遍三行: + +1. **卡片对齐**:product-arch.md 的模块卡片与本次结构性变更对齐了吗 + (新增/删除/改名/契约变化,验证路径仍走得住)? +2. **焦点重写**:frontier-focus.md 的焦点还指向真前沿吗(被本批改变的 + 要重写)? +3. **记忆索引**:MEMORY.md 相关条目刷新了吗(新决策/新踩坑入条)? + +动机(2026-09-21 标本):自迭代模式绕过 finish 代谢位 + 双仓拓扑规则 B +对嵌套仓提交失明 → 卡片停更 v1.83 被对齐审计逮住——自迭代的收尾纪律 +不能只靠"批内全治理环"自觉,run 级另设此明文检查位。 + +## 无感层(自动,零操作) + +(v1.81)分发双轨:marketplace 就绪(marketplace.json+hooks 全插件根相对)—— +用户可经个人市场源一键安装(插件开关/升级走宿主原生);开发轨 install.sh 不变。 +两轨只取其一(同装双钩子)。 + +(v1.80)UserPromptSubmit 治理上下文前置:每轮对话注入活跃清单边界行+规律账本 +TOP-1 召回行(`RG_PROMPT_CONTEXT=off` 逃生)——防线从"拦截时才教育"前移到"开工前就看见"。 + +(v1.78)Stop 级版本漂移警示:每轮收尾顺带比对已装副本与源仓版本,新漂移对 +首次出现 stderr+chat 各一(消除前不重复)——SessionStart 只跑一次的时代盲区补上。 + +- 会话启动:自愈/自动升级/老项目迁移/活跃清单哨兵(指路 /regress-resume) +- 编辑时:先读后改 + 文件指纹 + 边界拦截(AI 是第一现场,人看到的只是行为变好) +- 失败/风险/纠正:自动入考古地层,跨会话可考古 +- 断点续作:`/regress-resume` 一句话重建现场 +- 机器级经验:finish 代谢缝自动落机器事实卡(v1.31,见下节) + +## 哨兵巡检一条命令(v1.98:探针机器位) + +```bash +python3 scripts/health_probe.py <工作区根> +``` + +巡检二 8+2 条探针一条跑完,逐条 ✓/❌(退出码+stderr 尾行),任一崩/超时/ +零输出→整体非零。只治「崩与漏的可见性」;数字语义解读仍归哨兵(退出码≠ +业务异常)。两活体根治:run26 漏跑 v2.12 探针(软惯例)、features 崩两日 +无面叫红(崩失察)。 + +## 批评臂(v1.97:裁决矩阵 A 位——独立批评家 pass) + +一致性偏见的结构解:批作者与批验证者同模型同时点,恰在最需拒绝时放行——批评臂 +引入**跨模型独立批评家**(本地 DeepSeek 直连;不可达降级 fresh subagent,降级 +打标分段统计不等同视之)。 + +```bash +python3 scripts/critic_pass.py --assemble [--report <批报告>] +python3 scripts/critic_pass.py --run [--canary] # canary=埋已知缺陷种子测发现率 +python3 scripts/critic_pass.py --rebuttal '{"p1_0": "逐条回应"}' +``` + +- 摆渡包=工件(清单全文+HEAD diff+提交主题+报告原文),禁引作者自评叙事 +- 输出 P1 缺陷断言(附代码行证据)/P2 疑点/P3 提问;**advisory 零拦截**—— + 升格=两轮试点人裁(发现率 vs 0158a98b 人审基线+假阳性重提率,journal + critic_findings/critic_rebuttal 事件采集,mode 字段分段 normal/degraded) +- 与深查节互补:自问(反问三)vs 他者问——不互代 +- **canary 自指极限(v1.97.2 文告)**:自修批的 HEAD diff 本身展示种子的增删行 + ——去标签在自修批结构性失效(run2 方法论发现)。canary 发现率测量**只对 + 非自修批有效**;历史批审计用 --rev-range 组装后跑 canary 即可 +- **历史批审计**:--rev-range A..B 组装任意历史批 ferry(diff 取指定区间) + +## 清洁环境矩阵(v1.96.1:本地自造"外部环境") + +外部评审"干净环境红 48"类指控,本地即可自造环境提前逮住: + +```bash +bash scripts/clean_env_check.sh # venv 从零装 requirements-dev 跑全套件 +``` + +- 何时跑:发布前/哨兵节奏(成本 ~4 分钟),不进每次门禁 +- 首跑战果(2026-09-24):**逮住 fallback 解析器与 PyYAML 的三处行为分歧** + (行内注释/键行尾注释/嵌套映射压平)——零依赖运行路径上的真 bug,宿主 + 装 yaml 时全绿所以从未暴露;外加 install.sh 宣称"11 个命令"实况 16 的 + 漂移(宣称 lint 首跑逮住) +- 宣称一致性 lint(tests/test_claims_lint.py)随套件常驻:数字宣称 + (N 个 skill/命令)与实况对账、悬空目录引用、requirements 完整性 +- 诚实边界:单机单 Python 版本=单点非矩阵;locale/内核差异不在覆盖面; + pypi 不可达时脚本红属环境问题非产品问题(输出有区分提示) + +## 跳过语义(v1.95:跳过不算通过——外部评审实证洞) + +"1 passed, 1 skipped" 不是全过:skipped 的用例既没证明也没证伪。 + +- **分母含 skipped**(pytest 对齐 jest 既有语义):套件计数 N/N 的分母=全部执行的 + 用例——行尾计数宣称会自然对不上(";1/1" vs 实测 1/2,既有反谎报闸拦) +- **M/L done 盖章拦截**:skipped>0 时盖章被拦(done_with_skips)。放行三路: + 修掉 skip / config `test_runner.allow_skips: true`(xfail 型项目显式承认, + 开着即负债)/ S 档·quick 豁免(与验收入环同构) +- **边界**:xfailed/xpassed/deselected **不进分母**——xfailed=代码里已承认的已知 + 问题,deselected=显式筛除,语义与"没跑"不同;skip 密集仓一开即红是特性不是 + bug(逼出显式裁决),滥用 allow_skips 放水的面进观察位 +- jest/vitest:skipped/todo/pending/disabled 计入 skipped 字段(分母本就含) + +## 纠正闭环(v1.94:correction-as-incident,0158a98b 标本推动) + +用户纠正此前"只埋不挖"(化石入地层后无人消费——标本会话顾问零调用)。本层 +把它补成 PagerDuty 式完整生命周期: + +| PD 态 | 我们的形态 | 载体 | +|---|---|---| +| Triggered | 纠正语式自动埋 user_correction 化石 | prompt_intercept(已有) | +| Acknowledged | 一行批量确认(带 upto 游标=只清已展示的) | `journal.py . ack-corrections '{"how":"…","upto":"…"}'` | +| Resolved | 【错误方向+修正+理由】append decisions.md | Stop 提醒第二段 | +| Escalation | pending>0 时 Stop 提醒(高水位窗,非 10 分钟墙钟)+ 提交侧 record-only 留痕 | reflection_check 节 9 / 门禁 2.9 | + +- `how` 语义:advisor=已对质顾问 / self=自行判断(须标注未获第二意见)/ + not_correction=判定非纠正(FP 出口——叙事性"不对"实测会误埋) +- 硬约束语式(一定/必须/不许…+方向词)另埋 `user_constraint` 化石 → Stop + 侧提醒红线落盘(授权/门槛类约束不能只活在对话里) +- digest 纳入纠正聚类:同主题纠正跨会话 ≥2 次 = 规律候选(/regress-learn 消费) +- 重任务检测(发布/开源/公网类)+ 无活跃清单 → 一行 M 档建议(f77a+0158a98b + 两标本=钩子在场清单缺席,M/L 纪律全空转) +- phase-1 全不拦截;升格为门禁拦截=两轮野外数据后人裁(disposition 率/pending + FP 率观察位)。逃生:`RG_CORRECTION_LOOP=off` 或 config correction_loop.enabled=false + +## 产品+架构层(v1.35:治"跑通即终点") + +对话式开发的产品功能深度/广度瑕疵有四个结构性根因:计划从单一视角推导 +(无角色枚举/无伴侣功能/无竞品对照)、首版即终版(无 DoD 状态完备性)、 +AI 看不见渲染结果(代码审查发现不了空态尴尬)、架构盲区(无连接点地图、 +选型理由随对话蒸发)。四个机制对四因: + +| 机制 | 挂点 | 机器强制 | +|------|------|---------| +| 广度矩阵(角色×任务+伴侣10项+竞品,每行配验证路径) | plan 4.9 → 清单「广度矩阵」节 | 卡片行 + 伴侣留空=没想过 | +| DoD 状态完备性(空/错/载真渲染看过) | plan 脆弱点 kind=product → verify | **open 挡提交**(搭脆弱点既有强制) | +| DoD 自检+弹性打磨 | finish 步骤 2.5 | 缺口三选一(修/挂牌/进卡片),不许蒸发 | +| 模块卡片(完成度/能力/依赖/契约/缺口/验证路径) | init 生成 → finish 回写 | 验证路径走不通的卡=过期卡 | +| ADR(context/options/decision/红线) | plan 4.10 → `.regress/adr/` | 红线=grep 类 verify 进 arch 脆弱点 | + +知识层配套(装机 Skill,流程层仍以上表为准):brainstorming/writing-plans/ +TDD/systematic-debugging/verification-before-completion/executing-plans +(superpowers 六件)、frontend-design(Anthropic)、office-hours(YC)、 +ui-ux-pro-max。 + +## 全貌+对齐层(v1.40:理解是强制产物,对标 spec-first) + +行业对标(Kiro/spec-kit):spec-first 的本质是理解本身成为强制产物。我们的 +等价实现是机器位,不是仪式位(病例:028 新增桥脚本而模块卡片 8 天未回写、 +两批跳过 finish 卡片步骤,无机器拦——纪律对狗food实践者本人都会漏): + +| 机制 | 挂点 | 机器强制 | +|------|------|---------| +| scan 三行(入口/测试/动的哪张卡) | plan 步骤 4 清单 frontmatter | **M/L 缺任一行门禁拦**(规则A,空值/占位不算) | +| understood_intent 三件(复述/边界/判据) | 同上 | M/L 缺件拦——复述=行业对齐标配 restatement | +| 卡片新鲜度 scan_check(fresh/stale/absent) | plan 步骤 2a | stale 强制重扫受影响卡;absent M/L 先建卡 | +| 结构变更同步卡片(ADR 事件,tests/docs/md 豁免) | 提交门禁 | **全档含 S/quick**:staged 有结构性增删/改名而卡片未随同 → 拦(规则B);纯脚手架 scan.card_sync: false 显式豁免;无卡片项目警示不拦 | +| EARS-lite 验收行(When…则…验:命令) | 模板验收标准节 | M/L 用;验命令复用脆弱点 verify 惯例 | +| rollback 能力断言(v1.41) | 清单 frontmatter 一行 | **全档必填**;触及迁移/schema/破坏性 SQL(触发表)时"git revert"默认失效须真答案 | +| self_review 触发表键(v1.41) | finish 步骤 1.5 → 清单 | 键按触发激活(计划外=actual_changes 非空;调试残留=diff 命中模式且非 tests/);不适用=键不出现,「无」=查过没有——堵空转根源 | + +分工逻辑:**S 档豁免的是全貌仪式(规则A),不豁免结构纪律(规则B/rollback)**—— +新增/删除/改名文件本就不是"轻量内部改动"。对齐层的歧义检测/假设账本/广度 +矩阵不动(已是强项);本层补的是它们的原材料新鲜度——看不全就对不准。 +收官原则(v1.41,顾问):字段不可验又无后果必空转——**硬化接引信(rollback +默认在逃逸面失效),不接解释(self_review 不触发不填)**;校准看机器代理 +(revert 实际率/计划外对上 F3 率),不看填无比例;恒零键删除不措辞。 + +## 宣称纪律层(v1.88.1:宣称的强度被证据的强度封顶——better-harness 深研借抄) + +治"叙事先行":报告里的效果动词不许跑到证据前面。速览(唯一属主: +[claim-discipline.md](claim-discipline.md)): + +1. **计数≠行为**:遥测计数只 bound 范围不证明效果;引用计数做有效性宣称必须 + 降格为线索并点名待补证据。 +2. **负面宣称清单**:每份批报告/巡检报告末尾必含「本证据不支持的宣称」一节, + **至少一条**(空节=纸面化,连续两批空节升机械位——预审裁决)。 +3. **宣称-证据对齐**:效果动词须能指回已证活以上证据;证据七态词汇 + (已配置/已接线/已证活/已证效/缺/未观测/不适用)见 claim-discipline.md 映射表。 +4. **证据环境梯级**(v1.108 批160):「已证效」级宣称须点名梯级 R0–R3 + (本机套件/清洁环境/双跑发货门/陌生宿主+人感官),跨梯级外推与 + 「计数≠行为」同族——见 claim-discipline.md 环境梯级节。 + +判断合并两条(多源候选/巡检发现/子代理产出):**保留优先**——四要素 +(目标+后果+属主+修复路径)全同才合并,丢弃留由,绝不为整洁砍真候选; +**冻结先于叙事**——判断冻结后才写叙事,叙事不许反哺判断。 + +## 供应链层(v1.42:门禁只验测试不够,还要验安全) + +业界用三个真实 CVE 论证 hook 治理必要性(Endor Labs)——本层补密钥与依赖两面: + +| 机制 | 机器强制 | +|------|---------| +| secret_scan(gitleaks-lite 零依赖) | 扫 staged **新增行**(历史密钥是全仓审计工具的职责):高精度模式(AKIA/ghp_/私钥块/xox/AIza/sk-)不豁免任何路径——真密钥漏在测试里也是漏;通用 key=value 对 tests/ 与 *.md 豁免;命中 → **拦**(bypass 唯一出口);文档示例串内置允许表 | +| 依赖审计(best-effort) | 锁文件 staged 才触发 `npm audit --json --package-lock-only`(解析漏洞计数只认 high/critical,不信 exit code——顾问修正);**infra fail-open**(工具缺/超时/网络败 → warn+留痕),**findings fail-closed**(高危才拦);非 npm 锁 v1 提示人工审计 | +| 降级 | `supply_chain.secrets/deps=false`(沿用 strict 降级先例);`supply_chain.allowlist` 项目级追加 | +| 项目级 channels 信任(v1.66/1.67/1.68) | 项目 channels 仅机器侧信任表(`~/.zcode/regress-trusted-projects.json`)内项目可用,未受信回退机器级通道+stderr 提示;**授信=人工编辑该表**(`notify.py trust` 只读视图);**信任解析零环境变量影响(v1.68)**——表路径经 passwd 数据库派生(HOME 间接层同封),RG_TRUST_PROJECT_CHANNELS/RG_TRUSTED_PROJECTS 缝已删(git commit 前缀注入即旁路,测试迁 monkeypatch 模块属性);**内容钉(v1.72)**:边车 `regress-trusted-projects.fpr.json` 快照受信项目 notify 块——敏感面(channels/wecom 凭据/外发名单)变更即拒回退,人工出口=刷新信任表时间戳;非敏感变更 TOFU 重钉;部署自举=边车缺失首跑钉现状;共残差=agent 直接写表文件(transcript 可见,人侧定期 diff) | +| 钩子入口 env 白名单(v1.69) | launcher.js 构造洁净子进程环境:白名单只透传系统基础(PATH/HOME/LANG/TZ/TMPDIR)+代理+宿主契约五变量(SESSION/PROJECT_DIR/ZCODE_HOME/PLUGIN_ROOT)——`RG_*`/`WECOM_*`/`GIT_*` 注入在入口层失效(宿主 env 的信任/git 读数干扰整族封死;直接 CLI 调用不受影响,其信任面由 v1.68 表制管) | +| wecom 凭据字段门+基域钉住(v1.70) | 项目级 `wecom.corpid/secret/api_base_allowlist` 覆盖须项目受信,否则回退机器级+提示(克隆仓库携假凭据把通知发往自己企微的洞;`agentid/touser/事件开关`不受限=绑机器凭据的合法覆盖);`WECOM_API_BASE` 仅许本机桩(测试)或机器配置 allowlist,恶域钉回官方域——凭据 POST 外泄面 | + +## 增量测试档(v1.105.0 批156:快反馈位非验证完备位) + +门禁跑测试恒全量的快节奏代价(首次提交改一文件也跑全套)在此档缓解—— +**opt-in**:config `test_runner.incremental: true` + 可选 `test_map` 别名表: + +```json +"test_runner": { + "incremental": true, + "test_map": {"hooks/scripts/read_before_edit_guard.py": + "tests/test_read_before_edit.py"} +} +``` + +**策略=all-or-nothing 同名映射(顾问裁维持保守)**:每个 staged 文件映射 +`tests/test_.py` 或 test_map 别名;测试文件自身 staged 直接入集; +**全部可映射才跑子集,任一无映射或命中共享夹具(conftest/pytest.ini/ +pyproject/setup.cfg/package.json)即全量**——不混合子集与全量(混合的漏测 +组合面不可推理;漏测是正确性问题,触发率是收益问题)。 + +**三条诚实防线**:①增量子集结果缓存条目带 scope=incremental,lookup 永不 +回放(子集计数不冒充全量);②行尾 N/N 硬对账豁免+note 留痕(事件可查); +③commit_passed 事件带 incremental/subset(SLO 可观察增量使用率)。 + +**定性**:增量=快反馈位非验证完备位——全量兜底=项目 CI/哨兵轮(建议 +incremental 开启的项目配 CI 全量位)。触发率观察位:事件增量占比 <20% 且 +scope 隔离稳两轮后,才议混合策略(顾问替代方案门槛)。 + +## 周期校准位(v1.106.0 批157:同名映射盲区的统计兜底) + +同名映射的已知盲区:改共享模块时,依赖它的非同名测试不跑(批156 自 declared)。 +根治位是覆盖率映射(testmon,观察位未满前不引入),本位先补统计兜底—— +**Microsoft TIA 生产铁律「周期性全量」同款**:config +`test_runner.full_every_n_commits: N`(int ≥2 生效,缺省/0/1=关), +每第 N 次增量机会放弃子集跑全量(stderr「校准位」一行 + 事件 +note=full_calibration)。 + +**计数语义**:机会数=commit_passed 里 `incremental=true ∪ note= +full_calibration` 的 **base_head 去重**计数(amend/同树重试的重复事件 +不去重会让校准被无限推迟——顾问抓漏修复)。被拦提交不写事件 → 重试仍是 +校准。归档轮转计数变小 → 校准偏早触发=保守向可接受。校准跑全量 → 行尾 +计数对账走常规路径(不豁免)。仅在 incremental 开启时生效。 + +## 并行测试档(v1.106.0 批157:16 核闲置跑 118s 串行的对症药) + +**opt-in**:config `test_runner.parallel` —— `true` = `-n auto`;正整数 = +指定 worker 数(容器 cgroup 下 cpu_count 虚高的显式覆写位)。pytest 分支 +专用(jest/vitest 观察需求再议);pytest-xdist 未装 → 串行回退 + stderr +安装提示(优化位不拦提交)。生效时命令追加 `-n --dist loadfile` +(顾问护栏:文件不拆 worker——序敏感面最小化+worker 内缓存局部性), +commit_passed 事件带 `parallel` 字段(auto/workers:N/unavailable)。 + +**已知边界**:①xdist 改变执行模型,本套件有 order-dependency 前科—— +发货门=串行+并行双跑全绿(一次性,脆弱点 parallel_xdist_isolation 锁定); +②xdist 下失败明细行(stdout FAILED 行)格式未钉——失败时 failures 列表 +可能退化为 (unknown)(不影响 pass 判定与 junitxml 计数);③收益实测为本 +仓双跑计时数(社区典型压缩 50-80% 是推断,不是承诺);④ +test_read_before_edit.py 曾是裸 `-n auto` 下的互踩重灾区(25 用例共用 +会话键 test-unit 打同一状态文件,每轮失败用例不同、串行全绿,2026-10-29 +实证)——批159(v1.107.1)起按用例独立会话键隔离+teardown 整删状态 +文件,该文件裸并行不再红;loadfile 护栏照旧保留(其余序敏感文件仍 +依赖);guard 的同会话并发鲁棒性转由 +test_concurrent_same_session_no_lost_update 显式锁定(不再靠并行档的 +意外串味获得覆盖)。 + +## 待决积压老化报告(v1.106.0 批157:stale 降级人裁的前置观察位) + +`python3 lib/notify.py backlog`(只读,不代裁):未决笔数/最老天数/五档 +年龄桶(0-3/3-7/7-14/14-30/>30 天)/按类型/按项目/超 30 天候选清单。 +行业 stale-action 模式(打标降级→自动结案留痕)的决策材料——**降不降级、 +怎么降由人裁**(已裁事件全部有用的数据说明队列有真值,不能静默清仓); +TTL 方向性结论:行业主流是「过期=作废/降级」而非「过期=通过」。 + +## 多宿主适配层(v1.107.0 批158:AGENT-HOOKS-0.1 interceptor) + +`hooks/scripts/lib/agent_hooks_adapter.py` 把提交门禁暴露为 AGENT-HOOKS-0.1 +interceptor(零 SDK 依赖,鸭子类型 intercept(context)->Verdict)——面向 +ZCode 之外的七个框架宿主(LangGraph/CrewAI/Claude Agent SDK 等)。能力 +子集=仅 pre_tool_call 提交门禁;三档路径(反转白名单:不确定一律放慢让 +门禁自判);fail-closed 双层(拦截器自身 deny + 宿主 §6.3 兜底);evidence +不存原始命令(隐私律)。ZCode 宿主勿装(与原生钩子链双重门禁)。能力声明 +与安装片段:docs/AGENT-HOOKS.md。 + +## 影响面层(v1.101.0 批148:跨端变更从口头约定变机器位) + +标本:tongtu-anquan-ai-workspace——AGENTS 写明「破坏性变更三端同窗口」「契约只扩不缩」 +但全是人读条款;快节奏下跨三 App+共享包的提交视作普通变更零风险信号。本层把影响面 +评审钉进门禁。**通用产品口径**:零硬编码路径,阶梯默认(显式配置=强制,自动推导=仅 +警示,都无=惰性)。 + +**config 语法**(项目 `.regress/config.json`): + +```json +"blast_radius": { + "high_impact": ["packages/contracts/**", "packages/database/**"], + "units": ["apps/*", "packages/*"] +} +``` + +两键皆可选但至少一项非空才激活强制;glob 语义与清单 `boundary.include` 同源 +(`dir/**`=目录自身+任意深度,`apps/*`=首段展开)。`units` 显式给出时优先于 +工作区文件自动推导。 + +**阶梯默认**: + +| 场景 | 行为 | +|------|------| +| 显式配置 + staged 触碰高影响路径 或 横跨≥2单元 | **强制四查**(下表 reason 拦) | +| 无配置 + 检出 monorepo 工作区文件(pnpm-workspace.yaml/lerna.json 等)且跨≥2单元 | 仅警示(commit_warned note=blast_radius_hint + stderr 指路配置)——零配置不强迫 | +| 都没有 | 完全惰性(既有行为逐位一致) | + +强制四查(任一不过即拦,顺序如此): + +| reason | 含义 | 解法 | +|--------|------|------| +| blast_radius_no_manifest | 触发影响面但无活跃清单 | /regress-plan 建 M 档清单 | +| blast_radius_tier_floor | 清单 tier=S 低于影响面地板 M | 升档或拆分提交 | +| blast_radius_scope_missing | 清单缺 impact_scope 声明 | frontmatter 补 `impact_scope: [单元...]` | +| blast_radius_scope_mismatch | 声明未覆盖检出单元 | 补声明或拆分提交(声明面 ⊇ 触碰面) | + +附带机制:高影响路径出现 staged **删除** → commit_warned note=blast_radius_deletion +(「只扩不缩」契约的提示位,记不拦);四查全过 → note=blast_radius_passed(units 入 +history,trace 可见)。 + +**与既有面的交互**:bypass 窗口=可逃(人裁阀+审计+reflection 还债提醒,同规则 A/B +语义——tongtu 标本 6 连 bypass 证明激进拦截催生逃生常态化,强制面只对显式 opt-in +开放);配置节类型异常 → 惰性跳过+stderr 注记;工作区解析失败=降级无触发(安全侧 +漏检,不是误拦)。**自动推导边界**:pnpm-workspace.yaml 手撕 packages 段、lerna.json +读 packages 键;turbo/nx 无列表键产出空=无触发;嵌套 monorepo(工作区文件在子目录) +靠 staged 首段目录探测(dap 标本形态)。**决策标注**:自动推导选仅警示=顾问不可用 +降级自行决策(2026-10-24,化石在批148 清单),升格为拦须两轮野外数据人裁。 + +## SLO 记分牌(v1.101.1 批149:L9 骨架·步一——进化的目标函数) + +`history.py <项目>/.regress slo` 五指标一屏(health_probe 探针 history.slo,哨兵 +巡检二自动携带):**逃逸率**(带外+bypass/全部提交,批147 source 分桶语义)| +**误报率**(pending.stats——机器级全项目混合,勿按单项目解读)|**裁决积压** +(待决笔数+最老年龄;积压=晋升回路饿死的前兆)|**拦截复发**(同 reason ≥2 次 +的中位相邻间隔——间隔拉长=规则生效的代理)|**规则召回**(干净解决占比)。 + +定位:评测驱动运维的在线监控轨——规则/上下文的进化(批150 晋升管线)以这些 +指标为目标函数;缺数据显示「缺」不炸。**边界**:五指标与"项目交付质量"的因果 +链未验证,复发间隔/召回占比是行业同款代理而非金标准;SLO 异常→假设卡的机器 +提案(提出侧自动化)等本板经 ≥1 轮哨兵实跑且至少一项指标有分辨力后再立项。 + +## 规则晋升管线(v1.102.0 批150:L9 骨架·步二——知识变更的渐进交付) + +规律账本条目的 curation 生命周期:**shadow →(case 证据卡)→ promote 人裁 +curated|sweep 饿死 retired(留档可复活)**。新录默认影子——照常参与召回/ +注入/计 fires(行为零变化),差别全在策展簿记;curated 永不自动退役。 + +| 动词 | 语义 | +|------|------| +| `case [--top N]` | shadow 证据卡:hits/occurrences/服役天数 + history 召回开火联查 + 建议三态(hits≥3=晋升[与 health 固化候选同源阈值]/超 TTL 且 hits<2=退役/hold) | +| `sweep` | 饿死退役(幂等留档;TTL=config `rules_ledger.shadow_ttl_days` 默认 60——低频项目调大) | +| `promote --sig` | 人裁盖章(shadow→curated;retired→curated=复活;evidence 自动捕获) | + +与门禁侧的关系:**代码/配置变更**走清单门禁+重放实验(既有);**知识/规则 +变更**走本管线——两者合起来=规则变更的影子→量测→晋升渐进交付(生产共识: +Braintrust/Arize 金丝雀语义 + ACE 进化 playbook)。设计立场(材料包 A 病理 +根治):**度量自动(fires/开火联查机器做),人只守晋升**——回路里人的注意 +力只花在盖章这一处。注入面影子条目带 🔬 前缀(prompt_intercept 规律行 + +门禁召回段),消费方可辨知识的新旧成色。 + +## 产物 schema 契约(v2.0.0 批162:考古承诺的机器位) + +三族产物(journal/history/pending)写入口带 `schema_version: 1`(对账锚); +读侧宽容惯例不变(坏行跳过/缺字段默认)——**考古夹具** +(tests/fixtures/archaeology/,真实地层取样脱敏)钉死「当前读者读得起 +历史格式」,格式变更时 bump 版本号并补新形态夹具。因果链:清单带 +`run:` 字段(自迭代 run/哨兵派单时盖一行)→ 转写/journal/门禁事件带 +`run_id`;查询面 `history.py .regress trace `(跨执行体一屏看全)。 +span 树可视化=观察位不做(OTel 思想借字段不借 SDK)。 + +## 工程外围层(v2.1.0 批163:新鲜环境证明 + 静态检查 + 发布 tag) + +三件外围补课(成熟度体检裁定的欠账;核心治理机制零改动): + +- **CI(.github/workflows/ci.yml)**:publish.py 全树镜像自动把 workflow 带上 + 公网仓,每次 API push 触发——新鲜 runner 跑 ruff + mypy + pytest 串行。 + Python 钉 3.10(install.sh 契约下限——证明的是承诺);串行是官方口径 + (xdist 不进依赖)。本地绿不再是发布的唯一证据。 +- **静态检查(pyproject.toml)**:ruff E/F/W + line-length 120(中文注释不 + 虚增行宽),存量清零首跑即抓到两条真 F821(cochange_rules CLI 分支 + os 未导入、test_history 死行);E402 全局忽略=「sys.path 引导后置导入」 + 装载惯例;facts.py E501 豁免=内嵌 frontmatter 串折行即改产物。mypy + 非严格起步(零注解存量不回扫),能力降级模式(try-import-fallback) + 的三个误报错误码按模块定向关闭。validate.sh 挂两检(本机未装提示跳过, + CI 是硬执法点)。 +- **发布 tag 双写(publish.py)**:等值校验通过后本地 HEAD 打轻量 tag + + 公网 API 建 refs/tags/v{version}。版本取 plugin.json(权威源——commit + 主题常引用多个历史版本,解析有歧义);幂等(已存在跳过);失败不 + fail 发布(书签不是门禁)。远端历史每次发布压一个 commit,历史版本 + 无远端对象——旧版只补本地考古 tag(v2.0.0@4b8fe40),远端 tag 从 + v2.1.0 起前向。 + +- **平台支持矩阵(README 双语同步声明)**:批191 起 Linux=CI 实证 + + Windows 10+=CI 实证(ubuntu/windows × 3.10/3.12 四腿 matrix;钩子链 + 全走 node launcher 路由/安装器 Python 原生化/cp936 根治;真机 Windows + 验收待补——CI windows-latest 为替代证据,post_install_check 供真机 + 自检);macOS=预期兼容未实证(Unix 专属面仅 fcntl/pwd)。 +- **覆盖率基线(2026-10-30 首测:总 59%,7830 语句)**:观察位非门槛。 + 读数须知:本套件大量用例走 **subprocess 执行**(进程隔离是 hermetic + 绿的设计基础),进程内 coverage 看不见——pre_commit_guard 18%/ + read_before_edit 10% 是**测量伪影**(显示值远小于真实实测面),勿按 + 此排补课。真值区:lib/ 模块(lease 89%/pending 91%/session_relay 93%/ + manifest_fields 100%/test_cache 87%);低覆盖工具面(check_docs 35%/ + publish 46%)为可接受区。数字的用法=防回归趋势线+新增代码可见性, + 不作绝对质量宣称。 + +- **守卫自愈(v2.3.0 批165)**:提交门禁纳入 `boundary.include` 通配—— + 与 Edit 面边界守卫同一匹配语义(复用 boundary_guard.path_matches 单一 + 来源防复制漂移;导入缺席降级精确集,fail-safe 不破坏提交主路径)。 + 此前分叉曾逼机械清理批逐条枚举 35 测试文件入册(163 活体)。写目标 + 提取器对 pip/pip3/`python -m pip` install 前缀整组跳过(包名非写目标 + +「2>」幻影根修——动词捕获组在 2>&1 的 & 处截断),coreutils install + 保留捕获(反向用例钉死防过宽)。 + +## 机器事实卡(v1.32:跨项目地层,三层结构) + +机器级经验(服务器拓扑/通道/环境坑/跨项目工具坑)不属于任何项目——沉在单项目地层里 +别的项目借不到,人类被迫人肉跨会话转述(病例:企微通知任务跨会话交接)。 + +三层结构(形状由构造保证,不靠纪律维持——调研定稿见 REGRESS-2026-017): + +1. **路由层**:skill 描述常驻上下文,pushy 措辞对抗欠触发(平台官方指导) +2. **索引层**:SKILL.md 触发时加载——只有用法两行+日期倒序索引(recency 一眼可扫), + `facts.py record` 每次落账后机械重建 +3. **本体层**:`references/<域>.md` 按需整文件读,同键重录刷新日期+最新表述为准 + +三条纪律(涉机器事务时): + +1. **先查卡**:服务器/网络通道/本机环境与工具坑 → 读事实卡索引再动手 +2. **否定式断言有保质期**:「没有 X 权限/X 不可用」超过一天,引用前必须复查 + (病例:另一会话已部署 ssh 直连,本会话还揣着几小时前的"无权"前提继续推理) +3. **接手跨会话任务先读 journal 尾部**:落账即交接,人不当传输层 + +铁律:**事实带日期(=最后验证为真)、只当线索不当结论**——可信但过期的事实带权威性 +撒谎(病例:/opt 旧端口文件误导排查)。卡的生死判据在 /regress-stats:长期零新增=死重, +摘掉 finish 分支即退场(机制也要代谢)。 + +## 会话分层与执行基底(v2.7.4:super-agent 采用工程沉淀) + +三条纪律(速览;PR 评审协议唯一属主=[pr-auto-review.md](pr-auto-review.md)): + +1. **影子优先宪法**:一切新增自主能力(自动化/新评审判据/自动合并类) + 先影子(observe/shadow)跑 N≥2 轮、双向校准(该放的放+该拦的拦)才 + 转正。双先例:super-agent observe 模式验收(S1 拒外泄/S3 拒歧义)、 + PR 评审影子两轮(轮1 正确放行/轮2 正确升级)。 +2. **会话分层**:Router 暖会话只留索引卡与指针(账本引用),Worker 冷 + 会话带任务卡(清单=天然任务卡)用后即弃。上下文当缓存管:可重推导 + 的一律 Spill 到账本,只 Inline 热事实(病例:单暖会话装一切=上下文 + 债务,摘要压缩有损)。 +3. **执行基底决策律**:任务形状选基底——可单 PR 化的异步活=工单 + (super-agent);需跨任务语境的设计活=暖会话;并行研究=子代理; + 账本一律照记(账本让执行器可替换——框架会换,decisions.md 不会)。 + +## 降级 + +- `.regress/config.json` → `"strict": false`:门禁降级为仅警告 +- `"boundary_enforced": false`:关闭边界守卫 +- `/regress-bypass <分钟>`:限时赦免 + 审计(不可赦名单:执行阀=物理不可逆、先读后改=防盲改) +- `"read_before_edit_ratio": 0`:关闭先读后改门禁(降级留痕) +- `"execution_valve": false` / `RG_VALVE=off`:关闭执行阀(留痕) + +**RG_\* 环境开关的到达面(批138 文档修正)**:经 `launcher.js` 注册的钩子在 +洁净环境跑(v1.69 白名单)——你 shell 里的 `RG_*`/`WECOM_*`/`GIT_*` **到不了** +那些钩子(防注入设计)。环境开关只对直跑 CLI 生效;要影响钩子行为用 +config 开关或 `/regress-bypass`。 + +### fail-closed 语义(v1.99,批135)拦截器(边界/读门禁/执行阀/提交门禁/launcher)崩溃、载荷形态破坏(非 dict/ +畸形 JSON)一律 **exit 2 拦截**——这是设计不是故障:宿主钩子语义「非 2=放行」, +静默 fail-open 是看不见的治理退化(宿主契约漂移必须响亮失败)。拦截消息必带 +逃生路径;赦免判定(bypass 窗口/config 关闭项)独立于载荷解析——守卫坏了不 +等于连 bypass 都救不回来。观测/增强钩子(risk/fail watch、self_heal、桥)崩溃 +则干净跳过(exit 0 + stderr 一行)。已知残余:模块级 import 崩溃(PYTHONPATH +投毒)先于兜底层——注册命令 `python3 -E` 封 env 面 + health_probe 钩子探针兜 +可见性;5s 超时击杀结构性放行,同样只靠探针可见性。 + +### 深查节规约(v1.93.0/121,M/L 档) + +清单验收标准之后加「## 深查节」:三反问各一行实答(验证位错位/判据外推/心虚 +探测)。门禁 6.5b 在 M/L done 时查**结构在场**(三问+非占位答)——质量留纪律, +照抄问题原文不算答。这是把用户的「深入审查,是否还有遗漏」提前替用户说: +对账查询之外强制跑一次覆盖查询。 diff --git a/external_plugins/regress-guard/docs/agent-hooks-mapping.md b/external_plugins/regress-guard/docs/agent-hooks-mapping.md new file mode 100644 index 00000000..759cc691 --- /dev/null +++ b/external_plugins/regress-guard/docs/agent-hooks-mapping.md @@ -0,0 +1,57 @@ +# AGENT-HOOKS-0.1 适配映射表(批146 预研——采纳信号落地即开工的弹药) + +> 对照对象:Microsoft responsibleai/agent-hooks(MIT,2026-08-27 发布)—— +> 开放治理契约:8 拦截点 / allow-deny-transform / fail-closed 类型属性 / +> 审批绑定内容 SHA-256 / 47 场景一致性套件跨 8 框架(含 Claude Agent SDK)/ +> 五语言 SDK。本表=我们的七事件钩子链与它的逐点对照+适配评估。 +> **行动触发器**:ZCode 或 Anthropic 任一侧出现采纳信号(原生支持该契约) +> 即按本表开工适配——一次适配,八框架入口。 + +## 一、拦截点映射(8 vs 7) + +| AGENT-HOOKS 拦截点 | 本仓对应 | 语义差异/缺口 | +|---|---|---| +| prompt_submit(用户输入前) | UserPromptSubmit: prompt_intercept | 同形(治理上下文前置+纠正/约束埋点);AGENT-HOOKS 是纯拦截点,我们的 additionalContext 注入是宿主扩展 | +| pre_tool_call(工具调用前) | PreToolUse: 边界/读门禁/执行阀/提交门禁 | 同形核心位——deny≈exit 2(见二) | +| post_tool_call | PostToolUse: risk_watch / rbe post / plan_bridge | 同形;我们另有 PostToolUseFailure(失败流观察)为超集 | +| session_start | SessionStart(startup): self_heal | 同形(自愈/卫生/降级提醒) | +| session_end | —(Stop 是轮末非会话末) | 轻缺口:会话末钩子我们无对应(Stop 每轮=更细粒度超集,语义可映射) | +| permission_request | —(宿主领土:ZCode 权限层) | 缺口属宿主非插件——适配层若提供则透传 | +| notification | —(我们的 notify 是外发通道非拦截点) | 语义不同,无需映射 | +| context_compact | SessionStart(compact): compact_notice | 同形(压缩降级警示) | + +**结论**:核心治理位(pre_tool_call/post/prompt/session 四点)全部同形, +两缺口(session_end/permission_request)一属超集语义可映射、一属宿主领土。 + +## 二、语义对照(同形验证) + +| AGENT-HOOKS 机制 | 本仓实况 | 对照结论 | +|---|---|---| +| deny = 阻断决策 | PreToolUse exit 2(宿主契约) | **同形已具备**——适配层只需把 exit 2 翻译成契约 deny | +| fail-closed 类型属性(崩溃/超时必须合成 deny) | 批135:拦截器一切未捕获异常→exit 2;畸形 JSON 统一拦;`python3 -E` 封 env 投毒 | **部分同形**——崩溃面已收口;超时击杀(SIGKILL)仍结构性 fail-open(探针兜可见性),适配时契约侧 timeout→deny 语义反而补我们这个洞 | +| allow / transform 决策 | exit 0(放行);transform(改写工具参数)无对应 | 缺口:transform 是新能力(如命令改写拆弹)——适配时评估,非必需 | +| 审批绑定内容 SHA-256(防重放) | 内容钉 v1.72(同路径换内容分级防线)+plan_bridge 幂等键=session+plan_hash | **同思想不同位**——我们的钉在配置/计划层,契约钉在审批层;适配可互补 | +| 47 场景一致性套件(跨 8 宿主独立验) | held-out 18 场景冻结束(Goodhart 锁)+82 例基准集 | **互补位**:我们的束更深(Goodhart 锁/基线重冻),他们的跨宿主(我们正缺)——适配后我们的场景可贡献回其套件(上游回馈=可见度) | + +## 三、适配工作量评估 + +- **可原样复用**(语义已同形,只差适配层翻译):pre_tool_call 四守卫、 + post 观察、prompt 埋点、self_heal——约七成代码面零改动。 +- **需适配层**(薄):exit-code↔deny 翻译器、stdin 载荷格式转换(我们的 + {tool_name, tool_input} vs 契约 schema)、注册面(五语言 SDK 选 Python)。 +- **需评估**:transform 决策(新能力,可先 deny/allow 两态起步)。 +- **估计**:2-3 批(适配层+一致性套件跑通+场景回馈),单批 ≤10 文件。 + +## 四、时机与观察位 + +- 现状:ZCode/Anthropic 均无采纳信号(2026-10-24 复查);生态在暖 + (第三方分析/治理配置生成器赛道)。 +- 触发器:任一宿主原生支持 → 本表即开工令(分发最大杠杆:一次适配通吃 + 八框架=Claude Code 生态入口)。 +- 反向信号:若官方门禁框架(Anthropic blocking hooks)先落地,本表改为 + 竞合分析输入。 + +## 本表不支持的宣称 + +- "八框架入口全部等价"——各框架对契约的实现完整度未知(其套件测的是 + 契约语义非治理效果);"七成零改动"是基于本表映射的估计未经实现验证。 diff --git a/external_plugins/regress-guard/docs/claim-discipline.md b/external_plugins/regress-guard/docs/claim-discipline.md new file mode 100644 index 00000000..b59a6aec --- /dev/null +++ b/external_plugins/regress-guard/docs/claim-discipline.md @@ -0,0 +1,79 @@ +# 宣称纪律(claim discipline) + +> v1.88.1(批 100)。来源:better-harness 深研借抄(2026-09-21, +> 工作区 .regress/better-harness-deep-study-2026-09-21.md)。本文是宣称纪律的 +> 唯一属主;WORKFLOW「宣称纪律层」节是速览,不是副本。 + +**一句话原则:宣称的强度被证据的强度封顶。缺证据不是"低分",是不允许宣称。** + +## 证据七态与宣称词汇 + +来自 better-harness 七态的形状映射(分值带不进本仓——见文末解释边界): + +| 态 | 本仓词汇 | 语义 | 允许的宣称上限 | +|---|---|---|---| +| Present | 已配置 | 机制存在(文件/配置在场) | 「有这东西」 | +| Wired | 已接线 | 触发路由可达(hook 已挂/命令可调) | 「能用」 | +| Exercised | 已证活 | 用例或事件实际发生(测试过/journal 有事件) | 「跑通过」 | +| Outcome-supported | 已证效 | 后续可比结果改善(基线对比在场) | 「有效」 | +| Missing | 缺 | 查实缺席(不是没看见) | 只许宣称缺 | +| Unobserved | 未观测 | 观察边界决定不了(别硬填 Missing) | 不许宣称 | +| N/A | 不适用 | 证据证明不适用 | 只许宣称不适用 | + +两条格上规则: + +- **配置≠使用**(Present ≠ Exercised):装了不等于用了,用了不等于用对了。 + 特性点火探针(`history.py features`)的 unmeasurable 态就是本规则的反面发现—— + 连"有没有用"都看不见的特性,其宣称上限是零。 +- **未观测≠缺**:观察不到时保留 Unobserved,不许为整洁填 Missing + ("不可用"和"没查"是两种事实——机器事实卡的否定式断言保质期同源)。 + +## 三条硬规则(纪律位,v1.88.1 起) + +1. **计数≠行为**:遥测计数(拦截次数/命中率/事件量)只 bound 范围,不证明效果。 + 引用计数做有效性宣称时必须降格为线索并点名待补证据(如 est_saved_seconds + 的 est 前缀惯例——估算不自欺)。聚合计数永远推不出个体行为。 +2. **负面宣称清单**:每份批报告/巡检报告末尾必含「本证据不支持的宣称」一节, + **至少一条**,回指本批证据面(空节=纸面化,连续两批空节升机械位——预审裁决)。 + 这节是给下一手读者的护栏:拿着这份证据不许往哪说。 +3. **宣称-证据对齐**:报告里的每个效果动词(有效/提升/根治/省时)须能指回 + 七态中 Exercised 以上的证据;指不回的改用「已证活以下」词汇。 + +## 证据环境梯级(R0–R3,纪律位;v1.108 批160) + +七态回答「证到什么程度」,梯级回答「**在哪儿证的**」——两个正交维度。 +同一颗绿,本机跑出来的和清洁环境跑出来的不是同一份证据;跨梯级外推 +(R0 绿说成 R2 绿)与「计数≠行为」同族:宣称强度被证据强度封顶, +而证据强度包含它所在的环境梯级。 + +| 梯级 | 环境 | 本仓锚点 | +|---|---|---| +| R0 | 本机套件 | `validate.sh` 1/5 / 门禁自跑 pytest | +| R1 | 清洁环境(从零 venv 装依赖) | `scripts/clean_env_check.sh`(批126 首战即逮住 fallback 解析器与 PyYAML 三处分歧——宿主装 yaml 时全绿所以从未暴露) | +| R2 | 双跑发货门(串行+并行全绿) | 批157 发货门(xdist 改变执行模型,order-dependency 前科的套件不许单跑签发) | +| R3 | 陌生宿主真装 + 人感官 | `scripts/install_dogfood.sh` 陌生人漏斗 / human_check 化石 | + +规则(纪律位,观察两批再议升格——升格三条件见文末): + +1. **「已证效」级宣称必须点名梯级**,指不回某级锚点的降格为「已证活(R?)」。 +2. **跨梯级外推是宣称违规**:R0 的绿不覆盖 R1 的依赖面、R2 的并发面、R3 的 + 陌生宿主面——单机单 Python 版本是单点不是矩阵(clean_env_check 自己声明 + 的诚实边界即本条的第一条病例)。 + +## 判断合并纪律(多源候选合并,v1.88.1 起) + +- **保留优先**:合并多源候选(巡检发现/子代理产出/顾问意见)时,起点是保留一切; + 仅当**目标+后果+属主+修复路径四要素全同**才合并;每个被丢弃的候选留一句理由。 + 绝不为报告整洁砍真候选("never drop an eligible finding to reach five rows")。 +- **冻结先于叙事**:判断(发现/严重度/结论)先冻结,再写给人看的叙事; + 叙事(优先级排序/故事线/修复提示)不许反哺已冻结的判断。改判断要显式重开, + 不要顺着叙事滑。 + +## 解释边界(本文自己的) + +- 借的是 better-harness 的**形状**(词汇/规则/顺序),不是它的分值天花板表—— + 本仓没有 scoring 面,天花板列只作宣称词汇的语义参考,禁止被引用为打分公式。 +- 效果宣称("这个特性让 X 变好了")不受本文管辖,另归稳定性条件自审表 + (held-out/配对对比条件,见 docs/stability-conditions.md——批 101 起)。 +- 本文规则全部纪律位;升格机器位需满足预审裁决的三条件:形状连两批稳定、 + 实际观察到漏写、判据为无歧义布尔。 diff --git a/external_plugins/regress-guard/docs/dsh-support.md b/external_plugins/regress-guard/docs/dsh-support.md new file mode 100644 index 00000000..cca6e05d --- /dev/null +++ b/external_plugins/regress-guard/docs/dsh-support.md @@ -0,0 +1,225 @@ +# dsh 桌面版支持(批166,REGRESS-2026-164·特种分支 special/dsh-desktop) + +把 regress-guard 全量钩子链挂上 DeepSeek dsh 桌面版(DeepSeek Harness)。 +本文是架构决策记录(ADR 位)+安装/运维手册。 + +## 架构(为什么是桥而不是原生插件) + +**选型:官方 CC 方言桥 @deepseek-ai/dsh-hooks-claude-code(0.0.1-rc.1)。** + +- dsh 官方提供 dsh-hook-protocol(Claude Code/Codex 钩子线协议:matcher + 引擎+stdin/exit-code/stdout 编解码)与 dsh-hooks-claude-code 桥——后者 + 直接装载用户的 CC 方言 hooks.json 于 dsh 规范拦截点。regress-guard 的 + hooks.json 本就是 CC 方言(ZCode 钩子体系同族形态)。 +- 桥的映射(其 README 契约):PreToolUse→tools/pre-execute(deny→ + PreToolDecision.deny)、PostToolUse→tools/post-execute、SessionStart→ + agent/session-start、Stop→agent/turn-stopping、UserPromptSubmit→ + agent/pre-step。exit 2 语义=deny,与我们门禁拦截同构。 +- 备选不采:自写原生 cordis 插件(桥 README 自述"原生插件能做这一切", + 但绑定 cordis 内部 API 面,维护成本高于一条 hooks.json 配置;桥路径 + 零代码即得全链)。若未来需要 typed 增强点再评估原生化——观察位。 + +## 三处适配(侦察实证,2026-10-30) + +1. **变量+布局**:源仓命令串(ZCODE 变量 + 源仓脚本子目录前缀)→已装 + 根实体路径(变量=桥契约换 CLAUDE;前缀剥离=install.sh 把脚本摊平拷到 + 已装根;特例 self_heal 搬进 lib/,install.sh:98)——install_dsh.py + 生成 hooks.cc.json 入已装根并做**全命令实体校验**(每条命令引用的 + 文件必须在已装根存在——路径错位的失效形态=桥静默含错放行,e2e 活体)。 +2. **工具名 matcher 展宽**:桥 matcher 匹配宿主工具名;dsh 注册名是小写 + `bash`/`read`/`write`/`edit` 另有 `pwsh`(CC 系是首字母大写)。载荷 + 字段名两家一致(dsh-tool-fs README 明示 snake_case 对齐 Claude Code, + `file_path`/`command` 均同)——故只需 matcher 双方言:Bash→Bash|bash| + pwsh 等(MATCHER_WIDEN 表,install_dsh.py)。 +3. **会话身份+工具名规范化**:dsh 桥不设 CLAUDE_SESSION_ID env——会话身份 + 在 stdin payload 的 session_id 字段。lib/host_env.py absorb_session + 垫片:env 缺席时从 payload 吸收(只补缺不覆盖)。工具名规范化垫片: + dsh 小写名→CC 形态(钩子内部分支按 CC 大小写匹配,不规范化=全线漏判 + 假阴性)。接线:rbe/pre_commit/boundary/risk/fail 五钩子。 + +## 安装的三个坑(e2e 活体标本,install_dsh.py 已内置防护) + +1. **桥版本必须与 dsh 运行时配对**:裸 `dsh plugin add` 解析到旧线 + 0.0.1-rc,其 `agent.session.events` API 与运行时 0.2.1-rc 错配——所有 + bash 调用恒报 "agent.session.events is not iterable"。装版本配对 + `bridge@`(0.2.1-rc.5 实证)。 +2. **桥的 protocol 是 peerDependency**:宿主不显式装则桥 import 失败= + **静默含错放行**(提交穿门)。ensure_bridge_pkg 双包同装。 +3. **在位判定查 node_modules 实体**:lock 字符串会被 peerDependency + 声明行误中(假绿)。 + +## 攻克判据(e2e,2026-10-30 实证达成) + +真实 DeepSeek agent 会话(dsh --profile headless,deepseek-official)在 +fixture 仓(活跃清单 R1 边界=src/app.js,staged 越界文件)执行 +`git commit`:**提交未发生**(fixture git log 空),agent 收到完整拦截消息 +("commit 被阻断。以下 staged 文件不在回归清单中…请先运行 /regress-track +回写")并原样转达、零绕过。全链:dsh bash 工具→桥 PreToolUse→ +hooks.cc.json(摊平路径)→launcher/门禁→deny→agent。复跑可再现。 + +## 100% 冲刺(批167,v2.5.0)——两个探针实证与三处补位 + +- **SessionStart 实证**:工作区探针捕获 payload `source:"startup"`—— + 与 self_heal 的 matcher 完全吻合(compact_notice 同词对齐)。 + "待证"转"实证"。 +- **沙箱纪律(重大发现)**:dsh 桥经 ctx.shell 跑钩子=**会话 + workspace-write 沙箱辖内**——写 /tmp 被拒且各写者 except 静默。推论: + 状态文件在 /tmp 的钩子在 dsh 全部静默失效。修=host_env. + sandbox_aware_state_dir(/tmp 写探测→失败回退 CLAUDE_PROJECT_DIR/ + .regress/tmp 工作区内),接线 rbe/fail_watch/risk_watch/stop_notify/ + reflection 五写者。ZCode 无沙箱=探测恒成功零行为变化。 +- **fail_watch 补位**:dsh 无 PostToolUseFailure 桥点——hooks.cc 生成时 + 把 fail_watch 复挂到 PostToolUse,载荷带 tool_response 时自筛失败 + (dsh 归一化契约:`[exit code: N>0]` 或 `Error:` 前缀;无该键=宿主已 + 筛的 ZCode 语义直通)。 +- **能力矩阵终态**:ZCode 100%(原生全事件面);dsh 100%*(*唯一 N/A= + plan_bridge——dsh 无原生计划模式,宿主概念缺位非缺口;上游加计划 + 模式后 hooks.cc 生成器已预留复挂模式可循 fail_watch 先例扩展)。 + +## 如实声明(能力子集,spec §13.2 精神) + +**降级面**: +- PostToolUseFailure 无桥接点 → fail_watch(失败信号采集)在 dsh 缺位。 +- read_image 不计入读计数(图像不授文本认知——MATCHER_WIDEN 注记)。 +- ExitPlanMode matcher 在 dsh 无对应工具 → plan_bridge 两钩子不触发 + (dsh 无原生计划模式,本就无计划可转录)。 +- SessionStart matcher 主题=dsh 会话来源;startup/compact 若命名不同则 + self_heal/compact_notice 不触发(缺位非误拦)。 + +**不受降级影响的主链**:提交门禁(PreToolUse launcher)、边界守卫、 +执行阀、先读后改、风险采集(PostToolUse)、**失败信号采集(批167 起: +PostToolUse 自筛复挂)**、反思检查(Stop)、需求入口 +(UserPromptSubmit)、自愈/压缩提醒(SessionStart,source=startup/ +compact 实证契合)——全部在桥的映射点内。 + +**AGENTS.md 契约**:dsh-agent-instructions 原生装载(64KB 预算,dsh-base +默认启用)——回归契约注入免改自动生效。 + +## 安装 / 卸载 + +```bash +python3 scripts/install_dsh.py # 安装(默认 profile=web=桌面版) +python3 scripts/install_dsh.py --check # 诊断(桥包/patch 条目/hooks.cc.json) +python3 scripts/install_dsh.py --remove # 精确移除本批接线(不动他人条目) +python3 scripts/install_dsh.py --profile headless # 指定 profile +``` + +前提:已装 regress-guard(install.sh——已装根 = 桥的 pluginRoot)+本机 +有 ~/.dsh。桥包装入走 `dsh plugin --profile

add @deepseek-ai/ +dsh-hooks-claude-code`(版本配对 dsh 运行时)+protocol peer,实体在位 +判定)。 + +## 风险与观察位 + +- 桥 0.0.1-rc.1 漂移:失效形态=fail-closed(桥拒载不崩 dsh)。上游出 + stable 时钉版本复核。 +- 双宿主并发(ZCode+dsh 同项目):两宿主各自独立门禁(不同进程不同 + 钩子链,非同宿主双重拦截);read_before_edit 状态按会话分文件天然 + 隔离(P2#17)。 + +## 真实桌面 demo 战报(2026-10-31,浏览器全驱动 dsh web UI) + +demo 抓出**三个真缺口**(全部契约级测试测不出——载荷/环境形态只有真跑才现形): + +1. **字符串 tool_input 穿门(已修,v2.5.1)**:GLM/zai 通道的 tool-call + arguments 序列化为 JSON 字符串而非对象(deepseek 通道是对象)——钩子 + 找不到 command 键判"非提交"放行=越界提交穿门。修=host_env. + materialize_tool_input 六入口接线;复现 rc 0→2 钉回归测试。 +2. **web 钩子沙箱挂载视图隔离(已定位,观察位→上游)**:web 实例的钩子 + 进程文件系统视图与宿主隔离(cwd 只读层、预置脚本对钩子不可见 + "No such file"实证)——guard 在该视图内可能看不到项目 .regress → + 判"未接入"放行。headless 无此问题(钩子=进程 cwd)。修复方向=上游 + 桥 PreToolUse 载荷带 cwd(现仅 SessionStart 带)或原生插件;guard 侧 + 无文件系统外通道可自愈。 +3. **桥无 shell 执行**(排障纪律):钩子命令不经 shell 解释——管道/引号 + 是死字(tee 报错与 python exit 1 双实证)。探针必须免引号纯 argv 形态。 + +**demo 期间实证在岗**:门禁拦截(探针4/终验轮 hook 层 exit 2、git log +零新提交)、PostToolUse 风险采集、Stop 反思、UserPromptSubmit 需求入口 +(hook/invoked 全记录在会话事件流,可考古)。 + +## 原生轨道(批169,v2.6.0·special/dsh-native)——现在的默认 + +**为什么原生**(桥 README 自述"原生插件能做这一切"):无序列化边界(类型化 +exec/result)、无沙箱挂载缺口(插件跑 harness 进程内,spawn 钩子=真实 +文件系统视图——桌面 web 会话的"未接入项目"放行病根治)、无参数形态方差 +(原生物化)、失败判定原生(post-execute 的 result 直接判成败,fail_watch +不再靠文本启发式)。 + +**架构**:`deploy/dsh-native/`(仓内源)→ 装至 `~/.dsh/local-plugins/ +dsh-regress-guard/`(advisor 同构先例:自带 node_modules)。插件注册全部 +五个原生拦截点:tools/pre-execute(门禁/边界/执行阀/先读后改)、 +tools/post-execute(风险采集/失败采集/读计数)、agent/session-start +(自愈/压缩提醒,source=startup|resume|compact)、agent/pre-step(需求 +入口)、agent/turn-stopping(反思+完成推送,deny→agent.steer 强续步)。 +**python 钩子链仍是单一事实源**——原生层只做类型化适配(CC 形态 payload +构造+正确 cwd/env+Decision 映射),判定逻辑零复制。 + +**双 e2e 实证(2026-10-31)**:headless 与桌面 web 双通道,agent 越界提交 +均被完整拦截文顶回("commit 被阻断。以下 staged 文件不在回归清单中…" +),git log 零新提交。 + +**安装**:`python3 scripts/install_dsh.py`(默认原生;自动撤桥条目防双 +门禁);`--mode bridge` 退回 CC 桥兼容路径;`--remove` 移除;`--check` +诊断(原生在位+桥残留检测)。 + +**已知边界**:turn-stopping 的 stop_notify 仅在反思放行分支跑(同 ZCode +语义顺序);subagent 拦截点未接(guard 无对应钩子);pwsh 归入 Bash 门禁 +族(命令执行语义同构);**payload 携带 canonicalized tool_name(批171, +2026-11-02)**——此前原生路径 payload 缺 tool_name,boundary/execution_ +valve/read_before_edit_guard 三守卫整链 no-op(仅提交门禁生效,e2e 恰只 +验了提交);**worker jobs 后台命令的中间态限制**:dsh preset 把 5 秒以上 +前台命令转后台 job,post-execute 时拿到的是"moved to background"中间态 +结果,resultFailed 判 false——迟到的作业失败对 fail_watch 不可见(接 +job 完成事件是后续批候选,见 tests/test_dsh_native_payload.py 标本测)。 + +## 命令面(批205,v2.8.6·公网 issue#3 根治) + +**病根**:install.py 把 16 个 `/regress-*` 命令装到 `~/.zcode/commands/` +——ZCode 扫描该目录,**dsh 不扫**(dsh 指令面只有 skill 与 +`ctx.commands.register`)。命令在 dsh 是"死文件":安装器报 ✅、斜杠菜单 +不出现、输入被当普通文本。批166/169 适配了拦截面(钩子),入口面(命令) +缺位——公网 issue#3(wjsdxl,DSH desktop 0.2.1-rc.5 实测)。 + +**架构**:`deploy/dsh-native/lib/commands.js`(零外部依赖,pytest 探针 +可直载)。三层: + +1. **注册**:`ctx.commands.register({name, description, input:{hint}})`—— + name=文件名、description/argument-hint=frontmatter(单一事实源仍是 + `commands/*.md`,本层零复制流程逻辑)。走 `ctx.inject(["commands"],…)` + **运行时注入**而非顶层硬 inject:老宿主无该服务时硬 inject=整个插件 + 拒载=拦截面陪葬(fail-closed 回归,禁止)。注册面=目录扫描 + `regress-*.md`(同目录外来命令 handoff/ask-deepseek 不碰)。 +2. **执行**:handler fresh 读 md →剥 frontmatter→`$ARGUMENTS`→rawInput + 展开→构造 UserMessage 经 `agent.followup()` 注入为新回合——**ZCode + slash command 的 dsh 平移**(命令展开为用户回合交给模型,plan/track/ + verify 等 AI 类流程全量可用)。issue 作者以为"handler 不能给模型发 + 消息"(execute 文档"without sending it to the model"说的是宿主不自动 + 转消息)——`invocation.agent` 走 inbox 是正门:dsh-acp 同款 + (createUserMessage+followup),followup 排队 next-turn 并唤醒 driver; + 老宿主缺 followup 则 steer 兜底(idle steer 同样开 turn)。 +3. **对账**:session-start 时重扫补注新落盘的命令(`install.py` 升级后 + 免重启 dsh,新会话即入斜杠菜单);幂等(宿主同名注册 throw)。 + +**消息形状纪律(同族修复)**:agent-loop 的 step 读 `message.source.kind` +——裸 `{role, content}` 会 TypeError 炸 turn 静默丢消息。`userTextMessage()` +单点铸造 id(uuid)/role/content/source 四件套 frozen 消息;批205 同步收口 +turn-stopping 反思 deny 的 steer 老调用点(此前裸形状=拦截理由可能静默 +丢失)。 + +**fail-soft 纪律**:宿主无 commands 服务/命令目录不可读→警告一次静默返回, +钩子面(治理主体)不受牵连;命令文档缺失→error+修复指引(重跑 +install.py);宿主 Agent 面无注入通道→显式 error(不静默假装成功)。 + +**性质继承(宿主 commands 服务契约)**:全局生效(plain context 注册)、 +零 token 成本(描述不进系统提示,只在斜杠菜单展示)、确定性解析(宿主 +解析命令行,不依赖模型判断)、生命周期 `command/run`+`command/done` +入会话日志可考古。 + +**实证与防回归**:tests/test_dsh_commands.py 11 测——漂移 ratchet +(注册面==仓内命令全集,新命令 md 不接入 dsh 当场红=issue#3 类漂移不再 +靠用户提 issue 发现)、消息四件套、$ARGUMENTS 展开、幂等/外来不碰/ +文档缺失指引、运行时注入源码契约、late reconcile、安装器命令面诊断。 +`install_dsh.py --check` 增报命令面实况(原生在位而命令面 0 → exit 1 +并指向 install.py);安装后输出命令数与生效条件(重启 dsh;运行中装 +的新命令下次 session-start 自动对账入册)。 diff --git a/external_plugins/regress-guard/docs/feature-graph-design.md b/external_plugins/regress-guard/docs/feature-graph-design.md new file mode 100644 index 00000000..0c4ef277 --- /dev/null +++ b/external_plugins/regress-guard/docs/feature-graph-design.md @@ -0,0 +1,177 @@ +# 特性图设计稿(REGRESS-2026-086 · run8 波1-C) + +> 状态:**纯设计稿,未实现**。灵感源=ZCode 源码仓两个技能(只读调研): +> `architecture-governance`(yaml 政策+规则目录+基线感知违例)与 +> `feature-boundary-planner`(37 节点特征图+关系分级+稳定 ID+graph-drift 候选)。 +> 本文档的产出是**判断**(§6 裁决:不建),两个可打捞的漂移探针模式见 §5。 +> 写法遵循 PHILOSOPHY §13:写决定不写状态,史实句带日期与版本号。 + +--- + +## 1. 动机:特性/规则/测试三者间的漂移面 + +regress-guard 治理的对象里有三样东西各自独立演化: + +- **特性**——代码里活着的机制(门禁、召回、内容钉…),随版本推进; +- **规则**——对特性的声明(product-arch 模块卡片、规律账本、PHILOSOPHY 断言), + 由人/AI 手工维护,更新靠 finish 回写纪律; +- **测试**——验证路径(卡片"验证路径"字段里的 pytest 命令、套件本身), + 随重构改名/迁移。 + +三者两两之间都有漂移面,且**本仓已交过三次学费**: + +1. **卡片停更标本(特性→规则漂移)**:2026-09-21 自迭代 run 的卡片停在 v1.83 + 被对齐审计逮住(frontier-focus 082 候选的由来);更早一例是通知卡 wecom 段 + 描述的仍是 v1.70 前行为而代码已到 v1.85.4(product-arch 注记"卡片行为 v1.70 + 前存货指针")。声明滞后于实况,靠人记得去对才发现。 +2. **v1.80 治理行未首火(特性→事件漂移)**:UserPromptSubmit 治理上下文前置 + v1.80 上线,至今零次野外开火。观察位是 frontier-focus 里一行手工指针 + ("下次活跃清单")——**没有机制保证"上了但没响"的特性会被看见**,只有人记性。 +3. **rules_ledger 冷启动(规则→数据漂移)**:规律要求跨会话 ≥2 沉淀(公理三), + 新项目/新部署前 N 次失败**必然**零召回——特性活着、数据结构性为空, + 且这个空窗期没有任何产物显式标注它的存在与预期时长。 + +共性:都是"声明与实况的时差",都靠事后人工对账。图形化的原始动机是一个 +当前只能 grep 的问题:**改特性 X 时,哪些卡片段落/规律/验证路径会被波及**—— +卡片只有模块级粒度,规律只有文本,测试只有文件名。 + +## 2. 节点模型:节点是什么,稳定 ID 怎么定 + +**ZCode 的做法**(`zcode-feature-graph.yaml`,37 节点):节点=**语义边界**而非文件。 +七类 kind 前缀:`capability.` / `surface.` / `shared-ui.` / `state.` / `service.` / +`persistence.` / `boundary.`,如 `capability.model-selection`。稳定 ID 政策 +(stableIdPolicy)一句话:**语义边界不变则 ID 永不变,术语演化走 aliases** +(节点上挂 "model picker/模型选择/切换模型" 等别名承接改名)——ID 与命名解耦, +改名不再是图的操作。 + +**本仓等价设计**: + +- 节点五类,kind 前缀:`feature.`(能力,如 `feature.gate-cache`)、 + `module.`(模块卡对应的四个模块)、`rule.`(规律账本条目+PHILOSOPHY 断言)、 + `test.`(验证路径,锚到 pytest 文件/`-k` 表达式)、`task.`(清单)。 +- **稳定 ID 双轨制**:实体层用 `kind.slug`(长命锚,语义边界);事件层沿用既有 + REGRESS-YYYY-NNN(任务锚,已有 file_lock 临界区分配纪律,v1.56)。二者关系是 + 事件溯源的经典分层:**任务是事件,特性是实体;实体=对事件的折叠(fold)**。 + slug 不需要锁——它是声明不是稀缺配号。 +- 每节点带 codeSeeds(ZCode 同款:file+symbol/pytest 目标),对我们最自然的 + 锚是卡片"验证路径"字段里的命令本身。 +- 别名机制对本仓尤其值钱:中文仓里"门禁/提交门禁/gate"、"召回/rule_recall" + 是同一实体的多种叫法,规律账本 match() 的 bigram 匹配已在为此付费。 + +## 3. 边模型:关系分级 + +**ZCode 的做法**:50 条边、27 种语义动词(`renders-in` / `commits-to` / +`persists-to` / `must-not-replace`…),每边一个 rank,五级: +`must-inspect` / `should-inspect` / `conditional` / `invariant-only` / +`evidence-only`——rank 服务**遍历剪枝**(改动分析时决定先看哪条边)。 + +**本仓改造**:rank 语义不抄"检查优先级",换成**证据等级**——这更贴本仓的第一性 +原理(PHILOSOPHY §3:done 由门禁发,不由人自称)。边的可信度而非查看顺序: + +| 级 | 含义 | 例 | +|---|---|---| +| L1 机器复跑 | 门禁每提交实测的验证路径 | 卡片"验证路径"里被门禁复验的 pytest 命令 | +| L2 事件证实 | history.jsonl 里有过真实关联记录 | 某 task 落盘过 acceptance_passed | +| L3 声明 | 卡片/文档自述,无人复验 | 卡片"依赖"字段 | +| L4 推断 | grep/静态证据 | import 关系、文本共现 | +| INV 负边 | must-not 类不变量 | "验证主权不外借""子代理写通道不经过拦截链" | + +边类型初版**收缩到 5 种**(ZCode 的 27 种是 UI 产品面的 richness,我们不需要): +`verified-by`(feature→test)、`documented-in`(feature→卡片段落)、 +`recalled-by`(rule→feature)、`depends-on`(feature→module/feature)、 +`inhibits`(INV 负边)。防重叠硬规则:**边不进卡片正文**——卡片"依赖"字段保持 +人类叙事,图只是机器投影;两边各说各话,图错了不污染卡片,卡片改了图可派生。 + +## 4. 数据源:哪个最可靠 + +三个候选: + +| 源 | 富度 | 可靠性 | 结构性弱点 | +|---|---|---|---| +| manifests(79 份) | 最高(understood_intent/planned_changes/EARS) | 中——AI 写的声明 | 无实体键,只有文件路径弱关联 | +| **history.jsonl(289 事件)** | 中(五类事件:commit_passed 129 / commit_blocked 44 / fragile_verify 74 / test_failed 33 / acceptance_passed 9) | **最高** | 只看得见"走过的路" | +| 测试标签 | 低 | 高(ground truth) | pytest 无特性标签体系,test→feature 映射最弱 | + +**裁决:history.jsonl 做脊柱**,理由三点,核心是事件溯源优势: + +1. **写入者是门禁不是 AI**——证据律的直接应用:自述(manifests 是 AI 落的意图) + 与实况(门禁落的结果)分级,图的天性应该建立在后者上; +2. **append-only + 不可改写**——图可以**派生**(fold events → entities)而非手工 + 维护。派生图零维护成本,且消灭 Goodhart 面(§13"叙事手写,事实派生":没有 + 手写数字就没有可绕过的检查)——手工维护的图恰恰是 §13 判定的"对系统的声明", + 注定腐烂; +3. **双键已备**(manifest_id/session_id),join manifests 即可拿到意图富化层, + 不需要新写任何埋点。 + +manifests 按 manifest_id join 做意图富化;卡片与测试做探针叶子(§5)。 +手工只维护 ZCode 式 curated seeds 中事件看不到的部分——跨特性不变量(INV 负边)。 +诚实边界:事件源看不见**没发生过的事**——v1.80 治理行零开火意味着它在事件层 +零痕迹,所以 §5 Pattern B 需要一个极小的手工特性注册表(slug+shipped_at 版本), +这是全套设计里唯一的新手工产物。 + +## 5. 漂移检测提案:图能查出什么卡片查不出的 + +两个模式,各自挂着本仓真实标本: + +**Pattern A:幽灵验证路径(documented-in / verified-by 断链)** +卡片"验证路径"字段里的 pytest 命令做成 codeSeeds 探针:测试文件存在 + +`-k` 表达式 collect 非零 + (强形式)干跑不报错。断链即红。 +标本:通知卡 wecom 段停在 v1.70 前行为(若当时验证路径指向的测试已被改名/ +迁移,探针即红,不必等对齐审计)。**这正是 frontier-focus 082 候选 +(卡片对齐入硬清单)的机器化形态**——差异只在 082 是终报加一行人工/AI 对账, +本模式是探针自动跑。 + +**Pattern B:零命中间(feature→事件断链)** +特性注册表(slug+shipped_at)× history.jsonl 窗口 fold → 出厂后 N 天零事件的 +特性清单。标本:v1.80 治理行——今天靠 frontier-focus 手工指针盯,图化后变成 +一条可重复查询。同一查询同构服务两个既有需求:规律账本的衰变义务 +(§14:半年零命中降级候选)与 v1.83 热图"新键标注"的对称面(老键多久没响)。 + +图相对卡片的**本质**增量只有一条:边的传递性(改 feature.X → 反查 documented-in +得卡段落 → 反查 verified-by 得测试面,一次闭包回答"波及谁")。但诚实地说: +本仓 4 模块规模下,这个闭包 grep + 卡片也能做——这条诚实判断直接进入 §6。 + +## 6. 成本与裁决 + +**建设成本三档**(都含维护成本,维护才是大头): + +- ZCode 式全图(手工 curated 37 节点 50 边 + 每任务维护纪律):**大**。他们撑得住 + 是因为 monorepo 14 包、每次改码前必须生成 context-package(消费频率摊薄维护); + 本仓 4 模块,curation 负载与规模不成比例。 +- 纯派生图(fold history.jsonl):**小**。但只看得见事件层,卡片/测试断链全盲。 +- 混合(派生脊柱 + 卡片探针 + INV 手工种子):**中**。 + +**与既有机制划界(防重叠)**: + +| 机制 | 管什么 | 粒度 | +|---|---|---| +| scan_check 三态(v1.40) | 卡↔码新鲜度:卡片整体旧没旧 | 模块级,提交量代理 | +| product-arch 卡片 | 人类叙事档案:这个模块是什么/怎么连 | 模块级,人读 | +| 拦截热图(v1.62) | 拦截点热度:哪里常被拦 | 拦截点级 | +| 特性图(若建) | 特性/规则/测试**之间**的线断没断 | 边级,证据分级 | + +四者不重叠的前提是图不复制任何一者的输出:图不做新鲜度判定(scan_check 的), +不写模块叙事(卡片的),不数拦截(热图的)——只答"哪条边断了"。 + +**裁决:不建(现阶段)。** + +一句话理由:§5 的两个真实漂移模式各自都有更便宜的实现——Pattern A 就是 +082 候选本身(已在池,载体是终报+哨兵各一行),Pattern B 是"特性注册表 × +history 零命中"的 20 行哨兵查询——**都不需要图的传递性**;而图独有的传递闭包 +价值在 4 模块/79 清单的规模下 grep+卡片已覆盖。边际收益 < scan_check 三态 + +082 卡片对齐硬清单的组合,为边际零收益引入一个需要持续喂养的结构,违反 +§12(复用不重建)与 §13(手工维护的声明注定腐烂)。 + +拒绝也是资产(§12),留三条 falsifiable 重评触发器,任一命中则本裁决作废: + +- **T1**:模块卡 >8 张,或卡片"依赖"字段出现 ≥3 处对同一目标的相互矛盾描述 + (结构漂移超出 grep 能力,需要实体级锚); +- **T2**:双仓/多产品面成为常态(scan_check FP2 的治理仓活动代理失明恶化, + 迫切需要不依赖单仓 git 的实体锚); +- **T3**:规律/特性需要跨项目复用对齐(rules_ledger 出现第二个项目用户, + slug 成为公共契约)。 + +**顺手打捞(独立于本裁决成立)**:Pattern B 的零命中查询不依赖图,建议以 +20 行哨兵查询形态挂进缓存周检类闲时任务(特性注册表先只登记 v1.80 治理行 +一条,作为最小可行观察位);Pattern A 随 082 落地时,若探针跑通,本文件的 +codeSeeds 设计(§2)可直接复用。 diff --git a/external_plugins/regress-guard/docs/pr-auto-review.md b/external_plugins/regress-guard/docs/pr-auto-review.md new file mode 100644 index 00000000..78c1c639 --- /dev/null +++ b/external_plugins/regress-guard/docs/pr-auto-review.md @@ -0,0 +1,156 @@ +# PR 自动评审协议(独立第二通道) + +> 版本(批 172)。来源:super-agent 采用工程实战(REGRESS-2026-169/170, +> 2026-11-02 沙箱首飞 PR#6)+ 官方 auto-merge 安全模型的替代设计(其模型 +> 依赖 GitHub 分支保护,私有仓无 Pro 不可用——本协议以自家门禁链替代)。 +> 本文是 PR 自动评审的**唯一属主文档**:评审自动化 prompt 与本协议冲突时, +> 以本文为准并回写 prompt。 + +一句话原则:**评审者与执行者分权,机器只裁"验证完备+判据相符", +模糊一律升级人类,拒绝权永远留人。** + +## 为什么需要独立评审 + +super-agent 的 worker 已经受提交门禁约束(测试绿才能 commit)——但那是 +执行者**自环境**的验证。独立评审补三件事:①hermetic 复核(全新 clone +重跑测试,防环境漂移与自报失真);②需求保真度的机器可判部分(清单验收 +判据逐条对照 diff 与测试证据);③把"合并"这个不可逆动作的决定权放在 +执行体之外的第二通道。 + +## 评审协议(给评审 agent 的教程) + +对注册表(见下节)中每个仓库的每张 `state=open` 且作者为该仓 agent 账号 +的 PR,**顺序执行六步判据**。任何一步失败或模糊 → 该 PR 进入升级态, +不再继续后续步骤(早停省算力)。 + +1. **【边界】** PR 的 diff 文件集必须 ⊆ PR 内所含清单(`.regress/manifests/ + *.md`,随 PR 提交)的 `planned_changes` ∪ `.regress/**`(治理产物自身 + 允许入 diff)。同时逐文件对照该仓 `.regress/config.json` 的 + `boundary.forbidden`——触禁改区即失败。PR 内无清单 = 升级(不是拒绝: + 可能是清单缺失的流程违规,让人裁)。 +2. **【体量·记录性】** `gh pr diff ` 总行数(增+删)对照该仓 + `pr_review.max_diff_lines`(缺省 200)。**2026-11-02 人类策略升级 + (不冲突默认合并):本判据降级为记录性——超限不拦截、不升级,仅在 + 合并 subject 与 journal 事件里注明**。阻断判据见决策段。 +3. **【描述】** PR body 需含该仓 `pr_review.require_pr_sections` 的每一段 + (缺省:背景/改动/验证)。缺段 = 升级。 +4. **【验证·hermetic 重跑】** 不信 PR 描述里的"测试已过": + ``` + TMP=$(mktemp -d /tmp/prrev-XXXX) + git clone --quiet https://github.com/.git $TMP/repo + git -C $TMP/repo fetch --quiet origin pull//head && git -C $TMP/repo checkout --quiet FETCH_HEAD + python3 ~/.zcode/regress-guard-hooks/lib/test_runner.py $TMP/repo + rm -rf $TMP # 用后即清 + ``` + 退出码 0 且 JSON `status=passed` 才算过(test_runner 自动探测 + jest/vitest/pytest/mvn/gradle/go,timeout/parallel 读该仓 .regress 配置 + ——与提交门禁同一事实源)。失败/超时 = 升级。 +5. **【判据】** 读 PR 内清单的验收要点/EARS 行,逐条对照 diff 与第 4 步 + 测试结果找证据。**任一条找不到可对应的证据,或判据本身含糊到无法 + 机械对照 = 升级**。此步禁止发挥想象补证据。 +6. **【人类信号】** `gh pr view --json reviews`:存在 + `CHANGES_REQUESTED` 状态的 review → 不动该 PR,升级。 + +**决策(不冲突默认合并,2026-11-02 起)**:**阻断项**=①边界、③描述、 +④hermetic 测试、⑤判据对照、⑥人类信号,外加 mergeable≠MERGEABLE—— +阻断项全过即**默认合并**(`gh pr merge --merge --subject +"auto-review: passed …"`,体量超限时 subject 附注明);任一阻断项失败或 +模糊 → 升级(单条 chat 推送:PR 链接+卡在哪条判据)。体量②为记录性 +判据不阻断。**没有自动拒绝态**:机器不合时只升级,关闭/拒绝由人执行。 + +**红线**:永不 force push;永不操作注册表之外的仓库;token 永不回显; +不创建/修改/删除任何定时任务;同轮多 PR 的升级合并为一条推送;每张 PR +的判定(六步逐项结果)落 `journal add pr_review` 事件;任何工具连续失败 +×2 → 停手记 backlog。 + +## 项目接入指南(适配不同项目) + +新项目接入 = 三步,全部缺省可推导: + +1. **注册**:工作区 `.regress/pr-review-repos.json` 数组加一行 + `{"repo": "/", "agent_login": "", "since": "<接入日>"}`。 + 评审自动化只认这张表——不在表里的仓永不触碰。 +2. **仓内配置(可选)**:目标仓 `.regress/config.json` 加 `pr_review` 块: + `{"max_diff_lines": 200, "require_pr_sections": ["背景","改动","验证"], + "agent_login": "..."}`——全缺省时用上文缺省值。**测试命令零配置**: + test_runner 按仓自动探测(pytest/jest/vitest/npm-test/mvn/gradle/go)。 +3. **worker 侧约定**:目标仓 `AGENTS.md` 按 regress-sandbox 模板写机器通道 + 协议(立 in-progress 清单+验收判据 EARS 化+PR 描述三段+行尾套件计数) + ——评审六步的判据原料全部来自 worker 按协议产的清单与描述。 + +## 接入专题(批175 补) + +**双分支仓(开发/发布分流)**:默认分支应设为**开发分支**(如 dev)—— +super-agent 的 worktree 基线与 CI 观察都锚定 origin/HEAD,默认分支若为 +发布分支(main),agent 会基于过时基线工作制造冲突风暴。发布门 +(dev→main 的合并)永远由人把持,评审自动化不触碰。 + +**`pr_review.test_cmd` 覆盖**:多项目仓/非标测试栈(test_runner 自动探测 +不可用)在仓 `.regress/config.json` 声明 `pr_review.test_cmd`——评审 ④ +在 hermetic clone 内执行该命令(shell 语义),exit 0=过,输出尾行进 +journal 作证据。未声明则维持 test_runner 自动探测。**计数口径(批177)**: +test_cmd 输出尾行若非 `{passed,total}` JSON(如 pnpm 多步链),④与合并 +主题行标 **`exit0`**(以 exit code 为准的诚实口径)而非误导性的 `-1/-1`; +⑤的"全绿"措辞行在此口径下仍按 exit code 对账。 + +**治理随仓分发模式**(worker 的 hermetic clone 里守卫由此生效): +- 提交 `.regress/config.json`(执行阀/禁改区/pr_review 配置)与 + `.regress/manifests/`(工单清单随 PR 入库)进仓 +- `.gitignore` 排除 `.regress/` 运行态(journal/history/test-cache/ + .expect-commit/tmp/ 等)——配置与清单入库,状态留本地 + +## 验收要点 + +- When 评审自动化处理一张 agent PR,Then 阻断项(边界/描述/hermetic/ + 判据对照/人类信号)逐条执行且结果逐项落 journal(pr_review 事件), + 任一阻断项失败即停后续步骤;体量超限只记录不阻断。 +- When hermetic 重跑,Then 必须是全新 clone + PR head 检出, + 通过与否以 test_runner 退出码与 JSON 为准,不以 PR 描述自报为准。 +- When 阻断项全过且 mergeable,Then 默认合并(subject 带 auto-review + 审计前缀);任一失败或模糊,Then 只升级不拒绝,升级推送单条合并。 +- When 影子模式(前两轮),Then 全协议执行+落账+推送建议,但绝不 merge。 +- 来源与证据链:本协议每条判据的形态先例见 decisions.md 2026-11-02 + 各条目(首飞 PR#6 三拦三修、PR#8 冲突恢复环、PR#16 新策略首例 + hermetic 418/418=判据有效性的实证底)。 + +## 边界(不做) + +- 不判断"需求值不值得做"(人在 issue 里已经裁过)——只判"实现验证完备 + 且与清单判据相符"。 +- 不替代分支保护(私有仓不可用):本协议是应用层闸门,不是 GitHub 层 + 的强制状态检查。 +- 不自动关闭/拒绝 PR;不处理冲突状态(mergeable=false 的 PR 直接升级)。 +- 评审自动化不修改 super-agent 服务与其 prompts(分权:它只读 PR 面)。 + +## 事件化架构(批174,v2.7.4) + +机械层与判断层分离,触发源可替换: + +``` +PR 事件 ──> 触发源(当前=每 3 分钟 systemd timer 兜底扫描 pr_gate --sweep; + 未来=GitHub webhook 经 VPS 中继,pr_gate 入口不变) + ──> pr_gate.py 机械全判据(零 LLM:①③④⑤机械化⑥) + ├─ 全过 + auto-merge 档 ──> 合并+关单+journal(pr_review mode:machine)+WeCom 推送 + └─ 红/含糊 ──> 升级评论+journal+推送(人裁;LLM 判断层量大再议) +``` + +**⑤判据机械化口径**(保守,"该升不升"=假阴性通道):清单验收行含已验 +标记(门禁盖过 ✅/已验)=过;含 N/N 且与 hermetic 结果一致=过、不一致= +矛盾升级;含"全绿/全过"且测试全过=过;**其余一律含糊升级不猜**。 + +**仓级分档 `pr_review.mode`(舰队化)**:`auto-merge`(缺省,阻断项全过 +即合并)/ `human-merge`(机械全过也只评论建议,人执行合并)/ +`review-only`(只落账不评论)。配置随仓分发(.regress/config.json), +评审器从 hermetic clone 读权威值。 + +## 修订说明 + +- 2026-11-02 批172 建立:六步判据+两态决策+影子模式纪律+三步接入指南, + 随沙箱 PR#6/7 狗粮校准。 + +- 2026-11-02 批173:合并策略升级(人类口谕"不冲突默认合并")——体量②降级 + 记录性,决策改阻断项表述;阻断=边界/描述/hermetic/判据对照/人类信号/冲突。 + +- 2026-11-02 批174:事件化架构(机械层 pr_gate 产品化+3 分钟 timer 兜底 + +⑤判据机械化+仓级分档);webhook(秒级)列 Phase 2(需 VPS 入站中继, + 现 VPS 仅出网代理)。 diff --git "a/external_plugins/regress-guard/docs/reviews/2026-09-08-\345\205\250\346\226\271\344\275\215\350\257\204\345\256\241.md" "b/external_plugins/regress-guard/docs/reviews/2026-09-08-\345\205\250\346\226\271\344\275\215\350\257\204\345\256\241.md" new file mode 100644 index 00000000..979b2240 --- /dev/null +++ "b/external_plugins/regress-guard/docs/reviews/2026-09-08-\345\205\250\346\226\271\344\275\215\350\257\204\345\256\241.md" @@ -0,0 +1,141 @@ +# regress-guard 全方位评审报告(2026-09-08) + +> 证据基准:源码快照 `bc0753a`(v1.35.0);治理产物=本工作区 `.regress/`、 +> `~/.zcode/wecom-send.log`、`~/.zcode/regress-pending.jsonl`、`/tmp/regress-guard-*`。 +> 行号随修复会漂移——以快照为准。 +> 方法:三路只读子代理(功能完备性 / 代码鲁棒性 / 流程体验×真实数据)+ +> 顾问复核(方向与 P0 重组)。原始发现 44 条,去重合并后 36 条。 + +## 置顶:方向性结论 + +**停止加仪式,接通已装机闭环。**(顾问复核:方向成立,高把握) + +真实数据把机制劈成两半: + +- **写账类 100% 存活**:企微送达 128/128(errcode=0 为腾讯对端确认,非自报); + 地层五日不断层(本地自报,证据等级低一档);会话中继/盖章链活体全通。 +- **回流与强制类九项全线空转**(见下表)——门禁"hook 发 done"零次发生、 + blocked 推送从未送达、待决 resolve 零回流、quick 模式零使用。 + +v1.35 三新节(广度矩阵/DoD/架构影响)装机当天至今零真实使用——不是机制坏, +是**前一个闭环还没接通就开了新仪式**。修复批次一交付前,冻结新机制。 + +--- + +## P0(4 条,顾问重组后) + +### P0-1 门禁 fail-closed 统一入口(第一优先) + +三处"崩溃/解析失败 = 静默放行",与 fail-safe 语义相反: + +- `lib/manifest_parser.py:23-25` 只捕 `(IOError, OSError)`,非 UTF-8 清单抛 + `UnicodeDecodeError`(ValueError 子类,实测非 OSError)穿透 + `pre_commit_guard.py:235` 无保护循环 → exit 1 = 放行 +- 坏 YAML 清单走 loose_match + fallback 返回 `{}`(非 None)→ + `pre_commit_guard.py:236` 判 `parsed is None` 为假 → status 取不到 → + 走 `no_active_manifest` 放行(与 224 行注释方向相反) +- `execution_valve.py:150` `data.get` 无 isinstance 保护,非 dict stdin 载荷 + 抛 AttributeError → exit 1 = 放行(灾难级命令阀 fail-open) + +**修复方向**:门禁/阀门 main() 顶层"未预期异常 → emit_block"兜底; +解析器读文件 `errors="replace"`;fallback 结果无 `id` 且无 `status` 视同 None。 + +### P0-2 插件安装一致性 + +- `hooks/hooks.json` 仅注册 6 条钩子,`boundary_guard`/`execution_valve`/ + `fail_watch`/`risk_watch`/`compact_notice`/`stop_notify` 六个缺席 + (install.sh:217-297 注册了它们,插件模式却装不上)——插件模式下核心强制力全无 +- `install.sh` 注册 Stop→stop_notify.py 但 cp 清单(72-82 行)不含该文件—— + 安装后到下次 SessionStart 自愈前每轮 Stop 报文件不存在 +- `hooks.json:68` UserPromptSubmit 空 matcher——与 install.sh:214 自己的 + "空串违反 schema 会致整份 config 被丢弃"注释矛盾(v1.34 修了 Stop 漏了这处) + +**修复方向**:install.sh 九类事件条目同步进 hooks.json;cp 段补 stop_notify.py; +删空 matcher。 + +### P0-3 待决回流接线 + +`plan_approve.py` 批准/取消时不自动 resolve 同 id 待决——闭环上线即断 +(台账首条 REGRESS-001 plan_approval 至今悬挂;本次评审的待决#2 靠手工 resolve +完成首回流)。**修复方向**:approve/cancel 时按清单 id 匹配自动 resolve。 + +### P0-4 门禁发 done 零发生 + +runner 探测(`lib/test_runner.py:21`)只看 project_dir 根的 +pytest.ini/conftest.py——嵌套仓布局(.regress 在工作区根、代码在子仓)永远 +探测不到 → 17 份清单 0 份 `test_verified_by: hook`,"done 是门禁发的不是 +自称的"成了纸面承诺(当前逃生=finish 先翻 done 再提交,记在事实卡)。历史 +69 事件中 41 条 commit_passed 全部是 no_active_manifest 空转。 +**修复方向**:runner 探测向上/向子目录找 marker,或允许清单 frontmatter +显式声明 `runner_cwd:`。 + +--- + +## P1(13 条) + +| # | 发现 | 证据 | 修复方向 | +|---|------|------|---------| +| 5 | blocked 推送断线:门禁 blocked 分支只 record 无 notify | history 6 个 blocked 事件期间 wecom 台账 0 条 | 门禁各 blocked 分支接 notify.py | +| 6 | uninstall 残留死钩子:Stop 只滤 reflection_check、PreToolUse 只滤 launcher、PostToolUseFailure/SessionStart 不清、命令列表漏 resume/finish/stats | uninstall.sh:42,77-79,121 | 过滤统一为 command 含 "regress-guard",清单与 REQUIRED_COMMANDS 同源 | +| 7 | plan_approve 先截断后计算:`open(w)` 先清零再在 write 实参里算替换;note 含 `\1` 类文本触发 re.error → 清单 0 字节 | plan_approve.py:214-215,296-297(实测复现路径) | 先算后开文件;替换用 lambda;temp+rename | +| 8 | 通知超时预算倒挂:父进程 5s 处决 vs wecom 子进程 2×10s | notify.py:128 vs wecom_notify.py:74/98 | 外层 ≥ 内层之和,或内层各降 2s | +| 9 | 反思自动咨询挂死:冷却戳只在成功后写,顾问进程 hang 时每次 Stop 等 30 分钟 | reflection_check.py:148-174,timeout=1805 | 发起前写尝试戳;超时降到分钟级 | +| 10 | 清单字段塌方曲线:011-020 十份连续 0 脆弱点 0 假设(007-010 全字段) | manifests/ 17 份扫描 | plan 按规模分层:小改动只强制 F 列表+边界+1 脆弱点 | +| 11 | 事件双写:blocked 6/6 成对同毫秒双写,tool_fail 同样成对 | history.jsonl/journal 时间戳 | record() 调用路径去重 | +| 12 | 模板行内注释废掉 open 检测:`status: open # 注释` vs `_OPEN_FP_RE` 行尾即止 | templates/regress-manifest.md:45 vs manifest_fields.py:18 | 注释移独立行或解析器剥 `#` 尾注 | +| 13 | progress 心跳未接线:唯一指令在 WORKFLOW.md(不进常驻上下文) | grep 命令文档与 AGENTS 契约无 progress | finish/plan 授权段补推送指令 | +| 14 | stats.md 第 4 项路径断链:check_docs.py 不在 HOOK_HOME | stats.md:24 vs install.sh:379 | 改指 regress-guard-docs 或并入 HOOK_HOME | +| 15 | test 事件名存实亡:EVENTS 含 test 且豁免开关,但零发送点 | notify.py:31,96 全仓 grep | 删除或接线(门禁测试失败时推) | +| 16 | config bypass 清除无锁读改写+非原子 | pre_commit_guard.py:216-219 | filelock+temp/rename | +| 17 | read_before_edit 状态全会话共享无锁 | read_before_edit_guard.py:46-63 | 按 session 分文件(键已现成) | +| 18 | history 归档竞态:readlines→追加→重写,并发丢行 | history.py:64-84 | 持锁归档或大小短路判定 | +| 19 | quick/完整分界失灵:0 份 mode:quick,小改动走"剥字段的 full" | manifests 扫描 + quick.md:70 承诺与现实矛盾 | 分界改机器判据(文件数/类型);quick 豁免 runner 探测失败 | + +另录观察(不计条数):v1.35 三新节零真实使用;user_correction 误捕命令源文 +(本评审触发即活体复现,修法:过滤 `Run custom command` 前缀)。 + +## P2(13 条) + +| # | 发现 | 证据 | +|---|------|------| +| 20 | notify 通道模板 format 在 try 外,一个坏模板废全部通道 | notify.py:125-127 | +| 21 | pending 台账 id 分配竞态(max+1 后 append 无锁) | pending.py:66-72 | +| 22 | fail/risk_watch 截断重写竞态丢遥测 | fail_watch.py:105-112 | +| 23 | self_heal 升级就地覆盖运行中脚本(半截源码窗口) | self_heal.py:140-148 | +| 24 | 门禁热路径 O(n):history 每事件全量归档扫描+journal 无限增长 | history.py:61-77;pre_commit_guard.py:408-417 | +| 25 | read_before_edit ratio 缺省 2 与 config 缺省 3、docstring 3 三处矛盾 | read_before_edit_guard.py:108-112 | +| 26 | 漂移检查仓错配:.regress 向上撞到外层仓,拿别仓 HEAD 比对(本评审活体:假警报 da7482b) | plan_approve._drift_info | +| 27 | /tmp 状态文件零 GC:5 天 238 个 | /tmp/regress-guard-* | +| 28 | event 维度覆盖率 2/4,sensory/finish_open 零样本;stop_notify 直连通道 event=- | wecom-send.log 128 行 | +| 29 | commit_observed 观测链死链:post-commit 钩子从未装上嵌套仓 | history 0 条 + .expect-commit 残留 | +| 30 | WORKFLOW 声称"init 自动兜底"不存在 | prompt_intercept.py 无 init 分支 | +| 31 | 孤儿模板 ×2(git-post-commit.sh/regress-config.json 零引用,与 init 内联双份漂移) | grep 全仓 | +| 32 | 手写计数债四处 + 事件口径三处不一(四类/五类/七类)+ gen_reference 用 matcher 关键字计数是伪派生 | install.sh:7,399 等 | + +## 机制空转清单(9 项) + +| 机制 | 设计位置 | 真实数据 | +|------|---------|---------| +| blocked 事件推送 | config events.blocked=true | wecom 台账 0 条 | +| bypass_used 记账 | history 事件类型 | 0 次 | +| commit_observed 提交观测 | init 步骤 4 | 0 条 | +| quick 模式 | regress:quick.md | 0 份清单 | +| sensory / finish_open 推送 | DECISION_EVENTS | 各 0 条 | +| 待决 resolve 回流 | pending.py | 0 次(1 条悬挂)| +| adr: 清单关联 | plan 4.10 | 0/17 | +| 假设账本 | v1.23 模板 | 仅 007 有 1 行 | +| 门禁自动标 done | finish 步骤 3 | 0 份 | + +## 修复批次建议 + +- **批次一(P0,单独清单单独验证)**:P0-1 → P0-2 → P0-3 → P0-4。 + 只修一条修 P0-1(堵静默放行);求快速见效先 P0-3/P0-4(已装机断环点)。 +- **批次二(P1 闭环接线族)**:5/6/7/8/9/10/11/12/13。 +- **批次三(P2 卫生)**:竞态族 16-18/21-23 + GC + 口径统一。 + +## 证据等级声明 + +- 企微送达率:**对端确认**(errcode 来自腾讯 API 应答体) +- 地层/history 连续性、字段塌方:**本地自报/人工扫描**(低一档,顾问风险提示采纳) +- 代码类发现:静态通读 + 关键假设实测(UnicodeDecodeError 继承链、re 坏转义、 + format 花括号均有只读片段复现) diff --git a/external_plugins/regress-guard/docs/stability-conditions.md b/external_plugins/regress-guard/docs/stability-conditions.md new file mode 100644 index 00000000..162e0b6a --- /dev/null +++ b/external_plugins/regress-guard/docs/stability-conditions.md @@ -0,0 +1,50 @@ +# 稳定性条件自审表(self-improvement stability conditions) + +> v1.88.2(批 101)。来源:better-harness learning-loop-research-basis 深研借抄 +> (2026-09-21,工作区 .regress/better-harness-deep-study-2026-09-21.md 思想三)。 +> 他们把九篇 2026 自进化 Agent 论文压成七条稳定性条件并逐条自审;本文把同一张 +> 表套在 regress-guard 自己身上——因为本插件本身就是一个 self-improving 系统 +> (它开发自己、治理自己的开发)。 + +**核心句(借抄):无受控准入的自主修改是失败模式,不是默认。** + +## 七条件 × 本仓自审 + +| # | 条件(来源) | 本仓自审 | 补法草图 | +|---|---|---|---| +| 1 | 只在 held-out 分数严格改善时接受编辑(SkillOpt) | **Partial**(v1.90.0/106;v1.91.2/110 扩至十场景)——held-out **非退步门**在场:冻结束直连钩子(供应链/缓存完整性族 v1.91.2 进束),期望住 .regress(tests/ 可被批改写,held-out 期望只能显式 --refreeze 留痕);严格改善门仍缺(非退步≠改善) | 严格改善门:优化型批须束分高于基线才可合入(等首批优化型批病例) | +| 2 | 只保留验证 Pareto 前沿上的资产(EvoSkill) | **Absent**——命令/规则只进不出(代谢靠人肉) | 已有 /regress-stats 零火摘除惯例,缺前沿判定 | +| 3 | 验证器与测试内容隔离但仍给可行动反馈(CoEvoSkills) | **Partial**——门禁跑项目自己的测试(验证器=被验物同源);基准集独立于项目测试是隔离面的一半 | +| 4 | 同策略配对对比基线/注入运行,差值为更新信号(D2Skill) | **Partial**(v1.91.0/108 首例;v1.100.0/136 扩至三实例)——①测试缓存命中配对(2026-09-21,N=3 对,中位省 189.2s,范围 189.2–189.3;09-24 复跑 209.0s 随套件增长);②纠正闭环 v2 重放(2026-10-23):结构复刻语料 30 会话确定性——旧 10 分钟窗捕获 10/30 vs 高水位 30/30(**标本范围已证效**,边界∈(8,12)分钟);③读门禁 per-file 重放(2026-10-23):f77a 死亡螺旋形态旧式终点缺口 60 读 vs per-file 0(**标本范围已证效**;旧式对 1:1 正当流仍线性罚读同证)。两重放共变改动未隔离(归档 confounds 字段),野外效果不在宣称内 | 其余高引用特性逐个补配对(候选:held-out 门成本/计数对账);跨日/隔离机重跑缓存实验看漂移 | +| 5 | 每次编辑有界,被拒编辑进缓冲区,不再重提(SkillOpt) | **Partial**——decisions.md 记否决但无机器缓冲,砍掉的方案可换皮重提(白嫖/自驳分类学是手工补丁) | 走批前顾问预审附"近 90 天已否决方案"检索(候选) | +| 6 | 归因到 episode 粒度以下再怪某步骤(GiGPO) | **Absent**——归因在清单/staged 文件级 | 暂不补(单维护者批粒度已够用,防过度工程) | +| 7 | 生成 held-out 任务而非假设固定套件(AgentEvolver) | **Absent**——基准集冻结不生成 | 暂不补(生成器自身需要稳定性条件,递归风险) | + +## 宣称约束(本表的用途) + +- 在条件 1/4 补齐之前:**任何特性的"有效"宣称上限=已证活(Exercised)**, + 不许说已证效——与 claim-discipline.md 七态表联动。已证效级宣称另须点名 + 证据环境梯级(claim-discipline「证据环境梯级」R0–R3,v1.108 批160)。 +- v1.90.0 起条件 1 升 Partial:held-out 非退步门可支撑的宣称是 + **「未退化」**(冻结契约场景仍全过)——它不等于「有效」;效果宣称 + 升已证效仍需条件 4(配对对比)补齐。 +- 遥测计数(命中率/拦截数)按计数≠行为规则只当线索; + est_saved_seconds 类估算保持 est 前缀诚实。 +- 特性零火探测(`history.py features`)的 unmeasurable 态=宣称上限为零; + 量测位补齐后(如本批的 rule_recall_fired)从零升 Exercised 观察位。 + +## 配套词汇(借 better-harness 蒸馏七阶段,本仓映射) + +capture(journal/标本)→ generalize(规律账本 bigram 聚合)→ codify(规则/命令) +→ route(skill/命令路由)→ **exercise(点火=Exercised)**→ evaluate(本表条件) +→ maintain(零火摘除/代谢)。其中 exercise 态两个反模式名直接可用: +`routed-but-not-applied`(路由了没应用)、`asset-updated-not-reexercised` +(资产更新了没重训)。 + +## 解释边界 + +- 本表借的是形状;better-harness 的自审结论(他们哪些 Absent)不迁移到本仓。 +- 本表自身是 Exercised 以下产物:表建立=已配置,被走批引用=已接线, + 真正拦下一次越线效果宣称=已证活(观察位)。 +- 条件 6/7 的"暂不补"是裁决不是遗忘——重评触发器=出现批粒度归因冤案/ + 基准集被记忆污染的实际病例。 diff --git a/external_plugins/regress-guard/docs/subagent-playbook.md b/external_plugins/regress-guard/docs/subagent-playbook.md new file mode 100644 index 00000000..2d94b5d5 --- /dev/null +++ b/external_plugins/regress-guard/docs/subagent-playbook.md @@ -0,0 +1,125 @@ +# 子代理实现手册(肌肉记忆移植版) + +> 用途:把主会话 ~20 批(v1.66→v1.85)攒下的流程纪律与已知坑移交给并行子代理。 +> 你(子代理)领到一份清单(.regress/manifests/REGRESS-2026-0XX-*.md),按本手册 +> 实现到自测通过为止;**集成阶段(git/门禁/发布/清单盖章)回主会话串行**。 +> 每条坑都带标本出处——不是风格偏好,是真实翻过车的地方。 + +## 一、你的边界纪律(先读这个) + +1. **不跑任何 git 写命令**(add/commit/push/amend 都不行)——门禁是 PreToolUse + 钩子,git 提交会被拦;集成是主会话的活。 +2. **不改 .regress/manifests/ 下任何文件**——清单由主会话管理(指纹守卫会拦外部改动)。 +3. **只实现 + 自测**:自测 = 跑你自己批的测试文件(`python3 -m pytest tests/test_<你的>.py -q`), + **不跑全量**——全量留给主会话门禁(有缓存也只在同树时才命中)。 +4. 只动清单 `planned_changes` 声明的文件;要扩界就停下来报告,别绕(边界守卫会拦, + 拦了就是拦了)。 +5. 复用既有接口前**先读它的函数签名**(见坑 #7)。 + +## 二、实现阶段已知坑(按踩坑频率排序) + +### #1 指纹守卫:Read→Edit→Read→Edit(两连 Edit 必翻车) +对同一文件连续两次 Edit,第二次必被 🪞 拦("文件指纹不匹配")——第一次 Edit +本身就让读取态过期。纪律:每次 Edit 前重 Read 目标段落。 +标本:074 pre_commit_guard.py 两连 Edit;run4-6 每批至少一次。 + +### #2 锚点不中:别盲猜,Read 实文 +Edit 的 old_string 必须逐字符匹配(含缩进)。不中时禁止基于记忆改拼——Read +目标行区间,按实文重写锚点。**行号会漂**(别的钩子/会话改过文件),记忆里的 +行号只当近似值。 +标本:073 post_install_check 101/104 行"戳"字混合,两次不中后 Read 实文才落。 + +### #3 gen_reference 顺序:加完所有测试文件之后、跑全量之前 +`python3 scripts/gen_reference.py` 从测试目录推导计数写 README 派生区。顺序 +错了 `test_check_passes_on_fresh_generation` 会真红(它抓的是真实计数漂移, +不是误报——074 又验证了一次)。 +标本:074 加 `test_key_staged_equals_unstaged` 后忘了重新 gen,全量 1 failed。 + +### #4 git 三步分立 + 被拦重试必须重新 add +复合命令(add 与 commit 串在一起)会被 PreToolUse 拦截,**整条没跑**但 +你以为是 add 成了 commit 没成——暂存悄悄丢失。纪律:暂存 → `git status --short` +核对 → 提交,三个独立 Bash 调用。**被拦后重试尤其危险**:重试命令必须重新 +暂存并重新 status 验证——077 标本:重试只跑提交命令,提交里只剩旧暂存 +(ac0bdf1 实际只含 playbook 8 行,清单宣称的测试文件漏提交,run8 边界检查才逮住)。 +标本:run5 某批暂存丢失;run7 077 重试丢暂存(更隐蔽)。 +姊妹坑:**文档文本里出现提交命令字样同样触发门禁**(heredoc 写文档被拦实证)—— +文档写入用 Edit 工具或改措辞。 +**机器位(v1.87/091)**:此形态已由门禁直接拦(reason=compound_stage_commit), +教学消息自带正确姿势——本条从纪律位退役为背景知识,但引号/heredoc 载荷文本 +仍可能骗过你的眼睛,理解原理依然有用。 + +### #5 门禁跑的是已装副本:install.sh 先于门禁 +门禁钩子从安装位(~/.zcode/...)执行,不是从工作树。改了代码不 `python3 install.py`(安装位刷新) +就 commit = 门禁拿旧代码验证新提交。纪律:全量绿 → install.sh → add → commit。 +标本:058 鸡生蛋问题(timeout 钮修完门禁还是旧值)。 + +### #6 发布双坑(主会话专用,子代理不发布,备档) +- Git Data API 发树必须带 `git ls-files -s` 的**真实 mode**(本仓 15 个 100755 + 脚本,全发 100644 树 sha 必不等)。 +- 等值校验走 **ref→commit→tree 两跳链**;`GET /git/trees/main` 回显的是请求 + 对象 sha 不是根树 sha,直查必伪报 MISMATCH。 +标本:074 发布三跑(mode 坑→校验伪影→链式校验通过)。 + +### #7 复用接口先读签名:第一参/字段名别靠猜 +本仓有既成接口(rules_ledger.match() 第一参是 project_dir 不是查询串;条目 +字段叫 sig 不叫 signature)。用错不报错,只是永远查空——比崩溃更隐蔽。 +纪律:调用陌生函数前先 Read 它的定义处。 +标本:069 两次读错 match() 接口。 + +### #8 测试隔离:共享文件名/硬编码路径会互相覆盖 +tmp_path 里多个用例写同名文件、或硬编码 "proj" 路径,后写的覆盖先写的, +表现为"前面的用例随机红"。纪律:文件名带 marker(`stub-{marker.name}.sh`), +_mk 类工厂函数加 name= 参数。 +标本:057 通道 stub 互相覆盖;057 _mk 路径冲突。 + +### #9 时间戳方向:谁晚谁是"新" +"内容钉 ts 晚于信任表 ts = 人重新授信过"这类方向判断,方向反了规则永远走 +不到(deny 分支不可达 = 测试全绿但功能没验证)。写这类测试前先在纸上标一遍 +时间轴。 +标本:072 内容钉 08:00 早于表 09:00,deny 不可达,测试假绿。 + +### #10 EARS 验收行三件齐才算判据,勾了才算验过 +`- When 条件,则 结果(验:命令)` 三件齐;验完在行尾加 ✅(v1.73 起任意位置 +计勾)。缺(验:命令)= 不完整判据,门禁会拦(acceptance_missing/open)。 +标本:v1.73 前行尾只有 ✅ 没命令被拦的整族。 + +### #11 heredoc/引号载荷会骗过你自己 grep 的眼睛 +边界守卫标本四连:heredoc 里的路径、引号里的命令词、载荷文本里的 `>=3`—— +写拦截规则时先想"载荷和真写操作的区分特征是什么",别在原文上改正则。 +标本:v1.77/1.82 边界守卫四标本。 + +### #12 门禁 120s 默认超时:全量 >120s 的项目要配 test_runner.timeout +`.regress/config.json` 的 `test_runner.timeout`(秒)。本仓设 300(套件 ~119s +余量小)。新项目套件涨过 120s 会表现为"0 通过 + 超时"——不是测试坏了。 +标本:058(v1.69)。 + +### #13 门禁拦的是整条 Bash 命令,时机=命令提交瞬间 +命令串里含 `git commit` 字样就触发,门禁验的是**彼时**的工作树——复合命令 +(`seed && install && add && git commit`)里的步骤在门禁放行后才跑,内层 commit +不会被二次拦截。推论:想用测试缓存播种,seed 必须是**独立的前置调用**(命令串 +不含 git commit),下一提交调用才会命中;把 seed 和 commit 放同一复合命令 = +seed 与门禁全量并行双跑(想省的以并发形态重演)。 +标本:076 取证(guard_version 1.85.1 事件与 0 秒计时矛盾旋梯)。 + +### #14 gen_reference 无 dry-run:任何调用都是真写 +`scripts/gen_reference.py` 不校验未知参数——`--dry-run` 会被静默忽略并**真执行 +刷新**(改 README 派生区=边界外写入)。想检查一致性用 `--check`;别的 flag 一律 +别传。 +标本:run8 波1-C 臂(--dry-run 真写后外科还原)。 + +## 三、批流程七步(你在第 4 步) + +1. 清单落盘(主会话)——tier S/M/L、scan 三行、understood_intent 三件、EARS 验收、rollback +2. 顾问预审化石 + plan_approve --provisional(主会话) +3. **实现**(←你在这里:Read 相关文件 ≥2 → 改 planned_changes 声明的文件) +4. **自测**(←你在这里:本批测试文件绿 + gen_reference 刷新派生区) +5. 集成(主会话):install.sh → git 三步 → 门禁(全量或缓存命中) +6. 发布(主会话):Git Data API 全树替换 + ref→commit→tree 等值校验 +7. 落账(主会话):journal task_done / advisor_adoption、清单 ✅、decisions + +## 四、自测通过的判据 + +- 你的测试文件全绿(`python3 -m pytest tests/test_<你的>.py -q`) +- 没动边界外的文件(`git status --short` 里只有清单声明的路径) +- gen_reference 已刷新且 `--check` 过(`python3 scripts/gen_reference.py && python3 scripts/gen_reference.py --check`) +- 报告:改了什么/测试结果/有没有踩上面的坑/有没有计划外发现 diff --git "a/external_plugins/regress-guard/docs/\344\274\232\350\257\235\350\272\253\344\273\275\350\257\255\344\271\211.md" "b/external_plugins/regress-guard/docs/\344\274\232\350\257\235\350\272\253\344\273\275\350\257\255\344\271\211.md" new file mode 100644 index 00000000..773bfe68 --- /dev/null +++ "b/external_plugins/regress-guard/docs/\344\274\232\350\257\235\350\272\253\344\273\275\350\257\255\344\271\211.md" @@ -0,0 +1,44 @@ +# 会话身份语义(三方 id 单一真源) + +> 批218 根治档(REGRESS-261110-b9473428)立锚。lease / session_relay / +> 守卫三方凡涉「会话 id」的判定与写入,一律以本文为语义真源;后续新闸 +> (新钩子/新门禁)引用本文,不得另起口径。 + +## 三个 id 是什么 + +| 源 | 载体 | 写者 | 语义 | +|---|---|---|---| +| **env 快照 id** | `CLAUDE_SESSION_ID` / `ZCODE_SESSION_ID`(进程环境) | 平台(会话 shell 快照建立时定格) | 会话**开局时**的身份快照;长对话续接换平台号后**可陈旧** | +| **relay 真身 id** | `regress-guard-session-.json`(临时区,UserPromptSubmit 每轮原子重写) | 钩子侧(`session_relay.write_relay`) | 当前在该项目说话的会话**最新真值**(last-writer-wins + 5min 新鲜窗) | +| **清单 session 戳 / lease holder** | 清单 frontmatter `session:` / lease 块 | 盖章侧(`plan_approve`,relay 优先) | 清单归属的**登记事实**,判等的对照面 | + +## 判等规则(守卫侧,`session_relay.my_sids` 单一实现) + +1. **单源**:任一源单独在场即该源为身份——relay 无 id(新会话冷启)回退 env。 +2. **双源一致**:同一 id,无歧义。 +3. **双源冲突**(新鲜 relay ≠ env):**relay 真身为准,env 陈旧即弃**—— + relay 每轮写后重盖=最新真值。效果:Bash 写通道对真身会话直通, + 「中继重盖真身 id → 守卫比 env 旧 id 误拦 → 绕 Edit 通道」的对抗循环消除。 +4. **新鲜窗**:relay 仅 5min 内有效;窗外=陈旧中继不得代言(会话切换后 + 旧号失效语义保留)。 +5. **只读判等**:守卫侧判等不写回 relay、不继承进持久化戳(防中继回翻污染)。 + +## 写入规则(盖章侧) + +- 清单 session 戳 / lease holder 一律 **relay 优先、env 兜底** + (`plan_approve`:`read_relay(...).sid or sid_from_env()`)。 +- 守卫侧任何代码**不得**写 relay(写者只有 UserPromptSubmit 钩子)。 + +## 已知边界(backlog) + +- last-writer-wins:两会话同项目并发时判等可能偏向最后说话者——错向是 + 「自己被判他会话」(拦截可见可修),不是静默放行(顾问预审认可)。 +- 治本方向(backlog):单调序号 relay / shell 快照 env 刷新。 + +## 接线点(引用本真源的实现) + +- `hooks/scripts/lib/session_relay.py` — `my_sids`(判等唯一实现) +- `hooks/scripts/boundary_guard.py` — 编辑与 Bash 写目标跨会话判等 +- `hooks/scripts/pre_commit_guard.py` — 提交门禁清单归属判等 +- `hooks/scripts/lib/plan_approve.py` — 盖章(relay 优先) +- `hooks/scripts/lib/lease.py` — 租约 holder 语义 diff --git "a/external_plugins/regress-guard/docs/\345\260\217\347\231\275\346\214\207\345\215\227.md" "b/external_plugins/regress-guard/docs/\345\260\217\347\231\275\346\214\207\345\215\227.md" new file mode 100644 index 00000000..cb147b3d --- /dev/null +++ "b/external_plugins/regress-guard/docs/\345\260\217\347\231\275\346\214\207\345\215\227.md" @@ -0,0 +1,166 @@ +# regress-guard 小白使用指南 + +> 你不需要懂代码,跟着做就行。**你甚至不需要记任何命令**——正常说话即可。 + +--- + +## 这东西是干嘛的? + +简单说:**防止 AI 写代码时偷偷改坏东西你不知道。** + +AI 帮你写代码时,经常会"顺手"改一些你没让它改的文件,或者"自信地"留下没测过的坑。 +regress-guard 是一层**全天候的门卫+宪法**: + +1. 动手前:出一张 30 秒能看完的计划卡片,**你批准了它才能动手**(方向错误的最后纠偏点) +2. 动手中:越界的编辑被**物理拦截**;卡住了会带着精确的问题来找你,不会瞎磨 +3. 提交时:门卫自动跑测试、复核证据,**测试过了才放行** + +--- + +## 谁做什么?(先看这个,别的都可以忘) + +| | 做什么 | 多久一次 | +|---|---|---| +| **你在 ZCode 里** | 说需求 → 批卡片(30秒)→ 被问就答(受阻给东西/终验答一个词) | 每次任务 4 句话 | +| **你在 ZCode 外** | 物理事件:开权限、给凭据、听音质看画面;和批准前动脑理解 | 按需 | +| **AI 自动** | 分析、写码、修测试、收尾、提交(门卫接住) | 全程 | +| **门卫自动** | 拦越界、跑测试、验证据、断点指路、自愈升级 | 全程无感 | + +三条铁律:物理世界的动作只能你做;方向对不对只能你批;其余全自动,你随时可一句话介入。 +**你不需要说"提交"**——AI 自己提交,门卫把着关。 + +## 安装(就一句话) + +对 ZCode 的 AI 说: + +``` +/regress-install +``` + +**搞定。** 装完所有项目自动生效;项目第一次用时 AI 会自动初始化,你不用管。 + +> 手动党:`python3 install.py`(Windows 用 `python install.py`,POSIX 亦可 `bash install.sh`) + +### 装了什么? + +你想了解的话(数字会随版本变,以 README 为准): +- 13 个命令(AI 自动调用为主,你几乎不用敲) +- 11 个 hook 注册(边界拦截/提交门禁/自愈/哨兵/失败采集……全部无感) +- 项目里的 `.regress/` 数据目录(迷失时打开里面的 README) + +### 更新 + +**你什么都不用做。** 每次 ZCode 启动自动检测新版本静默升级;老项目缺什么也会自动补。 + +### 卸载 + +对 AI 说 `/regress-uninstall`,或 `python3 uninstall.py`(POSIX 亦可 `bash uninstall.sh`)。 + +--- + +## 日常使用:你只需要会说三类话 + +**第一类:说需求**(正常说话) + +``` +你:"帮我给登录页面加个记住密码功能" + +AI:(自动分析、出计划卡片——改哪些文件/有什么风险/按什么顺序做) +``` + +**第二类:回应卡片**(你的核心动作,30 秒) + +``` +AI:📋 计划卡片……等待批准 +你:"批准" ← 没问题就动手 +你:"不对,X 应该是 Y" ← AI 改完再给你看 +你:"取消" ← 不做了 +``` + +**第三类:被问到时回答**(系统不会没事烦你) + +``` +AI:🛑 受阻:需要你开一下防火墙白名单 ← 给它要的东西 +AI:改完了,对讲声音清晰吗?通过/不通过 ← 回答一个词 +``` + +然后(你不用管,AI 自己走完): + +``` +AI:收尾(/regress-finish)→ 自己提交 +🛡️ 门卫自动检查:文件都在清单?测试都过了?证据都齐? + → 都OK 放行 ✅ / 有问题 AI 自己修 +``` + +### 如果门卫拦住了(这是好事!) + +- **AI 越界改了文件** → AI 自己会收到拦截并找路(通常自动走 track 回写);它来问你时说"确实要改"即可 +- **测试没过** → 对 AI 说"修一下",它修完重提 +- **你着急** → `/regress-bypass 10` 开 10 分钟紧急通道——**会记账**,之后让 AI 补回归测试还债 + +--- + +## 高级用法(可选,记不住就忘掉) + +| 命令 | 什么时候用 | +|------|-----------| +| `/regress-resume` | 隔了几天回来续作(其实新会话开场哨兵会自动提示) | +| `/regress-finish` | 一条命令收尾(回写+验证+沉淀+报告),AI 也常自动走 | +| `/regress-quick 需求` | 小改动快速模式(跳过计划仪式) | +| `/regress-bypass 10` | 紧急通道(赦后记债) | +| `/regress-learn` | 让它总结这个项目的历史规律(越用越聪明) | + +**做产品功能时**:第一次会问你"用户是谁/什么不可牺牲"——答一次就记进 +`product-context.md`(产品上下文卡),以后每个设计都会对照这张卡,还会拿草图给你过目 +(否决发生在草图,不用等做完再返工)。 + +**你唯一值得记的一个:紧急时 `/regress-bypass 10`。其余全部可以说人话解决。** + +--- + +## 常见问题 + +### Q:不小心删了文件,会坏吗? +**不会。** 每次启动 ZCode 自动检查并修复(自愈)。 + +### Q:每次提交都要等测试,太慢了? +改了文档或小东西:`/regress-bypass 5`(会记债,别忘了还)。 + +### Q:团队其他人不用 ZCode 直接提交,门卫管得到吗? +**拦不到,但看得见。** git 观测钩子会记录"未走门禁的提交"——采用率有诚实镜子。 + +### Q:AI 拿着计划卡片来找我,我看不懂技术细节怎么办? +扫三样就够:**做什么/不做什么**(第一行)、**改动哪些文件**(大概)、**它假设了什么**(如有不符指出)。其余交给门禁复核。 + +### Q:不想用了怎么关掉? +- 临时关:`.regress/config.json` 把 `"strict"` 设为 `false` +- 彻底删:`/regress-uninstall` + +### Q:什么是 F1、F2、F3? +不用管。你只需要知道:AI 改的所有文件都会被记录、验证、留痕。 + +--- + +## 一张图 + +``` +你说需求 + ↓ +AI 出计划卡片 ──→ 你批准(30秒) ← 人类决策点 + ↓ +AI 干活(越界被拦/卡住会精确求助) + ↓ +AI 收尾(/regress-finish)──→ 感官终验问你 通过/不通过 + ↓ +AI 收尾后自己提交 + ↓ +🛡️ 门卫:清单齐?测试过?证据在? + ├─ 拦!AI 自己修 + └─ 放行 ✅ +``` + +--- + +## 需要记住的就 1 句话 + +**正常说需求、看卡片说批准、被问就答——其余的门卫在值班。** diff --git a/external_plugins/regress-guard/hooks/hooks.json b/external_plugins/regress-guard/hooks/hooks.json new file mode 100644 index 00000000..344d2943 --- /dev/null +++ b/external_plugins/regress-guard/hooks/hooks.json @@ -0,0 +1,159 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\"", + "timeout": 300, + "statusMessage": "regress-guard: 提交门禁检查..." + }, + { + "type": "command", + "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\" boundary_guard.py", + "timeout": 5, + "statusMessage": "regress-guard: 边界检查(Bash)..." + }, + { + "type": "command", + "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\" execution_valve.py", + "timeout": 5, + "statusMessage": "regress-guard: 执行阀检查..." + } + ] + }, + { + "matcher": "Edit|Write|ApplyPatch", + "hooks": [ + { + "type": "command", + "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\" read_before_edit_guard.py pre", + "timeout": 5, + "statusMessage": "regress-guard: 先读后改检查..." + }, + { + "type": "command", + "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\" boundary_guard.py", + "timeout": 5, + "statusMessage": "regress-guard: 边界检查..." + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "Bash|Edit|Write", + "hooks": [ + { + "type": "command", + "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\" risk_watch.py", + "timeout": 5, + "statusMessage": "regress-guard: 风险与重复信号采集..." + } + ] + }, + { + "matcher": "Read", + "hooks": [ + { + "type": "command", + "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\" read_before_edit_guard.py post", + "timeout": 5, + "statusMessage": "regress-guard: 读计数..." + } + ] + }, + { + "matcher": "ExitPlanMode", + "hooks": [ + { + "type": "command", + "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\" plan_bridge.py post", + "timeout": 5, + "statusMessage": "regress-guard: 原生计划转录..." + } + ] + } + ], + "PostToolUseFailure": [ + { + "matcher": "Bash|Edit|Write", + "hooks": [ + { + "type": "command", + "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\" fail_watch.py", + "timeout": 5, + "statusMessage": "regress-guard: 失败信号采集..." + } + ] + }, + { + "matcher": "ExitPlanMode", + "hooks": [ + { + "type": "command", + "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\" plan_bridge.py fail", + "timeout": 5, + "statusMessage": "regress-guard: 拒绝计划化石..." + } + ] + } + ], + "SessionStart": [ + { + "matcher": "startup", + "hooks": [ + { + "type": "command", + "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\" self_heal.py", + "timeout": 5, + "statusMessage": "regress-guard: 检查完整性..." + } + ] + }, + { + "matcher": "compact", + "hooks": [ + { + "type": "command", + "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\" compact_notice.py", + "timeout": 5, + "statusMessage": "regress-guard: 压缩检查..." + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\" reflection_check.py", + "timeout": 90, + "statusMessage": "regress-guard: 反思检查..." + }, + { + "type": "command", + "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\" stop_notify.py", + "timeout": 8, + "statusMessage": "regress-guard: 阶段完成推送(授权轮)..." + } + ] + } + ], + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\" prompt_intercept.py", + "timeout": 5, + "statusMessage": "regress-guard: 需求入口检查..." + } + ] + } + ] + } +} diff --git a/external_plugins/regress-guard/hooks/scripts/boundary_guard.py b/external_plugins/regress-guard/hooks/scripts/boundary_guard.py new file mode 100644 index 00000000..0472ed46 --- /dev/null +++ b/external_plugins/regress-guard/hooks/scripts/boundary_guard.py @@ -0,0 +1,632 @@ +#!/usr/bin/env python3 +"""boundary_guard — 开发边界守卫(PreToolUse Edit|Write|ApplyPatch 拦截)。 + +思想(2026-08-22 用户对话启示):检测≠拦截——Stop 钩子的方向漂移提醒是 +"事后追认",token 已烧、熵已生;边界必须在动作发生前物理拦截。 +实证:无边界的 agent 一天 6B 消耗半个月完不成任务、复检几十轮; +加边界后 <1B/天、多任务/天、自动复检即达标。约束不是拖慢,是提速。 + +拦截顺序(短路): + 0. 赦免:config.bypass_until 未过期 → 全放行(与 commit 门禁同源的唯一逃生口) + 1. 项目级禁改区:config.boundary.forbidden 通配,任何任务状态都拦(冻结区/遗留区) + 2. 任务边界:活跃清单(planning/in-progress/verifying/blocked)取并集 + planning=待批准拦编辑;approved.at 非空=人类产物直通视同已批准; + blocked=受阻拦编辑(合法停止不是绕过理由) + 3. 无边界信息 → 放行(fail-open,不误锁) +豁免:.regress/**、AGENTS.md、regress-guard 自身文件。 +越界的合法出口:/regress-track 回写 actual_changes(扩界留痕); +禁改区的合法出口:/regress-bypass <分钟>(赦后记债)或改 config。 +关闭:.regress/config.json 设 "boundary_enforced": false。 + +v1.20:字段读取与可编辑性判定收敛到 lib/manifest_fields.py(单一来源)。 + +退出码:0=放行,2=阻断 +""" +import sys +import os +import json +import re +import fnmatch +from datetime import datetime + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +LIB_DIR = os.path.join(SCRIPT_DIR, "lib") +if LIB_DIR not in sys.path: + sys.path.insert(0, LIB_DIR) + +from manifest_fields import ( # noqa: E402 + ACTIVE_STATUS_RE, block_value, editable, parse_core, +) +from manifest_parser import read_frontmatter # noqa: E402 长寿扫描:只读 frontmatter +from host_env import ensure_utf8_stdio, path_within # noqa: E402 批191:盘符大小写归一的包含判定 + +# 自定位绝对路径(源/安装两种布局同构)——拦截消息不留给读者猜的相对引用 +APPROVE_SCRIPT = os.path.join(LIB_DIR, "plan_approve.py") + + +def _load_config(project_dir): + cfg = os.path.join(project_dir, ".regress", "config.json") + try: + with open(cfg, encoding="utf-8") as f: + data = json.load(f) + return data if isinstance(data, dict) else {} + except (IOError, OSError, json.JSONDecodeError): + return {} + + +def find_project_dir(): + d = ( + os.environ.get("ZCODE_PROJECT_DIR") + or os.environ.get("CLAUDE_PROJECT_DIR") + or os.getcwd() + ) + d = os.path.abspath(d) + for _ in range(10): + if os.path.isdir(os.path.join(d, ".regress")): + return d + parent = os.path.dirname(d) + if parent == d: + return None + d = parent + return None + + +def config_disables_boundary(project_dir): + return _load_config(project_dir).get("boundary_enforced") is False + + +def bypass_active(project_dir): + """限时赦免(v1.20 赦免权普适化):与 pre_commit_guard 同源读 bypass_until。""" + until = str(_load_config(project_dir).get("bypass_until") or "").strip() + if not until: + return False + try: + return datetime.now() < datetime.fromisoformat(until) + except (ValueError, TypeError): + return False # 格式坏 = 过期 + + +def forbidden_globs(project_dir): + """项目级禁改区(v1.20):boundary.forbidden 通配列表。 + + 形状错误 fail-loud:stderr 警告但不砖(返回 [] 放行——禁改区是增强不是依赖)。 + """ + v = (_load_config(project_dir).get("boundary") or {}).get("forbidden") + if v is None: + return [] + if isinstance(v, list) and all(isinstance(x, str) for x in v): + return v + print("REGRESS-GUARD: ⚠️ config boundary.forbidden 形状错误(应为字符串数组)," + "禁改区未生效", file=sys.stderr) + return [] + + +def boundary_include(project_dir): + """config 级常设白名单(v1.34,020 装机债根因):boundary.forbidden 一直 + 被读而 include 从不被读——不对称陷阱。装机类机级绝对路径 + (如 /etc/systemd/system/**)的合法出口,不再逼 bypass(赦免记债)。 + path_matches 对绝对路径本就对称匹配,缺的只是读它的人。""" + v = (_load_config(project_dir).get("boundary") or {}).get("include") + if v is None: + return [] + if isinstance(v, list) and all(isinstance(x, str) for x in v): + return v + print("REGRESS-GUARD: ⚠️ config boundary.include 形状错误(应为字符串数组)," + "常设白名单未生效", file=sys.stderr) + return [] + + +def _manifest_session(manifest_path): + """清单的 session 戳(v1.34)——只认 frontmatter;无戳/读不到返回 "" + (共享语义:老清单不受会话作用域影响)。""" + try: + with open(manifest_path, encoding="utf-8") as f: + content = f.read() + except (IOError, OSError): + return "" + fm_text = content.split("---", 2)[1] if content.startswith("---") else "" + m = re.search(r"^session:\s*[\"']?([^\s\"'\n]+)", fm_text, re.M) + return m.group(1) if m else "" + + +def _manifest_lease_expired(manifest_path): + """清单租约是否过期(v1.109 批161):域逻辑在 lib/lease.py(判定单一 + 来源);无 lease 块=不过期。读不到清单=不过期(守卫对存疑者从宽, + 过期收回是放宽不是收紧——错向可见可修)。""" + try: + with open(manifest_path, encoding="utf-8") as f: + content = f.read() + from lease import expired + return expired(content) + except Exception: + return False + + +def parse_boundary(manifest_path): + """从清单 frontmatter 提取边界:显式 include 通配 或 精确文件集。 + + 返回 (patterns, exact_files, manifest_id, status, approved_at); + patterns 与 exact 皆空 = 无边界信息。 + """ + try: + with open(manifest_path, encoding="utf-8") as f: + content = f.read() + except (IOError, OSError): + return [], [], "", "", "" + core = parse_core(content) + if not core: + return [], [], "", "", "" + fm_text = content.split("---", 2)[1] if content.startswith("---") else "" + + patterns = [] + # 显式 boundary: include: 列表(支持 "- glob" 行与行内 [a, b]) + bm = re.search(r"^boundary:\s*\n((?:[ \t]+.*\n?)+)", fm_text, re.M) + if bm: + block = bm.group(1) + im = re.search(r"^[ \t]+include:\s*(.*)$", block, re.M) + if im: + inline = im.group(1).strip() + if inline.startswith("[") and inline.endswith("]"): + patterns = [p.strip().strip('"\'') for p in inline[1:-1].split(",") if p.strip()] + else: + for ln in block.splitlines(): + s = ln.strip() + if s.startswith("- "): + patterns.append(s[2:].strip().strip('"\'')) + + exact = set() + # 精确集(planned+actual)永远算边界的一部分——track 回写 actual_changes + # 即扩界(逃逸出口);显式 include 通配只是在精确集之上加宽。 + for fm_file in re.finditer(r"file:\s*[\"']?([^\"'\n#]+)", fm_text): + exact.add(fm_file.group(1).strip().strip("/")) + return patterns, sorted(exact), core["id"], core["status"], core["approved_at"] + + +def active_manifests(project_dir): + import glob + out = [] + mdir = os.path.join(project_dir, ".regress", "manifests") + if os.path.isdir(mdir): + for f in sorted(glob.glob(os.path.join(mdir, "*.md")), reverse=True): + # v1.23.2 长寿扫描:只搜 frontmatter——done 堆积后仍是 O(frontmatter), + # 且正文引用状态词不再让 done 清单诈尸(旧实现两头的坑) + try: + if ACTIVE_STATUS_RE.search(read_frontmatter(f)): + out.append(f) + except (IOError, OSError): + pass + return out + + +_HEREDOC_RE = re.compile( + r"(<<-?\s*['\"]?)(\w+)(['\"]?\n).*?\n[ \t]*\2(?:\n|$)", re.S) + + +def _strip_heredoc_bodies(cmd): + """剔除 heredoc 体(v1.77,066):体是文件内容不是命令——体内 rm/重定向 + 样文本曾成假目标;保留 << 定界行(其重定向目标仍可判)。""" + return _HEREDOC_RE.sub(r"\1\2\3", cmd) + + +def _dequote(seg): + """引号内容置空保结构(v1.77,066):命令词匹配跑在这份上——载荷/文档 + 内容里的英文命令词(活体:install smoke)不再被当命令,其后文本不再 + 成参数。重定向另有引号目标补充模式兜真写。""" + seg = re.sub(r"'[^']*'", "''", seg) + return re.sub(r'"[^"]*"', '""', seg) + + +def _seg_write_targets(seg): + """单段命令的高置信写目标(原始字符串)。 + + v1.77 误判根治:命令词/sed/of= 在去引号文本上匹配(引号内是数据不是 + 命令);重定向在原文与去引号文本双轨(原文捕裸目标,另加引号目标模式 + 捕 > 'my file' 形态)。""" + targets = set() + dq = _dequote(seg) + # v1.82(071):裸重定向改跑去引号文本+断言 (?![<=]) 跳过比较符与 + # herestring——第四标本(载荷 >=3 被捕 "=3")根治;引号目标补充模式 + # 保留在原文(> 'my file' 真写不因去引号而漏)。 + for m in re.finditer(r'(?{1,2}(?![<=])\s*([^\s;|&]+)', dq): + targets.add(m.group(1)) + # v1.92.2(113,野外报告#1 F4):fd 前缀重定向到**相对路径真文件** + # (2>err.log / 2>>log/)——v1.82 防 >= 的 (?&1 无文件目标((?![&<>]) 天然排除)。 + for m in re.finditer(r'(?=])\d>{1,2}(?![&<>])\s*([^\s;|&]+)', dq): + t = m.group(1) + # 批191:绝对判定走 os.path.isabs——字面 startswith("/") 在 Windows + # 上把 C:\... 误判为相对路径(fail-closed 噪音方向) + if not os.path.isabs(t): + targets.add(t) + for m in re.finditer(r'&>{1,2}\s*([^\s;|&]+)', dq): + targets.add(m.group(1)) + for m in re.finditer(r">(?{0,1}\s*'([^']+)'", seg): + targets.add(m.group(1)) + for m in re.finditer(r">{1,2}\s*\"([^\"]+)\"", seg): + targets.add(m.group(1)) + for m in re.finditer( + r'(?:^|[;|&\s])(rm|rmdir|mv|cp|tee|truncate|touch|install|shred)\s+([^;|&\n]+)', + dq): + name, rest = m.group(1), m.group(2) + if name == "install" and re.search( + r'(?:\bpip3?|\s-m\s+pip)$', dq[:m.start(1)].rstrip()): + # 批165(163 活体):pip/pip3/python -m pip 的 install 包名不是 + # 写目标(落 site-packages 在仓外),且动词捕获组在 2>&1 的 & + # 处截断会产出「2>」幻影——包管理器形态整组跳过;coreutils + # install 保留捕获(反向用例钉死,防收口过宽) + continue + toks = [t for t in rest.split() if not t.startswith("-")] + if not toks: + continue + if name in ("mv", "cp", "install"): + if len(toks) >= 2: + targets.add(toks[-1]) + else: + targets.update(toks) + # v1.90.1(107):-i 从可选改必须——sed 只有无 -i(原地写)才是写向量; + # 只读 sed -n 的地址/文件名曾被当写目标(×3 标本:'742,748p' 解析成文件名)。 + for m in re.finditer(r'(?:^|[;|&\s])sed\s+(?:-[^\s]*i[^\s]*\s+)+(?:-[^\s]*\s+)*([^;|&\n]+)', dq): + toks = [t for t in m.group(1).split() if not t.startswith("-")] + if toks: + targets.add(toks[-1]) + for m in re.finditer(r'\bof=([^\s;|&]+)', dq): + targets.add(m.group(1)) + # 批193+194(公网 issue#2 P0-2 + 遗留1/2):PowerShell 写动词全表 + # 两阶段提取:先匹配命名路径参数(-Path/-LiteralPath/-FilePath/-Destination/-Target), + # 无则取位置参数(跳过已知非路径参数值 -ItemType/-Value/-InputObject 等)。 + # v2.8.2 一阶段正则的 -ItemType Directory 被当位置目标的标本根治。 + _PS_WRITE = ( + r'(?:^|[;|&\s])(Set-Content|Out-File|New-Item|Add-Content|' + r'Copy-Item|Move-Item|Remove-Item|Rename-Item|Tee-Object|Clear-Content)' + ) + # 阶段一:命名参数(-Path xxx / -FilePath xxx / -Destination xxx / -Target xxx) + for m in re.finditer( + _PS_WRITE + + r'[^;|&\n]*?\s(?:-Path|-LiteralPath|-FilePath|-Destination|-Target)\s+([^\s;|&-]+)', + dq, re.I): + t = m.group(2).strip('\'"') + if t and not t.startswith("-"): + targets.add(t) + # 阶段二:位置参数兜底(跳过已知非路径命名参数的值) + _PS_NONPATH_FLAGS = r'(?:-[^\s]+\s+[^\s-]+\s+)*' # 贪心吃 flag+value 对 + for m in re.finditer( + _PS_WRITE + r'\s+' + _PS_NONPATH_FLAGS + r'([^\s;|&-]+)', + dq, re.I): + t = m.group(2).strip('\'"') + if t and not t.startswith("-"): + # Copy-Item/Move-Item 的第二个位置参数是目标(第一个是源) + if m.group(1).lower() in ("copy-item", "move-item"): + # 找第二个非 flag token + rest = dq[m.end(2):].strip() + toks = [x for x in rest.split() if not x.startswith("-")] + if toks: + targets.add(toks[0].strip('\'"')) # t 是源,第一个后续 token 是目标 + else: + targets.add(t) # 只有源无目标=原地操作(罕见,保守收) + else: + targets.add(t) + return targets + + +def extract_write_targets(cmd, project_dir): + """Bash 高置信度写目标,返回绝对路径(v1.29 旁路收口;v1.30 cd 链跟踪)。 + + 只认高置信形态,不确定一律漏过——fail-open 防误拦测试命令(2> 不收: + stderr 类误拦代价高于漏拦;/dev/null 豁免)。cd 链跟踪(病例 2026-09-03 + 首例活体误拦):`cd sub && cat > f` 的相对目标曾被解析到项目根——cd 后续 + 段的相对目标按链上目录解析;cd 目标含变量($)则后续相对目标放弃(fail-open)。 + """ + out, seen = [], set() + cwd = None + cwd_known = True + for seg in re.split(r"&&|\|\||;", _strip_heredoc_bodies(cmd)): + seg = seg.strip() + m = re.match(r"^cd\s+([^\s;&|]+)", seg) + if m: + d = m.group(1).strip('\'"') + if d.startswith("$") or d == "-": + cwd_known = False + else: + cwd = d if os.path.isabs(d) else os.path.join(cwd or project_dir, d) + continue + for t in _seg_write_targets(seg): + t = t.strip('\'"') + if (not t or t.startswith("$") or t in ("/dev/null", "/dev/stdout", "/dev/stderr") + or t.isdigit()): + continue + if os.path.isabs(t): + p = t + elif cwd_known and cwd: + p = os.path.normpath(os.path.join(cwd, t)) + elif cwd_known: + p = os.path.join(project_dir, t) + else: + continue + if p not in seen: + seen.add(p) + out.append(p) + return out + + +def path_matches(path_rel, patterns, exact_files): + p = path_rel.strip("/") + for e in exact_files: + e = e.strip().strip('"').strip("/") + if not e: + continue + if p == e or p.endswith("/" + e) or e.endswith("/" + p): + return True + for pat in patterns: + pat = pat.strip().strip("/") + if not pat: + continue + if fnmatch.fnmatch(p, pat) or fnmatch.fnmatch(p, pat + "/*"): + return True + # src/** 也直接匹配 src 自身路径前缀下的所有深度 + if pat.endswith("/**") and (p == pat[:-3] or p.startswith(pat[:-3] + "/")): + return True + return False + + +def is_exempt(fp_rel, fp_abs): + if ".regress/" in fp_rel or fp_rel.startswith(".regress"): + return True + if fp_rel.endswith("AGENTS.md") or "regress-guard" in fp_abs: + return True + # 批190(公网 issue#1②):ZCode 自动记忆目录——跨会话治理资产,与被 + # 保护的插件配置面(~/.zcode/hooks|skills|plugins 等)不同族。项目 + # forbidden 常含 ~/.zcode/** 罩配置面,误伤会话记忆写入。 + _mem = os.path.expanduser("~/.zcode/cli/memories") + if path_within(fp_abs, _mem): # 批191:盘符大小写归一比较 + return True + return False + + +def _escape_valve_open(): + """崩溃路径逃生阀前置检查(批135,顾问前置条件): + + 赦免判定只读 env+config,不碰载荷解析——任何 fail-closed 分支先过这里, + 确保「守卫坏了」不会变成「连 bypass 都救不回来的死锁」。 + """ + try: + project_dir = find_project_dir() + if project_dir and (config_disables_boundary(project_dir) + or bypass_active(project_dir)): + return True + except Exception: + pass + return False + + +def _fail_closed(reason): + """fail-closed(批135):载荷契约破坏/内部异常一律拦。 + + 旧行为:非 dict 载荷 AttributeError / 未捕获异常 → traceback → exit 1 → + 宿主按「非阻断错误」放行 = 编辑静默穿界。AGENT-HOOKS deny-means-deny 同律: + 状态未知时 deny。畸形 JSON 同样拦(stdin 来自宿主,畸形=宿主契约破坏, + 必须响亮失败——静默 fail-open 是看不见的治理退化)。 + """ + if _escape_valve_open(): + sys.exit(0) + try: + print( + f"REGRESS-GUARD: ⛔ 边界守卫 fail-closed\n" + f" {reason}\n\n" + f" 守卫无法判定状态时按「拦」处理——这是设计不是故障\n" + f" (静默放行=看不见的越界)。\n" + f" · 修复/排查后重试\n" + f" · 确要临时越过 → /regress-bypass <分钟>(限时赦免,赦后记债)\n" + f" · 项目级关闭 → .regress/config.json 设 boundary_enforced: false", + file=sys.stderr, + ) + except Exception: + pass + sys.exit(2) + + +def main(): + ensure_utf8_stdio() # 批191:cp936 流垫片(emoji/中文输出根治) + raw = sys.stdin.read() if not sys.stdin.isatty() else "" + if not raw: + sys.exit(0) + try: + data = json.loads(raw) + except json.JSONDecodeError: + _fail_closed("载荷不是合法 JSON(宿主契约破坏)") + if not isinstance(data, dict): + _fail_closed("载荷不是 JSON 对象(非 dict 形态)") + + # 批166(dsh 桥):工具名规范化(内部分支按 CC 大小写匹配——dsh 小写 + # 名不规范化则全线漏判=假阴性放行) + try: + from host_env import canonicalize_tool_names, materialize_tool_input + canonicalize_tool_names(data) + materialize_tool_input(data) + except ImportError: + pass + + tool = data.get("tool_name", "") + if tool not in ("Edit", "Write", "ApplyPatch", "Bash"): + sys.exit(0) + ti = data.get("tool_input", {}) + if not isinstance(ti, dict): + _fail_closed("tool_input 不是 JSON 对象") + project_dir = find_project_dir() + if project_dir is None: + sys.exit(0) + if tool == "Bash": + # v1.29 旁路收口:Bash 写目标走与 Edit 相同的边界判定(v1.30:cd 链跟踪防误拦) + fps = extract_write_targets(str(ti.get("command") or ""), project_dir) + if not fps: + sys.exit(0) + else: + fp = str(ti.get("file_path") or ti.get("path") or "") + if not fp: + sys.exit(0) + fps = [fp] + via = "(Bash 写目标)" if tool == "Bash" else "" + + if config_disables_boundary(project_dir): + sys.exit(0) + + # ─── 0. 赦免权(统一逃生口):bypass 有效期内放行一切编辑 ─── + if bypass_active(project_dir): + sys.exit(0) + + forb = forbidden_globs(project_dir) + stand_include = boundary_include(project_dir) # v1.34 常设白名单 + my_sid = (os.environ.get("CLAUDE_SESSION_ID") + or os.environ.get("ZCODE_SESSION_ID") or "") + manifests = active_manifests(project_dir) + boundaries = [] + for mf in manifests: + patterns, exact, mid, status, approved_at = parse_boundary(mf) + if (patterns or exact) and status not in ("done", "completed", "cancelled"): + boundaries.append((mf, patterns, exact, mid, status, approved_at)) + if not boundaries and not forb: + sys.exit(0) # 无边界信息,fail-open(含无活跃清单——设计语义) + + for fp in fps: + fp_abs = os.path.abspath( + fp if os.path.isabs(fp) else os.path.join(project_dir, fp)) + try: + fp_rel = os.path.relpath(fp_abs, project_dir).replace(os.sep, "/") + except ValueError: + fp_rel = fp_abs.replace(os.sep, "/") + if fp_rel.startswith("../"): + # 项目目录之外的文件:一律视为边界外(除非豁免路径) + fp_rel = os.path.normpath(fp_abs).replace(os.sep, "/") + if is_exempt(fp_rel, fp_abs): + continue + + # ─── 1. 项目级禁改区:与任务无关,任何状态都拦 ─── + if forb and path_matches(fp_rel, forb, []): + print( + f"REGRESS-GUARD: ⛔ 项目禁改区\n" + f" {fp_rel}{via}\n" + f" 命中 .regress/config.json 的 boundary.forbidden(冻结区/遗留区)。\n\n" + f" 禁改区与任务边界无关——它保护的是「不该再被碰」的代码。\n" + f" · 确有必要 → /regress-bypass <分钟>(限时赦免,赦后记债)\n" + f" · 冻结已解除 → 从 config 的 forbidden 列表移除(留痕于 git)", + file=sys.stderr, + ) + sys.exit(2) + + # ─── 1.5 常设白名单(v1.34):config boundary.include——装机类机级 + # 路径的合法出口。forbidden 先于 include(deny 优先于 allow)。 + if stand_include and path_matches(fp_rel, stand_include, []): + continue + + matched = [(mf, mid, status, appr, _manifest_session(mf)) + for mf, patterns, exact, mid, status, appr in boundaries + if path_matches(fp_rel, patterns, exact)] + # ─── 1.6 跨会话编辑守卫(v1.34,标本5 活体编辑竞态):文件在他会话 + # 活跃清单边界内——冲突只在集成态现形,编辑时就对焦比提交后 + # 回滚便宜。无戳清单不受影响(共享语义)。 + if my_sid: + # v1.109 批161:租约过期的他会话清单不再拦编辑(收回跨会话保护)—— + # 死会话占边界的出口从「人工重盖戳」前移为「到期自动可动」; + # 无 lease 块=旧语义不变(back-compat) + # 批218 根治档:身份判等用集合(env∪relay,冲突以 relay 真身为 + # 准、env 陈旧即弃——run65-67/217 四次自拦标本收口);语义真源 + # docs/会话身份语义.md;新鲜窗 5min 由 my_sids 保证 + try: + from session_relay import my_sids + my_ids = my_sids(project_dir) + except ImportError: + my_ids = {my_sid} + foreign = [m for m in matched + if m[4] and m[4] not in my_ids + and not _manifest_lease_expired(m[0])] + own = [m for m in matched if not m[4] or m[4] in my_ids] + if foreign: + f_ids = ", ".join(f"{mid or os.path.basename(mf)}({sid[:8]}…)" + for mf, mid, _, _, sid in foreign[:3]) + both = "(你的清单也声明了此文件——双方都计划了它,真冲突)" if own else "" + print( + f"REGRESS-GUARD: 🔀 跨会话作业中\n" + f" {fp_rel}{via}\n" + f" 在他会话的活跃清单边界内:{f_ids}{both}\n\n" + f" 多会话的冲突只在集成点现形——现在对焦比事后回滚便宜。\n" + f" · 哨兵视图看全局归属:python3 hooks/scripts/lib/sentinel.py\n" + f" · 与该会话串行作业,或让人类仲裁文件归属\n" + f" · 该会话已死?租约过期即自动可收回(v1.109);未过期可" + f"重盖 session+lease 戳接管后再动此文件\n" + f" · 确要并行抢改 → /regress-bypass <分钟>(限时赦免,赦后记债)", + file=sys.stderr, + ) + sys.exit(2) + if any(editable(status, appr) for _, _, status, appr, _ in matched): + continue # 已批准(status 翻转 或 approved.at 产物直通) + if matched: + blocked_hits = [(mf, mid) for mf, mid, status, _, _ in matched + if status == "blocked"] + if blocked_hits: + # 命中受阻清单:受阻是合法的停止状态,编辑被拦直到解阻 + mf0, mid0 = blocked_hits[0] + with open(mf0, encoding="utf-8") as f: + content = f.read() + reason = block_value(content, "blocked", "reason") if "---" in content else "" + need = block_value(content, "blocked", "need") if "---" in content else "" + ids = ", ".join(mid or os.path.basename(mf) for mf, mid in blocked_hits[:3]) + extra = "" + planning_ids = [mid or os.path.basename(mf) + for mf, mid, status, _, _ in matched if status == "planning"] + if planning_ids: + extra = f"\n (另有待批准清单:{', '.join(planning_ids[:3])})" + print( + f"REGRESS-GUARD: 🛑 任务受阻\n" + f" {fp_rel}{via}\n" + f" 在受阻清单({ids})的边界内——受阻是合法停止,不是绕过的理由。\n\n" + f" 阻塞:{reason or '(读清单 blocked 块)'}\n" + f" 需要人类:{need or '(读清单 blocked.need)'}\n\n" + f" · 把 need 转达给人类——这是受阻的出口,不是继续硬磨\n" + f" · 阻塞解除 → python3 {APPROVE_SCRIPT} <清单> --unblock 后恢复编辑\n" + f" · 认为不该受阻 → 在回复中说明理由,经人类同意后解阻" + f"{extra}", + file=sys.stderr, + ) + sys.exit(2) + # 只命中待批准(planning)清单:计划审批的机器强制——批准前拦编辑 + ids = ", ".join(mid or os.path.basename(mf) + for mf, mid, _, _, _ in matched[:3]) + print( + f"REGRESS-GUARD: ⏸ 计划待批准\n" + f" {fp_rel}{via}\n" + f" 在清单({ids})的边界内,但该计划尚未获得人类批准(status: planning)。\n\n" + f" 批准前只读探索,不动手——这是方向错误的最后低价纠偏点。\n" + f" · 人类回复「批准/开始/ok」→ python3 {APPROVE_SCRIPT} <清单> 完成转写\n" + f" (status→in-progress + approved 落产物 + 漂移检查 + 入地层)\n" + f" · 人类也可直接编辑清单填 approved.at(产物直通),守卫视同已批准\n" + f" · 计划需修改 → /regress-plan 继续对话完善(保持 planning)\n" + f" · 你认为不该等批准 → 在回复中说明理由,请人类显式批准", + file=sys.stderr, + ) + sys.exit(2) + + ids = ", ".join(b[3] or os.path.basename(b[0]) for b in boundaries[:3]) + print( + f"REGRESS-GUARD: 🚧 开发边界拦截\n" + f" {fp_rel}{via}\n" + f" 不在任何活跃清单({ids})的边界内。\n\n" + f" 边界 = 计划的物理化:越界动作在发生前被拦截,而不是烧完 token 后被提醒。\n" + f" 三条路(选一):\n" + f" 1. 这确实是本任务需要的 → /regress-track 把它回写 actual_changes" + f"(边界随之扩展,扩界留痕)\n" + f" 2. 这属于另一个任务 → 先收尾当前清单(status: done 解除边界),再开新清单\n" + f" 3. 误锁 → .regress/config.json 设 \"boundary_enforced\": false 项目级关闭\n\n" + f" 需要临时绕过:/regress-bypass <分钟>", + file=sys.stderr, + ) + sys.exit(2) + + +if __name__ == "__main__": + try: + main() + except SystemExit: + raise + except Exception as e: + # 全局兜底(批135,照 pre_commit_guard P0-1a 形态):未捕获异常→拦 + _fail_closed(f"内部异常 {type(e).__name__}: {e}") diff --git a/external_plugins/regress-guard/hooks/scripts/compact_notice.py b/external_plugins/regress-guard/hooks/scripts/compact_notice.py new file mode 100644 index 00000000..2cf3fc80 --- /dev/null +++ b/external_plugins/regress-guard/hooks/scripts/compact_notice.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""SessionStart(compact) hook:上下文压缩刚发生时的记忆降级警告。 + +压缩 = 无损摘要也丢细节(文件行号、报错原文、已否决的方案都可能残缺)。 +注入一条提醒:重要事实回读核对,别凭残缺记忆自决策;断点续作优先 /regress-resume +(v1.17 前指向 /handoff,已过时——第一入口是产物层单侧重建)。 +""" +import sys +import os as _os +_HERE = _os.path.dirname(_os.path.abspath(__file__)) +for _p in (_HERE, _os.path.join(_HERE, "lib")): + if _p not in sys.path: + sys.path.insert(0, _p) +from host_env import ensure_utf8_stdio # noqa: E402 +import json + + +def main(): + ensure_utf8_stdio() # 批191:cp936 流垫片(emoji/中文输出根治) + print(json.dumps({"additionalContext": ( + "【regress-guard 压缩警告】上下文刚被压缩——你的记忆是摘要不是原文:\n" + " • 断点续作优先 /regress-resume:从 .regress/ 产物层单侧重建现场" + "(零号入口 .regress/README.md:三步上手 + 完成判据)\n" + " • 关键事实(文件路径/行号、版本号、报错原文、清单状态)先重新 Read 核对," + "禁止引用记忆里的细节直接改代码\n" + " • 已否决过的方案可能被遗忘——认知已物质化在文件里,别靠回忆:\n" + " - .regress/decisions.md(决策日志:决定/依据/否决过的方案)\n" + " - .regress/manifests/(清单:含脆弱点挂牌状态 fragile_points)\n" + " - .regress/journal/events.jsonl(考古地层:历次失败/风险/纠正化石)\n" + " 动手前先读这三处,避免把否决路线再走一遍\n" + " • 任务复杂且 resume 后仍缺关键上下文时,再考虑 /handoff 重开会话," + "优于在残缺记忆上硬撑" + )}, ensure_ascii=False)) + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/external_plugins/regress-guard/hooks/scripts/execution_valve.py b/external_plugins/regress-guard/hooks/scripts/execution_valve.py new file mode 100644 index 00000000..bc4307fa --- /dev/null +++ b/external_plugins/regress-guard/hooks/scripts/execution_valve.py @@ -0,0 +1,401 @@ +#!/usr/bin/env python3 +"""execution_valve — 执行阀(公理四:建议权与执行权彻底剥离)。 + +LLM 的建议是概率猜测,不可逆执行是物理确定性动作——两者之间必须有 +"拆弹密码"级的单向阀门。本阀只认令牌,不听理由: + + 灾难级命令(mkfs / dd if= / force push / DROP/TRUNCATE / chmod -R 777 / + 项目外 rm -rf)默认 exit 2 阻断; + 命令中显式含令牌 REGRESS_CONFIRM=YES 才放行——令牌必须由意图方亲手敲入, + AI 不得替用户默认携带。 + +关闭途径(显式、留痕): + - env REGRESS_VALVE=off(紧急,如 /regress-bypass 场景) + - .regress/config.json {"execution_valve": false}(项目级长期关闭) + +顾问意见(auto-consult)与此阀的关系:顾问永远只产生文本 additionalContext, +不持有任何执行权限——猜测流与执行流物理隔离。 + +事件: PreToolUse(Bash),stdin JSON 同其他 hook +退出码: 0=放行,2=阻断(stderr 展示拆弹说明) +""" +import sys +import os +import re +import json +import tempfile + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +LIB_DIR = os.path.join(SCRIPT_DIR, "lib") +if LIB_DIR not in sys.path: + sys.path.insert(0, LIB_DIR) + +from host_env import (ensure_utf8_stdio, is_root, path_within, # noqa: E402 + is_abs_any, win_form) # 批191/195:跨平台根/包含/绝对性判定 + +TOKEN = "REGRESS_CONFIRM=YES" + +# 灾难级模式:几乎无合法未确认用途 → 无令牌即阻断 +CATASTROPHIC = [ + ("格式化磁盘", re.compile(r"\bmkfs(\.\w+)?\b")), + ("裸写磁盘 dd", re.compile(r"\bdd\s+if=")), + ("强推覆盖远端", re.compile(r"\bgit\s+push\b.*(\s--force\b|\s-f\b)(\s|$)")), + ("删库 DROP/TRUNCATE", re.compile(r"\b(DROP\s+(TABLE|DATABASE)|TRUNCATE\s+TABLE)\b", re.I)), + ("全员可写 chmod 777", re.compile(r"\bchmod\s+-\w*R\w*\s+777\b")), + # 批193+194(公网 issue#2 P0-3 + 遗留3):PowerShell 灾难级——dsh(pwsh) 下这些 + # 全部绕过 POSIX 模式。Format-Volume 系是裸磁盘操作,无"项目内"概念, + # 维持无条件拦;递归删三方言(Remove-Item -Recurse / rd|del /s)批195 + # 移出本表改目标判定(见 RM_RECURSIVE)——项目内清理与 rm -rf 对称。 + ("磁盘格式化", re.compile(r"\b(Format-Volume|Clear-Disk|Initialize-Disk|Remove-Partition)\b", re.I)), +] + +# rm -rf 单列:合法清理太常见,只拦"物理上不可恢复"的目标—— +# 绝对路径且在 /tmp 与项目目录之外(项目内相对路径 git 可恢复,归 risk_watch 记录) +RM_RF = [ + re.compile(r"\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)\b"), +] + +# ─── 批195(公网 issue#2 第三轮):递归删三方言统一目标判定 ────────── +# v2.8.2/3 把 PowerShell 递归删直接进 CATASTROPHIC 无条件拦——与 POSIX +# rm -rf 不对称:`rm -rf ./build` 放行而 `Remove-Item -Recurse -Force +# ./build` 被拦。dsh 在 Windows 只投递 pwsh,项目内 build/dist/ +# node_modules 日常清理全断;"每次清理带令牌"训练习惯性携带,反而削弱 +# 令牌信号价值(用户实测五形态全误拦)。改法:三方言同走 _target_dangerous +# (相对=项目内放行 / 临时区放行 / 根与项目外绝对路径拦)。 +# PowerShell:Remove-Item 及全部官方别名(rm/ri/del/erase/rd/rmdir), +# -Recurse 有无 -Force 皆递归(-Force 只影响只读文件是否中断)。 +PS_RM = re.compile( + r"\b(Remove-Item|ri|rm|erase|del|rd|rmdir)\b(?=[^;|&\n]*\s-Recurse)", re.I) +# cmd:rd/rmdir/del 带 s 开关(含组合 /sq /qs);纯 /q 静默单文件删不递归 +# 不算(第三轮附带误拦修复)。开关须独立成词(前置空白+后非词符)—— +# `del src/sub` 的路径斜杠段不当开关。 +CMD_RM = re.compile( + r"\b(rd|rmdir|del)\b(?=[^;|&\n]*\s/[a-z]*s[a-z]*(?![\w]))", re.I) +# Remove-Item 取值参数:-Path/-LiteralPath 的值是目标;其余取值参数的值 +# 不是路径。开关参数(-Recurse/-Force/-WhatIf/-Confirm)无值跳过。 +_PS_RM_PATH_PARAMS = ("-path", "-literalpath") +_PS_RM_VALUE_PARAMS = ("-filter", "-include", "-exclude", + "-stream", "-credential") + +_SEGMENT_SPLIT = re.compile(r"&&|\|\||;|\n") + + +def _project_dir(): + d = ( + os.environ.get("ZCODE_PROJECT_DIR") + or os.environ.get("CLAUDE_PROJECT_DIR") + or os.getcwd() + ) + return os.path.abspath(d) + + +def _strip_quotes(tk): + """剥 token 两端配对引号(引号内路径不丢)。""" + if len(tk) >= 2 and tk[0] == tk[-1] and tk[0] in "\"'": + return tk[1:-1] + return tk + + +def _win_norm(p): + """Windows 形态归一(批195):斜杠折反斜杠、整体小写(NTFS 全路径 + 大小写不敏感,不只盘符)、去尾分隔符。""" + return p.replace("/", "\\").lower().rstrip("\\") + + +def _win_is_root(p): + n = _win_norm(p) + if len(n) == 2 and n[1] == ":": + return True # 盘符根 D:\ + return n.startswith("\\\\") and n.count("\\") <= 2 # \\srv\share + + +def _win_within(child, parent): + """Windows 形态包含判定(大小写不敏感——NTFS 语义)。""" + c, pa = _win_norm(child), _win_norm(parent) + return c == pa or c.startswith(pa + "\\") + + +def _target_dangerous(t, project_dir, tmp_root): + """单删除目标判定(批195,三方言共用)。 + + 相对路径 = 项目内放行(git 可恢复);根/项目外绝对路径 = 拦;临时区 + 放行;变量展开($/%)不可知按最坏情形。Windows 形态目标走逐字符串 + 分支——POSIX 宿主的 realpath/abspath 会把它折进 cwd 成幻影路径 + (`C:\\data` 被判"项目内"假放行),跨方言比较只在 project/tmp 同为 + Windows 形态时才可能放行(真 Windows 宿主即是)。""" + t_exp = os.path.expanduser(t) + if "$" in t_exp or "%" in t_exp: + t_exp = os.path.expanduser("~") # 展开结果不可知,按最坏情况处理 + if not is_abs_any(t_exp): + return False # 相对路径 = 项目内,git 可恢复 + if win_form(t_exp): + if _win_is_root(t_exp): + return True + for zone in (tmp_root, project_dir): + if win_form(zone) and _win_within(t_exp, zone): + return False # 临时区/项目内(同方言才有可比性) + return True + real = (os.path.realpath(t_exp) if os.path.exists(t_exp) + else os.path.normpath(t_exp)) + # 批191:根/包含判定走 host_env 助手——is_root 通吃 POSIX/盘符/UNC + # (字面 == "/" 在 Windows 恒假=阀根保护失效);path_within 折盘符 + # 大小写(env 与 realpath 给的 "C:"/"c:" 不一致时项目内误判为外) + if is_root(real): + return True + if path_within(real, tmp_root): + return False # 临时区清理可接受 + if path_within(real, project_dir): + return False # 项目内,git 可恢复 + return True + + +def _dangerous_targets(targets, project_dir): + tmp_root = os.path.realpath(tempfile.gettempdir()) + return [t for t in targets if _target_dangerous(t, project_dir, tmp_root)] + + +def _ps_rm_targets(segment): + """Remove-Item 段提取删除目标:先命名路径参数(-Path/-LiteralPath), + 位置参数兜底;其余取值参数跳名+值,开关跳名。逗号数组展开、引号剥壳。""" + m = PS_RM.search(segment) + if not m: + return [] + tokens = segment[m.end():].split() + targets, i = [], 0 + while i < len(tokens): + tk = tokens[i] + low = tk.lower() + if tk == "--": + targets.extend(tokens[i + 1:]) + break + if low in _PS_RM_PATH_PARAMS: + if i + 1 < len(tokens): + targets.append(_strip_quotes(tokens[i + 1])) + i += 2 + continue + if low in _PS_RM_VALUE_PARAMS: + i += 2 # 名+值都跳(值不是路径目标) + continue + if tk.startswith("-"): + i += 1 # 开关 + continue + targets.append(_strip_quotes(tk)) + i += 1 + # 逗号数组(-Path a,b 或 a,b,c):逐段判定不失位 + out = [] + for t in targets: + out.extend(p for p in t.split(",") if p) + return out + + +def _cmd_rm_targets(segment): + """cmd rd/rmdir/del 段提取目标:跳过 / 开关 token,其余皆目标。""" + m = re.search(r"\b(?:rd|rmdir|del)\b", segment, re.I) + if not m: + return [] + return [_strip_quotes(tk) for tk in segment[m.end():].split() + if not tk.startswith("/")] + + +def _rm_targets_dangerous(segment, project_dir): + """提取 rm 的目标参数,判断是否落在不可恢复区(/tmp 与项目外绝对路径)。""" + m = RM_RF[0].search(segment) + if not m: + return None + tokens = segment[m.end():].split() + # 标准 rm 语义:flags 之后的第一个非 flag token 起全部是目标 + targets = [] + for i, tk in enumerate(tokens): + if tk == "--": + targets.extend(tokens[i + 1:]) + break + if tk.startswith("-"): + continue + targets.extend(tokens[i:]) + break + if not targets: + return None + return _dangerous_targets(targets, project_dir) or None + + +def _valve_disabled(): + """显式关闭(env 或项目 config)。关闭是显式决策,不是默认。""" + if os.environ.get("REGRESS_VALVE", "").lower() in ("off", "0", "false"): + return True + d = _project_dir() + for _ in range(10): + cfg = os.path.join(d, ".regress", "config.json") + if os.path.exists(cfg): + try: + with open(cfg, encoding="utf-8") as f: + if json.load(f).get("execution_valve") is False: + return True + except (IOError, OSError, json.JSONDecodeError): + pass + break + parent = os.path.dirname(d) + if parent == d: + break + d = parent + return False + + +def evaluate(command, project_dir=None): + """返回阻断原因列表(空=放行)。project_dir 缺省取 env/cwd。 + + v1.90.1(107)载荷剥离:CATASTROPHIC 匹配跑在剥 heredoc 体+引号段后的 + 文本上——引号内是数据不是命令(活体 ×2:grep 模式串/落账文本里的危险词 + 被当真命令拆)。**解释器入口例外**:bash/sh -c、eval、ssh、xargs sh 段 + 保留原文扫描——复制脚本/CI 单串的手滑确实把真命令藏在引号里(顾问微修)。 + RM_RF 检测在去引号文本、目标提取在原文(引号路径不丢)。威胁模型:阀防 + 误操作不防对抗(对抗面归门禁/信任表白名单)——但手滑藏引号属误操作, + 故解释器入口不豁免。""" + if not command or TOKEN in command: + return [] + if project_dir is None: + project_dir = _project_dir() + reasons = [] + _INTERPRETER = re.compile( + r'\b(?:bash|sh|zsh|dash|ksh)\s+-c\b|\beval\b|\bssh\b|\bxargs\s+' + r'(?:\w*sh|bash)') + try: + from boundary_guard import _strip_heredoc_bodies, _dequote + except ImportError: + _strip_heredoc_bodies = _dequote = None + # heredoc 体先于段切分剥离——按 \n 切段会把体切成独立段(本批活体), + # 事后再剥已无效;091 同款全命令级剥离。 + if _strip_heredoc_bodies: + command = _strip_heredoc_bodies(command) + segments = _SEGMENT_SPLIT.split(command) + for seg in segments: + interpreter_seg = False + if _dequote: + probe = _dequote(seg) + # 解释器入口:载荷真的会执行——回原文匹配 + interpreter_seg = bool(_INTERPRETER.search(probe)) + scan_segs = [seg, probe] if interpreter_seg else [probe] + else: + probe = seg + scan_segs = [seg] + for scan in scan_segs: + for label, pat in CATASTROPHIC: + if pat.search(scan): + reasons.append(f"{label} ← 「{seg.strip()[:80]}」") + # RM_RF 检测段:解释器段用原文(引号内 rm 同属手滑),其余用去引号文本 + if RM_RF[0].search(seg if interpreter_seg else probe): + dangerous = _rm_targets_dangerous(seg, project_dir) + if dangerous: + reasons.append( + f"项目外不可恢复删除: rm 目标 {', '.join(dangerous[:3])}" + f" ← 「{seg.strip()[:80]}」" + ) + # 批195:PowerShell/cmd 递归删目标判定(检测在去引号文本、提取在 + # 原文,同 RM_RF 纪律)。无法定位目标(管道输入形态)按拦—— + # fail-closed,位置/命名参数形态不受影响。 + for pat, extractor, label in ( + (PS_RM, _ps_rm_targets, "Remove-Item"), + (CMD_RM, _cmd_rm_targets, "cmd rd/del"), + ): + if pat.search(seg if interpreter_seg else probe): + targets = extractor(seg) + if not targets: + reasons.append( + f"{label} 递归删但无法确定目标(管道输入?改用位置/" + f"命名参数显式指定目标) ← 「{seg.strip()[:80]}」" + ) + else: + dangerous = _dangerous_targets(targets, project_dir) + if dangerous: + reasons.append( + f"项目外不可恢复删除: {label} 目标 " + f"{', '.join(dangerous[:3])} ← 「{seg.strip()[:80]}」" + ) + return reasons + + +def _fail_closed(reason): + """fail-closed(批135,P0-1c 全量收口):载荷契约破坏/内部异常一律拦。 + + 旧行为:非 dict 只修了一半(无说明静默 exit 2),其余未捕获异常 → + traceback → exit 1 → 阀门放行。逃生阀前置(顾问条件):_valve_disabled + 只读 config/env,不碰载荷解析。 + """ + try: + if _valve_disabled(): + sys.exit(0) + except Exception: + pass + try: + print( + f"REGRESS-GUARD: ⛔ 执行阀 fail-closed\n" + f" {reason}\n\n" + f" 阀门无法判定状态时按「拦」处理——这是设计不是故障\n" + f" (静默放行=不可逆命令无守卫)。\n" + f" · 修复/排查后重试\n" + f" · 确要执行 → 命令前显式加令牌 {TOKEN}(禁止习惯性携带)\n" + f" · 关闭阀门(留痕):.regress/config.json 设 execution_valve: false", + file=sys.stderr, + ) + except Exception: + pass + sys.exit(2) + + +def main(): + ensure_utf8_stdio() # 批191:cp936 流垫片(emoji/中文输出根治) + raw = sys.stdin.read() if not sys.stdin.isatty() else "" + if not raw: + sys.exit(0) + try: + data = json.loads(raw) + except json.JSONDecodeError: + # 批135(顾问裁决②):畸形 JSON 统一拦截——stdin 来自宿主, + # 畸形=宿主契约破坏;旧行为 exit 0 放行 = 阀门可被形态绕过 + _fail_closed("载荷不是合法 JSON(宿主契约破坏)") + + # 灾难级命令阀 fail-closed(评审批次一 P0-1c + 批135 补说明) + if not isinstance(data, dict): + _fail_closed("载荷不是 JSON 对象(非 dict 形态)") + + if data.get("tool_name", "") != "Bash": + sys.exit(0) + ti = data.get("tool_input", {}) + if not isinstance(ti, dict): + # 批135:非 dict tool_input 统一拦(原空命令静默放行=形态绕过面) + _fail_closed("tool_input 不是 JSON 对象") + command = ti.get("command") or "" + if not command: + sys.exit(0) + + if _valve_disabled(): + sys.exit(0) + + reasons = evaluate(command) + if not reasons: + sys.exit(0) + + print( + "REGRESS-GUARD 执行阀(公理四:不听猜测,只看令牌):\n" + "以下操作物理不可逆,AI 的任何理由都属概率猜测,不能作为执行依据:\n" + + "\n".join(f" 💣 {r}" for r in reasons) + + "\n\n拆弹方式(二选一):\n" + f" 1. 确认真的要做:在命令前显式加令牌 {TOKEN},例如:\n" + f" {TOKEN} <原命令>\n" + " (令牌必须是人类或你基于人类明确授权敲入的,禁止习惯性携带)\n" + " 2. 改用可恢复路径:git 可恢复的项目内清理不用令牌;" + "数据先备份再操作\n\n" + "关闭阀门(留痕):.regress/config.json 设 \"execution_valve\": false," + "或环境变量 REGRESS_VALVE=off", + file=sys.stderr, + ) + sys.exit(2) + + +if __name__ == "__main__": + try: + main() + except SystemExit: + raise + except Exception as e: + # 全局兜底(批135,照 pre_commit_guard P0-1a 形态):未捕获异常→拦 + _fail_closed(f"内部异常 {type(e).__name__}: {e}") diff --git a/external_plugins/regress-guard/hooks/scripts/fail_watch.py b/external_plugins/regress-guard/hooks/scripts/fail_watch.py new file mode 100755 index 00000000..896d489f --- /dev/null +++ b/external_plugins/regress-guard/hooks/scripts/fail_watch.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +"""fail_watch — PostToolUseFailure 探测器(轮内卡死信号,用户无感层)。 + +只写事实,不做判断:每次工具失败追加一条事件到会话状态文件。 +判断由 reflection_check(Stop hook)统一完成——探测器哑、评估器独裁。 + +事件: {"ts": ISO, "tool": 名, "sig": 归一化签名(命令首token+子命令 或 文件路径)} +状态: /tmp/regress-guard-fails-.jsonl +""" +import sys + +try: + import sys as _sys + import os as _os + _ld = _os.path.join(_os.path.dirname(_os.path.abspath(__file__)), "lib") + if _ld not in _sys.path: + _sys.path.insert(0, _ld) + from host_env import sandbox_aware_state_dir as _state_dir + from host_env import ensure_utf8_stdio +except ImportError: + import tempfile as _tf + def _state_dir(): + return _tf.gettempdir() + def ensure_utf8_stdio() -> None: + pass # lib 缺席兜底:no-op(既有降级语义) +import os +import json +from datetime import datetime, timedelta + +# 考古地层(公理三):失败事件同时埋进项目内 append-only 地层(/tmp 重启即失) +_LIB_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lib") +if _LIB_DIR not in sys.path: + sys.path.insert(0, _LIB_DIR) +try: + from journal import journal_append +except ImportError: # lib 缺失时地层降级关闭,不影响 /tmp 探测 + def journal_append(*a, **k): + return False + + +def session_id(): + return ( + os.environ.get("CLAUDE_SESSION_ID") + or os.environ.get("ZCODE_SESSION_ID") + or "default" + ) + + +def state_path(): + return os.path.join(_state_dir(), f"regress-guard-fails-{session_id()}.jsonl") + + +def normalize_sig(tool, tool_input): + """归一化签名:Bash 取命令前两段;Edit/Write 取文件路径。""" + if not isinstance(tool_input, dict): + return "?" + if tool == "Bash": + cmd = (tool_input.get("command") or "").strip() + parts = cmd.split() + return " ".join(parts[:2])[:60] if parts else "?" + fp = tool_input.get("file_path") or tool_input.get("path") or "?" + return str(fp)[:120] + + +def main(): + ensure_utf8_stdio() # 批191:cp936 流垫片(emoji/中文输出根治) + raw = sys.stdin.read() if not sys.stdin.isatty() else "" + if not raw: + sys.exit(0) + try: + data = json.loads(raw) + except json.JSONDecodeError: + sys.exit(0) + if not isinstance(data, dict): + # 批135:形态破坏不记事件(观测钩子,非阻断)——防 .get AttributeError + sys.exit(0) + + # 批167 补:会话身份同吸收(dsh 桥无 env——否则观测状态落 default 键跨会话合并) + try: + from host_env import absorb_session + absorb_session(data) + except ImportError: + pass + + # 批166(dsh 桥):工具名规范化(dsh 小写名→CC 形态,观测不漏采) + try: + from host_env import canonicalize_tool_names, materialize_tool_input + canonicalize_tool_names(data) + materialize_tool_input(data) + except ImportError: + pass + + # 批167(dsh 补位):ZCode 靠宿主只在失败时调本钩(PostToolUseFailure + # 事件);dsh 无此桥点,hooks.cc 生成时把本钩复挂到 PostToolUse—— + # 载荷带 tool_response 时自筛失败(dsh 归一化契约:bash 非零=文本含 + # [exit code: N](N>0);fs/工具错误=Error: 前缀)。无 tool_response + # 键=宿主已筛(ZCode 语义)直通。 + resp = data.get("tool_response") + if isinstance(resp, str): + import re as _re + failed = bool(_re.search(r"\[exit code: [1-9]\d*\]", resp)) \ + or resp.lstrip().startswith("Error:") + if not failed: + sys.exit(0) + + tool = data.get("tool_name", "?") + tool_input = data.get("tool_input", data) + event = { + "ts": datetime.now().isoformat(), + "tool": tool, + "sig": normalize_sig(tool, tool_input), + } + try: + with open(state_path(), "a", encoding="utf-8") as f: + f.write(json.dumps(event, ensure_ascii=False) + "\n") + except (IOError, OSError): + pass + journal_append("tool_fail", tool=tool, sig=event["sig"]) # 化石入地层 + sys.exit(0) + + +def _parse_ts(val): + """容错解析时间戳:统一剥时区(aware/naive 混比会 TypeError,静默杀探测器)。""" + try: + dt = datetime.fromisoformat(val) + return dt.replace(tzinfo=None) if dt.tzinfo else dt + except (ValueError, TypeError, AttributeError): + return None + + +def recent_failures(window_min=10): + """读近 window_min 分钟的失败事件(供 reflection_check 调用)。""" + path = state_path() + events = [] + try: + with open(path, encoding="utf-8") as f: + for line in f: + try: + events.append(json.loads(line)) + except json.JSONDecodeError: + continue + except (IOError, OSError): + return [] + cutoff = datetime.now() - timedelta(minutes=window_min) + recent = [] + for e in events: + ts = _parse_ts(e.get("ts")) + if ts is not None and ts >= cutoff: + recent.append(e) + # 顺手清理过期文件内容(超过窗口的截掉,防无限增长) + # P2#22:temp+os.replace 原子截断——旧 open("w") 与并发 append 交错时 + # 新失败事件被静默删掉,反思层低估失败数、卡死检测漏报 + if len(recent) < len(events): + try: + tmp = path + ".trim.tmp" + with open(tmp, "w", encoding="utf-8") as f: + for e in recent: + f.write(json.dumps(e, ensure_ascii=False) + "\n") + os.replace(tmp, path) + except (IOError, OSError): + pass + return recent + + +if __name__ == "__main__": + try: + main() + except SystemExit: + raise + except Exception as e: + # 观测面兜底(批135):失败信号采集是增强不是依赖——崩了干净跳过 + print(f"fail_watch: 采集异常(跳过本轮){type(e).__name__}: {e}", + file=sys.stderr) + sys.exit(0) diff --git a/external_plugins/regress-guard/hooks/scripts/launcher.js b/external_plugins/regress-guard/hooks/scripts/launcher.js new file mode 100644 index 00000000..277389bc --- /dev/null +++ b/external_plugins/regress-guard/hooks/scripts/launcher.js @@ -0,0 +1,145 @@ +#!/usr/bin/env node +/** + * launcher.js — 跨平台 hook 启动器(批191 泛化:全钩子统一入口)。 + * + * ZCode 的 hook command 串在 Windows 宿主下走 cmd 语义,python3 不存在 + * (通常是 python 或 py),所以用 Node(ZCode 必有)作为入口,由它找到 + * 正确的 Python 解释器并转发。 + * + * 用法(批191 起全部钩子走此形态): + * node launcher.js [args...] + * 无参默认 pre_commit_guard.py(向后兼容既有已装配置与 dsh 桥)。 + * + * hook.json 配置形态: + * { "type": "command", + * "command": "node \"${ZCODE_PLUGIN_ROOT}/hooks/scripts/launcher.js\" boundary_guard.py" } + * + * 布局自愈:目标脚本按三种部署布局解析——源码态(__dirname/