You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(config): derive the ignore glob from output.dir instead of hard-coding .enola
`.enola/**` was a literal in the default ignore list, sitting between `.next/**` and
`dist/**` as though it were another build-artifact glob, and agreeing with
cfg.Output.Dir only by coincidence. Point output.dir anywhere else and each snapshot
walked the previous one's artifacts — facts.jsonl, insights.json, llm_context.md, plus
the previous/ rotation from run 2 on — so an unchanged tree produced a different
snapshot every run. Reproducibility is the property the baseline diff rests on, and
this broke it for a reason having nothing to do with determinism, on a setting users
are invited to change, with a symptom pointing nowhere near the cause. Comparability
checking cannot catch it either: the config is identical on both sides.
config.Normalize() now fills in the defaults, validates them, and derives
`<output.dir>/**`. It runs from config.Load AND engine.New, because a config assembled
in code never passes through Load and has the same problem; it is idempotent. The
literal .enola/** stays in the defaults so a repository that used the default before
changing it does not start indexing its own history.
output.dir must name a subdirectory of the repository. It is joined to the repo path in
half a dozen places, so an absolute value silently produced /repo/private/tmp/.../out
rather than the location asked for, and no derived glob could describe it. Absolute
paths, `..` escapes and `.` are now rejected by name, and the value is cleaned so the
glob and the joined directory describe the same place.
Rejecting a bad output.dir exposed a worse behaviour above it: a config that EXISTED
but could not be used was a warning, replaced by the built-in defaults — whose
`repo: "."` is the working directory. A typo therefore made enola analyse whichever
repo you were standing in and present it as an answer about the one the config named.
ResolveConfig now errors for a config that is present and wrong; a missing config still
falls back, which is the intended leniency. Same rule the CLI already applies to an
explicitly-named path that does not exist.
Migration: the ignore list feeds ignore_glob_hash and config_hash, so a custom
output.dir yields one incomparable diff and needs a re-pin. The default location is
unaffected — the derivation dedupes against the literal already there.
TestSnapshot_CustomOutputDirIsNotIndexedAsSource takes three snapshots of an unchanged
tree, writing artifacts each round, and asserts files_seen is constant. It fails on the
code it replaces, reporting [2 7 11].
Copy file name to clipboardExpand all lines: ARCHITECTURE.md
+7-1Lines changed: 7 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -831,6 +831,12 @@ The bundled [`mcp-arch.yaml`](mcp-arch.yaml) ships a much fuller `ignore` list (
831
831
832
832
That line exists because the failure it prevents is silent. A config decides which extractors run and which paths are ignored, so loading the wrong one does not error — it analyses something other than what was asked for. Before the restriction and the announcement, a config sitting beside a `go build` output governed every repository that binary was ever pointed at, from any directory without one of its own; an eleven-extractor list written before the Rust extractor landed turned a 780-file Rust repository into `0 facts`, with no error and no mention of Rust anywhere in the log.
833
833
834
+
A config that is **missing** falls back to the built-in defaults. A config that is **present and unusable** — unparseable, or naming an `output.dir` that cannot be honoured — is a fatal error instead, because the fallback's `repo: "."` is the working directory: a typo would otherwise make enola analyse whichever repository you were standing in and present it as an answer about the one the config named. Same rule the CLI already applies to an explicitly-named path that does not exist.
835
+
836
+
**The output directory ignores itself, wherever it is.** `config.Normalize` — run by both `config.Load` and `engine.New`, so a config assembled in code is treated identically — appends `<output.dir>/**` to the ignore list. `.enola/**` remains in the defaults as a literal as well, so a repository that used the default before changing it does not start indexing its own history.
837
+
838
+
That derivation is load-bearing rather than tidy. The literal used to be the *only* entry, sitting between `.next/**` and `dist/**` as though it were another build-artifact glob, agreeing with `Output.Dir` only by coincidence. Set `output.dir` to anything else and each snapshot walked the previous one's artifacts — `facts.jsonl`, `insights.json`, `llm_context.md`, plus the `previous/` rotation from run 2 onward — so an unchanged tree produced a different snapshot every run. Reproducibility is the property the baseline diff rests on, and comparability checking cannot catch this: the config is identical on both sides.
839
+
834
840
**A list-valued key REPLACES its default; it does not merge.** `yaml.Unmarshal` overwrites the slice, so `extractors:` names the complete set — a config written before an extractor existed disables it permanently, and a disabled extractor is never tried and so never appears in the log. Two things make that visible: a bundled config that names no plugin lists at all, and a warning naming any *excluded* extractor that would have detected the repository (also recorded as `shadowed_extractors` in the snapshot receipt). The semantics are unchanged on purpose — an explicit list is the only way to disable an extractor.
835
841
836
842
| Field | Description | Default |
@@ -841,7 +847,7 @@ That line exists because the failure it prevents is silent. A config decides whi
| `output.dir` | Output directory for artifacts | `".enola"` |
850
+
| `output.dir` | Output directory for artifacts. Must name a **subdirectory of the repository** — it is joined to the repository path, so an absolute value would nest that whole path inside the repo rather than write where it says. An ignore glob is derived from it automatically (see below) | `".enola"` |
845
851
| `output.max_context_tokens` | Token budget for `llm_context.md` | `16000` |
846
852
| `dashboard.port` | The fixed **shared URL** port every server competes for, in addition to its own ephemeral one. A negative value serves only the ephemeral port. `ENOLA_DASHBOARD_PORT` overrides it (`off` disables). | `7171` |
847
853
| `incremental` | Reuse each extractor's cached facts across snapshots when its files are unchanged; set `false` to force full re-extraction every run | `true` |
0 commit comments