agentprecept 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ """AgentPrecept — AI 编码 Agent 方法论治理工具集"""
agentprecept/cli.py ADDED
@@ -0,0 +1,409 @@
1
+ """AgentPrecept CLI — 项目初始化(6阶段) / 同步 / 审计(10维) / 诊断 / setup / hooks / gnhf"""
2
+
3
+ import sys
4
+ import subprocess
5
+ import json
6
+ from pathlib import Path
7
+
8
+ SCRIPTS = Path(__file__).parent.parent / "scripts"
9
+ ROOT = Path(__file__).parent.parent
10
+
11
+ # ===== init (6 阶段) =====
12
+
13
+ def _check_git(project):
14
+ git_dir = Path(project) / ".git"
15
+ if not git_dir.exists():
16
+ subprocess.run(["git", "init", project], capture_output=True)
17
+ return True, "git init done"
18
+ return False, "Git already initialized"
19
+
20
+
21
+ def _install_hook(project):
22
+ hook_path = Path(project) / ".git" / "hooks" / "pre-commit"
23
+ hook_content = """#!/bin/bash
24
+ # AgentPrecept pre-commit gate
25
+ # Skip: git commit --no-verify
26
+
27
+ # --- Gate 1: branch policy ---
28
+ BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
29
+ CHANGED_COUNT=$(git diff --cached --name-only 2>/dev/null | wc -l | tr -d ' ')
30
+
31
+ if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
32
+ if [ "$CHANGED_COUNT" -gt 10 ] 2>/dev/null; then
33
+ echo ""
34
+ echo "[AgentPrecept] WARNING: $CHANGED_COUNT files on $BRANCH branch"
35
+ echo "[AgentPrecept] Major changes should use feature branches."
36
+ echo "[AgentPrecept] Create: git checkout -b feature/your-change"
37
+ echo "[AgentPrecept] Skip: git commit --no-verify"
38
+ exit 1
39
+ fi
40
+ fi
41
+
42
+ # --- Gate 2: design gate (shared logic with MCP tool) ---
43
+ CHANGED_CODE=$(git diff --cached --name-only | grep -Ev '^docs/|\\.md$|\\.ya?ml$|\\.json$|\\.cfg$|\\.toml$')
44
+ if [ -n "$CHANGED_CODE" ]; then
45
+ python scripts/design_gate_check.py --files $CHANGED_CODE
46
+ if [ $? -eq 1 ]; then
47
+ echo ""
48
+ echo "[AgentPrecept] Design docs missing. Create them first."
49
+ echo "[AgentPrecept] Skip: git commit --no-verify"
50
+ exit 1
51
+ fi
52
+ fi
53
+
54
+ # --- Gate 3: commit size ---
55
+ CHANGED_CODE_FILES=$(git diff --cached --name-only | grep -Ev '^docs/|\\.md$|\\.ya?ml$|\\.json$|\\.cfg$|\\.toml$' | wc -l | tr -d ' ')
56
+ if [ "$CHANGED_CODE_FILES" -gt 15 ] 2>/dev/null; then
57
+ echo ""
58
+ echo "[AgentPrecept] WARNING: $CHANGED_CODE_FILES code files in one commit"
59
+ echo "[AgentPrecept] Consider splitting into smaller commits (1-3 files each)"
60
+ echo "[AgentPrecept] Skip: git commit --no-verify"
61
+ exit 1
62
+ fi
63
+
64
+ # --- Gate 4: NEEDS_HUMAN_REVIEW ---
65
+ REVIEW_TAGGED=$(git diff --cached --name-only | xargs grep -l '\[NEEDS_HUMAN_REVIEW\]' 2>/dev/null)
66
+ if [ -n "$REVIEW_TAGGED" ]; then
67
+ echo ""
68
+ echo "[AgentPrecept] WARNING: staged files contain [NEEDS_HUMAN_REVIEW]"
69
+ echo "[AgentPrecept] Confirm design docs first, then remove NEEDS_HUMAN_REVIEW."
70
+ echo "[AgentPrecept] Skip: git commit --no-verify"
71
+ exit 1
72
+ fi
73
+
74
+ exit 0
75
+ """
76
+ hook_path.parent.mkdir(parents=True, exist_ok=True)
77
+ hook_path.write_text(hook_content)
78
+ # Unix: chmod +x
79
+ try:
80
+ hook_path.chmod(0o755)
81
+ except Exception:
82
+ pass
83
+ return True
84
+
85
+
86
+ def _check_gnhf():
87
+ try:
88
+ result = subprocess.run(["gnhf", "--version"], capture_output=True, text=True, timeout=5)
89
+ return result.returncode == 0
90
+ except Exception:
91
+ return False
92
+
93
+
94
+ def _setup_gnhf():
95
+ from agentprecept.gnhf_task import render_template
96
+ render_template()
97
+ return True
98
+
99
+
100
+ def _check_ci(project):
101
+ workflows = Path(project) / ".github" / "workflows"
102
+ return workflows.exists() and any(workflows.glob("*.yml"))
103
+
104
+
105
+ def _generate_ci_gate(project):
106
+ workflows = Path(project) / ".github" / "workflows"
107
+ workflows.mkdir(parents=True, exist_ok=True)
108
+ gate_yml = workflows / "agentprecept-gate.yml"
109
+ content = """# AgentPrecept CI Gate — PR merge 前自动运行 10 维审计
110
+ name: AgentPrecept Gate
111
+ on:
112
+ pull_request:
113
+ types: [opened, synchronize, reopened]
114
+ jobs:
115
+ audit-gate:
116
+ runs-on: ubuntu-latest
117
+ steps:
118
+ - uses: actions/checkout@v4
119
+ - uses: actions/setup-python@v5
120
+ with:
121
+ python-version: '3.10'
122
+ - run: pip install agentprecept
123
+ - run: agentprecept audit --gate
124
+ """
125
+ gate_yml.write_text(content)
126
+ return True
127
+
128
+
129
+ def _mcp_config():
130
+ config = {
131
+ "mcpServers": {
132
+ "agentprecept": {
133
+ "command": "python",
134
+ "args": ["-m", "agentprecept.mcp_server"],
135
+ "env": {"PYTHONIOENCODING": "utf-8", "PYTHONUTF8": "1"}
136
+ }
137
+ }
138
+ }
139
+ return json.dumps(config, indent=2)
140
+
141
+
142
+ def cmd_init(project=".", yes=False, dry_run=False, status_only=False,
143
+ ci=None, gnhf_opt=None):
144
+ """6 阶段项目接入"""
145
+ project = Path(project).resolve()
146
+
147
+ if status_only:
148
+ print_status(project)
149
+ return
150
+
151
+ if dry_run:
152
+ print(f"[dry-run] would init: {project}")
153
+ return
154
+
155
+ project.mkdir(parents=True, exist_ok=True)
156
+ docs = project / "docs"
157
+ docs.mkdir(exist_ok=True)
158
+
159
+ report = {}
160
+
161
+ # Phase 1: 骨架
162
+ if not (project / "AGENTS.md").exists() or yes:
163
+ (ROOT / "AGENTS.md").replace(project / "AGENTS.md")
164
+ for tmpl in ["INDEX.md", "L1_A02_naming-convention_命名规范.md",
165
+ "L1_B01_glossary_术语表.md", "HANDOFF.md",
166
+ "MEMORY.md", "project-graph.yaml",
167
+ "L4_O01_design-rationale_设计依据.md"]:
168
+ src = ROOT / "templates" / tmpl
169
+ dst = docs / tmpl
170
+ if src.exists() and not dst.exists():
171
+ src.replace(dst)
172
+ report["AGENTS.md"] = True
173
+ report["docs/ skeleton"] = True
174
+
175
+ # Phase 2: Git
176
+ report["Git"] = _check_git(project)[0]
177
+
178
+ # Phase 3: Git Hook
179
+ if report["Git"]:
180
+ report["Pre-commit Hook"] = _install_hook(project)
181
+
182
+ # Phase 3.5: gnhf
183
+ if report.get("Git") and gnhf_opt is not False:
184
+ if _check_gnhf():
185
+ if gnhf_opt or yes:
186
+ _setup_gnhf()
187
+ report["gnhf"] = "enabled"
188
+ else:
189
+ report["gnhf"] = "skipped (run: agentprecept gnhf setup)"
190
+ else:
191
+ report["gnhf"] = "not installed (pip install gnhf && agentprecept gnhf setup)"
192
+
193
+ # Phase 4: CI Gate
194
+ has_ci = _check_ci(project)
195
+ if has_ci and ci is not False:
196
+ if ci or yes:
197
+ _generate_ci_gate(project)
198
+ report["CI Gate"] = True
199
+ else:
200
+ report["CI Gate"] = "skipped (run: agentprecept init --ci)"
201
+ else:
202
+ report["CI Gate"] = "no CI detected"
203
+
204
+ # Phase 5: MCP
205
+ report["MCP"] = _mcp_config()
206
+
207
+ # Phase 6: 状态报告
208
+ print_status(project, report)
209
+
210
+
211
+ def print_status(project, report=None):
212
+ """输出 AgentPrecept 接入状态"""
213
+ project = Path(project)
214
+ if report is None:
215
+ report = {}
216
+
217
+ print("AgentPrecept 接入状态")
218
+ print("-" * 44)
219
+
220
+ def line(icon, name, detail=""):
221
+ print(f" {icon} {name:<20} {detail}")
222
+
223
+ ag = (project / "AGENTS.md").exists()
224
+ idx = (project / "docs" / "INDEX.md").exists()
225
+ git = (project / ".git").exists()
226
+ hook = (project / ".git" / "hooks" / "pre-commit").exists()
227
+ ci_gate = (project / ".github" / "workflows" / "agentprecept-gate.yml").exists()
228
+
229
+ line("✅" if ag else "❌", "AGENTS.md", "Agent 行为规则" if ag else "缺失")
230
+ line("✅" if idx else "❌", "docs/ skeleton", "7 核心文档" if idx else "缺失")
231
+ line("✅" if git else "⚠", "Git", "已初始化" if git else "运行: git init")
232
+ line("✅" if hook else "⚠", "Pre-commit Hook", "设计文档门禁" if hook else "需 git init")
233
+ if g := report.get("gnhf", ""):
234
+ status = "✅" if g == "enabled" else "⚠"
235
+ line(status, "gnhf", g)
236
+ if ci := report.get("CI Gate", ""):
237
+ line("✅" if ci is True else "⚠", "CI Gate", ci if isinstance(ci, str) else "PR merge 强制审计")
238
+ line("✅" if ag else "⚠", "MCP", "6 tools 可用" if ag else "需 AGENTS.md")
239
+
240
+ print("-" * 44)
241
+ total = sum(1 for v in [ag, idx, git, hook] if v)
242
+ print(f" {total}/4 核心能力已接入")
243
+
244
+
245
+ # ===== hooks =====
246
+
247
+ def cmd_hooks(action="status", project="."):
248
+ """Git hook 管理"""
249
+ hook_path = Path(project) / ".git" / "hooks" / "pre-commit"
250
+
251
+ if action == "install":
252
+ if not (Path(project) / ".git").exists():
253
+ print("error: git 未初始化")
254
+ return
255
+ _install_hook(project)
256
+ print("pre-commit hook installed")
257
+ elif action == "uninstall":
258
+ if hook_path.exists():
259
+ hook_path.unlink()
260
+ print("pre-commit hook removed")
261
+ elif action == "status":
262
+ if hook_path.exists():
263
+ print("pre-commit hook: installed")
264
+ else:
265
+ print("pre-commit hook: not installed")
266
+
267
+
268
+ # ===== gnhf =====
269
+
270
+ def cmd_gnhf(action="status"):
271
+ """gnhf 集成"""
272
+ if action == "setup":
273
+ if _check_gnhf():
274
+ _setup_gnhf()
275
+ print("[gnhf] task template generated: .gnhf/sync-task.md")
276
+ print("[gnhf] run: gnhf --goal .gnhf/sync-task.md --verify 'agentprecept audit'")
277
+ else:
278
+ print("gnhf CLI not found. Install: pip install gnhf")
279
+ elif action == "task":
280
+ _setup_gnhf()
281
+ print("[gnhf] task template regenerated")
282
+ elif action == "status":
283
+ task_file = Path(".gnhf/sync-task.md")
284
+ if _check_gnhf():
285
+ print("gnhf CLI: available")
286
+ print(f"task template: {'exists' if task_file.exists() else 'missing'}")
287
+ else:
288
+ print("gnhf CLI: not installed")
289
+
290
+
291
+ # ===== sync / audit / doctor / setup =====
292
+
293
+ def cmd_sync(src="src", graph="docs/project-graph.yaml"):
294
+ subprocess.run([sys.executable, str(SCRIPTS / "sync-graph.py"), src, graph])
295
+
296
+
297
+ def cmd_audit(docs="docs", gate=False, scope_args=None):
298
+ args = [sys.executable, str(SCRIPTS / "basic-audit.py"), docs]
299
+ if gate:
300
+ args.append("--gate")
301
+ if scope_args:
302
+ args.extend(scope_args)
303
+ subprocess.run(args)
304
+
305
+
306
+ def cmd_doctor():
307
+ root = Path.cwd()
308
+ checks = {
309
+ "AGENTS.md": root / "AGENTS.md",
310
+ "docs/INDEX.md": root / "docs" / "INDEX.md",
311
+ "docs/project-graph.yaml": root / "docs" / "project-graph.yaml",
312
+ "docs/HANDOFF.md": root / "docs" / "HANDOFF.md",
313
+ "docs/L4_O01": root / "docs" / "L4_O01_design-rationale_设计依据.md",
314
+ }
315
+ ok = 0
316
+ for name, path in checks.items():
317
+ status = "OK" if path.exists() else "MISSING"
318
+ if path.exists():
319
+ ok += 1
320
+ print(f" {status} {name}")
321
+ print(f"\n{ok}/{len(checks)} 项通过")
322
+ if ok < len(checks):
323
+ print("运行 agentprecept init . 修复缺失文件")
324
+
325
+
326
+ def cmd_setup():
327
+ print("=== agentprecept setup ===\n")
328
+ print("[1/3] 初始化项目文档...")
329
+ cmd_init(".", yes=True)
330
+ print()
331
+ print("[2/3] MCP Server 配置")
332
+ print(f" {_mcp_config()}")
333
+ print(" 重启 Agent 后即可使用 MCP tools: query/audit/diff/decision/handoff/design_gate\n")
334
+ print("[3/3] 诊断环境...")
335
+ cmd_doctor()
336
+
337
+
338
+ # ===== main =====
339
+
340
+ USAGE = """agentprecept — AI coding agent governance toolkit
341
+
342
+ 用法: agentprecept <command> [options]
343
+
344
+ 命令:
345
+ init [project] 一键接入(6 阶段: 骨架/Git/Hook/gnhf/CI/MCP)
346
+ --yes 全部 yes(非交互)
347
+ --dry-run 预览模式
348
+ --status 仅查看接入状态
349
+ --ci 追加 CI Gate
350
+ --no-ci 跳过 CI Gate
351
+ --no-gnhf 跳过 gnhf
352
+ sync [src] 从代码同步 project-graph
353
+ audit [docs] 8 维审计(--gate 开启 10 维)
354
+ doctor 诊断缺失文件
355
+ setup 一键安装(init + MCP + doctor)
356
+ hooks <action> Git hook 管理 (install/uninstall/status)
357
+ gnhf <action> gnhf 集成 (setup/task/status)
358
+ """
359
+
360
+
361
+ def main():
362
+ if len(sys.argv) < 2:
363
+ print(USAGE)
364
+ return
365
+
366
+ cmd = sys.argv[1]
367
+ args = sys.argv[2:]
368
+
369
+ if cmd == "init":
370
+ project = args[0] if args else "."
371
+ yes = "--yes" in args
372
+ dry_run = "--dry-run" in args
373
+ status_only = "--status" in args
374
+ ci = False if "--no-ci" in args else (True if "--ci" in args else None)
375
+ gnhf_opt = False if "--no-gnhf" in args else None
376
+ cmd_init(project, yes=yes, dry_run=dry_run, status_only=status_only,
377
+ ci=ci, gnhf_opt=gnhf_opt)
378
+
379
+ elif cmd == "sync":
380
+ cmd_sync(*(args[:2] if args else ["src", "docs/project-graph.yaml"]))
381
+
382
+ elif cmd == "audit":
383
+ gate = "--gate" in args
384
+ scope_next = [a for a in args if a.startswith("--scope")]
385
+ other = [a for a in args if a != "--gate" and not a.startswith("--scope")]
386
+ docs = other[0] if other else "docs"
387
+ cmd_audit(docs=docs, gate=gate, scope_args=scope_next)
388
+
389
+ elif cmd == "doctor":
390
+ cmd_doctor()
391
+
392
+ elif cmd == "setup":
393
+ cmd_setup()
394
+
395
+ elif cmd == "hooks":
396
+ action = args[0] if args else "status"
397
+ project = args[1] if len(args) > 1 else "."
398
+ cmd_hooks(action, project)
399
+
400
+ elif cmd == "gnhf":
401
+ action = args[0] if args else "status"
402
+ cmd_gnhf(action)
403
+
404
+ else:
405
+ print(USAGE)
406
+
407
+
408
+ if __name__ == "__main__":
409
+ main()
@@ -0,0 +1,93 @@
1
+ """gnHF 任务模板生成器
2
+
3
+ 用法: python agentprecept/gnhf_task.py [output_path]
4
+
5
+ 读取当前 project-graph 状态 + git diff 摘要,
6
+ 渲染为 gnhf --goal 可消费的 markdown 任务模板。
7
+ """
8
+ import sys
9
+ import subprocess
10
+ from pathlib import Path
11
+
12
+
13
+ def git_diff_summary() -> str:
14
+ """获取自上次 sync 以来的代码变更摘要"""
15
+ try:
16
+ result = subprocess.run(
17
+ ["git", "diff", "--stat", "HEAD"],
18
+ capture_output=True, text=True, timeout=10
19
+ )
20
+ return result.stdout.strip() or "(无变更)"
21
+ except Exception:
22
+ return "(无法获取 git diff)"
23
+
24
+
25
+ def current_graph_state() -> str:
26
+ """读取 project-graph 的概要统计"""
27
+ graph_path = Path("docs/project-graph.yaml")
28
+ if not graph_path.exists():
29
+ return "project-graph.yaml 不存在——首次同步"
30
+
31
+ import yaml
32
+ doc = yaml.safe_load(graph_path.read_text(encoding="utf-8")) or {}
33
+ structure = doc.get("structure", {})
34
+ relations = doc.get("relations", [])
35
+ evolution = doc.get("evolution", [])
36
+
37
+ from collections import Counter
38
+ type_counts = Counter(r.get("type") for r in relations)
39
+
40
+ return f"""structure: {len(structure)} 个包/模块
41
+ relations: {len(relations)} 条依赖
42
+ types: {', '.join(f'{t}:{c}' for t, c in sorted(type_counts.items()))}
43
+ evolution: {len(evolution)} 条 ADR"""
44
+
45
+
46
+ def render_template(output_path: str = ".gnhf/sync-task.md") -> str:
47
+ """生成 gnhf 任务模板"""
48
+ diff = git_diff_summary()
49
+ state = current_graph_state()
50
+
51
+ template = f"""# agentprecept: Auto-Sync Task
52
+
53
+ ## 目标
54
+ 根据最新代码结构更新 `docs/project-graph.yaml`。
55
+
56
+ ## 当前知识库状态
57
+ {state}
58
+
59
+ ## 代码变更摘要
60
+ ```
61
+ {diff}
62
+ ```
63
+
64
+ ## 执行规则
65
+ 1. 运行 `python scripts/sync-graph.py src docs/project-graph.yaml`
66
+ 2. 同步完成后运行 `python scripts/basic-audit.py docs/`
67
+ 3. 如果 audit 无 FAIL: 任务完成
68
+ 4. 如果 audit 有 FAIL: 修复对应问题后重新 audit
69
+ 5. structure 中的 stability 和 description 字段必须保留
70
+ 6. relations 全量替换——代码 import 是唯一真实来源
71
+ 7. evolution 追加新的 ADR,不动已有的
72
+
73
+ ## 期望输出
74
+ - `docs/project-graph.yaml` 已更新
75
+ - `python scripts/basic-audit.py docs/` exit 0
76
+ - 提交信息: `auto-sync: update project-graph ({len(diff.splitlines())} files changed)`
77
+ """
78
+
79
+ out = Path(output_path)
80
+ out.parent.mkdir(parents=True, exist_ok=True)
81
+ out.write_text(template, encoding="utf-8")
82
+ return template
83
+
84
+
85
+ def main():
86
+ output = sys.argv[1] if len(sys.argv) > 1 else ".gnhf/sync-task.md"
87
+ template = render_template(output)
88
+ print(f"[gnhf] 任务模板已生成: {output}")
89
+ print(f"[gnhf] 使用方法: gnhf --agent claude --goal {output} --verify 'python scripts/basic-audit.py docs/'")
90
+
91
+
92
+ if __name__ == "__main__":
93
+ main()
@@ -0,0 +1,146 @@
1
+ """agentprecept MCP Server
2
+
3
+ 启动: agentprecept-mcp
4
+ 提供 6 个 tool: query / audit / diff / decision / handoff / design_gate
5
+ """
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ try:
10
+ from fastmcp import FastMCP
11
+ except ImportError:
12
+ print("需要安装 fastmcp: pip install fastmcp", file=sys.stderr)
13
+ sys.exit(1)
14
+
15
+ _scripts = Path(__file__).resolve().parent.parent / "scripts"
16
+
17
+
18
+ def _load_script(name):
19
+ path = _scripts / f"{name}.py"
20
+ spec = __import__("importlib.util", fromlist=["util"]).util
21
+ mod_spec = spec.spec_from_file_location(name, path)
22
+ mod = spec.module_from_spec(mod_spec)
23
+ mod_spec.loader.exec_module(mod)
24
+ return mod
25
+
26
+
27
+ basic_audit = _load_script("basic-audit")
28
+ sync_graph = _load_script("sync-graph")
29
+
30
+ mcp = FastMCP("agentprecept")
31
+
32
+
33
+ @mcp.tool
34
+ def project_graph_query(module: str = "", query_type: str = "relations") -> dict:
35
+ graph_path = Path("docs/project-graph.yaml")
36
+ if not graph_path.exists():
37
+ return {"error": "project-graph.yaml not found—run agentprecept sync first"}
38
+ import yaml
39
+ doc = yaml.safe_load(graph_path.read_text(encoding="utf-8")) or {}
40
+ result = {}
41
+ if query_type in ("relations", "all"):
42
+ rels = doc.get("relations") or []
43
+ if module:
44
+ rels = [r for r in rels if r.get("from","").startswith(module) or r.get("to","").startswith(module)]
45
+ result["relations"] = rels
46
+ result["count"] = len(rels)
47
+ if query_type in ("structure", "all"):
48
+ st = doc.get("structure") or {}
49
+ if module:
50
+ st = {k:v for k,v in st.items() if k.startswith(module)}
51
+ result["structure"] = st
52
+ if query_type in ("evolution", "all"):
53
+ result["evolution"] = doc.get("evolution") or []
54
+ return result
55
+
56
+
57
+ @mcp.tool
58
+ def audit_run(docs_dir: str = "docs") -> dict:
59
+ import io
60
+ old = sys.stdout
61
+ sys.stdout = io.StringIO()
62
+ results = []
63
+ for check, name in [
64
+ (basic_audit.check_naming, "naming"),
65
+ (basic_audit.check_broken_links, "broken_links"),
66
+ (basic_audit.check_numbering, "numbering"),
67
+ (basic_audit.check_skeleton, "skeleton"),
68
+ (basic_audit.check_graph_schema, "graph_schema"),
69
+ (basic_audit.check_design_trace, "design_trace"),
70
+ (basic_audit.check_coverage, "coverage"),
71
+ (basic_audit.check_dogfood, "dogfood"),
72
+ ]:
73
+ findings = check(docs_dir)
74
+ has_fail = any(f["severity"] == "FAIL" for f in findings)
75
+ results.append({"dimension": name, "status": "PASS" if not findings else "FAIL" if has_fail else "WARN", "findings": findings})
76
+ sys.stdout = old
77
+ fail = sum(1 for r in results if r["status"] == "FAIL")
78
+ warn = sum(1 for r in results if r["status"] == "WARN")
79
+ return {"results": results, "summary": {"FAIL": fail, "WARN": warn, "PASS": 8 - fail - warn}}
80
+
81
+
82
+ @mcp.tool
83
+ def sync_diff(src_dir: str = "src") -> dict:
84
+ graph_path = Path("docs/project-graph.yaml")
85
+ old = {}
86
+ if graph_path.exists():
87
+ import yaml
88
+ existing = yaml.safe_load(graph_path.read_text(encoding="utf-8")) or {}
89
+ old = {"structure": existing.get("structure",{}), "relations": existing.get("relations",[])}
90
+ new_structure = sync_graph.build_structure(src_dir)
91
+ all_relations = []
92
+ for fn in [sync_graph.build_python_relations, sync_graph.build_js_relations, sync_graph.build_db_relations, sync_graph.build_api_relations, sync_graph.build_frontend_relations, sync_graph.build_external_relations]:
93
+ all_relations.extend(fn(src_dir))
94
+ old_keys = set(old.get("structure",{}).keys())
95
+ new_keys = set(new_structure.keys())
96
+ old_rels = {(r.get("from"), r.get("to"), r.get("type")) for r in old.get("relations",[])}
97
+ new_rels = {(r["from"], r["to"], r["type"]) for r in all_relations}
98
+ from collections import Counter
99
+ types = Counter(r["type"] for r in all_relations)
100
+ return {"structure": {"added": sorted(new_keys - old_keys), "removed": sorted(old_keys - new_keys)}, "relations": {"added": len(new_rels - old_rels), "removed": len(old_rels - new_rels), "total": len(all_relations)}, "type_counts": dict(types), "needs_sync": len(new_rels) != len(old_rels)}
101
+
102
+
103
+ @mcp.tool
104
+ def decision_search(query: str = "") -> list:
105
+ p = Path("docs/L4_O01_design-rationale_设计依据.md")
106
+ if not p.exists():
107
+ return []
108
+ return [l.strip() for l in p.read_text(encoding="utf-8").split("\n") if l.strip().startswith("| 为什么") and (query.lower() in l.lower() if query else True)]
109
+
110
+
111
+ @mcp.tool
112
+ def handoff_read() -> dict:
113
+ p = Path("docs/HANDOFF.md")
114
+ if not p.exists():
115
+ return {"status": "HANDOFF not found"}
116
+ content = p.read_text(encoding="utf-8")
117
+ status = [l.replace("> 状态:", "").strip() for l in content.split("\n") if l.startswith("> 状态:")]
118
+ next_lines = []
119
+ in_next = False
120
+ for l in content.split("\n"):
121
+ if "## 下一步" in l:
122
+ in_next = True
123
+ elif in_next and l.strip().startswith(("1.","2.","3.","4.")):
124
+ next_lines.append(l.strip())
125
+ return {"status": status[0] if status else "", "next_step": " ".join(next_lines)}
126
+
127
+
128
+ @mcp.tool
129
+ def design_gate(module: str = "", operation: str = "modify") -> dict:
130
+ """Agent 准备修改代码前调用。返回模块的前置设计文档状态。"""
131
+ import json, subprocess, sys as _sys
132
+ result = subprocess.run(
133
+ [_sys.executable, str(_scripts / "design_gate_check.py"), "--module", module, "--json"],
134
+ capture_output=True, text=True, timeout=10
135
+ )
136
+ if result.returncode not in (0, 1, 2):
137
+ return {"status": "ERROR", "gates": [], "message": result.stderr}
138
+ return json.loads(result.stdout)
139
+
140
+
141
+ def main():
142
+ mcp.run()
143
+
144
+
145
+ if __name__ == "__main__":
146
+ main()
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentprecept
3
+ Version: 0.3.0
4
+ Summary: AgentPrecept — AI coding agent governance toolkit (precept, not monitoring)
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ License-File: LICENSE
8
+ Requires-Dist: PyYAML>=6.0
9
+ Requires-Dist: fastmcp>=2.0
10
+ Dynamic: license-file
@@ -0,0 +1,10 @@
1
+ agentprecept/__init__.py,sha256=YjweZwiuqRqpGUekhm8om30cW6nnFDXSIUSjaqo5djU,63
2
+ agentprecept/cli.py,sha256=gGtngxLBeVWHNHC__EwkKwJat6vjVeSgEMZKB7IvUD4,13406
3
+ agentprecept/gnhf_task.py,sha256=QqBh2aMXq_eV3PdGo3U_DSoe6K2uwpmRz8MtKroOB6E,2927
4
+ agentprecept/mcp_server.py,sha256=5k6e4nYa7AKYjVyyT7D6KU_tUsUMX-BvF9dhlpoTFBE,5853
5
+ agentprecept-0.3.0.dist-info/licenses/LICENSE,sha256=9k4RFMvDVpacIGuDfT5BF0W0C954KXA8sXMLJq47vU4,1083
6
+ agentprecept-0.3.0.dist-info/METADATA,sha256=gnjJPNDzeG30ByjtLpHa0lLtdS83dEywRIi-7r26g_E,289
7
+ agentprecept-0.3.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
8
+ agentprecept-0.3.0.dist-info/entry_points.txt,sha256=7KGD6lOycqexAaIHIy2e01OIRQcPX7ygprmx4gov2dg,103
9
+ agentprecept-0.3.0.dist-info/top_level.txt,sha256=gKDqQGj_Rtzv0mSj8CFNxyZNNzZ3q2v2T-ydokqE3MQ,13
10
+ agentprecept-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ agentprecept = agentprecept.cli:main
3
+ agentprecept-mcp = agentprecept.mcp_server:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 agent-compass contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ agentprecept