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.
@@ -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,8 @@
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
+ """Amon Hen Code — AI agent grounded in team knowledge."""
7
+
8
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ # Confidential and Proprietary
2
+ # Copyright (c) 2024-2026 AmonHen AI.
3
+ # All rights reserved.
4
+ # Unauthorized copying or distribution is strictly prohibited.
5
+
@@ -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
+ )