From ca51546be03c8adb6882271045ffe744adac69f2 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 11 Aug 2026 20:48:00 +0200 Subject: [PATCH] allow graft mcp to be used within opencode --- .gitignore | 3 ++ .ignore | 5 +++ AGENTS.md | 97 ++++++++++++++++++++++++++++++++++++++------------- opencode.json | 10 ++++++ 4 files changed, 91 insertions(+), 24 deletions(-) create mode 100644 .ignore create mode 100644 opencode.json diff --git a/.gitignore b/.gitignore index ad84ffb..354fd11 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ chesstrainer-*.tar npm-debug.log /assets/node_modules/ + +# graft's local graph cache — regenerable, not committed (run `graft build`). +graft/ diff --git a/.ignore b/.ignore new file mode 100644 index 0000000..615e581 --- /dev/null +++ b/.ignore @@ -0,0 +1,5 @@ +# graft's cards are gitignored but should stay greppable: ripgrep reads +# .ignore before .gitignore, so this re-admits the tree to search only. +!graft/ +graft/.cache/ +graft/.graph/ diff --git a/AGENTS.md b/AGENTS.md index 73a6e08..c5e7277 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,49 @@ This is a web application written using the Phoenix web framework. + + +## Graft — repo context graph + +This repo is indexed in `graft/`: small linked markdown nodes that explain each +system and carry exact file:line spans, kept in sync with the code through git. + +For ANY task here — understanding how something works, finding where code lives, +or scoping a change — get context from the graph before grepping or opening +source files. Re-ask freely (it's cheap) and reuse literal identifiers you +already have (symbol, error string, file name) as the query. New to this repo? +Run `graft map` first — a token-budgeted orientation (dir clusters, hubs, +hotspots), no LLM, no key. + +- Run `graft ask "" --source` → ranked nodes with the relevant + code spans inlined (each hit's ≤8-line crux by default; `--full` for whole + definitions when the crux isn't enough). Match the tool to the task shape: + for understanding or editing, the top node IS the answer — cite its + `covers:` file:line spans and edit straight from `--source`. For + exhaustive tasks ("every occurrence / every caller of this pattern"), ranked + results are top-N, not complete — run `graft grep ""` instead + (exhaustive over indexed files, grouped by enclosing symbol), falling back + to raw `grep -rn` only for unindexed files. +- `graft skeleton ` → every definition's signature + span, ~10× cheaper + than reading the file; use it to skim an API surface. +- `graft callers ` gives precomputed, exact edges — who calls this. + Add `--direction out` for what it calls, or `--depth N` to walk + transitively for the full blast radius. For structural questions, skip + ranking and use this directly. +- Or browse: `graft/INDEX.md` lists every node; follow the links. +- Monorepos and folders of multiple repos rank fairly across sub-projects — + hits carry `[scope/]` labels naming which one they're from. Narrow with + `graft ask "" --in /` once you know where you're working. + +If a returned span is truncated ("+N more lines"), open the file at that exact +range before finalizing. Only open source files when a node genuinely lacks a +needed detail, and then at the exact file:line the node points to — never +re-read whole files. + +After big code changes, refresh the graph with `graft build` (deterministic, +no API key, $0). + + + ## Project guidelines - Use `mix precommit` alias when you are done with all changes and fix any pending issues @@ -16,7 +60,7 @@ This is a web application written using the Phoenix web framework. - Out of the box, `core_components.ex` imports an `<.icon name="hero-x-mark" class="w-5 h-5"/>` component for hero icons. **Always** use the `<.icon>` component for icons, **never** use `Heroicons` modules or similar - **Always** use the imported `<.input>` component for form inputs from `core_components.ex` when available. `<.input>` is imported and using it will save steps and prevent errors - If you override the default input classes (`<.input class="myclass px-2 py-1 rounded-lg">)`) class with your own values, no default classes are inherited, so your -custom classes must fully style the input + custom classes must fully style the input ### JS and CSS guidelines @@ -43,10 +87,10 @@ custom classes must fully style the input - Ensure **clean typography, spacing, and layout balance** for a refined, premium look - Focus on **delightful details** like hover effects, loading states, and smooth page transitions - + ## Elixir guidelines - Elixir lists **do not support index based access via the access syntax** @@ -64,7 +108,7 @@ custom classes must fully style the input Enum.at(mylist, i) - Elixir variables are immutable, but can be rebound, so for block expressions like `if`, `case`, `cond`, etc - you *must* bind the result of the expression to a variable if you want to use it and you CANNOT rebind the result inside the expression, ie: + you _must_ bind the result of the expression to a variable if you want to use it and you CANNOT rebind the result inside the expression, ie: # INVALID: we are rebinding inside the `if` and the result never gets assigned if connected?(socket) do @@ -97,13 +141,14 @@ custom classes must fully style the input - **Avoid** `Process.sleep/1` and `Process.alive?/1` in tests - Instead of sleeping to wait for a process to finish, **always** use `Process.monitor/1` and assert on the DOWN message: - ref = Process.monitor(pid) - assert_receive {:DOWN, ^ref, :process, ^pid, :normal} + ref = Process.monitor(pid) + assert_receive {:DOWN, ^ref, :process, ^pid, :normal} - - Instead of sleeping to synchronize before the next call, **always** use `_ = :sys.get_state/1` to ensure the process has handled prior messages - + - Instead of sleeping to synchronize before the next call, **always** use `_ = :sys.get_state/1` to ensure the process has handled prior messages + + ## Phoenix guidelines - Remember Phoenix router `scope` blocks include an optional alias which is prefixed for all routes within the scope. **Always** be mindful of this when creating routes within a scope to avoid duplicate module prefixes. @@ -122,6 +167,7 @@ custom classes must fully style the input + ## Ecto Guidelines - **Always** preload Ecto associations in queries when they'll be accessed in templates, ie a message that needs to reference the `message.user.email` @@ -134,6 +180,7 @@ custom classes must fully style the input + ## Phoenix HTML guidelines - Phoenix templates **always** use `~H` or .html.heex files (known as HEEx), **never** use `~E` @@ -163,7 +210,7 @@ custom classes must fully style the input ... <% end %> -- HEEx require special tag annotation if you want to insert literal curly's like `{` or `}`. If you want to show a textual code snippet on the page in a `
` or `` block you *must* annotate the parent tag with `phx-no-curly-interpolation`:
+- HEEx require special tag annotation if you want to insert literal curly's like `{` or `}`. If you want to show a textual code snippet on the page in a `
` or `` block you _must_ annotate the parent tag with `phx-no-curly-interpolation`:
 
       
         let obj = {key: "val"}
@@ -196,26 +243,28 @@ custom classes must fully style the input
 
   **Always** do this:
 
-      
- {@my_assign} - <%= if @some_block_condition do %> - {@another_assign} - <% end %> -
+
+ {@my_assign} + <%= if @some_block_condition do %> + {@another_assign} + <% end %> +
and **Never** do this – the program will terminate with a syntax error: - <%!-- THIS IS INVALID NEVER EVER DO THIS --%> -
- {if @invalid_block_construct do} - {end} -
- + <%!-- THIS IS INVALID NEVER EVER DO THIS --%> +
+ {if @invalid_block_construct do} + {end} +
+ + + ## Phoenix LiveView guidelines -- **Never** use the deprecated `live_redirect` and `live_patch` functions, instead **always** use the `<.link navigate={href}>` and `<.link patch={href}>` in templates, and `push_navigate` and `push_patch` functions LiveViews +- **Never** use the deprecated `live_redirect` and `live_patch` functions, instead **always** use the `<.link navigate={href}>` and `<.link patch={href}>` in templates, and `push_navigate` and `push_patch` functions LiveViews - **Avoid LiveComponent's** unless you have a strong, specific need for them - LiveViews should be named like `AppWeb.WeatherLive`, with a `Live` suffix. When you go to add LiveView routes to the router, the default `:browser` scope is **already aliased** with the `AppWeb` module, so you can just do `live "/weather", WeatherLive` @@ -235,7 +284,7 @@ custom classes must fully style the input -- LiveView streams are *not* enumerable, so you cannot use `Enum.filter/2` or `Enum.reject/2` on them. Instead, if you want to filter, prune, or refresh a list of items on the UI, you **must refetch the data and re-stream the entire stream collection, passing reset: true**: +- LiveView streams are _not_ enumerable, so you cannot use `Enum.filter/2` or `Enum.reject/2` on them. Instead, if you want to filter, prune, or refresh a list of items on the UI, you **must refetch the data and re-stream the entire stream collection, passing reset: true**: def handle_event("filter", %{"filter" => filter}, socket) do # re-fetch the messages based on the filter @@ -248,7 +297,7 @@ custom classes must fully style the input |> stream(:messages, messages, reset: true)} end -- LiveView streams *do not support counting or empty states*. If you need to display a count, you must track it using a separate assign. For empty states, you can use Tailwind classes: +- LiveView streams _do not support counting or empty states_. If you need to display a count, you must track it using a separate assign. For empty states, you can use Tailwind classes:
@@ -446,4 +495,4 @@ And **never** do this: - **Never** use `<.form let={f} ...>` in the template, instead **always use `<.form for={@form} ...>`**, then drive all form references from the form assign as in `@form[:field]`. The UI should **always** be driven by a `to_form/2` assigned in the LiveView module that is derived from a changeset - \ No newline at end of file + diff --git a/opencode.json b/opencode.json new file mode 100644 index 0000000..17f3f8d --- /dev/null +++ b/opencode.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "graft": { + "type": "local", + "command": ["npx", "-y", "@nanonets/graft", "mcp"], + "enabled": true + } + } +}