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.md

GitHub

Enggraph

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.md that is missing, or whose frontmatter version: differs, is replaced with what get_skill returns, written unchanged. Say once which skills were updated.
  • list_skills gives the same list on demand.

Plans

  • The first tool call of any non-trivial request is get_plans, default status: active. Plans live in one table for the whole database, so the call also returns global plans; project: "*" lists every project's and status: "*" every status. A listing carries titles, not text: read one plan in full with get_plans(plan_id: ...) and one prompt with get_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_plan as soon as the user approves it: a stable plan_id from 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_prompt under the plan's plan_id. A prompt is about whatever its plan is about, so it names no project.
  • get_plans lists the prompts of every plan. An active plan with a prompt is an approved plan: read the prompt with get_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_id when it changes, and rewrite its prompt under the same prompt_id. Retire both with status: completed once the work has landed. drop_plan is only for a plan written by mistake, and takes the plan's prompts with it; drop_prompt drops 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_roadmaps answers 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_item with the item and plan_id. get_plans lists the roadmap_items a plan carries.
  • Closing a plan updates its item. Nothing does it silently: save_plan names the items the plan carries, and the item is set to done with save_roadmap_item when 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, done and dropped are the usual ones. An item that is no longer wanted is kept as dropped; drop_roadmap_item and drop_roadmap are 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 and impact_analysis before get_code_graph_neighbors. Each result says whether a graph edge or a name match put it there; weigh a NAME_MATCH as 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 - is trace from that code's directory. A question that starts from a name - a host, an image, a package - starts at find_linked_name, which names every project defining and using it, and continues with trace from there. impact_analysis lists the other projects a change reaches under cross_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, with save_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_projects names that tree, so a host path is matched to a project by its root_path.

Organizations

  • describe_project says 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_nodes across projects with project: "*", narrowed by project_type when 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_code splits such a string into words and ranks near misses first. loose: true finds 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_memory holds 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_analysis and get_code_graph_neighbors list what was written about the nodes they reach, under knowledge.
  • Before changing code, read what is written about it: get_memory, get_plans and get_suggestions take node_id and answer with the records about that node or a directory above it. A node marked missing is 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 lever the fix moves - tokens when the answer was re-derived by hand, coverage when the graph does not describe it at all, runtime when 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, moves last_seen and counts the hit, so nothing needs reading before the write. bump: false corrects the wording without claiming a sighting.
  • The vocabularies are short. kind is empty-lookup, missing-summary, thin-summary, not-indexed, no-parser, stale-index or missing-tool; status is open, resolved or wontfix.
  • Retire a closed gap with status: "resolved"; drop_suggestion erases 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 as nodes: together they make the gap a test case that make replay asks the graph again.
  • get_suggestions with group_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.