Author SHA1 Message Date
weichen 651a2e1e03 Merge pull request 'Adopt the shared governance kit (development-governance-v2)' (#12) from feature/governance-adoption into main
ci / docker-checkout-hostgateway (push) Successful in 0s
governance / governance (push) Successful in 14s
ci / vet-and-test (push) Successful in 18s
2026-10-17 09:34:32 +00:00
weichen 660992ab10 Close and archive the governance adoption card
ci / docker-checkout-hostgateway (pull_request) Successful in 1s
governance / governance (pull_request) Successful in 21s
ci / vet-and-test (pull_request) Successful in 24s
Why: the adoption PR is reviewed and its candidate HEAD passed CI; the card closure is folded into the PR so no separate close PR is needed.
Evidence: CI GATE on 6ca6299b39406dd12f21e80462524ba0a9012517; independent Review REPLAY (cursor-agent-stable --mode ask 全新只读会话(fresh_session=true,session id not-observable)闭合重审 VERDICT: APPROVE(对象 6ca6299)).
Impact: task card only (moved to .task/archive/).
Refs: https://git-home.u1680832.nyat.app:30319/weichen/Agent-Intent/pulls/12
2026-10-17 09:31:05 +00:00
weichen 6ca6299b39 docs(task): record review repair run and evidence
ci / docker-checkout-hostgateway (pull_request) Successful in 0s
governance / governance (pull_request) Successful in 18s
ci / vet-and-test (pull_request) Successful in 21s
Why: independent Review blocked the adoption; the repair unit ran through the shared executor and was integrated with agent-exec apply.
Evidence: .task/evidence/governance-adoption/ (SELF): governance check and agent-exec validate on the repaired head.
Impact: task card and evidence logs only.
Refs: .task/feature-governance-adoption.md
2026-10-17 09:21:23 +00:00
agent-exec-transport 0f8e2de85f agent-exec transport 7ab07317ac5d7bc9c5429b0293bab22d/4bbb2a8762ef 2026-10-17 08:59:10 +00:00
weichen c66996e483 docs(task): complete the card and record repair-r2 blocked
ci / docker-checkout-hostgateway (pull_request) Successful in 1s
governance / governance (pull_request) Successful in 16s
ci / vet-and-test (pull_request) Successful in 19s
2026-10-17 08:58:27 +00:00
weichen 8d54bed798 docs(task): record review repair run and evidence
ci / docker-checkout-hostgateway (pull_request) Successful in 1s
governance / governance (pull_request) Successful in 22s
ci / vet-and-test (pull_request) Successful in 25s
Why: independent Review blocked the adoption; the repair unit ran through the shared executor and was integrated with agent-exec apply.
Evidence: .task/evidence/governance-adoption/ (SELF): governance check and agent-exec validate on the repaired head.
Impact: task card and evidence logs only.
Refs: .task/feature-governance-adoption.md
2026-10-17 08:36:54 +00:00
agent-exec-transport 511b8142b7 agent-exec transport 18b6c1a0ed84e5967f252879e75d05d4/5d6475d09de1 2026-10-17 08:35:53 +00:00
weichen 8895b3c877 Record the adoption unit run and positive/negative evidence
ci / docker-checkout-hostgateway (pull_request) Successful in 2s
governance / governance (pull_request) Successful in 42s
ci / vet-and-test (pull_request) Successful in 42s
Why: Phase 6 requires one positive executor run and the negative cases (missing config, bad receipt, out-of-scope path, unknown side effect) before adoption becomes active.
Evidence: .task/evidence/governance-adoption/ (SELF): governance check, agent-exec validate and adoption cases; this commit passed the generated pre-commit hook (admit).
Impact: task card and evidence logs only.
Refs: .task/feature-governance-adoption.md
2026-10-17 07:38:21 +00:00
agent-exec-transport 57f92f919f agent-exec transport 73a75d97d4a11ad672c195da4806208f/c172f14cd48b 2026-10-17 07:20:34 +00:00
weichen 0e0e815f45 Bootstrap AGENT-EXECUTION.md and the governance adoption card
Why: development-governance-v2 Phase 6 adopts the shared governance kit in every formal repository; the executor needs a valid AGENT-EXECUTION.md before any implementation unit can run.
Evidence: generated by dm-server governance init at Development-Manager bd3a7de57a3a9e1a4495b94c5fa5c08d71e7be8c; dm-server agent-exec validate passes.
Impact: configuration and task card only; no code or CI change yet.
Refs: Agent-System-Dev docs/superpowers/plans/2026-10-16-development-governance-v2-master.md
2026-10-17 07:17:39 +00:00
weichen 343b1e6d9a Merge pull request 'Add precision and recall metrics to the multi-turn evaluation (I2)' (#10) from feature/agent-stack-i2 into main
ci / vet-and-test (push) Successful in 22s
ci / docker-checkout-hostgateway (push) Successful in 1s
2026-10-07 11:05:58 +00:00
weichen ab55b54e9b Merge origin/main into the I2 branch
ci / vet-and-test (pull_request) Successful in 17s
ci / docker-checkout-hostgateway (pull_request) Successful in 1s
2026-10-07 11:05:28 +00:00
weichen db79b535ec Add precision and recall metrics to the multi-turn evaluation
ci / vet-and-test (pull_request) Successful in 16s
ci / docker-checkout-hostgateway (pull_request) Successful in 0s
Why: I2 acceptance - the evaluator must emit metrics over a
multiturn fixture no smaller than the existing corpus, not just
print branch names.

Evidence: go vet clean; full suite ok; intentctl eval prints
precision=1.000 recall=1.000 (TP=12 FP=0 FN=0) over the 11-case
multiturn fixture; MetricsOf unit test pins TP/FP/FN counting
including wrong-match counting as both.

Impact: CorpusMetrics + MetricsOf (expectation_hit counts as a
hit); intentctl eval prints the metrics line after the summary;
corpus_multiturn.json expanded to 11 cases (chained expectation,
tie-break, ambiguous, fallback, handler action, clarify paths).

Refs: Documents proposal 2026-10-07-agent-stack-n1q1i1-plans.md I2
2026-10-07 11:02:28 +00:00
weichen fcf9ec4b2a Merge pull request 'Add the semantic rerank port, examples, and the command generator port (I2)' (#9) from feature/agent-stack-i2 into main
ci / vet-and-test (push) Successful in 14s
ci / docker-checkout-hostgateway (push) Successful in 1s
2026-10-07 10:57:49 +00:00
weichen 89a43abf87 Add the semantic rerank port, examples, and the command generator port
ci / vet-and-test (pull_request) Successful in 15s
ci / docker-checkout-hostgateway (pull_request) Successful in 1s
Why: I2 layering - between rules and the L2 classifier there is now
a deterministic host-scored semantic layer (rules then semantic then
model), and fallback can be rescued by a validated command
generator instead of going straight to the default channel.

Evidence: go vet clean; full suite ok, including ten new tests
(rerank resolve/below-threshold/tie/error-then-L2, generator
valid/invalid/error, catalog top-k).

Impact: IntentSpec gains Examples; Options gains Semantic (with
threshold), Generator; ambiguous candidates rerank through host
scores before L2; fallback validates generated commands
(action/slots/kind fail-closed) and synthesizes Top with slots;
generated commands are no longer overwritten by deriveCommands;
CatalogTopK by priority.

Refs: Documents proposal 2026-10-07-agent-stack-n1q1i1-plans.md I2
2026-10-07 10:56:38 +00:00
weichen f57645c645 Merge pull request 'Add the multi-turn eval format and self-describing corpora (I1 WP6)' (#8) from feature/agent-stack-i1 into main
ci / vet-and-test (push) Successful in 16s
ci / docker-checkout-hostgateway (push) Successful in 1s
2026-10-07 08:22:33 +00:00
32 changed files with 1374 additions and 35 deletions
+150
View File
@@ -0,0 +1,150 @@
name: governance
on:
pull_request:
push:
branches: [main]
jobs:
governance:
runs-on: ubuntu-latest
env:
GOPROXY: https://goproxy.cn,direct
steps:
- name: Checkout
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN }}
run: |
set -euo pipefail
# 只物化两个治理 gitlink(Guidelines 与 deps/development-manager),非递归;
# 其它 gitlink 不物化、不取回。凭据只以 URL 限定的 extraheader 存在于本进程树内。
python3 - <<'PY_CHECKOUT'
import os
from pathlib import Path
import re
import subprocess
import sys
from urllib.parse import urlsplit
# 服务端基址取运行器注入的 GITHUB_SERVER_URL(平台控制、候选 PR 不可影响)。
SERVER = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
parsed_server = urlsplit(SERVER)
if parsed_server.scheme not in {"http", "https"} or not parsed_server.netloc:
sys.exit("Checkout server must be the platform-injected http(s) URL")
SSH = "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/"
# 审查过的白名单,独立于候选 .gitmodules:仅列出两个治理 gitlink。
REPOSITORIES = {
"deps/development-guidelines": "Development-Guidelines",
"deps/development-manager": "Development-Manager",
}
TARGET = "weichen/Agent-Intent"
root_token = os.environ.pop("TOKEN", "")
component_token = os.environ.pop("SUBMODULE_TOKEN", "")
if not root_token or not component_token:
sys.exit("Checkout requires TOKEN and SUBMODULE_TOKEN")
if os.environ.get("GITHUB_REPOSITORY") != TARGET:
sys.exit("Checkout repository is outside the reviewed allowlist")
sha = os.environ.get("GITHUB_SHA", "")
if not re.fullmatch(r"[0-9a-fA-F]{40}", sha):
sys.exit("Checkout requires a fixed commit SHA")
# 丢弃继承的 Git 追踪/配置/SSH 钩子;凭据只存在于单个 Git 进程树的精确 URL 头中。
clean_env = {k: v for k, v in os.environ.items() if not k.startswith("GIT_")}
clean_env.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_SYSTEM="/dev/null",
GIT_CONFIG_GLOBAL="/dev/null", GIT_TERMINAL_PROMPT="0")
config = {
"core.hooksPath": "/dev/null", "core.fsmonitor": "false",
"init.templateDir": "", "credential.helper": "",
"fetch.recurseSubmodules": "false", "submodule.recurse": "false",
"http.followRedirects": "false", "protocol.allow": "never",
"protocol." + urlsplit(SERVER).scheme + ".allow": "always",
}
def git(repo, *args, extra=None):
values = config | (extra or {})
env = clean_env | {"GIT_CONFIG_COUNT": str(len(values))}
for index, (key, value) in enumerate(values.items()):
env[f"GIT_CONFIG_KEY_{index}"] = key
env[f"GIT_CONFIG_VALUE_{index}"] = value
return subprocess.run(["git", "-C", str(repo), *args], env=env,
check=True, stdout=subprocess.PIPE).stdout
def materialize_governance(root):
# -r 列出任意深度的 gitlink,但不进入 submodule 内部;其它 gitlink 只记录、从不取回。
links = {}
for entry in git(root, "ls-tree", "-rz", "HEAD").split(b"\0"):
if not entry:
continue
metadata, path = entry.split(b"\t", 1)
mode, kind, commit = metadata.decode().split()
if mode == "160000":
links[path.decode()] = commit
for name in REPOSITORIES:
if name not in links:
raise ValueError("Governance gitlink is missing from the fixed commit")
raw = git(root, "config", "--no-includes", "--blob", "HEAD:.gitmodules", "--null", "--list")
entries = {}
for item in raw.split(b"\0"):
if not item:
continue
key, value = item.decode().split("\n", 1)
match = re.fullmatch(r"submodule\.(.+)\.(path|url)", key)
if not match:
continue
name, field = match.groups()
if name not in REPOSITORIES:
continue
if field in entries.setdefault(name, {}):
raise ValueError("Duplicate candidate .gitmodules setting")
entries[name][field] = value
# 先整体校验两个治理条目,再进行任何带凭据的取回。
for name, repository in REPOSITORIES.items():
fields = entries.get(name, {})
if fields.get("path") != name:
raise ValueError("Governance submodule path is outside the reviewed allowlist")
trusted_url = f"{SERVER}/weichen/{repository}.git"
if fields.get("url") not in {SSH + repository + ".git", trusted_url}:
raise ValueError("Governance submodule URL is outside the reviewed allowlist")
for name, repository in REPOSITORIES.items():
url = f"{SERVER}/weichen/{repository}.git"
# 仅持久化审查过的公开 URL,使 Git 在进程级配置消失后仍识别该 submodule。
git(root, "config", f"submodule.{name}.url", url)
git(root, "submodule", "update", "--init", "--checkout", "--", name,
extra={f"submodule.{name}.url": url,
f"submodule.{name}.update": "checkout",
f"http.{url}.extraheader": "AUTHORIZATION: token " + component_token})
child = root / name
if git(child, "rev-parse", "HEAD").decode().strip() != links[name]:
raise ValueError("Materialized governance gitlink does not match the fixed SHA")
try:
root = Path.cwd()
# 必须是干净的临时工作区;不复用既有 Git 状态。
if (root / ".git").exists():
raise ValueError("Checkout requires a fresh workspace")
git(root, "init", "-q")
url = f"{SERVER}/{TARGET}.git"
git(root, "remote", "add", "origin", url)
git(root, "fetch", "--depth=1", "origin", sha,
extra={f"http.{url}.extraheader": "AUTHORIZATION: token " + root_token})
git(root, "checkout", "-q", "--detach", "FETCH_HEAD")
materialize_governance(root)
except (ValueError, OSError, subprocess.CalledProcessError) as error:
sys.exit(f"Checkout refused: {error}")
print("Checkout complete; credentials were not persisted in Git configuration")
PY_CHECKOUT
unset TOKEN SUBMODULE_TOKEN
- name: Install Go 1.27.1
run: |
set -euo pipefail
curl -fsSL -o "$RUNNER_TEMP/go.tgz" https://mirrors.aliyun.com/golang/go1.27.1.linux-amd64.tar.gz
tar -C /usr/local -xzf "$RUNNER_TEMP/go.tgz"
ln -sfn /usr/local/go/bin/go /usr/local/bin/go
ln -sfn /usr/local/go/bin/gofmt /usr/local/bin/gofmt
- name: Validate agent execution configuration
run: scripts/agent-exec validate --repo .
- name: Governance check
run: scripts/dm governance check --repo .
+3
View File
@@ -7,3 +7,6 @@
[submodule "deps/logging-system"] [submodule "deps/logging-system"]
path = deps/logging-system path = deps/logging-system
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Logging-System.git url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Logging-System.git
[submodule "deps/development-manager"]
path = deps/development-manager
url = ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Development-Manager.git
@@ -0,0 +1,61 @@
# Agent-Intent 采纳 development-governance-v2 治理套件 任务卡
任务主记录:[Agent-Intent PR #12](https://git-home.u1680832.nyat.app:30319/weichen/Agent-Intent/pulls/12)(本文件随 PR 描述维护)。
状态:已完成(闭合随本 PR 合入;合入以平台记录为准);owner:weichen。
## 基线与授权
- 权威仓:weichen/Agent-Intent;root_target:main;immediate_target:main
- branch_kind:initiative;initiative_id:governance-adoption-Agent-Intent
- R0(initial_main_sha):343b1e6d9a5b58e47bf7e594826221036ae8175c;工作区:/home/weichen/projects/Agent-Intent-wt-feature-governance-adoption(worktree);分支:feature/governance-adoption
- 父任务信息(仅子任务填写):无
- accepting_requirements:false(单仓采纳 initiative,范围固定为下列十步,不接收新需求)
- 需求分流:verdict=NEW;selected_initiative=无;候选=无;理由=development-governance-v2 Phase 6 每个正式仓库独立 initiative;triage 收据=无(本仓采纳前尚无 triage 入口)
- 授权与交付终点:owner 已授权 development-governance-v2 全部实施(Agent-System-Dev 主记录 docs/superpowers/plans/2026-10-16-development-governance-v2-master.md);终点为本 PR 合入 main 且采纳状态 active。分支保护与 tag 由用户执行。
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md;外部 Agent 一单元一新进程/会话/工作区)
## 目标与非目标
目标(Phase 6 十步):固定 Guidelines SHA(deps/development-guidelines @ 2688fd2be6a52c0bf110e7e6366b6f291ab5e4eb);增加 AGENT-EXECUTION.md;增加共享执行器薄入口(deps/development-manager @ bd3a7de57a3a9e1a4495b94c5fa5c08d71e7be8c + scripts/dm、scripts/agent-exec、scripts/task);四条路由(implementation/repair/test/review);任务卡模板与分支元数据;CI 中启用 schema 校验(.gitea/workflows/governance.yml:agent-exec validate + governance check);一次正例运行;负例(缺配置、坏收据、越界路径、未知副作用);独立 Review + CI;采纳状态 active。
非目标:不改业务代码与既有测试;不改分支保护(由用户执行);不发布 tag。
引导说明:AGENT-EXECUTION.md 由 host 以固定版本治理套件(dm-server governance init @ bd3a7de57a3a9e1a4495b94c5fa5c08d71e7be8c)生成并作为引导配置提交(执行器运行前必须存在有效配置;沿用 DM 采纳先例);其余文件由实施单元生成。
## Agent 实施收据
| 单元 ID | 预期范围 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|
| governance-adoption-Agent-Intent-r1 | 13 个允许路径(治理文件 + 两个 gitlink) | 0e0e815f | implementation | cursor→antigravity→pi | run 73a75d97(dm-server pinned,attempts=1) | cursor / not-observable | success | 57f92f919f5a(apply 快进) | 否 | /tmp/gov-receipt-Agent-Intent-r1.json |
| governance-adoption-Agent-Intent-repair-r1 | PROJECT.md、policy/guidelines-adoption.md | repair | cursor→antigravity→pi | run 18b6c1a0(attempts=1) | cursor / not-observable | success | 511b8142b7e6(apply 快进) | 否 | /tmp/gov-receipt-Agent-Intent-repair-r1.json |
| governance-adoption-Agent-Intent-repair-r2 | AGENT-EXECUTION.md、policy/guidelines-adoption.md | repair | cursor→antigravity→pi | run(attempts=1) | cursor / not-observable | blocked(worker 正确指出卡片证据表尚无 GATE 行,引用将悬空;对账:本 worktree 干净、无 transport;先补卡片 GATE 行后以全新 r3 重跑) | 无 | 否 | /tmp/gov-receipt-Agent-Intent-repair-r2.json |
| governance-adoption-Agent-Intent-repair-r3 | AGENT-EXECUTION.md、policy/guidelines-adoption.md | repair | cursor→antigravity→pi | run 7ab07317(attempts=1) | cursor / not-observable | success | 0f8e2de85f55(apply 快进) | 否 | /tmp/gov-receipt-Agent-Intent-repair-r3.json |
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at | 结果 |
|---|---|---|---|---|
| 启动 | 343b1e6d9a5b58e47bf7e594826221036ae8175c | 343b1e6d9a5b58e47bf7e594826221036ae8175c | 2026-10-17T07:17:39Z | 一致 |
| 请求审核 | 343b1e6d9a5b58e47bf7e594826221036ae8175c | 8d54bed79833 | 2026-10-17T08:58:27Z | 已含 origin/main |
## 结果与验证(完成后填写)
- 结果:治理套件已采纳——Guidelines 与 Development-Manager gitlink 固定、AGENT-EXECUTION.md 四条路由、共享执行器薄入口、pre-commit admit、governance CI;采纳状态 active 随本 PR 合入生效。
- 验证:见下表(命令输出由 /tmp/gov-neg/ev.sh 写入 .task/evidence/governance-adoption/,SELF 除非另注)。
- 复现:正负例脚本冻结副本 Agent-System-Dev `docs/superpowers/plans/evidence/governance-adopt-cases.py`(sha256 3aa1bf6ea1cd48f3f59c0a307620e4112389a93fe21a113006f54e62eb8f0d06),用法 `python3 governance-adopt-cases.py <仓库根>`;它经本仓 `scripts/agent-exec` 运行假 CLI,不访问网络。
- 影响与剩余项:不改业务代码;本地启用钩子需由人执行 `git config core.hooksPath scripts/hooks`;将 governance 检查设为分支保护必需项属用户双钥匙事项(命令见 Agent-System-Dev 治理主记录)。
| 时间(UTC) | 命令 | 被测 HEAD | exit | 来源 | 摘要/位置 |
|---|---|---|---|---|---|
| 20261017T073820Z | `scripts/dm governance check --repo .` | 57f92f919f5a | 0 | SELF | ok fixtures;[输出](../evidence/governance-adoption/00-20261017T073820Z.log) sha256:1141eb0063f8ad39 |
| 20261017T073820Z | `scripts/agent-exec validate --repo .` | 57f92f919f5a | 0 | SELF | validate: ok;[输出](../evidence/governance-adoption/01-20261017T073820Z.log) sha256:19b56ec1b547dd80 |
| 20261017T073820Z | `python3 /tmp/gov-neg/adopt_cases.py .` | 57f92f919f5a | 0 | SELF | [verdict] ALL PASS cases=5;[输出](../evidence/governance-adoption/02-20261017T073820Z.log) sha256:acdf4ab65c895249 |
| 20261017T083653Z | `scripts/dm governance check --repo .` | 511b8142b7e6 | 0 | SELF | ok fixtures;[输出](../evidence/governance-adoption/03-20261017T083653Z.log) sha256:7040baf88f4510c1 |
| 20261017T083653Z | `scripts/agent-exec validate --repo .` | 511b8142b7e6 | 0 | SELF | validate: ok;[输出](../evidence/governance-adoption/04-20261017T083653Z.log) sha256:ceff66715e93873c |
| 2026-10-17T08:58:27Z | CI `ci / docker-checkout-hostgateway (pull_request)` | 8d54bed79833 | 0 | GATE | success;[运行](https://git-home.u1680832.nyat.app:30319/weichen/Agent-Intent/actions/runs/3588/jobs/5676) |
| 2026-10-17T08:58:27Z | CI `governance / governance (pull_request)` | 8d54bed79833 | 0 | GATE | success;[运行](https://git-home.u1680832.nyat.app:30319/weichen/Agent-Intent/actions/runs/3589/jobs/5677) |
| 2026-10-17T08:58:27Z | CI `ci / vet-and-test (pull_request)` | 8d54bed79833 | 0 | GATE | success;[运行](https://git-home.u1680832.nyat.app:30319/weichen/Agent-Intent/actions/runs/3588/jobs/5675) |
| 20261017T092123Z | `scripts/dm governance check --repo .` | 0f8e2de85f55 | 0 | SELF | ok fixtures;[输出](../evidence/governance-adoption/05-20261017T092123Z.log) sha256:e10c752533fee0f8 |
| 20261017T092123Z | `scripts/agent-exec validate --repo .` | 0f8e2de85f55 | 0 | SELF | validate: ok;[输出](../evidence/governance-adoption/06-20261017T092123Z.log) sha256:6e46dd574cf74ee4 |
| — | 候选 HEAD CI | 6ca6299b3940 | 0 | GATE | success ci / docker-checkout-hostgateway (pull_request)=success(/weichen/Agent-Intent/actions/runs/3633/jobs/5738);governance / governance (pull_request)=success(/weichen/Agent-Intent/actions/runs/3635/jobs/5740);ci / vet-and-test (pull_request)=success(/weichen/Agent-Intent/actions/runs/3633/jobs/5737) |
| — | 独立 Review | 6ca6299b3940 | 0 | REPLAY | cursor-agent-stable --mode ask 全新只读会话(fresh_session=true,session id not-observable)闭合重审 VERDICT: APPROVE(对象 6ca6299) |
@@ -0,0 +1,19 @@
command: scripts/dm governance check --repo .
cwd: /home/weichen/projects/Agent-Intent-wt-feature-governance-adoption
head: 57f92f919f5a38f83031e3810bd5573143b97789
exit: 0
utc: 20261017T073820Z
---
ok gitlink deps/development-guidelines
ok gitlink deps/development-manager
ok agent-execution
ok file AGENTS.md
ok file PROJECT.md
ok file policy/guidelines-adoption.md
ok file .task/templates/task-card.md
ok executable scripts/agent-exec
ok executable scripts/hooks/pre-commit
ok executable scripts/dm
ok executable scripts/task
ok adoption status
ok fixtures
@@ -0,0 +1,7 @@
command: scripts/agent-exec validate --repo .
cwd: /home/weichen/projects/Agent-Intent-wt-feature-governance-adoption
head: 57f92f919f5a38f83031e3810bd5573143b97789
exit: 0
utc: 20261017T073820Z
---
validate: ok
@@ -0,0 +1,13 @@
command: python3 /tmp/gov-neg/adopt_cases.py .
cwd: /home/weichen/projects/Agent-Intent-wt-feature-governance-adoption
head: 57f92f919f5a38f83031e3810bd5573143b97789
exit: 0
utc: 20261017T073820Z
---
[object] Agent-Intent-wt-feature-governance-adoption HEAD=57f92f919f5a38f83031e3810bd5573143b97789
PASS positive-run-apply: run exit=0 terminal=success apply exit=0 head=0bdcc997d3ee
PASS missing-config: run exit=2 terminal=invalid-config validate exit=2
PASS bad-receipt: apply exit=4 head_unchanged=True msg=apply: no local integrity record: changed paths do not match the transport diff
PASS out-of-scope-path: run exit=5 terminal=scope-expansion transport=False
PASS unknown-side-effect: run exit=4 terminal=failed-unknown transport=False
[verdict] ALL PASS cases=5
@@ -0,0 +1,19 @@
command: scripts/dm governance check --repo .
cwd: /home/weichen/projects/Agent-Intent-wt-feature-governance-adoption
head: 511b8142b7e6c2b62a323c08342ca84dbe06ea88
exit: 0
utc: 20261017T083653Z
---
ok gitlink deps/development-guidelines
ok gitlink deps/development-manager
ok agent-execution
ok file AGENTS.md
ok file PROJECT.md
ok file policy/guidelines-adoption.md
ok file .task/templates/task-card.md
ok executable scripts/agent-exec
ok executable scripts/hooks/pre-commit
ok executable scripts/dm
ok executable scripts/task
ok adoption status
ok fixtures
@@ -0,0 +1,7 @@
command: scripts/agent-exec validate --repo .
cwd: /home/weichen/projects/Agent-Intent-wt-feature-governance-adoption
head: 511b8142b7e6c2b62a323c08342ca84dbe06ea88
exit: 0
utc: 20261017T083653Z
---
validate: ok
@@ -0,0 +1,19 @@
command: scripts/dm governance check --repo .
cwd: /home/weichen/projects/Agent-Intent-wt-feature-governance-adoption
head: 0f8e2de85f559a23ee69db402afaf7b69d38c8c4
exit: 0
utc: 20261017T092123Z
---
ok gitlink deps/development-guidelines
ok gitlink deps/development-manager
ok agent-execution
ok file AGENTS.md
ok file PROJECT.md
ok file policy/guidelines-adoption.md
ok file .task/templates/task-card.md
ok executable scripts/agent-exec
ok executable scripts/hooks/pre-commit
ok executable scripts/dm
ok executable scripts/task
ok adoption status
ok fixtures
@@ -0,0 +1,7 @@
command: scripts/agent-exec validate --repo .
cwd: /home/weichen/projects/Agent-Intent-wt-feature-governance-adoption
head: 0f8e2de85f559a23ee69db402afaf7b69d38c8c4
exit: 0
utc: 20261017T092123Z
---
validate: ok
+66
View File
@@ -0,0 +1,66 @@
# <任务标题> 任务卡
任务主记录:<Issue/PR 链接,或注明"本文件随 PR 描述维护">。
状态:<进行中 / 待审核 / 已合入 / 已放弃>;owner:<填写>。
## 基线与授权
- 权威仓:<仓 URL 或逻辑 ID>;root_target:main;immediate_target:<main 或 feature/initiative>
- branch_kind:<initiative | child | standalone>;initiative_id:<稳定 ID,普通任务为空>
- R0(initial_main_sha):<完整 SHA>;工作区:<路径>(worktree / clone);分支:<feature|fix|hotfix/...>
- 父任务信息(仅子任务填写):parent_branch=<父分支>;parent_head_sha=<SHA>;parent_pr=<父 PR>
- accepting_requirements:<true | false>(仅 initiative 可为 true;冻结后为 false)
- 需求分流:verdict=<JOIN | NEW>;selected_initiative=<分支或无>;候选=<分支@HEAD 列表或无>;理由=<不适配/适配依据>;triage 收据=<源码外路径与摘要>
- 授权与交付终点:<本次允许的动作与终点;未授权事项写明>
- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md;外部 Agent 一单元一新进程/会话/工作区)
## 目标与非目标
<预期结果、受影响入口/模块/契约;明确不做什么。>
## Agent 实施收据
按根目录 AGENT-EXECUTION.md 路由;外部 Agent 一单元一新进程/会话/隔离工作区。实施、修复与测试必须通过登记的候选执行;未配置或候选安全耗尽停在 unavailable/blocked,禁止 host 隐式代写。
| 单元 ID | 预期范围 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 |
|---|---|---|---|---|---|---|---|---|---|
| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — |
## 复用材料(task-start 自动检索结果摘录)
- 相关近期提交:<git log -- <paths> 命中;无则写无>。
- 相关经验条目:<检索命中;无则写无>。
## 同步记录
| 节点 | sync_base_sha | tested_head_sha | checked_at | 结果 |
|---|---|---|---|---|
| 启动 | <R0> | <HEAD> | <日期时间> | <一致/已同步> |
| 验收前 | <填写> | <填写> | <填写> | <填写> |
字段没有发生的写"无",不省略。本卡与 PR 描述同源:开 PR 时将"目标与非目标、结果与验证、影响与剩余项"复制进 PR 描述,不另写第二份。
## 交付调度(有多个就绪任务时)
- 调度记录 / entry_id / 当前 attempt_id:<平台或调度服务链接及标识;未入队写无>。
- 入队源 HEAD / owner / 依赖:<完整 SHA、身份、依赖或无>。
- 固定候选引用:<调度尝试中的 base/head、组件与配置;未固定写无>。
- 窗口、最终 CI、Review 与真实合入结果:<引用调度记录及原始证据,不复制全队列>。
定义见 [合入调度与 CI 分配](../standards/merge-queue.md)。本段在候选固定前整理;固定后先使用源码之外的记录保存运行结果,再按项目闭合流程回填。
## 结果与验证(完成后填写)
- 结果:<交付了什么;未发生写无>。
- 验证:<结论性摘要:PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据见下表,来源标注与写入规则见 [按影响选择验证与证据](../standards/verification.md)>。
- 影响与剩余项:<影响面、遗留风险、后续任务>。
| 时间(UTC) | 命令 | 被测 HEAD | exit | 来源 | 摘要/位置 |
|---|---|---|---|---|---|
| <时间> | <命令;由项目 evidence 工具写入,无工具时附命令+对象 SHA+输出 digest 并标 SELF> | <HEAD> | <exit> | GATE/REPLAY/SELF | <摘要/位置> |
## 回收(task-finish)
- 合入核对:<分支已包含于目标分支的核对输出>。
- 未跟踪/ignored 文件处置:<归档/可再生/存疑 逐项;无则写无>。
- 回收动作:<worktree remove、分支删除、prune;放弃路径注明原因>。
+67
View File
@@ -0,0 +1,67 @@
# Project Agent Execution Configuration
# 项目工程 Agent 执行路由配置
适用范围:定义本项目中由 AI / Agent 主导的代码实施、修复、测试与审阅所调用的 Agent CLI 路由。
解析器识别正文中唯一一个 ```agent-execution 代码块。正文外的说明文字可根据项目需要调整。
通用硬规则见 Development-Guidelines 的 [Agent 执行与协作契约](deps/development-guidelines/standards/agent-execution.md)。
本配置为项目本地路由权威,声明具体的候选 profile、尝试次数上限、超时、集成入口与豁免路径;
代码块须通过 `deps/development-guidelines/templates/schemas/agent-execution.schema.json` 校验后才能登记为 active。
`fallback_to_host` 只能为 `never`;旧值 `all-unavailable` 仅供迁移工具读取,不能通过准入。
下例的 CLI 名称为维护主机上的实际执行器(cursor-agent-stable、agy、pi、codex);其他主机按实际执行器调整 executable。
```agent-execution
schema: agent-execution/v1
execution:
fresh_process_per_unit: true
permission: run-everything
fallback_to_host: never
max_parallel: 3
timeout_seconds: 7200
idle_timeout_seconds: 600
summary_bytes: 16384
raw_output_bytes: 10485760
max_attempts_per_profile: 3
emergency_profile: codex
roles:
implementation: [cursor, antigravity, pi]
repair: [cursor, antigravity, pi]
test: [pi, antigravity, cursor]
review: [pi, antigravity, cursor, codex]
profiles:
cursor:
adapter: cursor
executable: "cursor-agent-stable"
model: auto
enabled: true
antigravity:
adapter: antigravity
executable: agy
model: auto
enabled: true
pi:
adapter: pi
executable: pi
model: auto
enabled: true
codex:
adapter: codex
executable: codex
model: auto
enabled: true
integration:
exempt_paths: [".task/**", "docs/superpowers/specs/**", "docs/superpowers/plans/**"]
transport:
command: "scripts/agent-exec apply"
admission:
command: "scripts/agent-exec admit"
cleanup:
command: "scripts/agent-exec cleanup"
```
+18
View File
@@ -34,3 +34,21 @@
- **SSH key**:`~/.ssh/home_gitea`(ED25519,公钥指纹 - **SSH key**:`~/.ssh/home_gitea`(ED25519,公钥指纹
`SHA256:GYXaOXIMeloQ1fKSfdJ/ndgPQzs/Ix7Hk62ekfkfQ20`,Gitea 侧密钥名 GMKtec); `SHA256:GYXaOXIMeloQ1fKSfdJ/ndgPQzs/Ix7Hk62ekfkfQ20`,Gitea 侧密钥名 GMKtec);
`~/.ssh/config` 已有 `git-ssh.u1680832.nyat.app` 主机条目,克隆/推送直接可用。 `~/.ssh/config` 已有 `git-ssh.u1680832.nyat.app` 主机条目,克隆/推送直接可用。
## Agent 执行与新项目准入(强制)
1. 缺 Guidelines 固定版本(`deps/development-guidelines` gitlink 未就绪或 HEAD 不符)时,Agent 只能只读分析,不得实施。
2. 缺有效 `AGENT-EXECUTION.md`(`scripts/dm governance check` 未通过)时禁止 AI 实施。
3. 缺共享执行器(`scripts/agent-exec` 或 `deps/development-manager` 不可用)时,状态为 `blocked/unavailable`,不得改由 host 替代。
4. 不允许 host 代写实施单元;实施、修复与测试必须经 `AGENT-EXECUTION.md` 登记的候选执行。
5. 人类直接开发按其自身授权与项目规则进行,不受本条款约束。
6. 分支保护与规范 tag 由用户负责设置与维护,Agent 不得代为设置。
入口:
- 需求分流:`scripts/task triage discover|decide|verify`
- 任务启动:`scripts/task start …`
- PR 检查:`scripts/task pr-check …`
- Agent 执行:`scripts/agent-exec run|apply|admit|validate`
AI 实施单元仅通过 `scripts/agent-exec run` 运行(依据 AGENT-EXECUTION.md),并经 `scripts/agent-exec apply` 集成。
+5 -3
View File
@@ -5,15 +5,15 @@
## 仓库与操作 ## 仓库与操作
- 目标/产品边界:通用意图识别基础库。提供意图定义(spec)、注册表与配置合并(registry/loader)、四级漏斗识别器(guard → expectation → rules → adjudicate)、槽位抽取与诊断(conformance/catalogcheck/intentctl)。不做动作执行、不内置 LLM 客户端、不含任何业务意图内容,不依赖 Agent-Native / Agent-Squad / Agent-System。 - 目标/产品边界:通用意图识别基础库。提供意图定义(spec)、注册表与配置合并(registry/loader)、四级漏斗识别器(guard → expectation → rules → adjudicate)、槽位抽取与诊断(conformance/catalogcheck/intentctl)。不做动作执行、不内置 LLM 客户端、不含任何业务意图内容,不依赖 Agent-Native / Agent-Squad / Agent-System。
- 源码、资产、文档、机器契约位置:Go 源码在仓库根包目录;模块路径 `agent-intent`;人类文档在根 `README.md`、`docs/`、`policy/`;过程记录在 `docs/records/`;机器契约以 `intent` 包公开类型为准。 - 源码、资产、文档、机器契约位置:Go 源码在仓库根包目录;模块路径 `agent-intent`;人类文档在根 `README.md`、`docs/`、`policy/`;过程记录:任务卡与交付证据在 `.task/`,其他过程记录在 `docs/records/`;机器契约以 `intent` 包公开类型为准。
- 权威远端、集成基线、受保护分支:`ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Intent.git`,集成基线 `main`。 - 权威远端、集成基线、受保护分支:`ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-Intent.git`,集成基线 `main`。
- 并行共同基线记录位置、合入通知/主动检查方式:任务主记录在 Gitea Issue/PR;合入前在本仓 `main` 上 `git fetch` 并确认 HEAD。 - 并行共同基线记录位置、合入通知/主动检查方式:任务主记录在 Gitea Issue/PR;合入前在本仓 `main` 上 `git fetch` 并确认 HEAD。
- 发布线与批准版本/实际制品查询入口:当前无独立制品仓库。可构建性以本仓 `main` 的 `go test` 为准;依赖子模块版本以 gitlink 为准,不把工作区未提交文件当发布组合。 - 发布线与批准版本/实际制品查询入口:当前无独立制品仓库。可构建性以本仓 `main` 的 `go test` 为准;依赖子模块版本以 gitlink 为准,不把工作区未提交文件当发布组合。
- 提交/审核/合并/发布身份与权限:仓库所有者 weichen。本机提交不代替远端合并。 - 提交/审核/合并/发布身份与权限:仓库所有者 weichen。本机提交不代替远端合并。
- 任务唯一来源:Gitea `weichen/Agent-Intent` 的 Issue 与 Pull Request。 - 任务唯一来源:Gitea `weichen/Agent-Intent` 的 Issue 与 Pull Request。
- 规范性文档目录:`policy/`。 - 规范性文档目录:`policy/`。
- 过程文档仓/目录:`docs/records/`(项目内豁免,理由见 [采纳记录](policy/guidelines-adoption.md))。 - 过程文档仓/目录:任务卡与交付证据在 `.task/`;其他过程记录在 `docs/records/`(项目内豁免,理由见 [采纳记录](policy/guidelines-adoption.md))。
- 交接记录位置:随任务主记录;需要落盘的交接放入 `docs/records/`。 - 交接记录位置:随任务主记录;需要落盘的交接放入 `docs/records/`;任务卡与交付证据在 `.task/`。
- 凭证查找入口及所需字段:无凭证。本仓不保存密钥字段的值。 - 凭证查找入口及所需字段:无凭证。本仓不保存密钥字段的值。
## 依赖组合 ## 依赖组合
@@ -23,6 +23,7 @@
| development-guidelines | `deps/development-guidelines` | development-only,规范文本 | | development-guidelines | `deps/development-guidelines` | development-only,规范文本 |
| error-code-system | `deps/error-code-system` | build-required,规范码目录(catalogcheck 对齐 + `intent.*` 常量值) | | error-code-system | `deps/error-code-system` | build-required,规范码目录(catalogcheck 对齐 + `intent.*` 常量值) |
| logging-system | `deps/logging-system` | build-required,可选日志器 | | logging-system | `deps/logging-system` | build-required,可选日志器 |
| development-manager | `deps/development-manager` | development-only,共享 Agent 执行器与治理脚手架(`scripts/dm`、`scripts/agent-exec` 的钉住来源) |
克隆: 克隆:
@@ -44,6 +45,7 @@ git clone --recurse-submodules ssh://git@git-ssh.u1680832.nyat.app:31411/weichen
| 并发/快照隔离 | 仓库根:`GOWORK=off go test -count=1 -race ./...` | 覆盖 Reload 与识别并发 | | 并发/快照隔离 | 仓库根:`GOWORK=off go test -count=1 -race ./...` | 覆盖 Reload 与识别并发 |
| 依赖纪律 | 仓库根:`GOWORK=off go list -m all` 仅标准库 + 两个生态模块 | 不证明宿主接入正确性 | | 依赖纪律 | 仓库根:`GOWORK=off go list -m all` 仅标准库 + 两个生态模块 | 不证明宿主接入正确性 |
| 宿主行为验收 | 消费方仓内跑 `conformance.Run` | 本命令不覆盖真实宿主配置 | | 宿主行为验收 | 消费方仓内跑 `conformance.Run` | 本命令不覆盖真实宿主配置 |
| 治理与 Agent 执行配置 | 仓库根:`scripts/agent-exec validate --repo .` 与 `scripts/dm governance check --repo .` | 只校验 `AGENT-EXECUTION.md` 与治理 gitlink/文件,不证明执行器真实运行 |
## 环境与数据 ## 环境与数据
+16
View File
@@ -340,6 +340,22 @@ func cmdEval(args []string) error {
fmt.Printf("PASS %-28s %s\n", tc.Name, res.Branch) fmt.Printf("PASS %-28s %s\n", tc.Name, res.Branch)
} }
fmt.Printf("总计 %d 用例:%d 通过,%d 失败\n", len(corpus.Cases), len(corpus.Cases)-failed, failed) fmt.Printf("总计 %d 用例:%d 通过,%d 失败\n", len(corpus.Cases), len(corpus.Cases)-failed, failed)
// I2:指标输出(precision/recall 按意图标签计;确定性重跑,不计入通过判定)。
var results []intent.CaseResult
for _, tc := range corpus.Cases {
results = append(results, intent.RunCorpusCase(context.Background(), snap, tc))
}
metrics := intent.MetricsOf(corpus.Cases, results)
p, pok := metrics.Precision()
r, rok := metrics.Recall()
ps, rs := "-", "-"
if pok {
ps = fmt.Sprintf("%.3f", p)
}
if rok {
rs = fmt.Sprintf("%.3f", r)
}
fmt.Printf("指标: precision=%s recall=%s(TP=%d FP=%d FN=%d)\n", ps, rs, metrics.TP, metrics.FP, metrics.FN)
if failed > 0 { if failed > 0 {
return fmt.Errorf("%d/%d 用例失败", failed, len(corpus.Cases)) return fmt.Errorf("%d/%d 用例失败", failed, len(corpus.Cases))
} }
Vendored Submodule
+1
+24
View File
@@ -1,6 +1,8 @@
package intent package intent
import ( import (
"sort"
"fmt" "fmt"
"strings" "strings"
) )
@@ -119,3 +121,25 @@ func specSlotFormat(spec IntentSpec, key string) string {
} }
return "" return ""
} }
// CatalogTopK 取目录的前 k 条:按 Priority 降序、ID 升序(确定性排序;
// 排序口径是 priority,不是语义相关度——相关度排序由宿主经 examples 自行计算)。
// k <= 0 时等同 Catalog() 全量。
func (s *Snapshot) CatalogTopK(k int) []CatalogSpec {
all := s.Catalog()
if k <= 0 || len(all) <= k {
return all
}
// Priority 不在目录视图里;用 spec 表内的 priority 排序。
prio := map[string]int{}
for _, cs := range s.specs {
prio[cs.spec.ID] = cs.spec.Priority
}
sort.SliceStable(all, func(i, j int) bool {
if prio[all[i].ID] != prio[all[j].ID] {
return prio[all[i].ID] > prio[all[j].ID]
}
return all[i].ID < all[j].ID
})
return all[:k]
}
+70
View File
@@ -180,3 +180,73 @@ func containsString(list []string, s string) bool {
} }
return false return false
} }
// CorpusMetrics 评测指标(I2):按意图标签(matched/top)计 TP/FP/FN。
// 期望非 matched(fallback/ambiguous)的轮次作负例、不进指标。
type CorpusMetrics struct {
TP, FP, FN int
}
// Precision 精确率;分母为 0 时返回 (0, false)(未定义,由调用方决定展示)。
func (m CorpusMetrics) Precision() (float64, bool) {
if m.TP+m.FP == 0 {
return 0, false
}
return float64(m.TP) / float64(m.TP+m.FP), true
}
// Recall 召回率;分母为 0 时返回 (0, false)。
func (m CorpusMetrics) Recall() (float64, bool) {
if m.TP+m.FN == 0 {
return 0, false
}
return float64(m.TP) / float64(m.TP+m.FN), true
}
// MetricsOf 逐轮累计指标(I2):
// - TP:期望 matched/top=X 且实际 matched/top=X;
// - FP:实际 matched/top=Y 但期望的标签不是 matched/top=Y
// (期望别的 top、期望 fallback/ambiguous、或期望 matched 但 top 不同);
// - FN:期望 matched/top=X 但实际没拿到它(branch 不对或 top 不同)。
// 只有写了 expect_top 的轮次进入指标(意图识别通行口径)。
func MetricsOf(cases []CorpusCase, results []CaseResult) CorpusMetrics {
var m CorpusMetrics
byName := map[string]CorpusCase{}
for _, tc := range cases {
byName[tc.Name] = tc
}
for _, res := range results {
tc, ok := byName[res.Name]
if !ok {
continue
}
turns := tc.Turns
if len(turns) == 0 {
turns = []CorpusTurn{{ExpectBranch: tc.ExpectBranch, ExpectTop: tc.ExpectTop}}
}
for i, turn := range turns {
if turn.ExpectTop == "" || i >= len(res.Turns) {
continue
}
got := res.Turns[i]
wantMatched := turn.ExpectBranch == "" || turn.ExpectBranch == "matched" || turn.ExpectBranch == "expectation_hit"
gotMatched := got.Branch == "matched" || got.Branch == "expectation_hit"
if gotMatched && got.TopID == turn.ExpectTop && wantMatched {
m.TP++
continue
}
if gotMatched && got.TopID != "" {
// 错配同时是假阳(拿了错的)与假阴(期望的没拿到)。
m.FP++
if wantMatched {
m.FN++
}
continue
}
if wantMatched {
m.FN++
}
}
}
return m
}
+48
View File
@@ -77,3 +77,51 @@ func TestMatchCommandsFindsMismatches(t *testing.T) {
t.Fatalf("count mismatch = %v", mm) t.Fatalf("count mismatch = %v", mm)
} }
} }
// TestMetricsOfCounts:指标按意图标签计 TP/FP/FN(I2 验收的指标形态)。
func TestMetricsOfCounts(t *testing.T) {
_, snap := buildDemoRegistry(t)
data, err := os.ReadFile("testdata/corpus_multiturn.json")
if err != nil {
t.Fatal(err)
}
var doc struct {
Cases []CorpusCase `json:"cases"`
}
if err := json.Unmarshal(data, &doc); err != nil {
t.Fatal(err)
}
var results []CaseResult
for _, tc := range doc.Cases {
results = append(results, RunCorpusCase(context.Background(), snap, tc))
}
m := MetricsOf(doc.Cases, results)
// 通过的夹具:指标应当全 TP(precision=recall=1)。
if m.FP != 0 || m.FN != 0 {
t.Fatalf("metrics = %+v", m)
}
p, pok := m.Precision()
r, rok := m.Recall()
if !pok || !rok || p != 1.0 || r != 1.0 {
t.Fatalf("precision=%v recall=%v (m=%+v)", p, r, m)
}
if m.TP < 8 {
t.Fatalf("TP = %d, want >= 8(夹具内带 top 期望的轮次)", m.TP)
}
// 手工构造一个 FP+FN 用例验证计数方向。
bad := []CorpusCase{{Name: "bad", Turns: []CorpusTurn{
{Input: "查询任务 42 的进度", ExpectBranch: "matched", ExpectTop: "demo.greet.hello"},
}}}
got := []CaseResult{RunCorpusCase(context.Background(), snap, bad[0])}
m2 := MetricsOf(bad, got)
// 实际 matched status_query 但期望 greet.hello → 1 FP + 1 FN。
if m2.TP != 0 || m2.FP != 1 || m2.FN != 1 {
t.Fatalf("bad metrics = %+v", m2)
}
p2, _ := m2.Precision()
r2, _ := m2.Recall()
if p2 != 0 || r2 != 0 {
t.Fatalf("p=%v r=%v, want 0/0", p2, r2)
}
}
+17
View File
@@ -0,0 +1,17 @@
package intent
import "context"
// CommandGenerator 模型命令生成端口(I2):宿主用模型实现
// (借鉴 Rasa 的 LLM command generators:只把相关流程交给生成器)。
// 只在规则与语义都未命中(fallback)时被调用;实现自担延迟与成本。
// 返回错误时识别保持 fallback,不会使 Recognize 失败。
//
// 库侧校验与 deriveCommands 同款:start 命令的 ActionRef 必须可解析——
// auto/command/workflow/blueprint 的目标必须在 catalog 的 spec ID 集合内,
// handler 的目标必须在 catalog 的 Handler 集合内;槽位键必须在目标 spec 的
// 槽位键集合内(可解析时)。生成器幻觉一律 fail-closed 回 fallback。
type CommandGenerator interface {
// Generate 基于输入与能力目录产出命令列表(catalog 由调用方按 top-k 给)。
Generate(ctx context.Context, input string, catalog []CatalogSpec) ([]Command, error)
}
+234
View File
@@ -0,0 +1,234 @@
package intent
import (
"context"
"errors"
"strings"
"testing"
)
// fakeScorer 按 examples 里是否包含特定子串给分(测试用确定性打分器)。
type fakeScorer struct {
scores map[string]float64 // key: examples[0]
err error
}
func (f fakeScorer) Score(ctx context.Context, input string, examples []string) (float64, error) {
if f.err != nil {
return 0, f.err
}
if len(examples) == 0 {
return 0, nil
}
if s, ok := f.scores[examples[0]]; ok {
return s, nil
}
return 0, nil
}
func ambiguousFixture(t *testing.T, exA, exB []string) *Snapshot {
t.Helper()
b := NewRegistryBuilder(Options{})
if err := b.Register(IntentSpec{ID: "demo.alpha", Domain: "demo", Version: 1, Priority: 5,
Matcher: MatchSpec{IncludesAny: []string{"模糊词"}}, Examples: exA}); err != nil {
t.Fatal(err)
}
if err := b.Register(IntentSpec{ID: "demo.beta", Domain: "demo", Version: 1, Priority: 5,
Matcher: MatchSpec{IncludesAny: []string{"模糊词"}}, Examples: exB}); err != nil {
t.Fatal(err)
}
reg, err := b.Build()
if err != nil {
t.Fatal(err)
}
return reg.Snapshot()
}
// TestSemanticRerankResolvesAmbiguous:语义过阈且分差够 → matched(I2 分层)。
func TestSemanticRerankResolvesAmbiguous(t *testing.T) {
snap := ambiguousFixture(t, []string{"查运行进度"}, []string{"闲聊问候"})
rec := NewRecognizer(Options{Semantic: fakeScorer{scores: map[string]float64{"查运行进度": 0.95, "闲聊问候": 0.4}}}, nil, nil)
res, err := rec.Recognize(context.Background(), "模糊词", snap)
if err != nil {
t.Fatal(err)
}
if res.Branch != BranchMatched || res.Top == nil || res.Top.ID != "demo.alpha" {
t.Fatalf("semantic rerank: %+v", res)
}
if !strings.Contains(res.Top.Reason, "refined_by=semantic") {
t.Fatalf("reason = %q", res.Top.Reason)
}
}
// TestSemanticBelowThresholdKeepsAmbiguous:全部不达阈 → 保持 ambiguous。
func TestSemanticBelowThresholdKeepsAmbiguous(t *testing.T) {
snap := ambiguousFixture(t, []string{"查运行进度"}, []string{"闲聊问候"})
rec := NewRecognizer(Options{Semantic: fakeScorer{scores: map[string]float64{"查运行进度": 0.5}}}, nil, nil)
res, err := rec.Recognize(context.Background(), "模糊词", snap)
if err != nil {
t.Fatal(err)
}
if res.Branch != BranchAmbiguous {
t.Fatalf("below threshold: branch = %s", res.Branch)
}
}
// TestSemanticTieKeepsAmbiguous:两个过阈但分差不足 → 保持 ambiguous。
func TestSemanticTieKeepsAmbiguous(t *testing.T) {
snap := ambiguousFixture(t, []string{"查运行进度"}, []string{"查运行状态"})
rec := NewRecognizer(Options{Semantic: fakeScorer{scores: map[string]float64{"查运行进度": 0.9, "查运行状态": 0.89}}}, nil, nil)
res, err := rec.Recognize(context.Background(), "模糊词", snap)
if err != nil {
t.Fatal(err)
}
if res.Branch != BranchAmbiguous {
t.Fatalf("tie: branch = %s", res.Branch)
}
}
// TestSemanticErrorKeepsAmbiguousAndCallsL2Next:打分错误 → ambiguous,且 L2 接管。
func TestSemanticErrorKeepsAmbiguousAndCallsL2Next(t *testing.T) {
snap := ambiguousFixture(t, []string{"查运行进度"}, nil)
called := false
cls := rewriteClassifier{fn: func(c []Candidate) []Candidate {
called = true
out := append([]Candidate(nil), c...)
out[0].Confidence = 0.99
out[1].Confidence = 0.1
return out
}}
rec := NewRecognizer(Options{Semantic: fakeScorer{err: errors.New("embed down")}}, nil, cls)
res, err := rec.Recognize(context.Background(), "模糊词", snap)
if err != nil {
t.Fatal(err)
}
if !called {
t.Fatal("L2 should be consulted after semantic failure")
}
if res.Branch != BranchMatched || res.Top.ID != "demo.alpha" {
t.Fatalf("L2 fallback after semantic error: %+v", res)
}
}
// TestGeneratorValidStartBecomesMatched:合法 start 命令 → matched + 合成 Top(I2)。
func TestGeneratorValidStartBecomesMatched(t *testing.T) {
b := NewRegistryBuilder(Options{})
if err := b.Register(IntentSpec{ID: "report.generate", Domain: "report", Version: 1,
Matcher: MatchSpec{IncludesAny: []string{"生成报告"}},
Slots: []SlotSpec{{Key: "topic", Pattern: `主题是(.+)`}},
Handler: "reportHandler"}); err != nil {
t.Fatal(err)
}
reg, err := b.Build()
if err != nil {
t.Fatal(err)
}
gen := stubGenerator{cmds: []Command{
{Kind: CommandStart, Action: ActionRef{Kind: ActionWorkflow, ID: "report.generate"},
Slots: map[string]string{"topic": "电机"}},
}}
rec := NewRecognizer(Options{Generator: gen}, nil, nil)
res, err := rec.Recognize(context.Background(), "完全不相关的一句话", reg.Snapshot())
if err != nil {
t.Fatal(err)
}
if res.Branch != BranchMatched || res.Top == nil || res.Top.ID != "report.generate" {
t.Fatalf("generated: %+v", res)
}
if len(res.Commands) != 1 || res.Commands[0].Kind != CommandStart || res.Commands[0].Slots["topic"] != "电机" {
t.Fatalf("commands = %+v", res.Commands)
}
}
// TestGeneratorInvalidOutputFallsBack:幻觉动作/槽位 → 保持 fallback(fail-closed)。
func TestGeneratorInvalidOutputFallsBack(t *testing.T) {
b := NewRegistryBuilder(Options{})
if err := b.Register(IntentSpec{ID: "report.generate", Domain: "report", Version: 1,
Matcher: MatchSpec{IncludesAny: []string{"生成报告"}},
Slots: []SlotSpec{{Key: "topic", Pattern: `(.+)`}}}); err != nil {
t.Fatal(err)
}
reg, err := b.Build()
if err != nil {
t.Fatal(err)
}
snap := reg.Snapshot()
cases := map[string][]Command{
"unknown spec id": {
{Kind: CommandStart, Action: ActionRef{Kind: ActionWorkflow, ID: "ghost.flow"}},
},
"unknown kind": {
{Kind: CommandStart, Action: ActionRef{Kind: "bogus", ID: "report.generate"}},
},
"unknown slot key": {
{Kind: CommandStart, Action: ActionRef{Kind: ActionWorkflow, ID: "report.generate"}, Slots: map[string]string{"bogus": "v"}},
},
"unknown cmd kind": {
{Kind: "teleport"},
},
"empty": {},
}
for name, cmds := range cases {
rec := NewRecognizer(Options{Generator: stubGenerator{cmds: cmds}}, nil, nil)
res, err := rec.Recognize(context.Background(), "完全不相关的一句话", snap)
if err != nil {
t.Fatal(err)
}
if res.Branch != BranchFallback {
t.Fatalf("%s: branch = %s, want fallback", name, res.Branch)
}
}
}
// TestGeneratorErrorKeepsFallback:生成器错误 → 保持 fallback。
func TestGeneratorErrorKeepsFallback(t *testing.T) {
reg, err := NewRegistryBuilder(Options{}).Build()
if err != nil {
t.Fatal(err)
}
rec := NewRecognizer(Options{Generator: stubGenerator{err: errors.New("model down")}}, nil, nil)
res, err := rec.Recognize(context.Background(), "完全不相关的一句话", reg.Snapshot())
if err != nil {
t.Fatal(err)
}
if res.Branch != BranchFallback {
t.Fatalf("branch = %s", res.Branch)
}
}
// TestCatalogTopK:按 priority 降序取 k(确定性口径)。
func TestCatalogTopK(t *testing.T) {
b := NewRegistryBuilder(Options{})
if err := b.Register(IntentSpec{ID: "demo.low", Domain: "demo", Version: 1, Priority: 1, Matcher: MatchSpec{IncludesAny: []string{"a"}}}); err != nil {
t.Fatal(err)
}
if err := b.Register(IntentSpec{ID: "demo.high", Domain: "demo", Version: 1, Priority: 9, Matcher: MatchSpec{IncludesAny: []string{"b"}}}); err != nil {
t.Fatal(err)
}
if err := b.Register(IntentSpec{ID: "demo.mid", Domain: "demo", Version: 1, Priority: 5, Matcher: MatchSpec{IncludesAny: []string{"c"}}}); err != nil {
t.Fatal(err)
}
reg, err := b.Build()
if err != nil {
t.Fatal(err)
}
top := reg.Snapshot().CatalogTopK(2)
if len(top) != 2 || top[0].ID != "demo.high" || top[1].ID != "demo.mid" {
t.Fatalf("topk = %+v", top)
}
if all := reg.Snapshot().CatalogTopK(0); len(all) != 3 {
t.Fatalf("k=0 should be full catalog: %d", len(all))
}
}
type stubGenerator struct {
cmds []Command
err error
}
func (s stubGenerator) Generate(ctx context.Context, input string, catalog []CatalogSpec) ([]Command, error) {
if s.err != nil {
return nil, s.err
}
return s.cmds, nil
}
+169 -3
View File
@@ -19,17 +19,22 @@ type Recognizer interface {
// FunnelRecognizer 四级漏斗实现:guard → expectation → rules → adjudicate, // FunnelRecognizer 四级漏斗实现:guard → expectation → rules → adjudicate,
// 可选注入挂起预期提供者与 Ambiguous 分支的 L2 分类器。 // 可选注入挂起预期提供者与 Ambiguous 分支的 L2 分类器。
// I2 的分层:歧义先经语义重排(确定性宿主打分)再到 L2 分类器(模型)。
type FunnelRecognizer struct { type FunnelRecognizer struct {
log *logsys.Logger log *logsys.Logger
exp ExpectationProvider exp ExpectationProvider
cls Classifier cls Classifier
sem SemanticScorer
thr float64
gen CommandGenerator
} }
// NewRecognizer 构建漏斗识别器。opts 仅使用 Log 字段(阈值类参数在快照内); // NewRecognizer 构建漏斗识别器。opts 仅使用 Log 字段(阈值类参数在快照内);
// exp / cls 可为 nil(对应阶段不启用)。 // exp / cls 可为 nil(对应阶段不启用)。语义与生成端口经 Options 注入(I2)。
func NewRecognizer(opts Options, exp ExpectationProvider, cls Classifier) *FunnelRecognizer { func NewRecognizer(opts Options, exp ExpectationProvider, cls Classifier) *FunnelRecognizer {
o := opts.normalized() o := opts.normalized()
return &FunnelRecognizer{log: o.Log, exp: exp, cls: cls} return &FunnelRecognizer{log: o.Log, exp: exp, cls: cls,
sem: o.Semantic, thr: o.SemanticThreshold, gen: o.Generator}
} }
// Recognize 识别一次输入。确定性:同输入 + 同快照 → 同结果(日志中的耗时除外)。 // Recognize 识别一次输入。确定性:同输入 + 同快照 → 同结果(日志中的耗时除外)。
@@ -46,7 +51,10 @@ func (r *FunnelRecognizer) Recognize(ctx context.Context, input string, snap *Sn
} }
start := time.Now() start := time.Now()
res := r.recognize(ctx, input, snap) res := r.recognize(ctx, input, snap)
// 生成端口已经给出经过校验的命令列表时不再用规则展开覆盖(I2)。
if res.Commands == nil {
res.Commands = deriveCommands(res, snap) res.Commands = deriveCommands(res, snap)
}
topID, conf := "", 0.0 topID, conf := "", 0.0
if res.Top != nil { if res.Top != nil {
topID, conf = res.Top.ID, res.Top.Confidence topID, conf = res.Top.ID, res.Top.Confidence
@@ -89,6 +97,12 @@ func (r *FunnelRecognizer) recognize(ctx context.Context, input string, snap *Sn
hits := snap.evalRules(matchInput, folded) hits := snap.evalRules(matchInput, folded)
switch { switch {
case len(hits) == 0: case len(hits) == 0:
// I2:规则与语义都未命中时,可选经模型命令生成端口兜底(校验同款)。
if r.gen != nil {
if res, ok := r.generateCommands(ctx, input, snap); ok {
return res
}
}
return Result{Input: input, Branch: BranchFallback, SnapshotRevision: snap.rev} return Result{Input: input, Branch: BranchFallback, SnapshotRevision: snap.rev}
case len(hits) == 1 || hits[0].score-hits[1].score >= snap.margin || tieBreakByPriority(hits): case len(hits) == 1 || hits[0].score-hits[1].score >= snap.margin || tieBreakByPriority(hits):
cands := candidatesOf(trimHits(hits, snap.topN), input, true) cands := candidatesOf(trimHits(hits, snap.topN), input, true)
@@ -96,8 +110,15 @@ func (r *FunnelRecognizer) recognize(ctx context.Context, input string, snap *Sn
return Result{Input: input, Branch: BranchMatched, Top: &top, Candidates: cands, SnapshotRevision: snap.rev} return Result{Input: input, Branch: BranchMatched, Top: &top, Candidates: cands, SnapshotRevision: snap.rev}
} }
// Ambiguous:可选 L2 分类器重排;失败或结果不合法都保持 Ambiguous。 // Ambiguous:先语义重排(I2 确定性宿主打分),失败或不达阈才到 L2 分类器。
cands := candidatesOf(trimHits(hits, snap.topN), input, false) cands := candidatesOf(trimHits(hits, snap.topN), input, false)
if r.sem != nil {
if top, ok := r.semanticRerank(ctx, input, cands, snap); ok {
snap.fillTopSlots(&top, input)
top.Reason = top.Reason + "; refined_by=semantic"
return Result{Input: input, Branch: BranchMatched, Top: &top, Candidates: cands, SnapshotRevision: snap.rev}
}
}
if r.cls != nil { if r.cls != nil {
refined, err := r.cls.Refine(ctx, input, cands) refined, err := r.cls.Refine(ctx, input, cands)
if err != nil { if err != nil {
@@ -263,3 +284,148 @@ func sortScored(ss []ScoredSpec) {
func sortRejected(rs []RejectedSpec) { func sortRejected(rs []RejectedSpec) {
sort.SliceStable(rs, func(i, j int) bool { return rs[i].ID < rs[j].ID }) sort.SliceStable(rs, func(i, j int) bool { return rs[i].ID < rs[j].ID })
} }
// semanticRerank 用宿主打分器对歧义候选按 examples 重排(I2)。
// 规则:候选必须来自当前候选集(构造上不可能引入未知 ID);
// 只取相似度 ≥ 阈值的候选——唯一过阈或前两名分差 ≥ margin 才判 matched,
// 否则保持 ambiguous(与规则裁决同款语义)。打分错误一律记日志并保持 ambiguous。
func (r *FunnelRecognizer) semanticRerank(ctx context.Context, input string, cands []Candidate, snap *Snapshot) (Candidate, bool) {
type scored struct {
cand Candidate
score float64
}
var above []scored
for _, c := range cands {
spec, ok := snap.SpecByID(c.ID)
if !ok || len(spec.Examples) == 0 {
continue
}
s, err := r.sem.Score(ctx, input, spec.Examples)
if err != nil {
r.log.Module("intent").WithContext(ctx).Warn("intent semantic scorer failed; keep ambiguous",
"error", err.Error(), "id", c.ID)
continue
}
if s >= r.thr {
above = append(above, scored{cand: c, score: s})
}
}
if len(above) == 0 {
return Candidate{}, false
}
for i := 0; i+1 < len(above); i++ {
for j := i + 1; j < len(above); j++ {
if above[j].score > above[i].score {
above[i], above[j] = above[j], above[i]
}
}
}
if len(above) == 1 || above[0].score-above[1].score >= snap.margin {
return above[0].cand, true
}
return Candidate{}, false
}
// generateCommands 经模型命令生成端口兜底(I2)。校验:
// start 的 ActionRef 可解析(spec ID 集或 Handler 集内)、命令种类受限、
// 槽位键在目标 spec 的键集内(可解析时);全部通过则把首个 start 的目标
// spec 合成 Top(refined_by=generator 标注),任一项不过则保持 fallback。
func (r *FunnelRecognizer) generateCommands(ctx context.Context, input string, snap *Snapshot) (Result, bool) {
catalog := snap.CatalogTopK(DefaultGeneratorCatalogK)
cmds, err := r.gen.Generate(ctx, input, catalog)
if err != nil {
r.log.Module("intent").WithContext(ctx).Warn("intent command generator failed; keep fallback", "error", err.Error())
return Result{}, false
}
if err := validateGenerated(cmds, catalog); err != nil {
r.log.Module("intent").WithContext(ctx).Warn("intent generator output rejected; keep fallback", "error", err.Error())
return Result{}, false
}
res := Result{Input: input, Branch: BranchFallback, Commands: cmds, SnapshotRevision: snap.rev}
for _, c := range cmds {
if c.Kind != CommandStart {
continue
}
if spec, ok := generatedSpecOf(c.Action, catalog); ok {
top := Candidate{
ID: spec.ID, Domain: spec.Domain, Version: spec.Version,
Confidence: 0, Reason: "generated_by=command_generator",
Slots: c.Slots,
}
res.Top = &top
res.Branch = BranchMatched
res.Candidates = []Candidate{top}
}
break
}
return res, true
}
// DefaultGeneratorCatalogK 生成器目录默认 top-k。
const DefaultGeneratorCatalogK = 8
// validateGenerated 校验生成命令(I2;与 spec 级校验同款 fail-closed)。
func validateGenerated(cmds []Command, catalog []CatalogSpec) error {
if len(cmds) == 0 {
return fmt.Errorf("empty command list")
}
specIDs := map[string]bool{}
handlers := map[string]bool{}
slotKeys := map[string]map[string]bool{}
for _, cs := range catalog {
specIDs[cs.ID] = true
if cs.Handler != "" {
handlers[cs.Handler] = true
}
keys := map[string]bool{}
for _, sl := range cs.Slots {
keys[sl.Key] = true
}
slotKeys[cs.ID] = keys
}
for i, c := range cmds {
switch c.Kind {
case CommandStart:
ref := c.Action
switch ref.Kind {
case ActionAuto, ActionCommand, ActionWorkflow, ActionBlueprint:
if !specIDs[ref.ID] {
return fmt.Errorf("cmd[%d] start: action.id %q 不在目录内", i, ref.ID)
}
case ActionHandler:
if !handlers[ref.ID] {
return fmt.Errorf("cmd[%d] start: handler %q 不在目录内", i, ref.ID)
}
default:
return fmt.Errorf("cmd[%d] start: 未知 action.kind %q", i, ref.Kind)
}
if keys, ok := slotKeys[ref.ID]; ok {
for k := range c.Slots {
if !keys[k] {
return fmt.Errorf("cmd[%d] start: 槽位 %q 不在 %s 的槽位键集内", i, k, ref.ID)
}
}
}
case CommandClarify, CommandCancel, CommandSetSlot, CommandChangeSlot, CommandChitchat, CommandHandoff:
default:
return fmt.Errorf("cmd[%d]: 未知命令种类 %q", i, c.Kind)
}
}
return nil
}
// generatedSpecOf 把 start 的 ActionRef 解析回目录 spec(handler 键按 Handler 反查)。
func generatedSpecOf(ref ActionRef, catalog []CatalogSpec) (CatalogSpec, bool) {
for _, cs := range catalog {
if ref.Kind == ActionHandler {
if cs.Handler == ref.ID {
return cs, true
}
continue
}
if cs.ID == ref.ID {
return cs, true
}
}
return CatalogSpec{}, false
}
+9
View File
@@ -35,6 +35,12 @@ type Options struct {
MaxSlotsPerSpec int MaxSlotsPerSpec int
// Log 可选日志器;nil 时使用 Nop。识别日志不记录原始输入。 // Log 可选日志器;nil 时使用 Nop。识别日志不记录原始输入。
Log *logsys.Logger Log *logsys.Logger
// Semantic 语义相似度端口(I2):nil 时语义层不启用。
Semantic SemanticScorer
// SemanticThreshold 语义重排的过阈值(默认 0.8)。
SemanticThreshold float64
// Generator 模型命令生成端口(I2):nil 时 fallback 不生成。
Generator CommandGenerator
// SlotValidators 作用域槽位校验器。构建与校验时先查此表,查不到再回落 // SlotValidators 作用域槽位校验器。构建与校验时先查此表,查不到再回落
// 已弃用的全局表(RegisterSlotValidator)。不同 Registry 的同名校验器互不影响。 // 已弃用的全局表(RegisterSlotValidator)。不同 Registry 的同名校验器互不影响。
SlotValidators map[string]SlotValidator SlotValidators map[string]SlotValidator
@@ -61,6 +67,9 @@ func (o Options) normalized() Options {
if n.AmbiguityMargin <= 0 { if n.AmbiguityMargin <= 0 {
n.AmbiguityMargin = DefaultAmbiguityMargin n.AmbiguityMargin = DefaultAmbiguityMargin
} }
if n.SemanticThreshold <= 0 {
n.SemanticThreshold = DefaultSemanticThreshold
}
if n.MaxInputLen <= 0 { if n.MaxInputLen <= 0 {
n.MaxInputLen = DefaultMaxInputLen n.MaxInputLen = DefaultMaxInputLen
} }
+13
View File
@@ -0,0 +1,13 @@
package intent
import "context"
// SemanticScorer 语义相似度端口(I2 分层与评测):宿主注入向量模型,
// 核心不做 I/O——打分由宿主计算,库只消费分数。
// 只在规则裁决为 Ambiguous 时被调用(规则→语义→模型分层的中位);
// 实现应幂等且无副作用(同一输入与示例集应得到同一分数)。
type SemanticScorer interface {
// Score 返回 input 与 examples 的最高相似度,取值 [0,1]。
// 实现返回错误时识别保持 Ambiguous,不会使 Recognize 失败。
Score(ctx context.Context, input string, examples []string) (float64, error)
}
+5
View File
@@ -18,6 +18,8 @@ const (
DefaultBase = 0.7 DefaultBase = 0.7
DefaultPatternBonus = 0.2 DefaultPatternBonus = 0.2
DefaultThreshold = 0.6 DefaultThreshold = 0.6
// DefaultSemanticThreshold 语义重排默认过阈值。
DefaultSemanticThreshold = 0.8
) )
// MatchSpec 声明式匹配规则。 // MatchSpec 声明式匹配规则。
@@ -87,6 +89,9 @@ type IntentSpec struct {
// 声明后优先于 Handler 与 Metadata["blueprint"] 被 ActionRef 采用。 // 声明后优先于 Handler 与 Metadata["blueprint"] 被 ActionRef 采用。
// kind 必须是 command/workflow/blueprint/handler 之一。 // kind 必须是 command/workflow/blueprint/handler 之一。
Action *ActionRef `json:"action,omitempty"` Action *ActionRef `json:"action,omitempty"`
// Examples 示例句(I2 语义端口):歧义时语义打分器按它们路由
// (semantic-router 借鉴;向量模型由宿主注入,库不做 I/O)。
Examples []string `json:"examples,omitempty"`
} }
var ( var (
+190
View File
@@ -43,6 +43,196 @@
] ]
} }
] ]
},
{
"name": "ambiguous_then_tiebreak_match",
"turns": [
{
"input": "打结",
"expect_branch": "ambiguous",
"expect_commands": [
{
"kind": "clarify",
"candidates": [
"demo.tie.alpha",
"demo.tie.beta"
]
}
]
},
{
"input": "你好呀",
"expect_branch": "matched",
"expect_top": "demo.greet.hello"
}
]
},
{
"name": "single_matched_full_slots",
"turns": [
{
"input": "查询任务 42 的进度",
"expect_branch": "matched",
"expect_top": "demo.run.status_query",
"expect_commands": [
{
"kind": "start",
"action_kind": "handler",
"action_id": "taskrun.query_status",
"slots": {
"run_id": "42"
}
}
]
}
]
},
{
"name": "single_fallback",
"turns": [
{
"input": "天气不错",
"expect_branch": "fallback",
"expect_commands": []
}
]
},
{
"name": "single_ambiguous_tie",
"turns": [
{
"input": "打结",
"expect_branch": "ambiguous",
"expect_commands": [
{
"kind": "clarify",
"candidates": [
"demo.tie.alpha",
"demo.tie.beta"
]
}
]
}
]
},
{
"name": "single_tiebreak_priority",
"turns": [
{
"input": "你好呀",
"expect_branch": "matched",
"expect_top": "demo.greet.hello"
}
]
},
{
"name": "single_matched_handler_action",
"turns": [
{
"input": "查询任务 7 的进度",
"expect_branch": "matched",
"expect_top": "demo.run.status_query",
"expect_commands": [
{
"kind": "start",
"action_kind": "handler",
"action_id": "taskrun.query_status"
}
]
}
]
},
{
"name": "two_turn_complete_then_fallback",
"turns": [
{
"input": "生成报告,主题是电机",
"expect_branch": "matched",
"expect_top": "report.generate",
"expect_commands": [
{
"kind": "start",
"action_kind": "handler",
"action_id": "reportHandler",
"slots": {
"topic": "电机"
}
}
]
},
{
"input": "天气不错",
"expect_branch": "fallback",
"expect_commands": []
}
]
},
{
"name": "two_turn_missing_then_other_intent",
"turns": [
{
"input": "帮我生成报告",
"expect_branch": "matched",
"expect_top": "report.generate"
},
{
"input": "查询任务 42 的进度",
"expect_branch": "matched",
"expect_top": "demo.run.status_query"
}
]
},
{
"name": "single_clarify_missing_with_question",
"turns": [
{
"input": "帮我生成报告",
"expect_branch": "matched",
"expect_top": "report.generate",
"expect_commands": [
{
"kind": "start"
},
{
"kind": "clarify",
"missing": [
"topic"
],
"question": "要查询哪个主题?"
}
]
}
]
},
{
"name": "two_turn_set_slot_pump",
"turns": [
{
"input": "帮我生成报告",
"expect_branch": "matched",
"expect_top": "report.generate"
},
{
"input": "生成报告,主题是水泵",
"expect_branch": "expectation_hit",
"expect_top": "report.generate",
"expect_commands": [
{
"kind": "set_slot",
"name": "topic",
"value": "水泵"
},
{
"kind": "start",
"action_kind": "handler",
"action_id": "reportHandler",
"slots": {
"topic": "水泵"
}
}
]
}
]
} }
] ]
} }
+36 -27
View File
@@ -1,44 +1,53 @@
# 通用规范采纳记录 # 通用规范采纳记录
- 项目/负责人:Agent-Intent / 仓库所有者 weichen - 项目/负责人:Agent-Intent / weichen
- 状态:active - 状态:active
- 规范位置:`deps/development-guidelines` - 规范位置:`deps/development-guidelines`
- 规范性文档目录:`policy/` - 规范性文档目录:`policy/`(项目规则与制度放此处)
- 过程文档位置:`docs/records/`(项目内固定目录豁免。当前为个人维护的基础库,单独过程仓会让每份记录都变成跨仓 gitlink;任务主记录仍在 Gitea Issue/PR。豁免一经登记,过程记录不得再写入 `policy/` 或规范库。) - 过程文档位置:`.task/` 存放任务卡与交付证据(模板 `.task/templates/task-card.md`,本次任务卡 `.task/feature-governance-adoption.md`);`docs/records/` 存放其他过程记录,为既有项目内豁免目录,保持不变(理由见 PROJECT.md「仓库与操作」节与下方「项目差异」);任务主记录在 PR 描述与 Gitea Issue/PR。
- 版本权威:父仓 gitlink;可读标签:无(内容版本 `0.1.0-draft.6`,提交 `26b562d2e34fbc560711296a309c91c74ffc55b1`) - 版本权威:本仓 gitlink deps/development-guidelines @ 2688fd2be6a5(内容版本 `0.1.0-draft.12`;可读标签:无)
- 批准/生效依据:本仓接入提交;草案审阅完成后随该提交生效 - 批准/生效依据:feature/governance-adoption PR(Agent-Intent)
- 替代范围和迁移说明:本仓为新建仓库,无既往规则需要迁移。 - 替代范围和迁移说明:本次替代此前的 Guidelines gitlink `26b562d2e34f`(内容版本 `0.1.0-draft.6`),固定为 `2688fd2be6a5`。新增 `AGENT-EXECUTION.md` 执行路由、`deps/development-manager` 治理 gitlink(@ `bd3a7de57a3a`)、`scripts/dm`、`scripts/agent-exec`、`scripts/task`、`scripts/hooks/pre-commit`、`.gitea/workflows/governance.yml`。本仓无既往 Agent 执行路由需迁移。
| 规范/工作流 | 适用条件与范围 | 采纳或不适用理由 | 执行方式/证据 | | 规范/工作流 | 适用条件与范围 | 采纳或不适用理由 | 执行方式/证据 |
|---|---|---|---| |---|---|---|---|
| core/baseline.md | 日常工作 | 所有改动都要守硬边界 | manual | | core/baseline.md | 日常工作 | 所有改动守硬边界;集成分支不直接提交,经 feature 分支与 PR 合入 | manual;`scripts/hooks/pre-commit` 本地拒绝直提 `main` 与非法分支名(需 `core.hooksPath` 启用,由人执行);未验证(尚无直提与非法分支名的负例记录) |
| workflows/delivery.md、debugging.md、handoff.md | 交付、排障与交接 | 单人维护仍需要可交接的任务记录 | manual | | workflows/delivery.md、debugging.md、handoff.md | 交付、排障与交接 | 单人维护仍需要可交接的任务记录 | manual;任务卡 `.task/`,主记录在 PR |
| standards/collaboration.md | 并行共同起点、节点同步与陈旧候选拒绝 | 本仓会被 Agent-Squad / Agent-Native 以 gitlink 消费 | manual | | standards/collaboration.md | 并行共同起点、节点同步与陈旧候选拒绝 | 本仓被 Agent-Squad / Agent-Native 以 gitlink 消费,需固定共同基线 | manual;合入前在 `main` 上 `git fetch` 并确认 HEAD |
| standards/verification.md | 验证与结论 | 仓库根 `go vet` / `go test`(含 `-race`) | manual;命令见 PROJECT.md | | standards/merge-queue.md | 多个就绪任务的最终同步、CI、Review 与合入调度 | 不适用:当前为单维护者,无多任务同时合入的调度需求;出现并行合入时复查 | 不适用 |
| workflows/design-verification.md、standards/testability.md、standards/failure-containment.md | 识别漏斗、配置合并与快照切换 | 识别必须可脱离外部 I/O 独立验证;配置失败保留上一快照 | manual | | standards/agent-execution.md | 工程 Agent CLI 执行路由、会话隔离与受控集成 | 凡 AI 协助实施均经执行路由:配置 `AGENT-EXECUTION.md`,执行器入口 `scripts/agent-exec`(validate / run / apply / admit / cleanup) | advisory(CI 运行 `scripts/agent-exec validate` 与 `scripts/dm governance check`,分支保护尚未设为必需检查;设为必需检查并有阻断负例后再登记 enforced);证据:governance check + 正负例(.task/feature-governance-adoption.md) |
| standards/module-contracts.md | intent/conformance/catalogcheck/examples 边界 | 公开端口按层拆分;扩展点只留接口 | manual | | standards/verification.md | 验证与结论 | 仓库根 `go vet` / `go test`(含 `-race`)为正式验证 | 命令见 PROJECT.md「验证命令」;CI `.gitea/workflows/ci.yml` |
| standards/observability.md | 错误码、识别事件日志 | 与 Error-Code-System / Logging-System 对齐 | manual | | workflows/design-verification.md、standards/testability.md、standards/failure-containment.md | 识别漏斗、配置合并与快照切换的设计/验证、模块测试与故障控制 | 识别必须可脱离外部 I/O 独立验证;配置失败保留上一快照 | manual;快照并发测试(`-race`) |
| standards/configuration.md | spec/overlay 双源合并、快照 revision、生效时机与诊断 | 本仓核心就是配置驱动模块;check/explain 由 `intentctl` 承担 | manual | | standards/module-contracts.md | intent / conformance / catalogcheck / examples 的边界与公开契约 | 公开端口按层拆分;扩展点只留接口 | manual |
| standards/multi-repo.md | submodule 与固定组合 | 规范与基础库以 gitlink 锁定 | manual | | standards/observability.md | 错误码、识别事件日志 | 与 Error-Code-System / Logging-System 对齐 | manual;`catalogcheck` 测试 |
| standards/security.md | 凭证、配置信任边界 | 配置文件属宿主可信输入;本仓不含凭证 | manual | | standards/configuration.md | spec/overlay 双源合并、快照 revision、生效时机与诊断 | 本仓核心即配置驱动模块;诊断由 `intentctl` 承担 | manual;`intentctl lint` / `explain` |
| standards/documentation.md | 三层文档 | `policy/` 与 `docs/records/` 已分开 | manual | | standards/multi-repo.md | submodule 与固定组合 | 规范、执行器与基础库均以 gitlink 锁定;不执行 `submodule update --remote` | manual;`scripts/submodule-consistency.sh`;`scripts/dm governance check` |
| standards/data-reliability.md | 本仓自有数据库 | 无数据库;快照替换的原子性由并发测试覆盖 | 不适用 | | standards/security.md | 凭证、配置信任边界 | 配置文件属宿主可信输入;本仓不保存凭证 | manual |
| standards/agent-execution.md | Agent 执行契约 | 本仓不做执行,只做 LLM 前的确定性识别 | 不适用 | | standards/documentation.md | 三层文档(入口、规范性文档、过程记录) | `policy/` 为规范性文档;任务卡与交付证据放 `.task/`,其他过程记录按既有豁免放 `docs/records/` | manual;见下方「项目差异」 |
| standards/release-operations.md | 独立发布流水线 | 当前无制品仓库与部署入口 | 不适用 | | standards/tool-adaptation.md | 新工具/执行器适配 | 适用:仓库配置 Agent CLI profile(AGENT-EXECUTION.md)、pre-commit 钩子与 Gitea CI 任务,逐项登记见下方「工具适配矩阵」 | 适用,见下方矩阵 |
| workflows/automation.md | 定时与后台任务 | 本仓无 daemon | 不适用 | | standards/data-reliability.md | 本仓自有数据库 | 不适用:无数据库;快照替换的原子性由并发测试覆盖 | 不适用 |
| standards/release-operations.md | 独立发布流水线 | 不适用:当前无制品仓库与部署入口;可构建性以 `main` 的 `go test` 为准 | 不适用 |
| workflows/automation.md | 定时与后台任务 | 不适用:本仓无 daemon | 不适用 |
### 工具适配矩阵(standards/tool-adaptation.md)
| 要求 | 级别 | 机制 | 正例证据 | 负例证据 |
|---|---|---|---|---|
| (a) 本地 pre-commit 准入 `scripts/agent-exec admit` | advisory(本地钩子,需 `core.hooksPath` 启用,同 OS 用户可绕过,不是门禁) | `scripts/hooks/pre-commit` 调用 `scripts/agent-exec admit` | SELF:adoption cases 的 positive-run-apply 通过(`.task/evidence/governance-adoption/02-20261017T073820Z.log`) | SELF:bad-receipt、out-of-scope-path 被拒绝(同上日志;记录见 `.task/feature-governance-adoption.md`) |
| (b) governance CI(`.gitea/workflows/governance.yml`:`agent-exec validate` 与 `dm governance check`) | advisory(分支保护设为必需检查前不阻断合并) | Gitea Actions job `governance`,在 PR 与 `main` 推送时运行 | GATE:PR 头 CI(governance 与既有 ci 工作流)通过,运行链接见 .task/feature-governance-adoption.md 证据表 | 未验证 |
## 项目差异 ## 项目差异
| 原规则/章节 | 项目采用方式 | 原因与风险 | 负责人/复查条件 | | 原规则/章节 | 项目采用方式 | 原因与风险 | 负责人/复查条件 |
|---|---|---|---| |---|---|---|---|
| documentation.md 过程仓默认独立仓库 | 过程记录放 `docs/records/` | 个人基础库;风险是记录与代码同 PR 时容易把证据写成第二权威。任务状态仍只认 Gitea Issue/PR | 出现第二名维护者或对外发布流程时复查 | | documentation.md 过程仓默认独立仓库 | 其他过程记录放 `docs/records/`(既有豁免) | 个人基础库;风险是记录与代码同 PR 时容易把证据写成第二权威。任务状态仍只认 Gitea Issue/PR | 出现第二名维护者或对外发布流程时复查 |
| documentation.md 过程仓默认独立仓库 | 任务卡与交付证据放 `.task/`(模板 `.task/templates/task-card.md`) | 个人基础库;任务卡与证据随代码同仓,风险是证据被写成第二个任务状态权威。任务状态仍只认 Gitea Issue/PR | 出现第二名维护者或对外发布流程时复查 |
默认做法差异说明理由;硬边界冲突须有明确有效决策,不能用此表自授权。建议不采用无需登记。 默认做法差异说明理由;硬边界冲突须有明确有效决策,不能用此表自授权。建议不采用无需登记。
## 本次升级或首次接入验证 ## 本次升级或首次接入验证
- 入口:根 `AGENTS.md`、`PROJECT.md` 指向 `deps/development-guidelines`。 - 入口:根 `AGENTS.md` 与 `PROJECT.md` 指向 `deps/development-guidelines`;`AGENTS.md` 新增「Agent 执行与新项目准入(强制)」段落,`AGENT-EXECUTION.md` 为执行路由唯一权威。
- 子模块:`deps/development-guidelines` 固定 `26b562d2e34fbc560711296a309c91c74ffc55b1`;`deps/error-code-system` 固定 `28fa06eaa56a19906732681cc02474a87ac9d408`(含 `intent` 种子域);`deps/logging-system` 固定 `2c5716fdcffbc4696fc0371afc70f39c44174201`。三者与 Agent-Squad / Agent-Native 当前组合一致(error-code-system 为新增 intent 域后的新提交,属追加式变更)。 - 子模块:`deps/development-guidelines` 固定 `2688fd2be6a52c0bf110e7e6366b6f291ab5e4eb`;`deps/development-manager` 固定 `bd3a7de57a3a9e1a4495b94c5fa5c08d71e7be8c`;`deps/error-code-system` 与 `deps/logging-system` 不变。
- `.gitmodules` 使用批准的上游 SSH URL,不含本机路径或凭证。 - 门禁:`.gitea/workflows/governance.yml` 在 PR 与 `main` 推送时运行 `scripts/agent-exec validate --repo .` 与 `scripts/dm governance check --repo .`;实际正负例与 Review/CI 证据记录在 `.task/feature-governance-adoption.md`。
- 执行方式保持 manual:挂载 Markdown 不自动改变 CI。 - 回退:恢复 `deps/development-guidelines` gitlink 为 `26b562d2e34f`,删除 `AGENTS.md` 新增段落、`deps/development-manager` 条目、`scripts/dm`、`scripts/agent-exec`、`scripts/task`、`scripts/hooks/pre-commit`、`.gitea/workflows/governance.yml` 与 `.task/templates/task-card.md`,并将本文件状态改回 proposed。分支保护与规范 tag 由用户设置,回退时同步撤销。
- 回退:恢复本提交之前的 gitlink,并删除根入口与 `policy/guidelines-adoption.md` 中对应条款;无已生效的 CI 或权限变更需要另恢复。
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
# 共享 Agent 执行器薄入口:转调 scripts/dm 的 agent-exec 子命令(validate / run / apply / admit / cleanup)。
set -euo pipefail
exec "$(dirname "$0")/dm" agent-exec "$@"
Executable
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# 共享 dm-server 薄入口:读取 deps/development-manager 的 gitlink 钉住提交,
# 按该提交构建并缓存 dm-server,再 exec 其子命令(agent-exec、task、governance)。执行语义与退出码以该二进制为准。
set -euo pipefail
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
dm="$root/deps/development-manager"
die() { echo "dm: $*" >&2; exit 2; }
# git 钩子会导出仓库本地变量(GIT_DIR、GIT_INDEX_FILE 等),它们会让 `git -C "$dm"` 解析到外层仓库。
# 所有针对 DM 检出的 git 与 go 调用都在清除这些变量后执行;读取外层 gitlink 与最终 exec 仍保留继承环境。
dm_local_env="GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_PREFIX GIT_OBJECT_DIRECTORY GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_COMMON_DIR GIT_GRAFT_FILE GIT_NAMESPACE GIT_CONFIG GIT_CONFIG_PARAMETERS GIT_CONFIG_COUNT GIT_SHALLOW_FILE GIT_IMPLICIT_WORK_TREE $(git rev-parse --local-env-vars 2>/dev/null || true)"
dm_clean_env() { for v in $dm_local_env; do unset "$v"; done; }
dmgit() ( dm_clean_env; git -C "$dm" "$@" )
# 1. 钉住版本只来自索引中的 gitlink(mode 160000),不读取工作区文件。
entry=$(git -C "$root" ls-files -s -- deps/development-manager 2>/dev/null || true)
[ -n "$entry" ] || die "索引中缺少 deps/development-manager 的 gitlink,无法确定钉住的 DM 版本"
read -r mode pin _stage _path <<<"$entry"
[ "$mode" = 160000 ] || die "deps/development-manager 不是 gitlink(mode=$mode)"
[[ "$pin" =~ ^[0-9a-f]{40}$ ]] || die "gitlink 提交格式无效:$pin"
# 2. 检出必须存在且确为 DM 独立仓库(避免 git -C 误解析到外层仓库),HEAD 必须等于钉住提交。
[ -e "$dm/.git" ] || die "deps/development-manager 未检出;请按钉住提交 $pin 检出该 gitlink"
top=$(dmgit rev-parse --show-toplevel 2>/dev/null || true)
[ -n "$top" ] && [ "$top" = "$(cd "$dm" && pwd -P)" ] \
|| die "deps/development-manager 不是独立检出;请按钉住提交 $pin 检出该 gitlink"
head=$(dmgit rev-parse HEAD 2>/dev/null || true)
[ "$head" = "$pin" ] \
|| die "deps/development-manager HEAD(${head:-未知})与钉住提交 $pin 不一致;请按钉住提交检出"
# 3. 检出工作树必须干净(忽略嵌套 submodule 状态)。
dirty=$(dmgit status --porcelain --ignore-submodules=all) \
|| die "无法读取 deps/development-manager 的工作树状态"
[ -z "$dirty" ] || die "deps/development-manager 存在未提交改动,拒绝运行(请先清理该检出)"
# 4. 二进制按钉住提交缓存;缺失时在缓存目录内临时构建,完成后原子改名,避免留下半成品。
cache="${DM_CACHE:-${XDG_CACHE_HOME:-$HOME/.cache}/development-manager/dm-server}"
bin="$cache/dm-server-$pin"
if [ ! -x "$bin" ]; then
command -v go >/dev/null 2>&1 || die "缺少 go,无法构建 dm-server($pin)"
mkdir -p "$cache"
cache=$(cd "$cache" && pwd -P)
bin="$cache/dm-server-$pin"
work=$(mktemp -d "$cache/build-$pin.XXXXXX")
if ! (dm_clean_env; cd "$dm" && GOWORK=off go build -trimpath -o "$work/dm-server" ./cmd/dm-server); then
rm -rf "$work"
die "构建 dm-server 失败($pin)"
fi
mv -f "$work/dm-server" "$bin"
rm -rf "$work"
fi
exec "$bin" "$@"
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# 本地检查(pre-commit):集成分支直提拒绝 + 分支命名 + 本地完整性核对。权威准入在 PR(baseline.md:26)。
branch=$(git rev-parse --abbrev-ref HEAD)
case "$branch" in
main|master)
echo "pre-commit: 拒绝——集成分支 ${branch} 不得直接提交,走 feature 分支 + PR(collaboration.md)" >&2
exit 1 ;;
esac
echo "$branch" | grep -qE '^(feature|fix|hotfix|release)/[A-Za-z0-9._-]+$' || {
echo "pre-commit: 拒绝——分支名须为 feature|fix|hotfix|release/<slug>,当前:${branch}" >&2
exit 1
}
root=$(git rev-parse --show-toplevel)
# admit 只是本地完整性/篡改证据(SELF):同一 OS 用户可读取本地 HMAC 密钥并绕过本钩子,
# 它不是 GATE,也不是可信身份。缺失或无效的本地记录仍拒绝;交付信任来自精确 HEAD 的 CI 或独立 REPLAY。
"$root/scripts/agent-exec" admit --repo "$root"
Executable
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
# 通用任务入口:转调 scripts/dm 的 task 子命令(triage / start / pr-check)。
set -euo pipefail
exec "$(dirname "$0")/dm" task "$@"