context-mcp-server 1.0.7 → 1.1.0

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.
package/src/guard.js CHANGED
@@ -1,7 +1,13 @@
1
1
  import { resolve, sep } from 'node:path';
2
+ import { realpathSync } from 'node:fs';
3
+
4
+ // Follow symlinks to get the true path; fall back to resolve() if path doesn't exist yet.
5
+ function safeStat(p) {
6
+ try { return realpathSync(p); } catch { return resolve(p); }
7
+ }
2
8
 
3
9
  /**
4
- * Resolves `inputPath` and asserts it falls within `allowedRoot`.
10
+ * Resolves `inputPath` (following symlinks) and asserts it falls within `allowedRoot`.
5
11
  * Throws if no root is configured or if the path escapes the root.
6
12
  * Returns the resolved absolute path on success.
7
13
  */
@@ -11,8 +17,8 @@ export function guardPath(inputPath, allowedRoot) {
11
17
  'Project root not configured. Call context with action:"resume" and include rootPath to enable file/git access.'
12
18
  );
13
19
  }
14
- const resolved = resolve(inputPath);
15
- const root = resolve(allowedRoot);
20
+ const resolved = safeStat(inputPath);
21
+ const root = safeStat(allowedRoot);
16
22
  if (resolved !== root && !resolved.startsWith(root + sep)) {
17
23
  throw new Error(`Access denied: "${resolved}" is outside the project root "${root}"`);
18
24
  }
package/src/search.js CHANGED
@@ -1,13 +1,71 @@
1
- /**
2
- * search.js — unified search entry point
3
- *
4
- * Single function replaces direct calls to searchContext / vectorSearch / findRelated
5
- * across index.js, cli.js, and error_check. db.js + vector.js stay as low-level impls.
6
- */
7
-
8
1
  import { getContext, searchContext } from './db.js';
9
2
  import { vectorSearch, findRelated } from './vector.js';
10
3
 
4
+ // ── Vocabulary cache (lazy singleton per process) ─────────────────────────────
5
+
6
+ let _vocabCache = null;
7
+
8
+ function buildVocab(entries) {
9
+ const vocab = new Set();
10
+ for (const e of entries) {
11
+ const text = `${e.title || ''} ${e.content || ''}`.toLowerCase();
12
+ for (const w of text.split(/\W+/)) {
13
+ if (w.length > 2) vocab.add(w);
14
+ }
15
+ }
16
+ return vocab;
17
+ }
18
+
19
+ // ── Levenshtein fuzzy correction ──────────────────────────────────────────────
20
+
21
+ function levenshtein(a, b) {
22
+ const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
23
+ for (let i = 0; i < a.length; i++) {
24
+ const curr = [i + 1];
25
+ for (let j = 0; j < b.length; j++) {
26
+ curr.push(Math.min(prev[j + 1] + 1, curr[j] + 1, prev[j] + (a[i] === b[j] ? 0 : 1)));
27
+ }
28
+ prev.splice(0, prev.length, ...curr);
29
+ }
30
+ return prev[b.length];
31
+ }
32
+
33
+ function maxEditDist(len) {
34
+ if (len <= 4) return 1;
35
+ if (len <= 12) return 2;
36
+ return 3;
37
+ }
38
+
39
+ function fuzzyCorrect(word, vocab) {
40
+ const max = maxEditDist(word.length);
41
+ let bestWord = word;
42
+ let bestDist = max + 1;
43
+ for (const candidate of vocab) {
44
+ if (Math.abs(candidate.length - word.length) > max) continue;
45
+ const dist = levenshtein(word, candidate);
46
+ if (dist < bestDist) { bestDist = dist; bestWord = candidate; }
47
+ }
48
+ return bestDist <= max ? bestWord : word;
49
+ }
50
+
51
+ // ── Smart snippet (window centered on first match position) ───────────────────
52
+
53
+ function snippet(text, terms, windowSize = 200) {
54
+ if (!text) return text;
55
+ const lower = text.toLowerCase();
56
+ let bestPos = -1;
57
+ for (const term of terms) {
58
+ const idx = lower.indexOf(term.toLowerCase());
59
+ if (idx !== -1 && (bestPos === -1 || idx < bestPos)) bestPos = idx;
60
+ }
61
+ if (bestPos === -1) return text.slice(0, windowSize);
62
+ const start = Math.max(0, bestPos - Math.floor(windowSize / 3));
63
+ const end = Math.min(text.length, start + windowSize);
64
+ return (start > 0 ? '…' : '') + text.slice(start, end) + (end < text.length ? '…' : '');
65
+ }
66
+
67
+ // ── Unified search ────────────────────────────────────────────────────────────
68
+
11
69
  /**
12
70
  * @param {Object} opts
13
71
  * @param {string} opts.query - search query (keyword/semantic)
@@ -21,7 +79,14 @@ export function search({ query, mode = 'semantic', project, limit = 10, id, comp
21
79
  switch (mode) {
22
80
  case 'keyword': {
23
81
  if (!query) throw new Error('query required for keyword search');
24
- return searchContext({ query, project, limit, compact });
82
+ if (!_vocabCache) {
83
+ _vocabCache = buildVocab(getContext({ project, limit: 500 }));
84
+ }
85
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
86
+ const corrected = terms.map(t => fuzzyCorrect(t, _vocabCache));
87
+ const correctedQuery = corrected.join(' ');
88
+ const results = searchContext({ query: correctedQuery, project, limit, compact: false });
89
+ return results.map(e => ({ ...e, content: snippet(e.content, corrected) }));
25
90
  }
26
91
  case 'semantic': {
27
92
  if (!query) throw new Error('query required for semantic search');
@@ -33,7 +98,6 @@ export function search({ query, mode = 'semantic', project, limit = 10, id, comp
33
98
  const all = getContext({ limit: 1000 });
34
99
  const target = all.find(e => e.id === id || e.id.startsWith(id));
35
100
  if (!target) throw new Error(`No entry found with id starting "${id}"`);
36
- // explicit relations first, semantic enrichment for remainder
37
101
  const explicitIds = new Set([
38
102
  ...(target.relations || []).map(r => r.id),
39
103
  ...(target.relatedBy || []).map(r => r.id),
package/src/server.js CHANGED
@@ -5,7 +5,7 @@ import { getConfig } from './config.js';
5
5
 
6
6
  import * as contextTool from './tools/context.js';
7
7
  import * as searchTool from './tools/search.js';
8
- import * as discussionTool from './tools/discussion.js';
8
+ import * as planTool from './tools/plan.js';
9
9
  import * as errorCheckTool from './tools/errorCheck.js';
10
10
  import * as fileTool from './tools/fileTools.js';
11
11
  import * as gitTool from './tools/gitTools.js';
@@ -17,8 +17,9 @@ const CODEGRAPH_TOOL_NAMES = codegraphTool.TOOL_NAMES;
17
17
 
18
18
  export function createServer({ enableFileTools = false, enableGitTools = getConfig().access_git === true } = {}) {
19
19
  const state = {
20
- sessionProject: null,
21
- discussionId: null,
20
+ sessionProject: null,
21
+ discussionId: null,
22
+ projectRootPath: null,
22
23
  };
23
24
 
24
25
  const server = new Server(
@@ -30,7 +31,7 @@ export function createServer({ enableFileTools = false, enableGitTools = getConf
30
31
  const tools = [
31
32
  contextTool.definition,
32
33
  searchTool.definition,
33
- discussionTool.definition,
34
+ planTool.definition,
34
35
  errorCheckTool.definition,
35
36
  ];
36
37
  if (enableFileTools) tools.push(...fileTool.definitions);
@@ -56,8 +57,8 @@ export function createServer({ enableFileTools = false, enableGitTools = getConf
56
57
  result = await contextTool.handle(args, state);
57
58
  } else if (name === searchTool.definition.name) {
58
59
  result = await searchTool.handle(args, state);
59
- } else if (name === discussionTool.definition.name) {
60
- result = await discussionTool.handle(args, state);
60
+ } else if (name === planTool.definition.name) {
61
+ result = await planTool.handle(args, state);
61
62
  } else if (name === errorCheckTool.definition.name) {
62
63
  result = await errorCheckTool.handle(args, state);
63
64
  } else if (FILE_TOOL_NAMES.has(name)) {
@@ -1,87 +1,90 @@
1
1
  # Context-MCP — Codex CLI Usage Guide
2
2
 
3
3
  Persistent memory + codebase knowledge graph.
4
- Every conversation starts with `context.resume`. Every codebase question uses `codegraph_query`. Files only read for bugs/logic.
4
+ `context.resume` starts every session. `codegraph_arch` shows module structure. `codegraph_query` finds specific symbols. Files only for bugs/logic.
5
5
 
6
6
  ---
7
7
 
8
8
  ## 1. Start of Every Conversation (MANDATORY)
9
9
 
10
- Call the `context` MCP tool with `action: "resume"`, `project: "<project-name>"` **before anything else**.
10
+ Call `context` MCP tool: `action:"resume"`, `project:"<project>"` before anything else.
11
11
 
12
- Returns:
13
- - `recentEntries` — decisions, bugs, notes from previous conversations
14
- - `activeDiscussions` — ongoing topics
15
- - `codegraph` — `{ built: true/false, nodes, edges, communities }`
12
+ Returns: `recentEntries`, `activePlans`, `codegraph`, `stats.totalEntries`.
16
13
 
17
- Then:
18
- - `codegraph.built: true` → use `codegraph_query` before reading any files
19
- - `codegraph.built: false` → call `codegraph_build(path)` first, then proceed
14
+ - `codegraph.built: true` → use graph tools before reading files
15
+ - `codegraph.built: false` → call `codegraph_build(path)` first
16
+ - `stats.totalEntries ≥ 20` → write compaction summary FIRST (see Rule 4)
17
+ - `activePlans` non-empty → read them before starting new work
20
18
 
21
19
  ---
22
20
 
23
- ## 2. When to Auto-Save Context
21
+ ## 2. Save Triggers (MANDATORY)
24
22
 
25
- **After graph build or rebuild** every time `codegraph_build` completes:
26
- ```
27
- context.save type: "architecture" title: "ContextGraph built — <project>"
28
- content: "nodes: X | edges: Y | communities: Z"
29
- ```
23
+ Call `context.save` with `type: "note"` after finishing anything worth keeping:
30
24
 
31
- **User explicitly asks** "save this", "remember this", "note that" → save immediately.
25
+ | Trigger | Required fields |
26
+ |---|---|
27
+ | Task / fix / feature complete | title, why, outcome, files[] |
28
+ | Decision made | title, why, outcome |
29
+ | Discovery / constraint / gotcha | title, content |
30
+ | Config / env / deploy info | title, content |
31
+ | Graph build complete | title, content (nodes/edges count) |
32
+ | User says "save this" | title, content |
33
+ | "compact now" / "compress memory" | `type:"compaction"`, full session summary |
32
34
 
33
- **During plan / implementation / discussion / research** save only when genuinely valuable:
35
+ **Do NOT save:** routine reads, search results, explanations of existing code.
36
+
37
+ ---
34
38
 
35
- | What happened | Type |
36
- |--------------|------|
37
- | Approach / library / pattern decided | `decision` |
38
- | Bug found (root cause known) or fixed | `bug` |
39
- | System structure understood | `architecture` |
40
- | Gotcha, constraint, non-obvious behavior | `note` |
41
- | Config / env var / secret key discovered | `config` |
42
- | External API or service integration learned | `note` |
43
- | Performance insight (why something is slow/fast) | `note` |
44
- | How to run tests / test pattern discovered | `note` |
45
- | Deploy / release step discovered | `note` |
46
- | Milestone / feature / task completed | `note` |
39
+ ## 3. Plans (MANDATORY for multi-file work)
47
40
 
48
- Do NOT save: routine reads, search results, temporary debugging dead-ends.
49
- Feature spans sessions → `discussion.save` status: `"active"`.
50
- Need past info → `search` before asking. Always pass `project`.
41
+ **Create a plan when:** editing 2+ files, multi-step implementation, refactor, multi-file bug fix.
42
+
43
+ 1. Call `plan.save` with name, content, project before starting
44
+ 2. Call `plan.update status:"done"` when complete — deletes the plan
45
+
46
+ Check `activePlans` on resume — don't create duplicates.
51
47
 
52
48
  ---
53
49
 
54
- ## 3. ContextGraph Pipeline
50
+ ## 4. Auto-Summary at ≥ 20 Entries (MANDATORY)
55
51
 
56
- > The knowledge graph is also called **ContextGraph**. The MCP tools use the `codegraph_*` prefix — both names refer to the same thing.
52
+ When `totalEntries 20`, call `context.save` BEFORE the user's task:
57
53
 
58
- ### Step 1 — Build (once, fast, local)
59
54
  ```
60
- codegraph_build(path) → AST graph: functions, classes, imports, edges
55
+ type: "compaction" title: "Session summary <YYYY-MM-DD>"
56
+ content: "<what was built, decided, broke, current state>"
57
+ project: "<project>"
61
58
  ```
62
59
 
63
- ### Step 2 — Query (free, instant)
64
- ```
65
- codegraph_query(path, question) → fetch any details about the codebase
66
- codegraph_explain(path, node) → single node: type, file, connections
67
- codegraph_path(path, from, to) → shortest path
68
- codegraph_nodes(path, type) → list nodes by type
69
- codegraph_report(path) → full graph analysis
70
- ```
60
+ ---
61
+
62
+ ## 5. Search Before Asking
63
+
64
+ Call `search` before asking user to re-explain past work.
71
65
 
72
66
  ---
73
67
 
74
- ## 4. Graph vs File
68
+ ## 6. ContextGraph Tools
69
+
70
+ ```
71
+ codegraph_build(path) → build AST graph (run once)
72
+ codegraph_arch(path, limit?) → module map: files, exports, imports
73
+ codegraph_query(path, question?, node?) → find symbol or answer structural question
74
+ codegraph_nodes(path, type) → list all nodes of a type
75
+ codegraph_report(path) → structural analysis
76
+ ```
75
77
 
76
- **Graph** use for any question about what exists: finding functions, classes, files, dependencies, callers, imports, paths between concepts.
77
- **File** — bugs, logic, tracing unexpected behavior.
78
+ Use `codegraph_arch` first. Never read files for structure questions.
78
79
 
79
80
  ---
80
81
 
81
- ## 5. Rules
82
+ ## 7. Rules
82
83
 
83
- 1. **`context.resume` first** — before any tool or response
84
- 2. **Always pass `project`**
85
- 3. **`search` before asking**if user references past work
86
- 4. **`codegraph_query` before reading files**graph is faster and cheaper
87
- 5. **Read files for bugs/logic only**
84
+ 1. `context.resume` first — before any tool or response
85
+ 2. Always pass `project`
86
+ 3. Save on task complete `why` + `outcome` + `files`
87
+ 4. Compaction at 20 entries before starting task
88
+ 5. Plan before multi-file work — `status:"done"` deletes it
89
+ 6. Search before asking about past work
90
+ 7. Graph tools before files
@@ -1,109 +1,137 @@
1
1
  ---
2
2
  name: context-mcp
3
3
  description: >
4
- Guides Claude on using the context-mcp memory + knowledge graph system.
5
- Trigger at the start of every conversation, when the user mentions a project,
6
- asks to remember/save something, or says "pick up where we left off".
4
+ Persistent memory + ContextGraph for Claude.
5
+ Use at the START of every conversation to resume project context.
6
+ Use whenever the user mentions a project, asks to remember/save something,
7
+ references past work, or says "pick up where we left off".
8
+ Also use when the user asks about code structure — query ContextGraph before reading any files.
7
9
  ---
8
10
 
9
11
  # Context-MCP — Claude Usage Guide
10
12
 
11
- Persistent memory + codebase knowledge graph for Claude.
12
- Every conversation starts with `context.resume`. Every codebase question uses `codegraph_query`. Files only read for bugs/logic.
13
+ Persistent memory + codebase knowledge graph across every conversation.
14
+ `context.resume` starts every session. `codegraph_arch` shows module structure. `codegraph_query` finds specific symbols. Files only for bugs/logic.
13
15
 
14
16
  ---
15
17
 
16
18
  ## 1. Start of Every Conversation (MANDATORY)
17
19
 
18
- Call `context` tool **before anything else** with:
20
+ Call `context` tool **before any tool or response**:
19
21
  - `action: "resume"`
20
- - `project: "<basename of git repo root dir>"` — infer from cwd if not stated
21
- - `rootPath: "<absolute path to git repo root>"` — required for sandbox + graph lookup
22
-
23
- Both fields are required: `project` names the memory bucket, `rootPath` enables exact graph matching and file sandboxing.
22
+ - `project: "<basename of git repo root>"` — infer from cwd
23
+ - `rootPath: "<absolute path to git repo root>"` — required
24
24
 
25
25
  Returns:
26
- - `recentEntries` — decisions, bugs, notes from previous conversations
27
- - `activeDiscussions` — ongoing topics (auto-linked if exactly one active)
28
- - `codegraph` — `{ built: true/false, nodes, edges, communities }`
26
+ - `recentEntries` — last 15 entries; newest 5 have full content, rest have 200-char preview
27
+ - `activePlans` — in-progress plans; read them before starting any new work
28
+ - `codegraph` — `{ built: true/false, nodes, edges }`
29
+ - `stats.totalEntries` — if ≥ 20, write a compaction summary before proceeding (see Rule 4)
29
30
 
30
31
  Then:
31
- - `codegraph.built: true` → use `codegraph_query` before reading any files
32
- - `codegraph.built: false` → call `codegraph_build(path)` first, then proceed
32
+ - `codegraph.built: true` → use `codegraph_arch` for structure overview, `codegraph_query` for specific lookups
33
+ - `codegraph.built: false` → call `codegraph_build(path)` first
33
34
 
34
35
  ---
35
36
 
36
- ## 2. When to Auto-Save Context
37
+ ## 2. When to Save Context (MANDATORY TRIGGERS)
37
38
 
38
- ### Always save no user prompt needed
39
+ Call `context.save` with `type: "note"` whenever you finish something or discover something worth keeping:
39
40
 
40
- **After graph build or rebuild** — every time `codegraph_build` completes:
41
41
  ```
42
- context.save project: "<project>" type: "architecture" title: "ContextGraph built — <project>"
43
- content: "nodes: X | edges: Y | communities: Z"
42
+ context.save
43
+ project: "<project>"
44
+ title: "<what was done — up to 120 chars>"
45
+ why: "<why it mattered>"
46
+ outcome: "<what the result was>"
47
+ files: ["src/file.js", ...]
44
48
  ```
45
49
 
46
- **User explicitly asks** — any phrase like "save this", "remember this", "note that" → save immediately.
50
+ **Save immediately after:**
51
+ - Task / fix / feature complete
52
+ - Decision made (architecture, library, approach)
53
+ - Discovery — non-obvious behavior, constraint, gotcha
54
+ - Config / env / deploy info
55
+ - Graph build complete — include nodes/edges count in content
56
+ - User says "save this" / "remember this"
57
+
58
+ **Manual compaction** — "compact now", "compress memory", "clean up context":
59
+ Save a full session summary as `type: "compaction"`. Server removes old entries using it.
60
+
61
+ **Do NOT save:** routine reads, search results, explanations of existing code, dead-end debugging.
62
+
63
+ ---
47
64
 
48
- **During plan / implementation / discussion / research** — save only when genuinely valuable:
65
+ ## 3. Plans (MANDATORY for multi-file work)
49
66
 
50
- | What happened | Type |
51
- |--------------|------|
52
- | Approach / library / pattern decided | `decision` |
53
- | Bug found (root cause known) or fixed | `bug` |
54
- | System structure understood | `architecture` |
55
- | Gotcha, constraint, non-obvious behavior | `note` |
56
- | Config / env var / secret key discovered | `config` |
57
- | External API or service integration learned | `note` |
58
- | Performance insight (why something is slow/fast) | `note` |
59
- | How to run tests / test pattern discovered | `note` |
60
- | Deploy / release step discovered | `note` |
61
- | Milestone / feature / task completed | `note` |
67
+ **Create a plan when:** editing 2+ files, multi-step implementation, refactor, multi-file bug fix.
62
68
 
63
- Do NOT save: routine reads, search results, temporary debugging dead-ends.
69
+ **Skip plan for:** single-file edits, questions, simple config tweaks.
64
70
 
65
- Feature spans multiple sessions `discussion.create` or `discussion.update`.
66
- Need past info `search` before asking user.
67
- Always pass `project`. Auto-compact fires at >20 entries.
71
+ 1. Call `plan.save` with `name`, `content` (full plan in markdown), `project`, `planDir` before starting
72
+ 2. Work through the plan
73
+ 3. Call `plan.update status:"done"` when complete this deletes the plan
74
+
75
+ On `resume`, if `activePlans` is non-empty — read them before starting new work. Do not create a duplicate.
68
76
 
69
77
  ---
70
78
 
71
- ## 3. ContextGraph Pipeline
79
+ ## 4. Auto-Summary Rule (MANDATORY)
72
80
 
73
- > The knowledge graph is also called **ContextGraph**. The MCP tools use the `codegraph_*` prefix both names refer to the same thing.
81
+ When `resume` returns `stats.totalEntries 20`, call `context.save` **before doing anything else**:
74
82
 
75
- ### Step 1 — Build (once per project, fast, local)
76
83
  ```
77
- codegraph_build(path)
84
+ context.save
85
+ type: "compaction"
86
+ title: "Session summary — <YYYY-MM-DD>"
87
+ content: "<what was built, decided, broke, current state>"
88
+ project: "<project>"
78
89
  ```
79
- - Parses codebase into AST graph using tree-sitter (regex fallback for unsupported languages)
80
- - Extracts functions, classes, imports, call edges for all code files
81
- - Build files (package.json, pyproject.toml, go.mod, Dockerfile, etc.) get a single metadata node
82
- - Saves graph to `~/.context-mcp/graphs.json`. Visible on `context.resume`.
83
90
 
84
- ### Step 2 — Query (free, instant, forever)
91
+ ---
92
+
93
+ ## 5. Search Before Asking
94
+
95
+ If the user references past work → call `search` first:
85
96
  ```
86
- codegraph_query(path, question) → fetch any details about the codebase
87
- codegraph_explain(path, node) → one node: type, file, depends_on, used_by
88
- codegraph_path(path, from, to) → shortest path between two concepts
89
- codegraph_nodes(path, type) → list all nodes of a type
90
- codegraph_report(path) → god nodes, clusters, surprises
97
+ search query: "<what they're referencing>" project: "<project>"
91
98
  ```
92
99
 
93
100
  ---
94
101
 
95
- ## 4. Graph vs File
102
+ ## 6. ContextGraph Pipeline
103
+
104
+ ### Build (once per project)
105
+ ```
106
+ codegraph_build(path)
107
+ ```
108
+
109
+ ### Query tools
110
+ ```
111
+ codegraph_arch(path, limit?) → module map: every file, its exports, its imports
112
+ codegraph_query(path, question?, node?) → find function/class/file or answer structural question
113
+ codegraph_nodes(path, type) → list all nodes of a type
114
+ codegraph_report(path) → god nodes, clusters, structural analysis
115
+ ```
96
116
 
97
- **Graph** use for any question about what exists in the codebase: finding functions, classes, files, understanding what a module contains, dependencies, callers, imports, paths between concepts.
117
+ | Question | Tool |
118
+ |---|---|
119
+ | What files exist and what do they export? | `codegraph_arch` |
120
+ | Where is function X defined? | `codegraph_query node:"X"` |
121
+ | What does module Y depend on? | `codegraph_query question:"what does Y import?"` |
122
+ | What are all the classes? | `codegraph_nodes type:"class"` |
123
+ | Most connected files? | `codegraph_report` |
98
124
 
99
- **File** use for bugs, logic inside a specific function, tracing unexpected behavior.
125
+ **Never read files for structure questions use graph tools first.**
100
126
 
101
127
  ---
102
128
 
103
- ## 5. Rules
129
+ ## 7. Rules
104
130
 
105
- 1. **`context.resume` first** — before any tool or response
106
- 2. **Always pass `project`** — never save to global unless truly cross-project
107
- 3. **`search` before asking**if user references past work, find it first
108
- 4. **`codegraph_query` before reading files**graph is faster and cheaper
109
- 5. **Read files for bugs/logic**graph is structure only, not behavior
131
+ 1. `context.resume` first — before any tool or response
132
+ 2. Always pass `project`
133
+ 3. Save on task complete `why` + `outcome` + `files`
134
+ 4. Compaction at 20 entries before starting the task
135
+ 5. Plan for multi-file work save before starting, `status:"done"` deletes it
136
+ 6. Search before asking about past work
137
+ 7. Graph tools before files