agentleFS
VECTORG99VECTORG99/acf/skills/09-graph-scope/SKILL.md

graph-scope

Skill2 starsChanged 3 months agoMITView on GitHub

Cross-cutting skill for ACF. Decomposes a project into a dependency graph so the agent loads context only for the affected subgraph, not the entire project. Builds the graph using grep/find (no external dependencies), stores it as a markdown file under .slim/acf/, and provides query patterns for blast-radius (forward) and context-tracing (backward). Use before context-load to narrow scope, when the user asks "what's affected by changing X", "map the dependencies", "scope this change", or when ACF detects the project is large enough that full context-load would waste tokens.

The file

Show its rules and text

We split this skill into the rules it gives an agent

25 rules. About 0 are common, 16 are distinctive, and 9 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈16

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 9

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Before context-load, when the project has more than ~20 source files

    line 29
  2. Distinctive:

    When the user asks "what's affected by changing X?"

    line 30
  3. Distinctive:

    When the user asks to "map the dependencies" or "build the graph"

    line 31
  4. Distinctive:

    When the user asks to "scope this change" or "narrow the context"

    line 32
  5. Distinctive:

    When ACF detects a large project where full context-load would waste tokens

    line 33
  6. Distinctive:

    When the user explicitly requests "full scope" (skip graph-scope, load everything)

    line 34
  7. Distinctive:

    Compute a content hash: after each build — hash file mtimes and file count:

    line 55
  8. Distinctive:

    If hashes match → reuse the cached graph (skip rebuild)

    line 66
  9. Distinctive:

    If hashes differ → rebuild the graph and update the hash

    line 67
  10. Very specific:

    After building the adjacency list, run a cycle detection pass (DFS with a visited stack).

    line 196
  11. Distinctive:

    When computing the forward (blast radius) traversal, skip cyclic edges to prevent infinite loops:

    line 218
  12. Very specific:

    If a node is already in visited, skip it (do not recurse further)

    line 222
  13. Very specific:

    When a change is requested (e.g., "fix the flashlight bug in EntityController.luau"), graph-scope computes the affected subgraph:

    line 232
  14. Very specific:

    Read their exports/types/interfaces

    line 270
  15. Distinctive:

    After computing both traversals, graph-scope writes a directive to the progress file:

    line 275
  16. Very specific:

    Check if .slim/acf/graph.json exists and is fresh

    line 331
  17. Distinctive:

    If not, run graph-scope build phase

    line 332
  18. Distinctive:

    Run graph-scope query phase with the change target

    line 333
  19. Distinctive:

    When the token budget is tight:

    line 379
  20. Distinctive:

    After graph-scope runs, the progress file contains:

    line 387
  21. Very specific:

    .slim/acf/ directory doesn't exist: create it before writing graph files.

    line 404
  22. Very specific:

    gh not authenticated: not applicable to graph-scope.

    line 411
  23. Distinctive:

    GitHub API rate limits: not applicable to graph-scope.

    line 412
  24. Very specific:

    If .slim/acf/ doesn't exist, create it.

    line 414
  25. Very specific:

    Commands fail: if grep or find fails (permissions, binary files), skip the affected files and note them: "[file]: scan error — skipped." Do …

    line 416

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Graph-Scope

Decomposes a project into a dependency graph so the agent navigates only the relevant subgraph for each change. Instead of reading the entire project (default context-load behavior), graph-scope identifies which files matter for the current change and limits context-load to those files.

This is the difference between a flashlight (context-load: read all MDs) and a laser (graph-scope: read only what the change touches).

When to Trigger

  • Before context-load, when the project has more than ~20 source files
  • When the user asks "what's affected by changing X?"
  • When the user asks to "map the dependencies" or "build the graph"
  • When the user asks to "scope this change" or "narrow the context"
  • When ACF detects a large project where full context-load would waste tokens
  • When the user explicitly requests "full scope" (skip graph-scope, load everything)

What It Produces

Two files under .slim/acf/:

  1. graph.md — human/agent-readable graph summary (hubs, leaves, layers)
  2. graph.json — machine-readable adjacency list (for programmatic traversal by the agent)

The graph is rebuilt when:

  • The project is scanned for the first time
  • The user asks to "rebuild the graph"
  • Files have been added/removed since the last build (check with git status)

Cache Invalidation

To avoid rebuilding the graph on every run, graph-scope caches the graph and rebuilds only when the project has changed:

  1. Compute a content hash after each build — hash file mtimes and file count:
    # Hash: file count + sum of mtimes for all source files
    find . -type f \( -name "*.luau" -o -name "*.lua" -o -name "*.py" \
      -o -name "*.ts" -o -name "*.js" -o -name "*.go" -o -name "*.rs" \) \
      -not -path './.git/*' -not -path './node_modules/*' \
      -exec stat -c '%Y' {} \; | sort | sha256sum
  2. Store the hash in .slim/acf/graph.json under a "content_hash" field.
  3. On next run, recompute the hash and compare:
    • If hashes match → reuse the cached graph (skip rebuild)
    • If hashes differ → rebuild the graph and update the hash
  4. When to rebuild (hash changes):
    • Files added or removed (file count changes)
    • Files modified (mtime changes)
    • User explicitly requests "rebuild the graph"
    • .slim/acf/graph.json is missing or corrupted

Build Phase: Constructing the Graph

Step 1: Detect language(s)

Identify the project's primary language(s) by file extension frequency:

find . -type f -not -path './.git/*' -not -path './node_modules/*' \
  | sed 's/.*\.//' | sort | uniq -c | sort -rn | head -10

Map extensions to import patterns:

LanguageExtension(s)Import pattern (regex)
Luau.luaurequire\(.*\)
Lua.luarequire\(.*\)
Python.py^import |^from .+ import
JavaScript.js, .jsx`require(^import .+ from`
TypeScript.ts, .tsx`^import .+ fromrequire(`
Go.go^import \(
Rust.rs^use
Java.java^import
C#.cs^using
C/C++.c, .cpp, .h^#include

Step 2: Extract imports per file

For each source file, extract its imports using grep:

# Example: Luau
grep -rEn "require\(" --include="*.luau" . \
  | sed 's/:\s*require(\(.*\))/\1/' \
  | sort -u

The agent should adapt the pattern to the detected language(s). For multi-language projects, run each pattern and merge results.

Step 3: Resolve import paths

Map import strings to actual file paths. This is language-specific:

  • Luau/Lua: require(game:GetService("ReplicatedStorage").Packages.Knit) → src/shared/ + module name + .luau
  • Python: from src.shared.constants import X → src/shared/constants.py
  • JS/TS: import X from './utils' → ./utils.ts or ./utils/index.ts
  • Go: import "github.com/user/repo/pkg" → pkg/ directory

The agent resolves paths using find and grep — no external parser needed.

Step 4: Build adjacency list

For each file, record:

  • id: the file path (relative to project root)
  • imports: list of file paths this file imports (resolved in step 3)
  • imported_by: list of file paths that import this file (reverse edges)

Write to .slim/acf/graph.json:

{
  "version": 1,
  "project": "project-name",
  "languages": ["luau"],
  "built_at": "2026-08-04T12:00:00Z",
  "content_hash": "a1b2c3d4...",
  "node_count": 53,
  "edge_count": 127,
  "cycles": ["src/server/Services/A.luau ↔ src/server/Services/B.luau"],
  "nodes": {
    "src/shared/Constants.luau": {
      "imports": [],
      "imported_by": [
        "src/server/Services/CycleManager.luau",
        "src/server/Services/EconomyService.luau"
      ]
    }
  }
}

Step 5: Build human-readable summary

Write to .slim/acf/graph.md:

# Dependency Graph

## Stats
- Nodes: 53
- Edges: 127
- Languages: luau
- Built: 2026-08-04

## Hub nodes (most depended-on, top 10)
1. src/shared/Constants.luau (26 dependents)
2. src/shared/Types.luau (22 dependents)
3. src/shared/Utils.luau (18 dependents)
...

## Leaf nodes (no dependents — safe to change without blast radius)
- src/client/Controllers/UIController.luau
...

## Isolated nodes (no imports, no dependents)
- scripts/test_core.luau
...

## Layers (by dependency depth)
- Layer 0 (no imports): Constants, Types, Utils
- Layer 1 (imports Layer 0): Services, SignalRefs
- Layer 2 (imports Layer 1): Controllers

Circular Dependency Handling

During graph build, detect circular dependencies (import cycles):

Detection

After building the adjacency list, run a cycle detection pass (DFS with a visited stack). For each node, traverse its imports edges recursively. If a node is revisited on the current DFS path, a cycle exists.

A → B → C → A  (cycle detected)

Reporting

  • Report cycles as warnings, not errors — do not crash the graph build
  • Record detected cycles in graph.json under a "cycles" field (list of cycle descriptions)
  • Record cycles in graph.md under a ## Circular Dependencies section:
    ## Circular Dependencies (warnings)
    - src/server/Services/A.luau ↔ src/server/Services/B.luau (mutual import)
    - src/client/Controllers/X.luau → Y.luau → Z.luau → X.luau (3-node cycle)

Blast-radius traversal

When computing the forward (blast radius) traversal, skip cyclic edges to prevent infinite loops:

  • Maintain a visited set during traversal
  • If a node is already in visited, skip it (do not recurse further)
  • Note in the scoped directive if cycles were encountered and skipped:
    Cycles skipped in blast-radius: A↔B (mutual import)

This ensures the blast-radius computation always terminates, even with cycles.

Query Phase: Scoping a Change

When a change is requested (e.g., "fix the flashlight bug in EntityController.luau"), graph-scope computes the affected subgraph:

Forward traversal (blast radius — what might break)

Starting from the changed file, traverse imported_by edges outward:

Changed: src/server/Services/EntityController.luau
  → imported_by: src/server/Services/CycleManager.luau
    → imported_by: src/server/init.luau
  → imported_by: src/client/Controllers/EntityController.luau
    → imported_by: src/client/init.luau

Result: the blast radius is [CycleManager, init.luau (server), EntityController (client), init.luau (client)].

The agent should:

  1. List all files in the blast radius
  2. Flag any hub nodes in the radius (high-risk changes)
  3. Flag any test files in the radius (must re-run those tests)

Backward traversal (context — what to load)

Starting from the changed file, traverse imports edges inward:

Changed: src/server/Services/EntityController.luau
  → imports: src/shared/Constants.luau
  → imports: src/shared/Types.luau
  → imports: src/shared/SignalRefs.luau
  → imports: src/shared/Utils.luau

Result: the context scope is [Constants, Types, SignalRefs, Utils].

The agent should:

  1. Load context for these files (via context-load, but scoped)
  2. Read their exports/types/interfaces
  3. Skip files not in this subgraph

Output: Scoped Context Directive

After computing both traversals, graph-scope writes a directive to the progress file:

## Graph Scope

Change target: src/server/Services/EntityController.luau

Blast radius (forward, 3 hops):
- src/server/Services/CycleManager.luau
- src/client/Controllers/EntityController.luau
- src/server/init.luau
- src/client/init.luau

Context scope (backward, full depth):
- src/shared/Constants.luau
- src/shared/Types.luau
- src/shared/SignalRefs.luau
- src/shared/Utils.luau

Test files in scope:
- scripts/test_services.luau (covers EntityController)
- scripts/test_controllers.luau (covers EntityController client)

Hub nodes in blast radius: none (all leaves/low-degree)

Scope mode: scoped (9 files of 53 total — 83% reduction)

Scope Modes

ModeBehaviorWhen to use
scopedLoad context only for the affected subgraphDefault, large projects
fullLoad context for the entire projectSmall projects, or when user says "load everything"
manualUser specifies which files to scope toWhen the user knows the exact scope

The orchestrator selects the mode:

  • Default: scoped if project has >20 source files, full otherwise
  • Override: user can say "full scope" or "scope to [file list]"

Integration with ACF Pipeline

Graph-scope runs before context-load when in scoped mode:

graph-scope → context-load (scoped) → stack-audit → issue-craft → ...

In full mode, graph-scope is skipped and context-load reads everything:

context-load (full) → stack-audit → issue-craft → ...

The orchestrator should:

  1. Check if .slim/acf/graph.json exists and is fresh
  2. If not, run graph-scope build phase
  3. Run graph-scope query phase with the change target
  4. Pass the scoped file list to context-load
  5. Context-load reads only those files + all project MDs (MDs are always loaded — they contain conventions and AC patterns)

What Graph-Scope Does NOT Do

  • Static type analysis — it traces imports, not type flows. For type-level impact, use a language-specific tool (tsc, mypy, luau-analyze).
  • Runtime dependency detection — it scans source code, not runtime behavior. Dynamic imports, reflection, or dependency injection are invisible.
  • Replace tests — it identifies which tests to run, but doesn't run them. The agent should use the test list from graph-scope to run the right tests.
  • Require external tools — no tree-sitter, no AST parser, no Python. The agent uses grep, find, and its own language understanding to build the graph.

Comparison to Existing Tools

ToolApproachDependenciesACF integrationCompaction
change-impact-analysis-skillAST + BFSPython, PyYAMLNoneNo
project-understanding (evil-skills)Tree-sitterNode, tree-sitterNoneToken budgeting
codespaces (diskd-ai)Tree-sitter, belief-mapPython, tree-sitterNoneNo
HawkeyeAST + graphPythonNoneCompact JSON
ConstrictorASTPythonNoneNo
ACF graph-scopegrep + agent reasoningNoneYes (pipeline)Yes (phase 7/8)

ACF graph-scope is the only one that:

  1. Requires zero external dependencies (agent uses grep/find)
  2. Integrates with an issue/PR crafting pipeline
  3. Can be compacted by phase 7 and caveman phase 8
  4. Works on any language the agent understands (not limited to Python/TS)
  5. Produces agent-readable markdown (not just JSON for tools)

The trade-off: ACF graph-scope is less precise than AST-based tools (it uses regex, not parsing). For projects that need type-level analysis, use an AST tool alongside ACF. For projects that need fast scope narrowing without dependencies, ACF graph-scope is sufficient.

Token Budget

The graph itself is compact:

  • graph.md: ~200-500 tokens (summary, top hubs, layers)
  • graph.json: ~500-2000 tokens (full adjacency list)
  • Scoped directive: ~50-200 tokens (just the affected subgraph)

When the token budget is tight:

  • Phase 7 (compaction) compresses the graph to hub/leaf counts + the scoped directive only
  • Phase 8 (caveman) compresses to SCOPE: [file list] | BLAST: [count] | TESTS: [file list]

Output

After graph-scope runs, the progress file contains:

  • The scoped file list (context scope)
  • The blast radius (forward traversal)
  • The test files in scope
  • The scope mode used
  • The reduction percentage (files scoped vs total)

Empty State Handling

  • Project has <5 files: graph-scope is not useful for tiny projects. Skip graph-scope entirely and use full mode for context-load. Report "Project has [N] files — too small for graph-scope. Using full context-load."
  • Project has no source files (only docs/config): report "No source files detected — graph-scope skipped. Context-load will read MDs only."
  • No imports detected (all files are isolated): build the graph with all nodes as isolated. Report "No import relationships found — all files are isolated. Blast radius is limited to the changed file only."
  • .slim/acf/ directory doesn't exist: create it before writing graph files. This is expected on first run.

Error Handling

  • gh CLI not installed: graph-scope does not require gh — it uses grep, find, and stat only. Proceed normally.
  • gh not authenticated: not applicable to graph-scope. Proceed normally.
  • GitHub API rate limits: not applicable to graph-scope. Proceed normally.
  • Required files/directories don't exist: see Empty State Handling above. If .slim/acf/ doesn't exist, create it. If no source files are found, skip graph-scope.
  • Commands fail: if grep or find fails (permissions, binary files), skip the affected files and note them: "[file]: scan error — skipped." Do not abort the graph build. If graph.json is corrupted, delete it and rebuild from scratch.

Example

graph.json (excerpt)

{
  "version": 1,
  "project": "flashlight-game",
  "languages": ["luau"],
  "built_at": "2026-08-04T12:00:00Z",
  "content_hash": "a1b2c3d4e5f6...",
  "node_count": 53,
  "edge_count": 127,
  "cycles": [],
  "nodes": {
    "src/shared/Constants.luau": {
      "imports": [],
      "imported_by": [
        "src/server/Services/CycleManager.luau",
        "src/server/Services/EconomyService.luau",
        "src/client/Controllers/ToolController.luau"
      ]
    },
    "src/server/Services/EntityController.luau": {
      "imports": [
        "src/shared/Constants.luau",
        "src/shared/Types.luau",
        "src/shared/SignalRefs.luau"
      ],
      "imported_by": [
        "src/server/Services/CycleManager.luau",
        "src/client/Controllers/EntityController.luau"
      ]
    }
  }
}

graph.md (excerpt)

# Dependency Graph

## Stats
- Nodes: 53
- Edges: 127
- Languages: luau
- Built: 2026-08-04
- Cycles: none

## Hub nodes (most depended-on, top 10)
1. src/shared/Constants.luau (26 dependents)
2. src/shared/Types.luau (22 dependents)
3. src/shared/Utils.luau (18 dependents)

## Leaf nodes (no dependents — safe to change without blast radius)
- src/client/Controllers/UIController.luau
- src/client/Controllers/ToolController.luau

## Layers (by dependency depth)
- Layer 0 (no imports): Constants, Types, Utils
- Layer 1 (imports Layer 0): Services, SignalRefs
- Layer 2 (imports Layer 1): Controllers

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.