enggraph
Work through the `enggraph` MCP server - plans, graph exploration, memory, the suggestion backlog - and close the task by reporting how the answer was produced. Use at the start of any non-trivial req
SKILL.mdskills/enggraph/SKILL.md
GitHubEnggraph
The enggraph MCP server holds what this project knows across sessions: the
code graph, the plans, the memory and the suggestion backlog. A session that
never reads it repeats work another session already did, and a session that
never writes to it loses everything worked out here.
Skills
- The server's instructions, and the answer to the first tool call of a
session, list the skills this project should have, each with its version:
the sha256 of its text. A local copy under
.claude/skills/<name>/SKILL.mdthat is missing, or whose frontmatterversion:differs, is replaced with whatget_skillreturns, written unchanged. Say once which skills were updated. list_skillsgives the same list on demand.
Plans
- The first tool call of any non-trivial request is
get_plans, defaultstatus: active. Plans live in one table for the whole database, so the call also returns global plans;project: "*"lists every project's andstatus: "*"every status. A listing carries titles, not text: read one plan in full withget_plans(plan_id: ...)and one prompt withget_prompts(prompt_id: ...). - The order is plan, then prompt, then work. A plan says what was decided and why; its prompt is what a session is handed to carry it out: the file set, the constraints and what "done" means.
- Save a plan with
save_planas soon as the user approves it: a stableplan_idfrom the topic,status: active, and enough content that another session can understand it without this conversation. - The next step is always its prompt: write it and save it with
save_promptunder the plan'splan_id. A prompt is about whatever its plan is about, so it names no project. get_planslists thepromptsof every plan. An active plan with a prompt is an approved plan: read the prompt withget_prompts(plan_id: ...)and execute it. Do not re-run its research, re-verify its conclusions or re-enter plan mode. An instruction from the user outranks both.- An active plan with no prompt is not executed. Write the prompt first, or ask the user when the plan is too thin to write one from.
- Write a plan back under the same
plan_idwhen it changes, and rewrite its prompt under the sameprompt_id. Retire both withstatus: completedonce the work has landed.drop_planis only for a plan written by mistake, and takes the plan's prompts with it;drop_promptdrops one prompt alone.
Roadmaps
- A roadmap is the ordered list of what is to be done for a project: items,
each with a state and the plan that carries it out.
get_roadmapsanswers with every item, its plan's status and that plan's prompts, so "what is next" and "what is it waiting for" are one call. - A plan for work that is on a roadmap names its item when it is saved:
save_roadmap_itemwith the item andplan_id.get_planslists theroadmap_itemsa plan carries. - Closing a plan updates its item. Nothing does it silently:
save_plannames the items the plan carries, and the item is set todonewithsave_roadmap_itemwhen the work has landed. - Work the user agrees to "later" goes onto the roadmap as an item with the
status
open, not into a plan's free text. - States are free text;
open,next,in progress,doneanddroppedare the usual ones. An item that is no longer wanted is kept asdropped;drop_roadmap_itemanddrop_roadmapare for what was written by mistake.
Explore
- Search the graph before any Read, Grep or explore agent:
search_code_nodes,get_code_graph_neighbors,get_node_summary,shortest_path. - A broad question - how a subsystem works, what a change would touch - starts
at
get_context: one call that searches, expands the graph around the hits into callers, tests, callees and imports, and answers within a token budget, each entry saying why it is there. The tools above stay for precise navigation once the packet has named the place. - A question about how a codebase or a part of it is organised starts at
get_overview: the summary tree from the repository (./) down through directories (ids end in a slash) to files and symbols. Drill down one directory at a time, and read source only once a summary names the place. - A question about one symbol - where it is defined, who calls it, what it
calls, what implements it, which tests reach it, what a change to it would
touch - goes to the
find_*tools andimpact_analysisbeforeget_code_graph_neighbors. Each result says whether a graph edge or a name match put it there; weigh aNAME_MATCHas a lead, not a fact. - A question that crosses repositories - who uses this project, what runs
its image, which repository creates the hosts a deployment reaches, how one
service reaches another - starts at
get_project_links. Where a piece of code ends up - what builds it, what deploys it, onto which hosts - istracefrom that code's directory. A question that starts from a name - a host, an image, a package - starts atfind_linked_name, which names every project defining and using it, and continues withtracefrom there.impact_analysislists the other projects a change reaches undercross_project. - A relation between projects worked out by hand and stated by no manifest
is saved with
save_project_link; a name a project provides that no file states, such as an image its CI builds, withsave_project_export. - Open only the files the graph named. Fall back to Read or Grep when the graph cannot answer: the question is about literal file content, the entity is not indexed, or the index is older than the tree and needs a re-index first.
- While a plan is loaded it defines the file set, and nothing outside it is touched. A step that needs an unnamed file is a defect in the plan - name the file, amend that step, continue.
- A node whose summary answers nothing is worth fixing: write one and persist
it with
save_node_summary. - A node id is a path relative to the tree the project reads.
list_projectsnames that tree, so a host path is matched to a project by itsroot_path.
Organizations
describe_projectsays what the session is connected to. Call it when the session opens: an organization is a set of projects rather than a tree, its own graph is empty by design, and reading that as an unindexed codebase is the one wrong conclusion no amount of searching corrects.- It answers with the members and the sentence written about each. That sentence is what a member is picked by, so an empty one is worth saying so and asking for.
describe_project(path: "<the working directory>")names the project that reads that directory. That is how the current project is established, not by matching the directory name against a project name.- Naming the organization is what reads all of it, and every read does it: the graph reads, the plans, the memories and the suggestions. Each row says which member answered. Name a member to read only that one.
- Writing is not spread. A record saved about an organization belongs to the organization; a summary and a file hash are refused there and name the member to write to instead.
Questions
An open question - how something is set up, how it is built, where the code that does a given thing lives - is answered out of the context and nothing else.
- Search the context first and only:
search_code_nodesacross projects withproject: "*", narrowed byproject_typewhen the kind of tree is known, or by naming an organization when the question is about what it holds, then neighbours and summaries on whatever came back. - A literal string - a host name, a config key, an error message - goes to
search_text, which reads the trees the way grep does and answers every line with its file;search_codesplits such a string into words and ranks near misses first.loose: truefinds it whatever separates its words. - Build the answer strictly from what the context returned. Do not complete it from general knowledge or from what such a setup usually looks like - a plausible answer about this user's estate is indistinguishable from a true one, and wrong.
- When the context covers only part of the question, answer that part, say plainly which part is missing, and name what would close it: a tree to index, a summary to write, a parser that does not exist.
- That gap is worth a
save_suggestion. An open question the graph could not answer is the clearest kind of coverage gap there is.
Memory
save_memoryholds what stays true after the task ends - a convention, a decision, the reason something is the way it is. A memory is what stays true; a plan is what to do next.- Write what the user asks to remember, and what this session worked out that the tree records nowhere. Nothing indexes into memory, so a re-index never prunes it.
- Name the code a memory, plan or suggestion is about with
nodes- a directory for a module-wide decision, a file or a symbol for a narrow one. That is what brings it back:get_context,impact_analysisandget_code_graph_neighborslist what was written about the nodes they reach, underknowledge. - Before changing code, read what is written about it:
get_memory,get_plansandget_suggestionstakenode_idand answer with the records about that node or a directory above it. A node markedmissingis gone from the graph; the record may be stale.
Suggestions
- When the context could not answer and the work had to be done by hand, that
is a defect of the tooling:
save_suggestion. Nothing missing means nothing written. - The slug is the mechanism. Derive it from the gap and keep it stable, so the same gap reported next week counts a hit instead of filing a duplicate.
- Name the
leverthe fix moves -tokenswhen the answer was re-derived by hand,coveragewhen the graph does not describe it at all,runtimewhen it was answerable but slow - and say which concrete change closes it. A gap whose fix is not named is a complaint. - Saving under an existing slug keeps
first_seen, moveslast_seenand counts the hit, so nothing needs reading before the write.bump: falsecorrects the wording without claiming a sighting. - The vocabularies are short.
kindisempty-lookup,missing-summary,thin-summary,not-indexed,no-parser,stale-indexormissing-tool;statusisopen,resolvedorwontfix. - Retire a closed gap with
status: "resolved";drop_suggestionerases the count and is for a suggestion written by mistake. - Pass the question or search that failed as
query, and the nodes that should have answered it asnodes: together they make the gap a test case thatmake replayasks the graph again. get_suggestionswithgroup_by(kind,lever,about,directory) says where the gaps pile up.
Recap
Close a task longer than one answer with a short recap: counts, not adjectives, written from what happened rather than from what should have happened.
- Context calls - how many, by tool and purpose, and which lookups came back empty.
- Files - how many read and how many written, and why the graph was not enough.
- Suggestions - the gaps this turn hit, each named by the slug it was saved under. Say nothing when nothing was missing.