-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathconfig.go
More file actions
172 lines (157 loc) · 5.93 KB
/
Copy pathconfig.go
File metadata and controls
172 lines (157 loc) · 5.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package config
import (
"fmt"
"os"
"gopkg.in/yaml.v3"
)
// Config represents the mcp-arch.yaml configuration.
type Config struct {
Repo string `yaml:"repo"`
Ignore []string `yaml:"ignore"`
TestGlobs []string `yaml:"test_globs"`
Extractors []string `yaml:"extractors"`
Explainers []string `yaml:"explainers"`
Renderers []string `yaml:"renderers"`
Output OutputConfig `yaml:"output"`
// Incremental enables per-extractor caching: an extractor's facts are reused
// across snapshots when the files it owns (and the repo's shared config files)
// are unchanged. Defaults to true. Set `incremental: false` to force a full
// re-extraction every run.
Incremental *bool `yaml:"incremental,omitempty"`
}
// IncrementalEnabled reports whether per-extractor caching is on (the default).
func (c *Config) IncrementalEnabled() bool {
return c.Incremental == nil || *c.Incremental
}
// OutputConfig controls where and how output artifacts are generated.
type OutputConfig struct {
Dir string `yaml:"dir"`
MaxContextTokens int `yaml:"max_context_tokens"`
}
// Default returns a Config with sensible defaults.
func Default() *Config {
return &Config{
Repo: ".",
Ignore: []string{
"vendor/**",
"node_modules/**",
".git/**",
"**/*_test.go",
"**/*.test.ts",
"**/*.test.tsx",
"**/*.spec.ts",
"**/*.spec.tsx",
// Ruby, unlike Go and TS, has no co-located test convention: RSpec
// requires spec/, Minitest defaults to test/. Demand the directory as
// well as the filename — a bare "**/*_test.rb" also swallows production
// code that merely ends in the token (a job named cache_warmup_ab_test.rb),
// deleting it from the graph.
// Keep in sync with TestGlobs below: a file that stops being a test must
// stop being ignored, or it is dropped without being recovered.
"**/spec/**/*_spec.rb",
"**/test/**/*_test.rb",
".enola/**",
// Build / cache artifacts. These are generated output (often transpiled
// JS, e.g. Next.js .next/) and must never be indexed as source — doing so
// pollutes query_facts with thousands of spurious facts. The **/<dir>/**
// form matches the directory at ANY depth (e.g. Gradle/Android emit
// data/build/kspCaches/...), not just at the repo root.
".next/**",
"dist/**",
"**/build/**",
"out/**",
".vercel/**",
".turbo/**",
"coverage/**",
".nuxt/**",
".svelte-kit/**",
"**/__pycache__/**",
// Python virtual environments, installed dependencies, and tool caches.
// A repo-local .venv/venv holds the entire dependency tree (thousands of
// third-party .py files); indexing it is never wanted and dominates
// snapshot time. site-packages is the definitive catch for any oddly-named
// env (.tox/.nox/conda/direnv all nest one). Any-depth (**/x/**) form so
// monorepo sub-project venvs are pruned too.
"**/.venv/**",
"**/venv/**",
"**/site-packages/**",
"**/.tox/**",
"**/.nox/**",
"**/.eggs/**",
"**/.mypy_cache/**",
"**/.pytest_cache/**",
"**/.ruff_cache/**",
"**/Pods/**",
"**/.gradle/**",
// Minified / bundled JS by name. The extractor also detects minified
// content heuristically (very long lines), but these globs cheaply skip
// the common named cases before a file is ever read. Keep in sync with
// the bundled mcp-arch.yaml ignore list.
"**/*.min.js",
"**/*.bundle.js",
},
// TestGlobs identify test/spec files. They stay ignored for normal indexing
// (still listed in Ignore above) — production architecture facts must not
// include test symbols — but the engine collects them separately for
// reference-only extraction so the dead-code detector can see that a
// production symbol is exercised by a test and not mis-report it as dead.
// A glob here without an extractor implementing plugin.TestRefExtractor is a
// no-op (engine.runTestRefExtractors skips non-implementers), so extend this
// list only alongside the matching extractor. Go's and TypeScript's dotted
// suffixes are correct: the toolchain/convention reserves *_test.go and
// *.test.ts(x)/*.spec.ts(x) for tests, so — unlike Ruby's _test.rb (v97) — no
// production file can collide with them.
TestGlobs: []string{
"**/*_test.go",
"**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx",
"**/spec/**/*_spec.rb", "**/test/**/*_test.rb",
},
Extractors: []string{"cpp", "go", "grpc", "java", "kotlin", "openapi", "php", "python", "typescript", "swift", "ruby"},
Explainers: []string{"cycles", "layers", "crossrepo", "coverage", "unused-routes", "god-class", "hotspots", "dependency-depth", "exported-surface", "complexity-outliers"},
Renderers: []string{"llm_context"},
Output: OutputConfig{
Dir: ".enola",
MaxContextTokens: 16000,
},
}
}
// Load reads a configuration file from the given path.
// Missing fields are filled with defaults.
func Load(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading config %s: %w", path, err)
}
cfg := Default()
if err := yaml.Unmarshal(data, cfg); err != nil {
return nil, fmt.Errorf("parsing config %s: %w", path, err)
}
// Ensure required defaults
if cfg.Output.Dir == "" {
cfg.Output.Dir = ".enola"
}
if cfg.Output.MaxContextTokens == 0 {
cfg.Output.MaxContextTokens = 16000
}
return cfg, nil
}
// IsExtractorEnabled returns true if the named extractor is enabled.
func (c *Config) IsExtractorEnabled(name string) bool {
return contains(c.Extractors, name)
}
// IsExplainerEnabled returns true if the named explainer is enabled.
func (c *Config) IsExplainerEnabled(name string) bool {
return contains(c.Explainers, name)
}
// IsRendererEnabled returns true if the named renderer is enabled.
func (c *Config) IsRendererEnabled(name string) bool {
return contains(c.Renderers, name)
}
func contains(ss []string, s string) bool {
for _, v := range ss {
if v == s {
return true
}
}
return false
}