amonhen-code 0.1.0__tar.gz
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.
- amonhen_code-0.1.0/.gitignore +50 -0
- amonhen_code-0.1.0/PKG-INFO +13 -0
- amonhen_code-0.1.0/assets/icon-256.png +0 -0
- amonhen_code-0.1.0/assets/icon.png +0 -0
- amonhen_code-0.1.0/pyproject.toml +23 -0
- amonhen_code-0.1.0/src/amonhen_code/__init__.py +8 -0
- amonhen_code-0.1.0/src/amonhen_code/agents/__init__.py +5 -0
- amonhen_code-0.1.0/src/amonhen_code/agents/grounded.py +99 -0
- amonhen_code-0.1.0/src/amonhen_code/cli.py +91 -0
- amonhen_code-0.1.0/src/amonhen_code/context.py +67 -0
- amonhen_code-0.1.0/src/amonhen_code/hooks.py +61 -0
- amonhen_code-0.1.0/src/amonhen_code/prompts.py +29 -0
- amonhen_code-0.1.0/src/amonhen_code/tools.py +71 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Python bytecode
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
|
|
5
|
+
# Virtual environments
|
|
6
|
+
.venv/
|
|
7
|
+
.venv-*/
|
|
8
|
+
|
|
9
|
+
# Environment variables
|
|
10
|
+
.env
|
|
11
|
+
|
|
12
|
+
# OS files
|
|
13
|
+
.DS_Store
|
|
14
|
+
|
|
15
|
+
# Editor settings
|
|
16
|
+
.vscode/
|
|
17
|
+
.claude/
|
|
18
|
+
CLAUDE.md
|
|
19
|
+
backup.sh
|
|
20
|
+
|
|
21
|
+
# VSCode extension (has its own build artifacts / node_modules)
|
|
22
|
+
vscode-amonhen/
|
|
23
|
+
|
|
24
|
+
# Large / Box-synced plan docs (track manually when needed)
|
|
25
|
+
docs/AmonHen_VSCode_Extension_Project_Plan.md
|
|
26
|
+
docs/AmonHen_iOS_App_Project_Plan.pdf
|
|
27
|
+
docs/ios-app-project-plan.md
|
|
28
|
+
docs/Amon Hen - Delivery Mechanisms.md
|
|
29
|
+
docs/AmonHen-Brand-System-v1.md
|
|
30
|
+
docs/AmonHen-NDA-and-IP-Assignment-Agreement.md
|
|
31
|
+
docs/architecture/amonhen-db-flow.html
|
|
32
|
+
|
|
33
|
+
# SQLite databases
|
|
34
|
+
*.db
|
|
35
|
+
|
|
36
|
+
# Large PDF audit reports
|
|
37
|
+
docs/AmonHen_Enterprise_Readiness_Audit_*.pdf
|
|
38
|
+
|
|
39
|
+
# Uploaded documents (local filesystem storage)
|
|
40
|
+
document_storage/
|
|
41
|
+
!document_storage/.gitkeep
|
|
42
|
+
|
|
43
|
+
# iOS app – source tracked; build artifacts excluded by ios/.gitignore
|
|
44
|
+
|
|
45
|
+
# UI image assets not yet committed
|
|
46
|
+
src/amonhen/ui/Images/amonhen_128x128.png
|
|
47
|
+
|
|
48
|
+
# Local MCP server config
|
|
49
|
+
.mcp.json
|
|
50
|
+
mcp-server-amonhen/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: amonhen-code
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Amon Hen Code — AI agent grounded in team knowledge
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: claude-agent-sdk>=0.1.40
|
|
8
|
+
Requires-Dist: click>=8.0
|
|
9
|
+
Requires-Dist: mcp-server-amonhen>=0.1.0
|
|
10
|
+
Requires-Dist: rich>=13.0
|
|
11
|
+
Description-Content-Type: text/plain
|
|
12
|
+
|
|
13
|
+
Amon Hen Code — AI agent grounded in team knowledge.
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "amonhen-code"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Amon Hen Code — AI agent grounded in team knowledge"
|
|
9
|
+
readme = {text = "Amon Hen Code — AI agent grounded in team knowledge.", content-type = "text/plain"}
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"claude-agent-sdk>=0.1.40",
|
|
14
|
+
"mcp-server-amonhen>=0.1.0",
|
|
15
|
+
"click>=8.0",
|
|
16
|
+
"rich>=13.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
amonhen-code = "amonhen_code.cli:main"
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.wheel]
|
|
23
|
+
packages = ["src/amonhen_code"]
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Confidential and Proprietary
|
|
2
|
+
# Copyright (c) 2024-2026 AmonHen AI.
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
# Unauthorized copying or distribution is strictly prohibited.
|
|
5
|
+
|
|
6
|
+
"""Grounded Code Agent -- modify code while enforcing Amon Hen project rules."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from claude_agent_sdk import (
|
|
13
|
+
AssistantMessage,
|
|
14
|
+
HookMatcher,
|
|
15
|
+
ClaudeAgentOptions,
|
|
16
|
+
ResultMessage,
|
|
17
|
+
TextBlock,
|
|
18
|
+
query,
|
|
19
|
+
)
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
|
|
22
|
+
from amonhen_code.context import fetch_project_context, format_context_for_prompt
|
|
23
|
+
from amonhen_code.hooks import enforce_rules_on_write, track_file_changes
|
|
24
|
+
from amonhen_code.prompts import GROUNDED_CODE
|
|
25
|
+
from amonhen_code.tools import build_amonhen_server
|
|
26
|
+
|
|
27
|
+
console = Console()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def run_grounded_agent(
|
|
31
|
+
*,
|
|
32
|
+
project_id: str,
|
|
33
|
+
cwd: str = ".",
|
|
34
|
+
max_turns: int | None = None,
|
|
35
|
+
task: str | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Launch the Grounded Code Agent."""
|
|
38
|
+
|
|
39
|
+
# 1. Fetch project context from Amon Hen (no LLM call).
|
|
40
|
+
console.print("[dim]Fetching project context from Amon Hen...[/dim]")
|
|
41
|
+
context_result = await fetch_project_context(project_id)
|
|
42
|
+
context_block = format_context_for_prompt(context_result)
|
|
43
|
+
|
|
44
|
+
item_count = len(context_result.get("context", []))
|
|
45
|
+
console.print(f"[dim]Loaded {item_count} context items.[/dim]")
|
|
46
|
+
|
|
47
|
+
# 2. Build system prompt with injected context.
|
|
48
|
+
system_prompt = GROUNDED_CODE.format(context_block=context_block)
|
|
49
|
+
|
|
50
|
+
# 3. Build in-process MCP server for Amon Hen tools.
|
|
51
|
+
amonhen_server = build_amonhen_server()
|
|
52
|
+
|
|
53
|
+
# 4. Configure the agent.
|
|
54
|
+
prompt = task or (
|
|
55
|
+
"What would you like me to work on? "
|
|
56
|
+
"I will follow the project rules and decisions."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
options = ClaudeAgentOptions(
|
|
60
|
+
system_prompt=system_prompt,
|
|
61
|
+
cwd=str(Path(cwd).resolve()),
|
|
62
|
+
permission_mode="acceptEdits",
|
|
63
|
+
max_turns=max_turns,
|
|
64
|
+
mcp_servers={"amonhen": amonhen_server},
|
|
65
|
+
allowed_tools=[
|
|
66
|
+
"Read",
|
|
67
|
+
"Write",
|
|
68
|
+
"Edit",
|
|
69
|
+
"Bash",
|
|
70
|
+
"Glob",
|
|
71
|
+
"Grep",
|
|
72
|
+
"mcp__amonhen__ask_amon_hen",
|
|
73
|
+
"mcp__amonhen__propose_context_item",
|
|
74
|
+
],
|
|
75
|
+
hooks={
|
|
76
|
+
"PreToolUse": [
|
|
77
|
+
HookMatcher(
|
|
78
|
+
matcher="Write|Edit",
|
|
79
|
+
hooks=[enforce_rules_on_write],
|
|
80
|
+
),
|
|
81
|
+
],
|
|
82
|
+
"PostToolUse": [
|
|
83
|
+
HookMatcher(
|
|
84
|
+
matcher="Write|Edit",
|
|
85
|
+
hooks=[track_file_changes],
|
|
86
|
+
),
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# 5. Run the agent.
|
|
92
|
+
|
|
93
|
+
async for message in query(prompt=prompt, options=options):
|
|
94
|
+
if isinstance(message, ResultMessage):
|
|
95
|
+
console.print(f"\n{message.result}")
|
|
96
|
+
elif isinstance(message, AssistantMessage):
|
|
97
|
+
for block in message.content:
|
|
98
|
+
if isinstance(block, TextBlock):
|
|
99
|
+
console.print(block.text)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Confidential and Proprietary
|
|
2
|
+
# Copyright (c) 2024-2026 AmonHen AI.
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
# Unauthorized copying or distribution is strictly prohibited.
|
|
5
|
+
|
|
6
|
+
"""CLI entry point for Amon Hen Code."""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
_MOUNTAIN_ART = (
|
|
17
|
+
" /\\\n"
|
|
18
|
+
" / \\ /\\\n"
|
|
19
|
+
" / \\ / \\\n"
|
|
20
|
+
" / \\_/ \\\n"
|
|
21
|
+
" /________________\\"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _print_banner() -> None:
|
|
26
|
+
console.print(Text(_MOUNTAIN_ART, style="#5E81F4"))
|
|
27
|
+
console.print("[bold] Amon Hen Code[/bold] [dim]v0.1.0[/dim]")
|
|
28
|
+
console.print("[dim] Clarity for Complex Projects.[/dim]\n")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@click.group()
|
|
32
|
+
@click.version_option(version="0.1.0")
|
|
33
|
+
def main():
|
|
34
|
+
"""Amon Hen Code -- AI agent grounded in team knowledge."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@main.command()
|
|
38
|
+
def login():
|
|
39
|
+
"""Authenticate with Amon Hen (Auth0 device flow)."""
|
|
40
|
+
from mcp_amonhen.auth import login_interactive
|
|
41
|
+
|
|
42
|
+
login_interactive()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@main.command()
|
|
46
|
+
def logout():
|
|
47
|
+
"""Clear stored authentication tokens."""
|
|
48
|
+
from mcp_amonhen.auth import logout
|
|
49
|
+
|
|
50
|
+
logout()
|
|
51
|
+
console.print("[green]Logged out.[/green]")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@main.command()
|
|
55
|
+
def projects():
|
|
56
|
+
"""List your Amon Hen projects."""
|
|
57
|
+
from mcp_amonhen.client import AmonHenClient
|
|
58
|
+
|
|
59
|
+
async def _list():
|
|
60
|
+
client = AmonHenClient()
|
|
61
|
+
projs = await client.list_projects()
|
|
62
|
+
if not projs:
|
|
63
|
+
console.print("[dim]No projects found.[/dim]")
|
|
64
|
+
return
|
|
65
|
+
for p in projs:
|
|
66
|
+
name = p.get("name", "?")
|
|
67
|
+
pid = p.get("id", "?")
|
|
68
|
+
count = p.get("context_count", 0)
|
|
69
|
+
console.print(f" {name} [dim]{pid}[/dim] ({count} items)")
|
|
70
|
+
|
|
71
|
+
asyncio.run(_list())
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@main.command()
|
|
75
|
+
@click.option("--project", "-p", required=True, help="Amon Hen project UUID")
|
|
76
|
+
@click.option("--cwd", "-d", default=".", help="Working directory for the agent")
|
|
77
|
+
@click.option("--max-turns", "-t", default=None, type=int, help="Maximum agent turns")
|
|
78
|
+
@click.option("--task", "-m", default=None, help="Task description for the agent")
|
|
79
|
+
def code(project, cwd, max_turns, task):
|
|
80
|
+
"""Grounded Code Agent -- modify code while enforcing project rules."""
|
|
81
|
+
from amonhen_code.agents.grounded import run_grounded_agent
|
|
82
|
+
|
|
83
|
+
_print_banner()
|
|
84
|
+
asyncio.run(
|
|
85
|
+
run_grounded_agent(
|
|
86
|
+
project_id=project,
|
|
87
|
+
cwd=cwd,
|
|
88
|
+
max_turns=max_turns,
|
|
89
|
+
task=task,
|
|
90
|
+
)
|
|
91
|
+
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Confidential and Proprietary
|
|
2
|
+
# Copyright (c) 2024-2026 AmonHen AI.
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
# Unauthorized copying or distribution is strictly prohibited.
|
|
5
|
+
|
|
6
|
+
"""Fetch and format project context from Amon Hen for system prompt injection."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from mcp_amonhen.client import AmonHenClient
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def fetch_project_context(project_id: str, intent: str | None = None) -> dict:
|
|
14
|
+
"""Fetch raw assembled context items via GET /v1/projects/{id}/context."""
|
|
15
|
+
client = AmonHenClient()
|
|
16
|
+
return await client.get_context(project_id, intent=intent)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def format_context_for_prompt(context_result: dict) -> str:
|
|
20
|
+
"""Format assembled context items as a structured text block for system prompt injection.
|
|
21
|
+
|
|
22
|
+
Groups items by type in priority order: rules -> decisions -> code_context -> notes.
|
|
23
|
+
"""
|
|
24
|
+
items = context_result.get("context", [])
|
|
25
|
+
if not items:
|
|
26
|
+
return "(No project context items found.)"
|
|
27
|
+
|
|
28
|
+
buckets: dict[str, list[str]] = {
|
|
29
|
+
"rule": [],
|
|
30
|
+
"decision": [],
|
|
31
|
+
"code_context": [],
|
|
32
|
+
"note": [],
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for item in items:
|
|
36
|
+
item_type = item.get("item_type", "note")
|
|
37
|
+
content = item.get("content", "")
|
|
38
|
+
if item_type in buckets:
|
|
39
|
+
buckets[item_type].append(content)
|
|
40
|
+
|
|
41
|
+
sections = []
|
|
42
|
+
|
|
43
|
+
if buckets["rule"]:
|
|
44
|
+
sections.append("## RULES (absolute law — must be followed)")
|
|
45
|
+
for i, r in enumerate(buckets["rule"], 1):
|
|
46
|
+
sections.append(f"{i}. {r}")
|
|
47
|
+
sections.append("")
|
|
48
|
+
|
|
49
|
+
if buckets["decision"]:
|
|
50
|
+
sections.append("## DECISIONS (committed team choices — apply, do not revisit)")
|
|
51
|
+
for i, d in enumerate(buckets["decision"], 1):
|
|
52
|
+
sections.append(f"{i}. {d}")
|
|
53
|
+
sections.append("")
|
|
54
|
+
|
|
55
|
+
if buckets["code_context"]:
|
|
56
|
+
sections.append("## CODE CONTEXT (implementation patterns)")
|
|
57
|
+
for i, c in enumerate(buckets["code_context"], 1):
|
|
58
|
+
sections.append(f"{i}. {c}")
|
|
59
|
+
sections.append("")
|
|
60
|
+
|
|
61
|
+
if buckets["note"]:
|
|
62
|
+
sections.append("## NOTES (observations and context)")
|
|
63
|
+
for i, n in enumerate(buckets["note"], 1):
|
|
64
|
+
sections.append(f"{i}. {n}")
|
|
65
|
+
sections.append("")
|
|
66
|
+
|
|
67
|
+
return "\n".join(sections)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Confidential and Proprietary
|
|
2
|
+
# Copyright (c) 2024-2026 AmonHen AI.
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
# Unauthorized copying or distribution is strictly prohibited.
|
|
5
|
+
|
|
6
|
+
"""Agent SDK hooks for Amon Hen Code."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Accumulate file changes during a session for summary.
|
|
14
|
+
_session_changes: list[dict[str, Any]] = []
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def enforce_rules_on_write(
|
|
18
|
+
input_data: dict[str, Any],
|
|
19
|
+
tool_use_id: str | None,
|
|
20
|
+
context: dict[str, Any],
|
|
21
|
+
) -> dict[str, Any]:
|
|
22
|
+
"""PreToolUse hook: remind the agent to check rules before file writes."""
|
|
23
|
+
tool_name = input_data.get("tool_name", "")
|
|
24
|
+
if tool_name in ("Write", "Edit"):
|
|
25
|
+
return {
|
|
26
|
+
"systemMessage": (
|
|
27
|
+
"REMINDER: Before completing this file write, verify that the "
|
|
28
|
+
"content complies with all project RULES from the Amon Hen "
|
|
29
|
+
"context. If any rule is violated, modify the content to comply "
|
|
30
|
+
"before proceeding."
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
return {}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def track_file_changes(
|
|
37
|
+
input_data: dict[str, Any],
|
|
38
|
+
tool_use_id: str | None,
|
|
39
|
+
context: dict[str, Any],
|
|
40
|
+
) -> dict[str, Any]:
|
|
41
|
+
"""PostToolUse hook: track file modifications for session summary."""
|
|
42
|
+
tool_name = input_data.get("tool_name", "")
|
|
43
|
+
if tool_name in ("Write", "Edit"):
|
|
44
|
+
file_path = input_data.get("tool_input", {}).get("file_path", "unknown")
|
|
45
|
+
_session_changes.append(
|
|
46
|
+
{
|
|
47
|
+
"tool": tool_name,
|
|
48
|
+
"file_path": file_path,
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
return {}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_session_changes() -> list[dict[str, Any]]:
|
|
55
|
+
"""Return the accumulated file changes for this session."""
|
|
56
|
+
return list(_session_changes)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def clear_session_changes() -> None:
|
|
60
|
+
"""Reset the session change tracker."""
|
|
61
|
+
_session_changes.clear()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Confidential and Proprietary
|
|
2
|
+
# Copyright (c) 2024-2026 AmonHen AI.
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
# Unauthorized copying or distribution is strictly prohibited.
|
|
5
|
+
|
|
6
|
+
"""System prompt templates for Amon Hen Code agent modes."""
|
|
7
|
+
|
|
8
|
+
GROUNDED_CODE = """\
|
|
9
|
+
You are a senior software engineer working on a codebase governed by explicit \
|
|
10
|
+
team rules and decisions stored in Amon Hen.
|
|
11
|
+
|
|
12
|
+
AUTHORITATIVE PROJECT CONTEXT:
|
|
13
|
+
{context_block}
|
|
14
|
+
|
|
15
|
+
RULES listed above are absolute law. Every code change you make MUST comply \
|
|
16
|
+
with these rules. If a rule conflicts with your general knowledge, the rule wins.
|
|
17
|
+
|
|
18
|
+
DECISIONS represent committed team choices. Apply them; do not revisit them \
|
|
19
|
+
unless the user explicitly asks.
|
|
20
|
+
|
|
21
|
+
CODE CONTEXT shows established implementation patterns. Follow them for \
|
|
22
|
+
consistency.
|
|
23
|
+
|
|
24
|
+
When modifying or generating code:
|
|
25
|
+
1. Check every change against the project rules before writing
|
|
26
|
+
2. Follow established patterns from code context items
|
|
27
|
+
3. If unsure whether a change violates a rule, use the ask_amon_hen tool to verify
|
|
28
|
+
4. After significant changes, briefly note which rules or decisions governed your approach
|
|
29
|
+
"""
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Confidential and Proprietary
|
|
2
|
+
# Copyright (c) 2024-2026 AmonHen AI.
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
# Unauthorized copying or distribution is strictly prohibited.
|
|
5
|
+
|
|
6
|
+
"""In-process SDK MCP tools for Amon Hen operations."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from claude_agent_sdk import tool, create_sdk_mcp_server
|
|
13
|
+
from mcp_amonhen.client import AmonHenClient
|
|
14
|
+
|
|
15
|
+
_client = AmonHenClient()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@tool(
|
|
19
|
+
"ask_amon_hen",
|
|
20
|
+
"Ask Amon Hen for project-scoped advisory on a technical question. "
|
|
21
|
+
"Use this when you need guidance grounded in the team's rules and decisions.",
|
|
22
|
+
{"project_id": str, "question": str},
|
|
23
|
+
)
|
|
24
|
+
async def ask_amon_hen(args: dict[str, Any]) -> dict[str, Any]:
|
|
25
|
+
"""Get advisory from Amon Hen, grounded in project context."""
|
|
26
|
+
result = await _client.advise(args["question"], args["project_id"])
|
|
27
|
+
advice = result.get("advice", "No advice returned.")
|
|
28
|
+
hints = result.get("follow_up_hints", [])
|
|
29
|
+
text = advice
|
|
30
|
+
if hints:
|
|
31
|
+
text += "\n\nFollow-up questions you might consider:\n"
|
|
32
|
+
text += "\n".join(f"- {h}" for h in hints)
|
|
33
|
+
return {"content": [{"type": "text", "text": text}]}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@tool(
|
|
37
|
+
"propose_context_item",
|
|
38
|
+
"Propose a new rule, decision, note, or code_context item to the Amon Hen project. "
|
|
39
|
+
"Use this to capture knowledge discovered while working on the codebase.",
|
|
40
|
+
{
|
|
41
|
+
"project_id": str,
|
|
42
|
+
"item_type": str,
|
|
43
|
+
"content": str,
|
|
44
|
+
"rationale": str,
|
|
45
|
+
},
|
|
46
|
+
)
|
|
47
|
+
async def propose_context_item(args: dict[str, Any]) -> dict[str, Any]:
|
|
48
|
+
"""Create a new context item in Amon Hen."""
|
|
49
|
+
result = await _client.create_context(
|
|
50
|
+
project_id=args["project_id"],
|
|
51
|
+
item_type=args["item_type"],
|
|
52
|
+
content=args["content"],
|
|
53
|
+
rationale=args.get("rationale"),
|
|
54
|
+
)
|
|
55
|
+
item_id = result.get("id", "unknown")
|
|
56
|
+
return {
|
|
57
|
+
"content": [
|
|
58
|
+
{
|
|
59
|
+
"type": "text",
|
|
60
|
+
"text": f"Created {args['item_type']} item (id: {item_id}).",
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def build_amonhen_server():
|
|
67
|
+
"""Create the in-process MCP server with Amon Hen tools."""
|
|
68
|
+
return create_sdk_mcp_server(
|
|
69
|
+
name="amonhen",
|
|
70
|
+
tools=[ask_amon_hen, propose_context_item],
|
|
71
|
+
)
|