Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .agents/skills/ux-assessment
13 changes: 8 additions & 5 deletions .claude/hooks/guard-pr-reviewer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ command -v jq >/dev/null 2>&1 || exit 0
input="$(cat)"
[ -n "$input" ] || exit 0

subagent_type="$(printf '%s' "$input" | jq -r '.tool_input.subagent_type // ""' 2>/dev/null)" || exit 0
prompt="$(printf '%s' "$input" | jq -r '.tool_input.prompt // ""' 2>/dev/null)" || exit 0
subagent_type="$(printf '%s' "$input" | jq -r '.tool_input.subagent_type // .tool_input.agent_type // .tool_input.task_name // ""' 2>/dev/null)" || exit 0
prompt="$(printf '%s' "$input" | jq -r '.tool_input.prompt // .tool_input.message // ""' 2>/dev/null)" || exit 0

# Already the correct reviewer -> allow.
[ "$subagent_type" = "pr-reviewer" ] && exit 0
# Already the correct reviewer -> allow. Codex task names use underscores while
# the custom agent profile and Claude subagent type use a hyphen.
case "$subagent_type" in
pr-reviewer|pr_reviewer) exit 0 ;;
esac

# Heuristic: a PR/merge-gate review delegation mentions BOTH a review verb AND a
# PR/merge context. Both must be present to deny, to keep false positives low.
Expand All @@ -36,7 +39,7 @@ pr_re='\bPR\b|pull request|pull/[0-9]|merge gate|merge-gate|origin/main\.\.\.|--

if printf '%s' "$prompt" | grep -iqE "$review_re" \
&& printf '%s' "$prompt" | grep -iqE "$pr_re"; then
reason='Merge-gate / PR reviews must be dispatched to subagent_type=pr-reviewer (.claude/agents/pr-reviewer.md) — it carries the required posting instructions: post findings ON the PR via `gh pr review <N> --comment` (verdict in the body; NEVER --request-changes/--approve on your own PR), falling back to `gh pr comment`. Re-spawn this review with subagent_type: pr-reviewer.'
reason='Merge-gate / PR reviews must use the independent pr-reviewer profile (.codex/agents/pr-reviewer.toml for Codex; .claude/agents/pr-reviewer.md for Claude). It carries the required posting instructions: post findings ON the PR via `gh pr review <N> --comment` (verdict in the body; NEVER --request-changes/--approve on your own PR), falling back to `gh pr comment`. Re-dispatch with the pr-reviewer profile (or Codex task_name pr_reviewer with that full rubric).'
jq -n --arg r "$reason" \
'{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}'
exit 0
Expand Down
4 changes: 4 additions & 0 deletions .claude/skills/ux-assessment/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "HexGraph UX Assessment"
short_description: "Run HexGraph’s two-role UX walkthrough"
default_prompt: "Use $ux-assessment to run a role-separated HexGraph UI contract walkthrough."
23 changes: 23 additions & 0 deletions .codex/agents/pr-reviewer.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name = "pr-reviewer"
description = "Independent HexGraph merge-gate reviewer for correctness, security invariants, tests, docs, and migrations; use only on code written by another agent."
sandbox_mode = "workspace-write"
developer_instructions = """
You are the independent PR reviewer for the HexGraph merge gate. You did not write the code under review.

Before reviewing, read AGENTS.md, CLAUDE.md (especially "The merge gate"), and the relevant entries in .codex/memories/MEMORY.md when that local file exists.

1. Establish the PR number and review both `gh pr diff <N>` and `git diff origin/main...HEAD`. Do two explicit passes:
- correctness and test quality, including behavior regressions, edge cases, and missing coverage;
- security and repository invariants: loopback-only binding, all hostile target-byte handling inside the sandbox, secrets never logged/stored/returned, and execution/network/rehosting relaxed only at the policy seam.
Also verify that user-facing behavior, UX-contract changes, docs, and model migrations ship in the same PR when required.

2. Posting is the primary deliverable. Before reporting to the parent, write the verdict and every finding (severity, file:line, impact, and concrete fix) to a temporary file and post it with:
`gh pr review <N> --comment --body-file <file>`
Never use `--request-changes` or `--approve` on the owner's own PR. Put APPROVE or REQUEST-CHANGES in the body text. If the review command fails, immediately fall back to `gh pr comment <N> --body-file <file>`.

3. Add line-level comments for concrete findings when practical. Verify that the summary and inline comments landed with `gh pr view <N> --json reviews,comments` and/or the pull-request comments API. Retry with the plain-comment fallback until the durable PR record is confirmed.

4. Fix only blocking correctness or security issues in the current worktree. Commit and push those fixes using the repository's imperative subject rules, reference the review comment, and rerun affected tests. Leave non-blocking findings for discussion. Never merge; the dispatching parent owns the merge.

5. Report only after posting is verified. Return the verdict, findings with severity, fixes versus discussion items, test results, and exact verification evidence proving the findings are on the PR.
"""
18 changes: 18 additions & 0 deletions .codex/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"description": "HexGraph project hooks imported from the Claude project setup.",
"hooks": {
"PreToolUse": [
{
"matcher": "Agent",
"hooks": [
{
"type": "command",
"command": "\"$(git rev-parse --show-toplevel)/.claude/hooks/guard-pr-reviewer.sh\"",
"timeout": 10,
"statusMessage": "Checking PR-review dispatch"
}
]
}
]
}
}
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,9 @@ src/hexgraph/_build_info.py
!.claude/hooks/
!.claude/settings.json

# Local project-memory bridge. It can contain real-target history and must never
# enter this public repository; tracked Codex config lives elsewhere in .codex/.
.codex/memories

# Potentially sensitive docs
docs/dev/
docs/dev/
25 changes: 25 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# HexGraph Codex instructions

## Shared project guidance

- Before doing repository work, read `CLAUDE.md` completely. It is the shared, authoritative project guide for HexGraph even when the active agent is Codex.
- Then read `README.md` and inspect the current branch, recent history, and relevant open work as the task requires.
- Apply every security, architecture, testing, worktree, PR, documentation, and public-repository confidentiality rule in `CLAUDE.md` unless this file gives a Codex-specific translation below.

## Project memory

- At the start of work that could benefit from prior implementation history, read `.codex/memories/MEMORY.md` if it exists. It is a local-only link to this directory's Claude project memory.
- Load only the linked memory notes relevant to the current task. Treat them as historical context that may be stale; verify current behavior in code, tests, git history, and open PRs.
- Never commit `.codex/memories/` or quote real-engagement identifiers from it in code, docs, commits, PRs, or review comments.

## Codex translations

- References in `CLAUDE.md` to Claude Code, the `Agent` tool, `Read`, `Grep`, or Claude skills mean the equivalent Codex client, subagent, file-inspection, search, or project-skill capability.
- For substantial implementation work, follow the repository worktree and branch rules. The Codex app's isolated worktree support satisfies the dedicated-worktree requirement.
- For the merge gate, delegate the independent review to the project custom agent named `pr-reviewer`. The reviewer must not be the agent that authored the change. If that profile is unavailable, use a separate subagent and include the full rubric from `.codex/agents/pr-reviewer.toml`.
- The Claude-specific `Co-Authored-By: Claude Opus ...` trailer applies only to work authored by that Claude model. Do not falsely add it to Codex-authored commits; preserve every other commit-message rule.
- The `ux-assessment` workflow is exposed to Codex from `.agents/skills/ux-assessment` and remains shared with Claude's source skill.

## Shell commands

- The global RTK instruction remains in force: prefix shell commands with `rtk`, including commands copied from `CLAUDE.md` examples.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,18 @@ just serve # → http://127.0.0.1:8765
Either one hands off to an interactive setup wizard. The wizard walks you through the optional
features, and for each one that relaxes the security posture it shows you the implication and asks you
to confirm before turning it on. It then writes your settings and builds the images you picked, and it
can optionally register HexGraph's MCP server with a coding agent and install the VR skill for you
(both local-only, no secret). If you accept the defaults you stay in the static-only posture;
can optionally register HexGraph's MCP server with a coding agent and install the VR skill for you.
When you opt in to the skill, it detects Claude Code and Codex on your `PATH` and installs the same
bundle into both clients' native user-level skill directories (both local-only, no secret). If you
accept the defaults you stay in the static-only posture;
everything beyond that is something you turn on yourself, with eyes open. To skip the prompts and take
the static-only defaults, pass `--yes` (`just setup --yes` or `./setup.sh --yes`). For the wizard, the
manual step-by-step, the non-interactive CI mode, and Ghidra, see **[docs/setup.md](docs/setup.md)**.

After a later `git pull`, `just refresh` (a.k.a. `just setup --refresh`) is a quick sanity-sync: it
keeps your configuration and rebuilds only what's stale — the package, the web UI, any images whose
Dockerfile moved, the MCP registration, and the VR skill. Run it before `just serve` to be sure
Dockerfile moved, the MCP registration, and every detected Claude/Codex copy of an already-installed
VR skill. Run it before `just serve` to be sure
you're on the latest build of everything.

> To install `just` without sudo:
Expand Down
10 changes: 6 additions & 4 deletions docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ hypotheses, and annotations, and run sandboxed tasks.

The setup wizard can do this for you. When you run `hexgraph setup` interactively it offers to
register the MCP server with the agent of your choice (for this project or globally) and to install
the VR skill, so you usually do not need to wire it up by hand. The commands above are the manual path
the VR skill for every detected Claude Code and Codex client, so you usually do not need to wire it up
by hand. The commands above are the manual path
if you skipped that step or want to script it.

The MCP server speaks JSON-RPC over stdio, and your agent spawns it on demand. Run it by hand and it
Expand Down Expand Up @@ -140,7 +141,8 @@ tools even when a per-feature probe still passes) — rebuild with `just sandbox

```bash
hexgraph mcp install [--agent claude|codex|gemini] # print registration steps
hexgraph mcp install --write-skill .claude/skills # also install the VR skill
hexgraph mcp install --write-skill ~/.claude/skills # install for Claude Code
hexgraph mcp install --write-skill ~/.agents/skills # install for Codex
hexgraph mcp --tools read,write # serve a restricted tool set
```

Expand All @@ -150,8 +152,8 @@ The spine teaches the whole engagement arc (ingest a path, orient, decompose the
across parallel sub-agents, prove, and synthesize) and routes the agent to the matching sub-file
when it enters a phase, so the deep methodology for fuzzing or live-surface assessment only costs
context when it is actually being used. `--write-skill` emits the whole set into the skill
directory; `--print-skill` prints the entire bundle as one document for a Codex or gemini system
prompt that cannot read the sub-files on demand.
directory; `--print-skill` prints the entire bundle as one document for a system prompt that cannot
read the sub-files on demand.

### Tool results, the substrate, and the curation contract

Expand Down
15 changes: 10 additions & 5 deletions docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@ something you opt into, informed.

Near the end the wizard also offers to wire HexGraph up to a coding agent, if you want to drive it
that way. It can register HexGraph's MCP server with Claude Code, Codex, or gemini-cli (you pick the
agent and whether to register it just for this project or for all of them), and it can drop the VR
skill, the file that teaches the agent the workflow and the hostile-target rules, wherever you like
(your global `~/.claude/skills`, a project `.claude/skills`, or a path you type in). Both steps are
agent and whether to register it just for this project or for all of them), and it can install the VR
skill, the file that teaches the agent the workflow and the hostile-target rules. If Claude Code or
Codex are present on your `PATH`, one confirmation installs the same bundle for every detected client:
`~/.claude/skills/hexgraph-vr` for Claude Code and `~/.agents/skills/hexgraph-vr` for Codex. If neither
is detected, the wizard lets you choose a Claude project directory, a Codex project directory, or a
custom path. Both steps are
just local edits to the agent's own config and a skill file on disk: nothing goes over the network,
and no secret is written, because the MCP command carries no key (the server reads any key from your
environment or `config.toml` when it runs). Both are optional and you can decline either one, and
Expand Down Expand Up @@ -78,8 +81,10 @@ actually stale. Concretely it reinstalls the package when its version changed, r
the bundle is older than the front-end sources, rebuilds any image you've already built whose
Dockerfile has moved (preserving your Ghidra choice — and rebuilding *with* Ghidra if your settings
ask for headless Ghidra but the current image happens to lack it), re-affirms wherever the `hexgraph`
MCP server is registered, regenerates the VR skill into whatever location it's already installed,
and applies any pending database migration. Run it before `just serve` whenever you want to be sure
MCP server is registered, regenerates the VR skill into whatever location it is already installed,
adds a missing native user-level copy when another supported agent is detected (but only after a
user-level skill install has already opted in), and applies any pending database migration. Run it
before `just serve` whenever you want to be sure
you're on the latest build of everything.

## Manual install (or adding Ghidra)
Expand Down
69 changes: 56 additions & 13 deletions src/hexgraph/agent/agent_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,37 @@ def register_agent(agent: str, scope: str = "user", project_dir: str | None = No
}


def default_skill_dir() -> str:
"""The default destination for the VR skill: the user-global Claude skills dir."""
SKILL_AGENTS = ("claude", "codex")


def skill_dir_for_agent(agent: str) -> str:
"""Return the native user-global skill directory for a supported agent."""
import os

return os.path.join(os.path.expanduser("~"), ".claude", "skills")
home = os.path.expanduser("~")
if agent == "claude":
return os.path.join(home, ".claude", "skills")
if agent == "codex":
return os.path.join(home, ".agents", "skills")
raise ValueError(f"unknown skill host {agent!r}; choose one of {SKILL_AGENTS}")


def default_skill_dir() -> str:
"""Back-compatible default destination: Claude Code's user-global skill directory."""
return skill_dir_for_agent("claude")


def detected_skill_targets() -> list[tuple[str, str]]:
"""Installed skill-capable agents and their native user-global directories.

Detection intentionally follows the executable on PATH: setup runs from a shell, and
this avoids treating stale config left by an uninstalled client as an active install.
"""
return [
(agent, skill_dir_for_agent(agent))
for agent in SKILL_AGENTS
if shutil.which(agent) is not None
]


# The skill bundle's spine file — its presence under <base>/hexgraph-vr marks an install.
Expand All @@ -201,19 +227,34 @@ def default_skill_dir() -> str:


def detect_skill_dirs(project_dir: str | None = None) -> list[str]:
"""Base dirs where the VR skill is ALREADY installed (so a refresh can regenerate it
in place, never inventing a new location).
"""Base dirs a refresh should regenerate.

Existing user-global installs opt in to the skill across supported installed agents:
if one native copy exists, refresh also returns the native directory for each detected
Claude Code/Codex client. This lets an existing Claude user gain the Codex copy after an
upgrade without making refresh install the skill for someone who declined it entirely.

A base counts as installed when `<base>/hexgraph-vr/SKILL.md` exists. We check the
user-global dir (`~/.claude/skills`) and the project-local `./.claude/skills`; pass
`write_skill(base)` each one to refresh it. Returns an ordered, de-duplicated list."""
Existing project-local installs stay project-local. Both `.claude/skills` and
`.agents/skills` are scanned, and custom paths remain the caller's responsibility.
Returns an ordered, de-duplicated list of base directories for `write_skill(base)`.
"""
import os

proj = os.path.abspath(project_dir or os.getcwd())
candidates = [default_skill_dir(), os.path.join(proj, ".claude", "skills")]
native = [skill_dir_for_agent(agent) for agent in SKILL_AGENTS]
local = [
os.path.join(proj, ".claude", "skills"),
os.path.join(proj, ".agents", "skills"),
]
native_opted_in = any(
os.path.isfile(os.path.join(base, _SKILL_DIR_NAME, _SKILL_SPINE))
for base in native
)
detected = {base for _, base in detected_skill_targets()} if native_opted_in else set()
out: list[str] = []
for base in candidates:
if base not in out and os.path.isfile(os.path.join(base, _SKILL_DIR_NAME, _SKILL_SPINE)):
for base in native + local:
installed = os.path.isfile(os.path.join(base, _SKILL_DIR_NAME, _SKILL_SPINE))
if base not in out and (installed or base in detected):
out.append(base)
return out

Expand Down Expand Up @@ -391,6 +432,8 @@ def install_help(agent: str | None = None) -> str:
"(emits SKILL.md + the capability sub-files):\n"
" hexgraph mcp install --write-skill .claude/skills # Claude Code (project-local)\n"
" hexgraph mcp install --write-skill ~/.claude/skills # Claude Code (global)\n"
"(For Codex/gemini, paste the whole bundle into AGENTS.md / your system prompt — "
"print it with `hexgraph mcp install --print-skill`.)")
" hexgraph mcp install --write-skill .agents/skills # Codex (project-local)\n"
" hexgraph mcp install --write-skill ~/.agents/skills # Codex (global)\n"
"(For agents without native skill discovery, print the whole bundle with "
"`hexgraph mcp install --print-skill` and add it to their instructions.)")
return header + "\n\n".join(blocks) + footer
4 changes: 2 additions & 2 deletions src/hexgraph/agent/vr_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -852,15 +852,15 @@


def skill_markdown() -> str:
"""The skill SPINE as a Claude Code skill file (YAML frontmatter + the SKILL.md body)."""
"""The skill spine as an Agent Skills file (YAML frontmatter + SKILL.md body)."""
return _FRONTMATTER + SPINE


def full_skill_markdown() -> str:
"""The WHOLE skill bundle as one document: the spine followed by every sub-file.

For consumers that can't read on-demand sub-files — `hexgraph mcp install --print-skill`
(paste into a Codex/gemini system prompt) and the delegate-task brief — so no
(paste into their system prompt) and the delegate-task brief — so no
"read static-analysis.md" pointer dangles.
"""
parts = [skill_markdown()]
Expand Down
Loading
Loading