v5.0.0
Changelog
All notable changes to this project are documented here. Format follows
Keep a Changelog; the project adheres
to Semantic Versioning.
[v4.0.0] — 2026-05-18
v4.0 — Adoption fixed-point + bug triage
Tickets under tickets/v4-*.md. v4 focuses on adoption quality:
making the existing tools work reliably on real codebases. The three
themes are (1) adoption-health observability (B1–B3), (2) bug triage
from the first TS field test (T1–T7), and (3) the get_references
class-symbol fix. No new languages, no new symbol kinds — Rust
and route literals are deferred to v5. No new architectural pillars.
See LIMITATIONS.md for what stays out of scope.
Fixed
-
get_references(ClassName)now includes static and instance method
calls. The TS resolver resolvesCsEnv.from()to the method symbol
(env.CsEnv.from), not the class symbol (env.CsEnv). Previously
symbolsByTargetonly matched the target's own qualified name, so
get_references("CsEnv")missed allCsEnv.from()/CsEnv.empty()
call sites. Fix: after collecting direct matches, a second pass
expands each qualified name to its children viaLIKE q+".%", then
deduplicates. Regression test inget_refs_test.gowith fixture
testdata/fixtures/get-refs/(reproduces the codesphere monorepo-4
finding where 3/3get_referencescalls returned null on a class
used only through static-method calls). -
F1 follow-up batch (T3-T7): five smaller field-test fixes.
- T3 —
search_lexicalergonomics. Three changes: empty results
return[]LexicalHit{}notnil(JSON consumers see[]instead
ofnull, distinguishable from "tool errored"); regex compile
errors gain an actionable hint ("Go regexp syntax: |, ., [], (?i)
all supported"); the path-eliminate-all error message suggests
omitting the filter; the daemon logs a one-line stderr hint when
path_containsnarrowed the candidate set but no hits surfaced
(debuggable signal inmyco daemonoutput). - T4 — adaptive corpus for
bench-counterfactual. New
bench.BuildAdaptiveCorpus(client)probes the daemon
(list_files→ pick the heaviest indexed file bysymbol_count
→get_file_outline→ pick the first non-trivial symbol) to
construct a corpus targeting REAL symbols + files in any
indexed repo. New--adaptiveflag opts in. When--repois
passed without--adaptiveand every row fails, the bench
prints a concrete hint: "the default corpus is mycelium-tuned;
re-run with --adaptive". Closes the F1/T4 silent-DRIFT case. - T5 — counterfactual gates on success.
Summary.OutputBytesOK
new field tracks bytes from successful calls only. Aggregator
changes counterfactual basis fromOutputBytesto
OutputBytesOKso afind_symbolthat returnednull(4-14
bytes for the failed envelope) doesn't earn savings credit.
F1's session showed-0.1% savingsbecause failed myco calls
diluted the math; post-fix the model is honest — failed calls
contribute zero to the without-myco estimate, so savings goes
appropriately negative when myco failed and the agent paid the
fallback anyway. Backward-compat shim: whenOutputBytesOK == 0
ANDOK == Count, falls back toOutputBytesso v3.4-shape
fixtures continue to work. - T6 — top-level
myco findandmyco searchaliases. Users
(and agents) typedmyco find symbol WorkspacePlanand
myco search "WorkspacePlan|plans"during F1 — both errored
because the actual subcommands are buried undermyco query.
Two new top-level cobra commands delegate to the existing
runQueryFind/runQueryLexicalso reflexive command names
just work.myco findalso aliases asfind-symbol;
myco searchaliases asgrep. The originalmyco query find
/myco query grepkeep working unchanged. - T7 — strip IDE wrapper tags from session "Task" field. A
session that begins with an IDE-injected<ide_opened_file>...
or<system-reminder>...block used to surface that tag-soup
as the session's task description inmyco session export.
NowparseTranscriptReaderskips messages whose body is
exclusively a wrapper tag (<ide_opened_file>,<ide_selection>,
<system-reminder>,<command-name>,<local-command-stdout>)
and falls through to the next user message. Mixed-content
messages (wrapper + prose) keep their content. Nine-case unit
test pins the heuristic.
All five land in one bundle because each is small and they
collectively complete the F1 v4 P0+P1+P2 follow-ups. T3 ergonomics- T4 adaptive corpus + T5 counterfactual honesty are the load-
bearing fixes; T6 + T7 are usability polish.
- T3 —
-
Daemon fd-leak on large repos (F1/T2 EMFILE). Codesphere
monorepo-4(3079 files, 49 packages) hittoo many open files
on macOS during a real session because fsnotify consumes one fd
per watched directory and the macOS defaultRLIMIT_NOFILEsoft
limit is 256. Three-layer fix:-
Layer 3 (the real fix): bump
RLIMIT_NOFILEto hard at
daemon startup. Newinternal/daemon/RaiseFileDescriptorLimit()
is called inrunDaemonbefore any fd-consuming code runs.
macOS goes 256 → ~10240 (40× headroom); Linux goes 1024 →
~1048576 (1024× headroom). Verified live: daemon now logs
[daemon] RLIMIT_NOFILE raised to 1048576 (hard cap 1048576)
on startup. macOS quirk handled: when Setrlimit to hard fails
(kernelkern.maxfilesperproccap), we step down through
{10240, 4096, 2048, 1024} and accept whichever the kernel
allows. Failure is non-fatal (warning logged, daemon continues
at original limit). Windows is a no-op (per-process handle
limit is 16M+, not a real constraint). Build-tagged
rlimit_unix.go+rlimit_other.gofor portability. -
Layer 1 (diagnostic):
daemon_fd_headroomdoctor check.
Newinternal/doctor/fd_headroom_linux.goreads the daemon's
PID file (newly written byrunDaemon) and probes
/proc/<pid>/fd+/proc/<pid>/limitsto surface fd-headroom
pressure before EMFILE hits. WARN at 60% utilisation, FAIL at
90% (Thresholds.FDHeadroomWarn/FDHeadroomFail). Skips
gracefully on missing pid file (daemon not running), stale pid
(daemon died without cleanup), or unreadable/proc
(sandboxed environments). Linux-only;fd_headroom_other.go
is a no-op stub on macOS / Windows because counting another
process's fds requireslsof(sub-process, brittle) or
platform-specific syscalls — and on macOS the layer 3 setrlimit
bump preempts the EMFILE case anyway, so the check is less
load-bearing there. Three unit tests cover the no-pid-file,
stale-pid, and real-process paths. -
Layer 2 (already shipped): Watchman backend. Inventory
during T2 work showedinternal/watch/watchman/is fully
implemented (1477 LOC, opt-in viawatcher.backend: watchman
in.mycelium.ymlsince v1.7). Users with monorepos beyond
even Layer 3's headroom set the config flag and watchman takes
over. The existinginotify_headroomdoctor check already
points at this path; no changes needed.
Daemon also writes
.mycelium/daemon.pidon startup (best-effort,
cleaned up on shutdown via defer) so the doctor check has
something to probe. Not exposed as a public API — implementation
detail of T2 layer 1.Closes the F1/T2 blocker. Phase 2 field tests can now run on
monorepo-scale repos without hitting the macOS 256-fd cap; Linux
users get a doctor warning before crossing 60% utilisation. -
-
TypeScript
.d.tsindexing — the F1/T1 adoption blocker. The
default include glob wassrc/**/*.{ts,tsx}which narrowed TS
coverage to files insidesrc/. Codespheremonorepo-4's field
test surfaced this:find_symbol{name: "WorkspacePlan"}returned
null because the type is defined in
packages/payment-service/common/lib/Product.d.ts—
outsidesrc/, never walked. Default include broadened to
**/*.{ts,tsx,d.ts,mts,cts}so all common TS source locations
(package roots,lib/, ambient declarations, test config files)
are picked up; compiled outputs continue to be excluded by
**/dist/**/**/build/**. Wizard inherits viaconfig.Default()
— no separate template change needed.Also fixed the
moduleNamequalifier for.d.tsfiles: previously
Product.d.tsproduced symbols qualified asProduct.d.WorkspacePlan
(the.dsuffix wasn't stripped), now strips.d.ts/.d.mts/
.d.ctsas a unit so qualified names match the source file
(Product.d.ts→Product.WorkspacePlan). Pure rename — symbol
identity / refs continue to resolve.New fixture
testdata/fixtures/sample/src/types.d.tsmirrors the
monorepo-4 shape:interface WorkspacePlan,type WorkspacePlanMap,
type PlanSelector,enum PlanTier,declare module. New
integration testfind_symbol_in_d_ts_v4_T1_fixasserts each
shape returns fromfind_symbol. Existingstatsandlist_files
expected file count bumped from 3 → 4 to include the new fixture.
task checkgreen;myco bench-counterfactualcontinues to pass
(unrelated surface). Closes the F1/T1 blocker; Phase 2 Python
field test (planned) will replicate the fixture for.pyistub
files.
Added
-
B3 — multi-repo bench-counterfactual harness. The bench-counterfactual
corpus + runner moved out ofcmd/myco/main.gointo a new
internal/bench/package:Case,Corpus,Rowtypes,
MyceliumDefaultCorpus()returning the v3.4-calibrated mycelium-self
corpus,Run(client, repoRoot, corpus, language, driftThreshold)
the orchestrator,PrintTable(rows, threshold, corpusName, language)
the renderer. The CLI command shrinks to a thin flag-parsing
orchestrator. Two new flags:--repo <path>overrides the daemon
socket location so the bench can target another mycelium-indexed
repo withoutcd;--language <lang>selects the per-language
multiplier override when one is populated. Renderer header now
showscorpus=<name> language=<lang>so multi-repo runs are
unambiguous.Per-language multiplier framework wired in
counterfactualModel.
counterfactualEntrygained aperLang map[string]float64field;
new variantsEstimateCounterfactualFor(tool, bytes, language)and
CounterfactualMultiplierFor(tool, language)return the override
when present, falling back to the default. Existing
EstimateCounterfactual/CounterfactualMultiplierdelegate with
language=""for backward-compat.ComputeSessionCostFor(..., language)plumbs it through to per-row counterfactual computation;
ComputeSessionCostkeeps the v3.4 signature and delegates with
empty language. No per-language overrides populated in v4 B3 —
the framework is wired but data hasn't been gathered. F1
(Python/Django) and F2 (Rust/Axum) field tests will populate via
myco bench-counterfactual --language <lang>and update the pinned
test incalibration_test.goin the same commit.Re-calibration during the v4 Phase 1 churn: B1's preview path +
B2's adoption.go added enough new code that referenced
ComputeSessionCostto bump theget_referencesmeasured ratio
from 1.65× to 1.95×. Multiplier moved 1.2 → 1.8 to match;
calibration_test.goupdated in the same change. The pinned
test caught the regression on the first re-bench, exactly as
designed.Deferred to v4.1+ per the B3 ticket's honest caveats: (a) BYO
corpus via--corpus-file <yaml>— useful but no consumer asking;
(b)--update-multiplierssource-mutating flag — flagged in the
ticket as "If this feels too magical, drop it from v4 and require
manual table edits — it's a convenience, not load-bearing"; (c)
BuildAdaptiveCorpus(client)that probes the daemon for repo-
appropriate targets — needs multi-repo data first to validate the
heuristic. v4 ships the architectural extraction + per-language
framework; v4.1+ fills in the convenience layers.Two new tests:
TestMyceliumDefaultCorpus_Wellformedpins the
corpus shape (every Case has exactly one of FallbackCmd/FallbackFile,
every Method is a known IPC method, all nine tools covered);
TestCounterfactualModel_PerLanguageOverrideextends
calibration_test.goto assert the per-language fallback semantics- pin the (currently empty) override table so accidentally-added
overrides break the test loudly.task checkgreen; live
myco bench-counterfactual --repo <self>reproduces the table;
--repo /tmperrors cleanly with a "start the daemon in that repo"
message instead of a cryptic socket-not-found.
- pin the (currently empty) override table so accidentally-added
-
B2 — adoption-health doctor checks.
myco doctornow reads recent
session telemetry + per-session external logs and surfaces three
documenteddocs/adoption.mdfailure modes as a separate
adoption section. Findings never gate CI — they are
informational, do not roll intoSummary, do not affect ExitCode.
The classicpass/warn/failChecks block is unchanged.
Modes evaluated:search_lexical_only:search_lexical / total_myco_calls > 70%
→ agent treats myco as faster grep, missing the graph layer.read_focused_under_used:read_focused / (read_focused + Read) < 15%→ agent reaches for general-purpose Read instead of the
indexed reader (the v3.4 G2 / v4 B1 pattern made measurable).grep_over_myco:myco_calls / Bash/grep_calls < 1.5→ agent's
grep reflex outpaces myco usage; CLAUDE.md priming is missing.
Each warn ships with aHintpointing the user at the docs/adoption.md
remediation. Modes whose denominator is zero (e.g. zero file-reads
of either kind) are silently skipped — no opinion possible, noise
isn't useful. The fourth catalogued mode (read_focused_without_focus)
is deferred to v4.1+ because it requires per-call params in
the telemetry log; v4 B1's tool-side fix already gives agents
per-call feedback so the doctor surface is less urgent.
New CLI flags onmyco doctor:--window <duration>(default 7d) scopes which sessions count
toward the evaluation.--window 1hfor "what's been happening
in the last hour";--window 720hfor monthly.--no-adoptionsuppresses the adoption section entirely.
Pure-function evaluator ininternal/doctor/adoption.go(no DB,
no I/O — caller hands in pre-aggregated summaries) so future
HTTP/dashboard surfaces can reuse it. Six unit tests pin each
failure mode at boundary values plus the rg/ripgrep aggregation
case + the insufficient-data short-circuit. Two new telemetry
helpers underneath:AggregateSince(path, since)filters the
myco JSONL by timestamp;SummarizeAllExternalSince(dir, since)
walks everysession_*_external.jsonland folds the windowed
aggregate into one summary list. Both default to "no filter" when
passed a zerosince— backward-compat with the v3.4 callers.
The legacy v3.4adoption_tool_diversityCheck is replaced by
the newsearch_lexical_onlyfinding; same signal, now in the
separate adoption section instead of the regular Checks list.
task checkgreen; livemyco doctoragainst this repo surfaces
two real warns (read_focused_under_used at 2%, grep_over_myco at
1.0) — the dogfooding history this very feature is meant to
measure.
-
B1 —
read_focusedno-focus preview path. Emptyfocusused
to expand every symbol — Content was the entire file plus the
outline metadata, so the call was heavier than a plain Read
(the v3.4 A3 bench measured 14 KiB of myco output for a 12 KiB
file). v4 B1 makes empty-focuscalls return a preview instead:
the symbol outline (viaExpanded, unchanged shape) + the first
ReadFocusedPreviewLines(default 50) lines verbatim + a new
Hintfield telling the agent to passfocus=<query>(with a
concrete example pulled from the file's first symbol) or call
get_file_outlinefor symbol-only listing. Files shorter than
the cap return their full content with no Hint (no truncation
to advertise). The non-empty-focus path is untouched — same
collapse markers, sameExpandedshape, same Stats math; only
the no-focus branch changed. Re-bench against
internal/telemetry/aggregate.go(12 KiB / 280 lines): myco
output drops from 14 KiB → 2.8 KiB, measured ratio jumps from
0.87× (heavier than Read) to 4.43× (lighter than Read by 4×).
Counterfactual multiplier re-calibrated 1.0× → 4.0× with
high quality; pinnedcalibration_test.goupdated in the same
commit so the change can't be silently undone. CLImyco read
prints the Hint to stderr after the content (so stdout stays
clean for piping). MCP tool description updated to document the
new no-focus shape so agents know what to expect.
Three new integration tests:
read_focused_no_focus_returns_outline_only_envelope
(small file: full content + outline + no Hint),
read_focused_no_focus_truncates_above_cap(cap shrunk via
package var: Hint set, content < original, outline populated),
read_focused_with_focus_unchanged(focus path snapshot).
task checkgreen;myco bench-counterfactualgreen at 11%
drift. Closes the v3.4 A3 G2 net-negative case.
v3.4 — Adoption fixed-point (in progress)
Gated on a non-TS field test for the route-literal + new-language
work. Today's contribution is one telemetry-darkspot doctor warning
that closes the G5 finding from the Go dogfooding pass.
Added
-
A3 follow-up —
myco bench-counterfactualcalibration harness.
New top-level subcommand that closes the calibration loop the A3
ticket described: runs each myco tool against the live daemon, runs
the equivalent shell fallback (grep -rn,wc -c,find -name),
and compares the measured byte ratio against the modelled
multiplier ininternal/telemetry/counterfactual.go. Drift > 50%
(configurable via--drift-threshold) on any tool exits with status
1, except low-quality entries (graph walks the model already
self-tags as rough) which printinfoinstead of failing — a single
corpus point shouldn't break CI on a tool that never claimed
precision. Stale-daemon errors (unknown method) get a dedicated
message pointing at the fix instead of looking like a calibration
regression. Output formats: human-readable table (default) and
--format jsonfor machine consumption. Corpus is hard-coded
(ComputeSessionCostsymbol,internal/telemetry/aggregate.gofile)
so re-runs across machines are comparable.First run surfaced four real calibration mistakes in the v3.4
A3 ship — the multiplier table was guessed-not-measured, and the
bench told us where:tool pre-bench measured post-bench quality read_focused2.0× 0.87× 1.0× high get_file_outline10.0× 2.49× 2.5× medium → high get_file_summary30.0× 2.85× 3.0× medium → high list_files1.0× 0.13× 0.2× medium find_symbol0.8× 0.84× 0.8× (kept) medium ✓ search_lexical1.0× 0.72× 1.0× (kept) high ✓ get_references1.2× 1.65× 1.2× (kept) medium ✓ The
read_focusedfinding is the headline G2 adoption-fixed-point
signal made measurable: whenfocusisn't set, myco's read_focused
output is heavier than a plain Read (14 KiB vs. 12 KiB on the
bench file) because of the JSON envelope and line markers. The
initial 2.0× guess assumed every call sets focus and harvests the
v2.4 byte-reduction story; reality at v3.4 is closer to parity, so
the model now stops over-crediting myco for a tool that's
net-negative when used wrong. Block B will address the tool-side fix.Outline and summary were honestly wishful — a Go file with extensive
doc comments doesn't compress to 5-15% the way the original guess
assumed.list_fileswas the headline surprise: myco is genuinely
heavier thanfindon this surface because it returns structured
metadata (language, path, line counts) per row, whilefindemits
bare paths. The model now reflects that honest negative-savings
signal.New
internal/telemetry/calibration_test.gopins every multiplier- quality tag in
counterfactualModel. Editing the table without
also updating the pinned test fails loudly in CI — prevents the
silent "bumped a constant, retroactively changed every session's
recorded savings" regression. The pinned test also catches the
reverse case: a new tool added to the model without an entry in
the test list.task checkgreen; livemyco bench-counterfactual
reproduces the calibrated table on the self-index.
- quality tag in
-
A3 — counterfactual cost model (without-myco estimate). Answers
the second long-term question: "how expensive would the same session
have been without myco?". Newinternal/telemetry/counterfactual.go
carries a per-tool multiplier table (find_symbol0.8×,read_focused
2.0×,get_file_summary30×,get_neighborhood2.5×,stats0×, etc.)
with each entry taggedEstimateQualityHigh|Medium|Low|Noneso callers
can downweight graph-tool guesses.EstimateCounterfactual(tool, outputBytes)
is the per-call estimator;ComputeSessionCostnow sums the estimates
intoMycoCounterfactualBytes, derivesWithoutMycoEstimateBytes
(= counterfactual + actual fallback, treated as a lower bound),
EstimatedSavingsBytes(with honest negatives when myco cost more
than the modelled alternative — the G2 adoption-fixed-point signal),
and aSavingsRatioheadline number in[-1.0, 1.0]. A
CounterfactualQualityMixmap counts calls per quality bucket so
the renderer can warn when savings come mostly from low-quality
estimates. Modelled, not measured — running realgrep/Readin
parallel during each myco call would 10× latency, kill the v2.4 speed
promise, and contend with the user's editor; a v4--benchmode
could opt into the slow path. The estimate lives at the aggregate
pass, not the write path: no schema change totelemetry.jsonl, so
every existing session's savings number is recomputable.myco session export(table, markdown, json) now shows a Modelled savings vs.
fallback-only trio (with-myco actual, without-myco modelled,
estimated savings ± %), the quality-mix caveat, and acf bytes
column on the per-tool Top Contributors table. The markdown export
surfaces an extra honesty paragraph when savings go negative
(usually the agent reaching for myco where a single grep would have
been cheaper). Three tests pin the math:
TestEstimateCounterfactual_KnownAndUnknown(multipliers + zero
multiplier + missing-tool fallback),
TestComputeSessionCost_Counterfactual(full rollup, per-row cf
bytes, savings sign, quality mix counts),
TestComputeSessionCost_NegativeSavings(stats-only session →
negative savings, ratio stays at 0 when WithoutMyco is 0).
task checkgreen; livemyco session export --format markdown
on a fallback-only session correctly shows+0.0%savings (no
myco calls → no modelled saving). -
A2 — session cost aggregator (with-myco baseline). Answers
"how expensive was this session, with myco?". New
telemetry.SessionCost+ComputeSessionCost(myco, fallback, charsPerToken)rolls per-tool byte totals (from A1) into a single
cost block: split by source (myco vs. fallback), input vs. output,
total bytes, and a directional token estimate via a configurable
chars-per-token ratio. New config field
telemetry.chars_per_tokenin.mycelium.yml(default 4.0, also
exposed asconfig.DefaultCharsPerToken); non-positive values fall
back to the default at use time.myco session export(markdown,
json, table) now appends a Cost estimate section with the
per-source breakdown, total bytes, estimated tokens, and a Top
Contributors table that ranks every tool (myco + fallback) by
byte contribution. Token numbers are explicitly documented as
directional — for trend-watching, not billing. The "all" rollup
row from the myco summaries is excluded from the cost calculation
so it doesn't double-count. Three tests pin the contract:
TestComputeSessionCost_RollsBytesAndTokens(full aggregation +
ordering),
TestComputeSessionCost_DefaultRatio(fallback to 4.0 on 0/negative
inputs),TestComputeSessionCost_EmptyInputs(zero costs, no
divide-by-zero). Live-tested against a fresh session: 9 Edit calls- 4 Bash + 6 Read = 5,960 estimated tokens, confirming the
fallback-tool side now produces actionable numbers. Foundational
for A3 (counterfactual savings model).
- 4 Bash + 6 Read = 5,960 estimated tokens, confirming the
-
A1 — fallback tool output-byte telemetry. The session-tracking
hook recordedinput_sizefor every fallback tool call (Read, Bash,
Edit, …) but explicitly ignoredtool_response. That left exactly
half of the byte budget invisible — myco output was in
telemetry.jsonlbut Read/Bash output bytes weren't anywhere. Now
ExternalRecordcarriesoutput_size(rawtool_responsebyte
length), the JSONL line includes it, andSummarizeExternal
aggregates per-toolInputBytes/OutputBytes. New helper
TotalExternalBytes(summaries)returns the session-level total.
myco session export <id> --format markdowngains anout_bytes
column in the fallback-tools table and aFallback total bytes
summary line. Foundational for A2 (session cost) and A3 (modelled
counterfactual savings). Legacy session files (without
output_size) parse silently with OutputSize=0. -
telemetry_dark_spotdoctor check. Closes the v3.4 G5
field-test finding: a session-tracking-active but
telemetry.enabled: falserepo records the fallback half of
agent behaviour (Bash/grep, Read, etc. via.mycelium/session_*_external.jsonl)
but loses the myco-call half.myco doctornow warns when
it sees*_external.jsonlfiles buttelemetry.jsonlis
missing or empty — exactly the state today's mycelium-on-mycelium
dogfooding sessions left behind. Quiet on fresh repos (no
sessions yet) and on repos with both streams populated. The
detector is filesystem-only (no DB read, no telemetry parse) so
it costs zero on every doctor invocation. Three new tests pin
the three states (flag / quiet-when-both / quiet-on-fresh).
v3.3 — Documents surface
A parallel track to the symbol graph for files whose value is in
their (key, value) pairs rather than callable structure. Motivated
by F3 from the v3.1 field test: agents fell to Bash(grep -rn) to
find i18n keys because mycelium only indexed code symbols.
Added
-
v3.3 documents surface (B3) —
package.json+go.modkinds + doctor coverage.
Closes v3.3 with two more document parsers and the doctor check.internal/parser/document/package_json.go: matches files named
exactlypackage.json. Emits one entry per dependency across
dependencies,devDependencies,peerDependencies,
optionalDependencies. Section disambiguation is not encoded in
the entry — most agent queries are "where is this dep?", not "is
it dev?". Token-based walk preserves per-entry line numbers.
workspace:*and other non-SemVer values flow through unchanged.internal/parser/document/go_mod.go: matchesgo.modby
basename. Delegates togolang.org/x/mod/modfile(now a direct
dep) for parsing — block vs. single-line requires, indirect
annotations, version validation are all handled there. Each
require becomes a(module path → version)entry; indirect deps
keep the// indirectmarker appended to the value so agents
can distinguish first-party from transitive without an extra
field.replaceandexcludeare intentionally out of scope.Reader.Stats.DocumentsByKind map[string]int: per-kind entry
counts populated from a singleGROUP BY kindquery. Empty when
no documents are indexed (additive field,omitempty).- Doctor
documents_indexedcheck: emits when at least one file
has a non-NULLdocument_kindORStats.DocumentsByKindis
non-empty. Reports per-kind counts; warns when files registered
with adocument_kindproduce zero entries (the symptom of a
parser claim with no extracted rows). Code-only repos see no
check at all —Reader.HasDocumentFilesis the gate. - Both new parsers are auto-registered alongside the i18n parser
inbuildDocumentRegistry()(one helper incmd/myco/main.go
shared by the indexer subcommand and the daemon's catch-up
scan). All three kinds are always wired; they only fire when
matching files exist, so code-only repos pay nothing. - Fixtures:
testdata/fixtures/documents/package.json(deps +
devDeps + peerDeps + workspace value),go.mod(direct +
indirect requires). Eight new integration subtests cover
per-kind indexing, exact-match-wins ordering, indirect markers,
stats aggregation, and the doctor check across its three
states (skipped / pass / warn).
-
v3.3 documents surface (B2) —
find_document_keyMCP tool.
Turns B1's stored entries into an agent-reachable surface. New
query.Reader.FindDocumentKey(key, kind, project, limit)runs
exact-then-prefix-then-substring matching againstdocuments.key
with optionalkind(i18n_json|package_json_deps|
go_mod_requires) and workspaceprojectfilters. Result type
DocumentHitcarriespath+projectper the v3.1.2 convention —
pass them verbatim toread_focused. New IPC method
MethodFindDocumentKey+FindDocumentKeyParams, daemon dispatch,
MCPmapToolToIPCwiring (now covered by a contract test that
every advertised tool resolves to an IPC method). Tool description
inpkg/mcpschema/tools.gofollows the v3.1.2 priming convention
("reach for this beforesearch_lexicalwhenever you have a
key liketopbar.nav.back"). The CLAUDE.md priming snippet adds
find_document_keyto the navigation list;docs/adoption.md
notes when the tool should appear in a healthy telemetry log
(existing CLAUDE.md files keep their v3.1.2 snippet — the
idempotency marker is preserved, so re-runningmyco initis a
no-op on already-primed projects). -
v3.3 documents surface (B1) — schema + parser infrastructure + i18n JSON kind.
The architectural commitment. Documents do not participate in
find_symbol/get_references/get_neighborhood; agents
reach entries viafind_document_key(B2) and via the existing
search_lexicalover file content. New surfaces:- New migration
0007_documents.sql:documentstable (file_id,
kind, key, value, line) with FTS5 trigram index over key+value,
plus a new nullablefiles.document_kindcolumn (NULL for code
files, set for document-only files — keeps doctor/stats groupings
honest without overloadingfiles.language). - New
parser.DocumentParserinterface andDocumentEntry/
DocumentResulttypes parallel to the symbol-sideParser. - New
internal/parser/document/package withRegistry(parallel
toparser.Registry) and the first parserI18NJSONParser:
handles.jsonfiles under anylocales/ori18n/directory,
flattens nested objects via dotted keys (topbar.nav.back),
encodes array indices in the key (items.0), tracks the leaf
string's source line via byte-offset binary search. - New
Index.UpsertDocumentFile+Index.ReplaceFileDocumentEntries
write helpers mirroring the symbol path. - Pipeline: new
Pipeline.Documents *document.Registryfield. When
set, after the main symbol passRunOnceruns a second walker per
workspace withdocumentWalkIncludes = {"**/*.json", "**/go.mod"}
(using the same excludes as the symbol walker).Report.Documents
reports per-run changed document count.HandleChange(watcher
path) extended to dispatch to the document registry when no
symbol parser claims a changed file. NilDocumentskeeps the
legacy single-surface behaviour byte-for-byte. - New fixture
testdata/fixtures/documents/locales/en.jsonand
integration testdocuments_integration_test.gocovering file
registration, key flattening, line tracking, content-hash
idempotency, and FTS-index population.
- New migration
v3.2 — Setup wizard polish & install plumbing
The setup-wizard milestone from the v3.1 plan (C1/C2/C3) plus the
install-side ergonomics that surfaced from real onboarding pain.
Added
myco uninstallself-command. Mirrorsmyco initin reverse:
prompts Y/N for each component init writes — session-tracking hook
entries in.claude/settings.local.json, the post-commit git hook
(restoring any.mycelium-backup), themyceliumentry in
~/.claude.json, and the.mycelium/index directory — then removes
themycobinaries last so the running process stays executable
through the run. New helpers:wizard.RemoveClaudeCodeMCPand
hook.UninstallPostCommit. Flags:-y/--yes(non-interactive),
--dry-run(preview without changes),--keep-binary(unwire project
state only),--purge(also remove.mycelium.yml). Binary removal
walks$PATHplus the currently-running binary (and one level of
symlink target so a release-binary install at/opt/myco-<platform>/
is cleared together with its/usr/local/bin/mycosymlink); when a
removal needs root, the command prints thesudo rminvocation
instead of escalating itself. Closes the install/uninstall asymmetry
that left users hand-rollingrms across/opt,/usr/local/bin,
~/.local/bin, and/tmp.task install. Companion totask buildthat resolves
command -v myco, follows one level of symlink, and overwrites the
target so a freshly built dev binary always replaces the onemyco
actually runs from. Falls back to/usr/local/bin/mycowhen no
existing install is on PATH, and usessudoonly when the target
directory is not user-writable. Fixes the long-standing footgun where
task buildwrote to~/.local/bin/mycobut the macOS PATH order
silently shadowed it with whatever/usr/local/bin/mycoalready
contained — the README's release-binary install path. CLAUDE.md and
the README "From source" section now point attask build && task installas the canonical dev install flow.
v3.1.2 — Workspace path fixes
The four-ticket bundle that fixed the v3.1 field test's largest
adoption defect: read_focused failing 7/13 times on workspace
monorepos via a path-doubling bug. v3.1.1 hotfix below only patched
the SQL match; v3.1.2 completes the disk-side fix and adds the
preventive surfaces (project annotation, description rewrites,
did-you-mean hints).
Added
-
Helpful "Did you mean" hints on path-not-found errors.
WhenReadFocusedcan't resolve a path (sql.ErrNoRowsfrom the
index lookup), and whenSearchLexical'spath_containsfilter
eliminates every indexed file, both methods now run a basename
match against thefilestable and append up to 3 suggestions to
the error message:file not in index: wrong/dir/server.go Did you mean: server.go (project: api)Previously
SearchLexicalreturned an empty result for the
zero-candidates case, leaving the agent unable to distinguish
"pattern not present in matching files" from "your filter
eliminated every file" — so it would correctly conclude "no match"
for the wrong reason. Now the headlineno indexed files match path_contains=…makes the failure mode explicit. New shared
helperinternal/query/suggest.go::suggestPathsruns a single
indexed query (LEFT JOINprojectsfor the project annotation)
withLIMIT 3; the leading-wildcard LIKE is a full scan but on
a 10k-file index completes in single-digit ms. No suggestions are
emitted when basename match finds nothing — silence beats noisy
suggestions. -
MCP tool descriptions rewritten for workspace-mode path
conventions. v3.1.2. The old descriptions forread_focused,
get_file_outline, andget_file_summarysaid "Repo-relative path
to the file" — misleading in workspace mode where indexed paths
are project-relative. Agents following the spec literally prepended
the project root themselves, hitting the v3.1.2 path-doubling bug
for the entire pre-fix lifetime. New descriptions explicitly say:
"Accepts thepathreturned byfind_symbol/list_files/
get_referencesverbatim, a repo-relative path, or an absolute
path. In workspace mode the indexed form is project-relative; pass
it through unchanged — do not prepend the project root yourself."
Result-returning tools (find_symbol,get_references,
list_files,get_neighborhood,impact_analysis,
search_lexical,search_semantic) now mention that the new
path+projectfields on each hit are accepted verbatim by the
read-side tools.path_containsfilters insearch_lexical/
search_semanticdocument that they match both project-relative
and repo-relative substrings (the v3.1.2 SQL OR pattern). The
CLAUDE.md priming snippet appended bymyco initand the new
"Paths in workspace mode" section indocs/adoption.mdcarry
the same guidance for human-side documentation and agent priming. -
projectannotation on every path-bearing result type. v3.1.2.
SymbolHit,FileHit,FileSummary,LexicalHit,SemanticHit,
NeighborNode,NeighborEdge(asSrcProject),ReferenceHit(as
SrcProject),ImpactHit, andPathVertexall carry the workspace
project name when the file belongs to a configured project, or""
when it doesn't. Each query joinsLEFT JOIN projects p ON p.id = f.project_idand selectsCOALESCE(p.name, ''). JSON tags use
omitemptyso single-project mode emits no visible change. Motivation:
agents in a multi-project workspace previously got back paths like
src/index.tsfromfind_symbolwith no way to know which of the
five packages it belonged to — leading to guessed prepended prefixes
(the v3.1.2 path-doubling bug). Withprojectannotated, agents can
passpathandprojectback verbatim toread_focused(or just use
the existingprojectfilter on read queries with confidence).
Fixed
- Workspace-mode path-doubling. A v3.1.1 follow-up.
ReadFocused's SQL match accepted both project-relative and
repo-relative input paths, but the on-disk path reconstruction still
used the input path infilepath.Join(repoRoot, projectRoot, path)—
so a repo-relative input (e.g.packages/ui-tests/src/foo.ts) got the
project prefix prepended a second time and produced
…/packages/ui-tests/packages/ui-tests/src/foo.ts: no such file or directory. A field-test session (monorepo-4) hadread_focused
failing 7/13 times via this path. Fix: alsoSELECT f.pathfrom the
LEFT JOIN and use the database value for the disk-side join. Absolute
inputs are now normalised to repo-relative up front before the SQL
match, soread_focusedaccepts project-relative, repo-relative, and
absolute paths uniformly.SearchLexical'spath_containsfilter now
matches both path forms via the same OR pattern; previously a filter
likepath_contains: "services/api"against a workspace project
returned zero candidates because the LIKE was applied only to
project-relativef.path. Worker-sideos.OpenENOENT no longer
silentlycontinues — it logs to daemon stderr so stale-index or
path-reconstruction bugs fail loudly in regression runs. New tests in
workspace_integration_test.gocover all three input forms for
ReadFocused, both forms forSearchLexicalpath_contains, and
pin the (already-correct) behaviour ofGetFileSummary/
GetFileOutlineagainst future refactors.
v3.1.1 — Workspace-mode disk-read hotfix
Partial fix superseded by v3.1.2. Documented here so the chronology
of how the bug got resolved survives.
Fixed
- Workspace-mode disk reads.
ReadFocusedand
SearchLexicalboth joinedrepoRootwith the index-stored path
to locate the file on disk, but in workspace mode (v1.5+) the
index stores project-relative paths — the disk file lives at
repoRoot + projectRoot + path. Pre-fix consequences observed in
a v3.1 field test:read_focusedfailed unconditionally on any monorepo workspace
project, returning"no such file or directory"for the
canonical path thatlist_fileshad just returned.search_lexicalsilently swallowed the read error and returned
zero results (the workercontinues on read failure), so a
lexical search across an entire monorepo project would look like
a real "no matches" — false negatives that destroyed agent trust.
Both tools nowLEFT JOIN projects ON projects.id = files.project_id
to recover the project root and prepend it to the disk-side path.
Single-project mode (project_idNULL) keeps the existing
repoRoot + pathjoin. New regression tests in
workspace_integration_test.gocover both code paths against the
existing 3-project fixture. (Disk-side path construction still
used the input path here, not the database value — fully resolved
in v3.1.2 above.)
v3.1 — Adoption-driven fixes
First slice of the broader-hyphae roadmap. Three surgical changes
targeting the field-test findings from a real TS-monorepo session:
agents fell into the documented "search_lexical only" pattern,
find_symbol returned null instead of an empty list with a hint,
and read_focused was never reached for despite multiple full-file
reads.
Added
- v3.1 adoption-driven fixes (A1 + A3 + A4). Three surgical changes targeting the field-test
findings from a real TS-monorepo session: agents fell into the
documented "search_lexical only" pattern,find_symbolreturned
nullinstead of an empty list with a hint, andread_focused
was never reached for despite multiple full-file reads.- A4 —
Stats.ConfiguredProjects+projects_configured_but_empty
doctor check. NewProjectStatsshape (name,root,
file_count) populated from a LEFT JOIN onprojects×files,
so a configured project whose include glob matched nothing still
appears withfile_count=0. Doctor fails when any configured
project has zero files (likely a misconfiguredincludeor
rootin.mycelium.yml), warns under 10 files (likely a
too-narrow include), passes otherwise. Skipped entirely when no
projects:block is configured (single-project mode, the
default — keeps the report clean for the common case). New
thresholdsEmptyProjectFail/EmptyProjectWarnin
doctor.Thresholds. Surfaced viamyco statsas a per-project
file-count line. Tests in
internal/doctor/doctor_projects_test.gocover skip /
pass / warn / fail paths. - A1 —
FindSymbolResult{Matches, Hints}envelope.
Reader.FindSymbolnow returns a result struct instead of a
bare[]SymbolHit.Matchesis always non-nil (empty slice,
not nil → JSON[], notnull). WhenMatchesis empty,
Hintspopulates with diagnostic lines explaining why a filter
eliminated everything: typo'd project name (with the configured
project list),kindfilter that eliminated all real matches
(with the kinds the name actually matches), or unknown kind
value (with the index's known kinds). Newinternal/query/diagnose.go
holds the helpers; they only run on the empty-result path so
the hot path stays unchanged. Hint phrasing is intentionally a
flat[]stringof human-readable lines so wording can iterate
without breaking schema. Integration tests inintegration_test.go
cover successful match (no hints), bogus project (project hint),
kind-eliminated-all (kind hint), and genuine miss (no hints). - A3 — MCP tool descriptions rewritten for first-reach priming.
Every entry inpkg/mcpschema/tools.gofollows a uniform "what
it does + when to reach for me" shape. The five most-affected
tools (find_symbol,read_focused,get_references,
get_neighborhood,search_lexical) explicitly contrast with
the wrong-tool reflex agents fell into during the field test —
e.g.search_lexicalnow reads "Use this only for literal
strings or regex patterns. For symbol navigation prefer
find_symbol; for 'who calls X' preferget_references."
Wording stays competitor-neutral ("the agent's general-purpose
file reader") rather than naming Claude Code tools literally
so it survives client renames. Structural test in
pkg/mcpschema/tools_test.golocks in ≥ 2 sentences + a
reach-for-me cue (one ofreach/use/instead/prefer/
before/after) for every tool, plus a stricter contrast
assertion for the five high-priority tools.
- A4 —
Changed
Reader.FindSymbolreturn shape.[]SymbolHit, error→
FindSymbolResult, error. Direct callers in this repo
(cmd/myco/main.go, integration tests) updated; external
consumers of the IPC / HTTP / MCPfind_symbolmethod see a JSON
shape change from[…]to{"matches":[…], "hints":[…]}. Other
query methods (get_references,search_lexical, etc.) keep
their bare-list shape — extending the envelope to them is a v3.2
/ v3.3 decision once the shape proves itself in the field-test
re-run.
v3.0 — Agent-native release & v2.5 incremental skills
Documentation pass, release-binary packaging, and the v2.5 hash-gated
skills regen. Originally separate releases but bundled here because
the merge of "agent-native polish" and "skills become cheap to
regenerate" was the v3.0 story in practice.
Changed
- v3.0-rc polish + docs. Canonicalises the
docs/layout (the
old rootRESEARCH.mdmoves todocs/research.mdand gains a
design-decision crosswalk plus a "read but not acted on" section),
rewrites the README around the v3 agent-native story (skills tree
and focused reads as the headline; structural MCP tools demoted
to "for programmatic use") while leaving the project header /
badges untouched, shipsdocs/adoption.mdas a guide to verifying
agent uptake via the v2.2 telemetry log, and adds a navigation
integration test (navigation_integration_test.go) that
mechanisesdocs/navigation-example.mdso the
INDEX.md → SKILL.md → read_focusedpath is enforced in CI.
Release tarballs now bundle the matching sqlite-vec shared library
next to the binary, andindex.OpenWithExtensionauto-discovers
it whenindex.vector.extension_pathis left empty in
.mycelium.yml— semantic search at scale is now zero-config on
release builds. - Incremental skills regeneration (Pillar H, v2.5 in the v3 plan).
The v2.3 skills tree gets a hash gate: every rendered file (per-
package SKILL.md, per-aspect INDEX.md, root INDEX.md) is hashed
before write; ifskill_files.skill_hashmatches, the WriteFile and
store update are both skipped. New migration0006_skills.sql
introduces theskill_filestable; newinternal/indexhelpers
(SkillFileHash,UpsertSkillFile,DeleteSkillFile,
PruneSkillFiles,ListSkillFiles) satisfy a smallskills.Store
interface so the renderer stays storage-agnostic.Compilegrows
Options.Store,Options.Stats,Options.DryRun; passing a Store
enables hash-gated writes, and Stats reportsRendered / Written / Skipped / Pruned. The wall-clockgenerated:frontmatter line is
stripped from the hash input so two renders of the same structural
content produce the same hash regardless of when they ran — without
this the gate would fire on every daemon batch and defeat the
whole milestone. - Daemon-driven incremental regen. New
Daemon.SkillsRegen func(ctx, packages []string) errorfield plus a debounced batcher in
the watcher event loop: everypath.Dir(relPath)from
Pipeline.HandleChangeis collected into a dedup set, and after
SkillsDebounce(default 200ms) of channel idle the batch is
flushed to a worker goroutine that calls SkillsRegen exactly once.
A second worker serialises regen calls so two bursts can't race on
the same.mycelium/skills/tree.cmd/myco daemonwires
SkillsRegen toskills.RegenerateAffectedonly when
.mycelium/skills/already exists, so users who never opted into
the skills feature aren't surprised by a regenerated tree.
RegenerateAffectedfor v2.5 is a thin wrapper overCompilewith
the Store set: the per-render cost is ~100ms on the self-index and
the hash gate makes the actual write cost zero on a clean tree, so
fully exploiting per-package short-circuiting was deferred — the
packages slice is captured for telemetry and reserved for a future
optimisation hook. skills_coveragedoctor metric. NewStats.SkillsPackagesIndexed
(distinct directories holding indexed files) plus a filesystem walk
ininternal/doctorthat counts presentSKILL.mdfiles under
.mycelium/skills/packages/. Coverage = on-disk / indexed; pass at
≥ 0.95, warn below, fail below 0.5. Skipped when the skills dir
doesn't exist (opt-in feature, not a regression). Walking the
filesystem rather than readingskill_filescatches the case where
the DB row outlives the file on disk.myco skills compile --statusand--incrementalflags.
--statusruns the renderer in DryRun mode against the live
skill_fileshashes and reportsrendered / unchanged / would changewithout touching disk or the DB.--incrementalis the
hash-gated equivalent ofcompile: it writes only the files whose
rendered bytes differ and prints the same per-call counters the
daemon logs.
Measured
- v2.5 hash gate on the self-index (Tiger Lake, 105 files / 30
packages / 35 rendered files). Cold compile: 35 rendered, 35
written, ~100ms. Warm compile (no source changes): 35 rendered, 0
written, 35 skipped, ~70ms. Single-symbol change (added one
top-levelfunc): 35 rendered, 6 written, 29 skipped — the changed
package + root INDEX.md + four aspect indices. Pure-formatting
source change (added a blank line to a comment): 35 rendered, 0
written, 35 skipped — the index hash didn't move, so neither did
the SKILL.md hash.
v2.4 — Focused reads
The token-saving read primitive. Lexical filter, no neural model —
keeps the single-binary distribution story while picking up the
"hide irrelevant symbols" pattern from SWE-Pruner.
Added
- Focused reads (Pillar I). New
internal/focuspackage implements the deterministic lexical filter
promised by the v3 roadmap: tokenize a focus string (lowercase,
stopword-strip), then score candidates against name (3.0 exact / 2.0
substring), qualified name (2.0 substring), docstring (1.0
substring), and ref targets (0.5 substring). Pure Go, no neural
model — we adopt the SWE-Pruner pattern but explicitly not the
mechanism, so the single-static-binary distribution story holds.
Wired into three existing reader methods as an optionalfocus
param:FindSymboldrops non-matchers and re-ranks survivors by
score;GetFileOutlinekeeps top-level items whose subtree
contains any match;GetNeighborhoodprunes nodes outside the
focus and surfaces afocus filter pruned N node(s)note. Empty
focus is byte-identical to prior behaviour — verified by the
pre-existing integration suite. read_focusedMCP tool /myco readCLI. New top-level read
primitive that returns one indexed file with non-focus-matching
symbols collapsed to one-line markers in the file's native
comment style (// signature ... // collapsed (lines N-M)for
Go/TS/JS,# ...for Python). Empty focus returns the file in
full, so the tool also functions as a daemon-mediatedcatwhen
the agent isn't sure how big the file is. Multi-line signatures
(Go interface bodies, struct definitions) are flattened to their
first line with…appended so the marker stays single-line.
Response carries aStats { TotalSymbols, ExpandedSymbols, OriginalBytes, ReturnedBytes }block plus anExpandedlist of
surviving symbols with their original[StartLine, EndLine]ranges
so agents can map back to source. Wire-up: newFocusfield on
FindSymbolParams/GetFileOutlineParams/GetNeighborhoodParams,
newReadFocusedParams+MethodReadFocused, daemon dispatch,
MCP tool schema entry, HTTP route auto-derived from the
dispatcher,--focusflag onmyco query find|outline|neighbors,
andmyco read <path> --focus "<q>"(with--statsfor the
collapse counters on stderr).
Measured
read_focusedbyte reduction (self-index, Tiger Lake). Three
representative queries on this repo:file focus returned/original reduction cmd/myco/main.go (44 KB) "telemetry recorder" 8443 / 44337 81% cmd/myco/main.go (44 KB) "skills compile" 8909 / 44337 80% internal/daemon/daemon.go (9 KB) "dispatch read_focused" 6540 / 9163 29% Results vary with focus specificity and file shape — large files with many independent symbols collapse aggressively, small dense files less so. We're explicitly not claiming SWE-Pruner's 23–54% range against a trained reranker; the lexical filter trades precision for distribution simplicity.
v2.3 — Static skills tree
The agent-readable static index. Markdown-only, generated from the
graph, navigable with just Read.
Added
- Static skills tree (Pillar L). New
internal/skillspackage +myco skills compileCLI generate a
deterministic Markdown tree under.mycelium/skills/that an agent
can navigate with only theReadtool. Layout: per-package
SKILL.md(one per directory of source, language unified for
mixed-language directories), rootINDEX.mdlisting every package,
and anaspects/subtree with four cross-cutting filters
(error-handling, context-propagation — clean signature matches;
config-loading, logging — heuristic ref-driven, frontmatter-flagged).
Output islanguage: complementaryto MCP — SKILL.md is lean
(≤~160 lines on the largest mycelium package), points the reader at
myco query refs/neighborsfor specifics. New reader helpers
(*query.Reader).PackageRefAggregates,
SymbolsBySignatureLike,SymbolsByOutboundRefkeep the "query is
the only reader" rule intact.--packageand--aspectflags
scope regen for fast iteration; both correctly skip everything
outside their scope. Self-dogfood on the mycelium repo: 28 packages
/ 88 files / 589 symbols, full tree compiles in ~52ms; tree
gitignored as a sibling ofindex.db. Incremental hash-gated
regeneration is v2.5.
v2.2 — Opt-in telemetry log
Added
- Opt-in telemetry log (Pillar K). New
internal/telemetrypackage with aRecorderinterface and a
JSONLFileRecorder. Off by default; enabled via
telemetry: { enabled: true }in.mycelium.yml. When on, the
daemon dispatcher ininternal/daemon/daemon.gorecords one line
per IPC/MCP call to.mycelium/telemetry.jsonl(timestamp, tool
name, input bytes, output bytes, wall-clock ms, ok). No network,
no aggregation off-host — purely a local file the user can
tail -f. Open failure falls back toDisabledso observability
never gates daemon startup. myco stats --telemetryaggregator: streams the JSONL log and
prints per-tool counts, byte totals, and p50/p95 durations, plus
anallrollup. Friendly hints when telemetry is off in config or
when no records exist yet, so users who flipped the flag but
haven't generated traffic understand what they're seeing.
v2.0.x — Post-rc1 fixes & benchmarks
Fixed
- sqlite-vec extension entrypoint.
LoadExtensionwas being called
with an empty entry symbol, which makes SQLite derive the symbol name
from the filename (vec0.so→sqlite3_vec0_init). The shipped
library exportssqlite3_vec_initregardless of filename, so loading
failed with an emptyundefined symbol:error. Now pass the explicit
entry ininternal/index/vss.go.
Measured
- Semantic search benchmark matrix — ran the full grid (10k /
50k / 100k chunks × 384 / 768 / 1536 dims × {brute-force, vec0})
on Tiger Lake. vec0 is a consistent 5-8× speedup over pure-Go
brute-force; absolute numbers land in README. Important finding:
at sqlite-vec v0.1.9 the vec0 path is SIMD-optimized flat scan,
not HNSW, so both paths scale linearly in the corpus. The
roadmap's "p95 < 50ms at 100k chunks" target is not met on
laptop-class CPU — vec0 at 100k/768 is 171 ms. The 50 ms
threshold holds up to ~50k/384 with vec0. Benchmark is
reproducible viaMYCELIUM_VEC_PATH=... go test -bench=....
[v2.0.0-rc1] — 2026-04-24
First release candidate for v2.0 ("precision and scale"). No new
functional changes since v1.7; this tag consolidates the v1.1 → v1.7
series into a single release and gates the remaining v2.0 work.
Per-milestone details remain in the sections below.
Delivered against the v2.0 acceptance criteria
- Type-aware references for Go, TypeScript, Python. Self-index
reportsself_loop_count = 0,unresolved_ref_ratio = 0.0%.
(v1.2, v1.3) - Workspace mode: one daemon, one SQLite, N sub-projects with
per-project config and optionalprojectfilter on every query
tool. (v1.5) - Graph-native tools:
impact_analysis,critical_path. (v1.6) - PR-scoped queries:
--since <ref>on five read methods. (v1.6) - Doctor + quality signals:
myco doctorexits 0/1/2 on
pass/warn/fail with configurable thresholds. (v1.1, v1.2, v1.7) - Watchman opt-in behind
watcher.backend. (v1.7) - sqlite-vec integration compiled in; brute-force fallback
measured. (v1.4)
Architectural invariants from v1.0 are preserved: SQLite is still
source of truth and query engine; internal/query is the sole
reader; internal/pipeline is the sole writer; no new top-level
processes; all schema changes are additive.
Known gaps before the final v2.0 tag
libsqlite_vec.{so,dylib,dll}not bundled in the release
tarball. Users installsqlite-vecmanually per the README.- No 100k+ file monorepo validation.
myco doctor,
workspace mode, and the inotify-headroom check have only been
exercised against the self-index and the committed fixtures. - Roadmap p95 target not met. The "p95 < 50ms at 100k chunks"
metric from the v2.0 plan was aspirational against an HNSW-style
index; sqlite-vec v0.1.9 is flat SIMD scan so neither path hits
50 ms at 100k/768 on laptop-class CPU. Full matrix in the
benchmark table (see README). HNSW in sqlite-vec upstream is the
path forward; not gating v2.0 final.
[v1.7.0] — 2026-04-24
"Watchman opt-in" — the seventh v2.0 milestone (Pillar G). Pluggable
watcher backend so users on 100k+ file repos can escape the
fs.inotify.max_user_watches ceiling without changing anything
else about how mycelium runs.
Added
internal/watch/watchman/— minimal in-tree watchman client.
Talks JSON-over-unix-socket:get-sockname,watch-project,
subscribe,unsubscribe. Read pump demultiplexes command
responses vs subscription deliveries so one connection handles
both.$MYCELIUM_WATCHMAN_SOCKoverrides sockname discovery for
container setups.- Watcher backend selection. New
watcher.backendconfig field
("fsnotify"default,"watchman"opt-in) plus
myco daemon --watcher-backend <name>CLI override. Unknown
values are a hard error; watchman unavailability falls back to
fsnotify with a stderr warning so the daemon still starts. internal/watchrestructure. Old monolithicwatch.gosplit
intowatcher.go(publicWatcherinterface +Options),
common.go(shared debounce/coalesce/filter wrapper), and
per-backend sources:fsnotify.go,watchman.go. Both backends
route through the same wrapper so behavior is identical — the
two honest-surface bugs the old struct had (unused
MaxFileSizeKB, unusedCoalesceMS) are fixed once, not twice.CoalesceMSis now wired. Bursts of debounced events within
a coalesce window flush as one batch to the output channel.- Doctor:
inotify_headroomcheck. Linux-only. Counts repo
directories vs/proc/sys/fs/inotify/max_user_watchesand warns
above 50%, fails above 90%. The warn message suggests either
switching towatcher.backend: watchmanor raising the sysctl.
Changed
watch.Newsignature went from positional args to anOptions
struct (source-incompatible; migrates cleanly — all call-sites
updated).daemon.Daemon.Watcheris nowwatch.Watcher(interface) rather
than*watch.Watcher(struct pointer), matching the new backend
split.
Fixed
- Shutdown race in the watcher's shared wrapper: coalesce/debounce
timers could firew.sendafter the output channel closed. Pump
now owns every write toout; timers signal through internal
channels.go test -race ./internal/watch/...confirms.
[v1.6.0] — 2026-04-24
"Graph-native tools + PR scope" — the sixth v2.0 milestone (Pillars E
- F). Two new graph traversals that become cheap once v1.2/v1.3's
type-aware resolvers landed, plus a--since <ref>path filter on the
existing read surface for PR-scoped queries.
Added
-
impact_analysis(symbol)— new MCP tool and CLImyco query impact. Returns the transitive inbound closure around a symbol as
a flat list ranked by distance (1 = direct caller). Optionalkind
filter narrows the reported set (typical use:kind=methodto find
test methods covering the target). Default depth 5, hard ceiling
10. Composes withprojectandsince— they scope the reported
callers, not the walk, so cross-file / cross-project chains still
surface. -
critical_path(from, to)— new MCP tool and CLImyco query path. Returns up tokshortest outbound call paths. Bounded BFS
at depth ≤ 8 via a single recursive CTE; cycles prevented by the
SQLiteinstr()idiom on a comma-delimited accumulated path
column. Hydrates the distinct vertices in one second-pass query to
avoid the N+1 fan-out. Default k = 5. -
--since <ref>filter onfind_symbol,get_references,
list_files,search_lexical,search_semantic. Resolved via
git -C <root> diff --name-only <ref>...HEADat the transport
boundary (daemon RPC handler and CLI offline fallback), then passed
to the reader aspathsIn []string. Three-dot form uses the merge-
base so "files on my branch" stays correct after the base advances. -
internal/gitref/— thin helper (ResolveSince) that runs the
git diffwith a 5s timeout and surfaces stderr verbatim on
failure. Returns a non-nil empty slice when the ref has no diff
against HEAD so the reader's zero-row sentinel distinguishes "no
changes" from "no filter." -
internal/query/graph.go—ImpactAnalysis,CriticalPath,
ImpactHit,Impact,PathVertex,CriticalPathResult. Reuses
resolveSeedandloadNodefromneighborhood.go. -
internal/query/paths.go— sharedpathsInClausesplicer
renders theAND f.path IN (?, ?, ...)WHERE fragment used across
the five filtered methods. Caps the path list at 500 entries
(SQLite's 999-parameter limit) and returns a clear error when a PR
diff expands beyond that — the correct fix is a tighter base ref. -
Reader signature change (additive, source-incompatible) — five
methods gained a finalpathsIn []stringargument:FindSymbol(ctx, name, kind, project, limit, pathsIn)GetReferences(ctx, target, project, limit, pathsIn)ListFiles(ctx, language, nameContains, project, limit, pathsIn)SearchLexical(ctx, pattern, pathContains, project, k, repoRoot, pathsIn)Searcher.SearchSemantic(ctx, query, k, kind, pathContains, project, pathsIn)
pathsIn = nilis "unscoped";pathsIn = []string{}is an
explicit zero-row sentinel. Existing callers passnilto preserve
prior behavior. An options-struct refactor was considered and
rejected for mid-release API churn. -
MCP tool schemas — two new tool entries (
impact_analysis,
critical_path), plus asinceinput onfind_symbol,
get_references,list_files,search_lexical,
search_semantic. MCP server dispatch ininternal/mcp/server.go
routes the two new tools. -
CLI subcommands —
myco query impact <symbol>andmyco query path <from> <to>.--since <ref>added tofind,refs,files,
grep,search. Offline fallback path runsgitref.ResolveSince
locally so--sinceworks even without the daemon. -
Integration tests at
graph_integration_test.go:TestIntegration_ImpactAnalysis— seeds onauth.normalizeEmail
and assertsauth.AuthService.fingerprintat distance 1 and
auth.AuthService.issueTokenat distance 2. Subtests for the
kind-filter narrowing and the depth-clamp note.TestIntegration_CriticalPath— asserts the pathissueToken → fingerprint → normalizeEmailsurfaces.TestIntegration_PathsInFilter— exercises the reader-level
filter (no git process) across three cases: matching file,
non-matching file, empty-slice sentinel.
-
internal/gitref/resolve_test.go— temp-git-repo tests covering
the happy path (two-commit diff), empty ref (error), unknown ref
(error), and the no-changes case (non-nil empty slice).
Notes
vec0KNN fast path is skipped whensearch_semanticis called
with aprojectfilter (v1.5) or asincefilter (v1.6) —
vec0 MATCHdoesn't compose with arbitraryWHEREclauses.
Brute-force cosine handles scoped semantic search.impact_analysisis intentionally not a superset of
get_neighborhood(direction=in). The shapes serve different
workflows: graph (nodes + edges) vs. flat distance-ranked list; 2
vs. 5 default depth; 5 vs. 10 max; no kind filter vs. yes.- Cross-repo federation (N worktrees, one graph) remains a v3
non-goal.
Verification
Integration suite green on TestIntegration_IndexAndQuery,
TestIntegration_WorkspaceMode, TestIntegration_ImpactAnalysis,
TestIntegration_CriticalPath, TestIntegration_PathsInFilter, plus
all four internal/gitref cases. go vet -tags sqlite_fts5 ./...
clean. No schema changes, no migration.
[v1.5.0] — 2026-04-23
"Workspace mode" — the fifth v2.0 milestone (Pillar C). One daemon, one
SQLite, N sub-projects under one worktree. Not cross-repo federation
(that's v3): the unit of isolation is a directory inside the same repo,
each with its own languages / include / exclude overrides.
Added
- Migration
0005_projects.sql— newprojects(id, name, root, created_at)table plusfiles.project_idFK with cascade delete. A
NULLproject_idmeans the file belongs to the implicit root project
(v1.4 configs keep working untouched). config.ProjectConfig— optionalprojects:list in
.mycelium.yml. Each entry hasname,root, and optional
languages/include/excludeoverrides. Embedder/chunking stay
inherited from the top level (one DB can't mix embedding dims).internal/index/projects.go—UpsertProject,PruneProjects,
ListProjects. Idempotent upsert by name; prune drops rows no longer
in config (cascades remove their files + symbols + refs + chunks).pipeline.Workspace— per-project walker + project_id. The
pipeline now accepts aWorkspaces []Workspaceslice; each walker
runs with its own roots/filters and every file it emits is tagged
with the owning project before hitting the writer. Legacy single-
Walkermode still works whenWorkspacesis empty.Pipeline.FileProjectFor— longest-prefix resolver so fsnotify
events from the watcher can attribute a changed file back to its
project on the single-file update path.- Query-side
projectparameter —FindSymbol,GetReferences,
ListFiles,SearchLexical,SearchSemantic,GetNeighborhood
each accept an optional project name. A splicer (projectScope) adds
AND f.project_id = ?when set; unknown project names return zero
hits rather than silently falling back to unscoped (config bug
visibility). ForGetNeighborhood, only the seed lookup is scoped —
traversal stays global so cross-project call graphs surface. - IPC + MCP + CLI plumbing —
Projectfield added to every
params struct that touches files. MCP tool schemas advertise the
optionalprojectinput. CLI gains--project <name>onmyco query find | refs | files | grep | search | neighbors. - Workspace integration test + fixture at
testdata/fixtures/workspace(3 sub-projects: Goapi, TSweb,
Pythonworker) inworkspace_integration_test.go. Verifies
per-project scoping onfind_symbolandlist_files, the
unknown-project zero-hit contract, and that every indexed file has a
non-nullproject_idpointing at the right row.
Notes
- The vec0 fast path is skipped when a project filter is active — vec0
MATCH doesn't compose with arbitrary WHERE clauses. Brute-force
cosine handles project-scoped semantic search. - Embedder inheritance is intentional: a single SQLite DB can't mix
embedding dimensions cleanly, so per-project embedder overrides are
deliberately out of scope.
[v1.4.0] — 2026-04-22
"Semantic at scale" — the fourth v2.0 milestone (Pillar B). Adds optional
sqlite-vec integration behind
runtime feature detection. Brute-force Go cosine stays as the honest
fallback; nothing breaks when the extension is missing.
Added
internal/index/vss.go— extension loader via a per-process named
driver +ConnectHookthat auto-loads the library on every new DB
connection.EnsureVSS(dim)creates avss_chunksvirtual table
named by dimension and backfills rows from any pre-existing
chunks.embedding.VSSAvailable()andVSSTableName()let callers
branch at query time.index.OpenWithExtension(path, extPath)— new opener that
transparently handles both the extension-loaded and fallback cases.
index.Open(path)keeps its pre-v1.4 behavior.- Dual-write in
WriteEmbedding— every embedding lands in both
chunks.embedding(source of truth / fallback) andvss_chunks
(KNN index). Mirrored in one transaction; safe to lose either. Searcher.VSSTable— opt-in fast path. When set and the user has
no kind/path filter,SearchSemanticissuesembedding MATCH ? AND k = ?against vec0 and skips the scan. Falls back softly on any
query error (e.g. table missing for a changed dim).- Config —
index.vector.extension_path,index.vector.auto_create,
index.vector.ef_search(reserved for HNSW tuning when vec0 ships it). embed.UnpackInto— alloc-free variant ofUnpackused in the
brute-force hot loop. Avoids 100k[]float32allocations per query
at 100k-chunk scale.- Two-pass brute-force search — first pass scans only
(id, embedding)columns to find top-k; second pass hydrates the 10
winners with path/symbol/content. Eliminates ~30× the per-row I/O
vs v1.3. At 10k chunks this took latency from 166 ms → 114 ms. - Semantic-search benchmark matrix at
internal/query/semantic_bench_test.go— 10k / 50k / 100k / 768 dim
on brute-force. Numbers published in README.
Measured
On an Intel i7-1165G7 (Tiger Lake), 768-dim, brute-force fallback, k=10:
| corpus | p50 |
|---|---|
| 10k chunks | ~114 ms |
| 50k chunks | ~555 ms |
| 100k chunks | ~1.10 s |
The plan's aspirational target was <50 ms at 100k via vec0 KNN. That
requires the extension installed; the brute-force path is ~22× slower
at 100k but still correct. The vec0 fast path is architecturally
complete but untested in this release — validate on your machine with
the install recipe in README.
Honest scope note
The vec0 KNN code path in Searcher.searchViaVSS is written and
compiles, and the dual-write + extension-loading plumbing is tested on
the fallback path (no extension present in this dev env). We do not
claim measured vec0 numbers until a contributor benchmarks with the
extension loaded.
[v1.3.0] — 2026-04-22
"TS and Python scope resolvers" — the third v2.0 milestone (Pillar A,
completed for non-Go languages). Brings v0 textual refs up to the
visited-and-stamped floor for TypeScript (ResolverVersion=2) and
Python (ResolverVersion=3).
Added
internal/resolver/python— stateless per-file resolver. Handles
import/from-importbindings (including aliases),self.method()
andcls.method()inside classes, module-qualified calls like
foo.bar()via namespace-style imports. Every visited call is stamped
ResolverVersion=3so the SQL short-name fallback skips it.internal/resolver/typescript— same shape for TS/TSX. Named
imports + aliased imports + default imports +import * as ns
namespace imports all resolve.this.method()inside classes resolves
to the class's own methods. StampsResolverVersion=2.pipeline.Resolverinterface +Pipeline.Resolvers map[string]Resolver— replaces the per-resolver field pile. Legacy
GoResolverfield still honored for backward compatibility.- Three new integration-test cases —
v1.3_ts_this_method_resolution
(AuthService.issueToken → this.fingerprint lands as a resolved ref),
v1.3_python_self_method_resolution(JobQueue.drain → self.dequeue),
v1.3_no_truly_unresolved_refs(all TS + Python calls in the fixture
are visited and stamped).
Explicit non-goals (stays textual)
- TS: generics, conditional types, declaration merging, ambient modules
beyondtsconfig.paths, arbitraryobj.method()that needs type
inference. - Python:
super()chain resolution,getattr(obj, 'm')(...)dynamic
attribute access, type-based method dispatch.
Fixture additions
testdata/fixtures/sample/src/auth.tsgrewnormalizeEmail,
issueToken,fingerprint— together they exercise cross-module
imports,this.-calls, and cross-function linking within a class.testdata/fixtures/sample/py/worker.pygrewdrain— exercises
self.-calls and param-typed calls we deliberately don't resolve.
Self-index unchanged
The self-index already hit 0.0% unresolved in v1.2 (pure Go repo).
v1.3 additions keep it there: 66 files, 454 symbols, 2488 refs, 0
resolution-bug self-loops, 0 truly-unresolved non-import refs.
[Unreleased (v1.2 hotfixes)]
LIMITATIONS.mdat repo root — single source of truth for what
doesn't work today, grouped by cause (resolution quality, graph queries,
indexing/scale, distribution, tooling surface). Linked from README and
CLAUDE.md. Edit on every milestone.- Depth-clamp surfaces a note — requesting
get_neighborhoodwith
depth > 5 now returns anotesentry on the result explaining the
clamp and pointing at LIMITATIONS.md. Visible in the CLI (stderr),
HTTP, and MCP responses. Silent clamp was too easy to miss.
[v1.2.0] — 2026-04-22
"Go, but honest" — the second v2.0 milestone (Pillar A for Go). Type-aware
reference resolution kills the self-loop class of resolution bugs and pushes
the unresolved-ref ratio on mycelium's own repo from 74.8% to 0%.
Added
internal/resolver/golang— Go type resolver built on
golang.org/x/tools/go/packages+go/types. Loads the whole module
once, walks each file's AST using the cached*types.Infoside tables,
and rewrites call-refDstNameinto the samepkg.Receiver.Method
shape the parser uses for its own symbols. Stamps every visited call
withResolverVersion=1regardless of whether it could rewrite the
name, so builtins/conversions/erased-receiver calls are correctly
classified as "analyzed, no local target" rather than "unknown."- Migration
0004_resolver_version.sql—refs.resolver_version
column + index. 0 = textual, 1 = go-types resolver, 2+ reserved for TS
(v1.3) / Python (v1.3). - Honest metrics in
query.Stats—NonImportRefs,RefsTypeResolved,
RefsExternalKnown,RefsTrulyUnresolved,RecursionSelfLoops.
UnresolvedRatio()now measures genuine unresolved-ness (v0 + no link,
non-import), not "dst_symbol_id IS NULL" (which lumped stdlib calls in
as "failures"). MYCELIUM_RESOLVER_DEBUG=1env var — per-file resolution counts on
stderr for diagnosing edge cases without a rebuild.
Changed
- SQL resolver's unique-short-name fallback is now v0-only. Refs the
type-aware pass visited skip the ambiguity-prone fallback, eliminating
the self-loop class (e.g.ix.db.Close()no longer resolves to our
Index.Close). self_loop_countnow counts only resolution-bug self-loops (v0);
genuine recursion (v1) is reported separately asrecursion_self_loops.Tests: truein thepackages.Config— integration and bench test
files are now part of the type graph.- Go
godirective bumped to 1.25.0 (required bygolang.org/x/tools).
Self-index baselines (Tiger Lake laptop, myco doctor)
| metric | v1.1 | v1.2 |
|---|---|---|
| self_loop_count (bugs) | 11 | 0 |
| recursion_self_loops (informational) | n/a | 12 |
| unresolved_ref_ratio | 74.8% | 0.0% |
| refs_resolved_local | 556 | 550 |
| refs_external_known | n/a | 1425 |
| doctor exit code | 2 (fail) | 0 (pass) |
Benchmarks (10k synthetic Go symbols, Tiger Lake)
| op | v1.1 | v1.2 |
|---|---|---|
| initial index | 2433 sym/sec | 2347 sym/sec (−3.5%) |
Note: benchmark fixtures don't carry a go.mod, so the resolver is nil in
this measurement. The resolver adds a fixed one-time cost per Pipeline
construction for the packages.Load call (~200ms on the self-index).
[v1.1.0] — 2026-04-22
First milestone on the v2.0 roadmap ("Honest signals"). Adds health checks
so later milestones can measure themselves against honest baselines.
Added
myco doctorsubcommand with per-check Pass/Warn/Fail output and
conventional exit codes (0/1/2).--jsonflag for CI.internal/doctorpackage — configurable thresholds, pluggable into
future MCP introspection.- Extended
stats—self_loop_count,unresolved_by_language,
total_refs_by_language,stale_chunks,embed_queue_depth, DB size and
fragmentation, plusUnresolvedRatio()/DBFragmentation()helpers. - Benchmark harness —
GenerateSyntheticRepo()emits deterministic
Go-only fixtures at arbitrary symbol counts. Benchmarks for initial index,
FindSymbol, andGetNeighborhooddepth-2. Baselines at 10k symbols on
a Tiger Lake laptop: 2433 sym/sec, 11.4 ms point lookup, 3.8 ms
neighborhood query.
Baselines captured
Self-index of mycelium under provider=none:
- 57 files · 387 symbols · 2045 refs
- self_loop_count: 11 (Pillar A in v1.2 targets 0)
- unresolved_ref_ratio: 72.8% (Pillar A target <8% for Go)
- db_fragmentation: 11.1%
[v1.0.0] — 2026-04-22
First stable release. Nine MCP tools, three transports, three languages.
Added
- Release binaries. GitHub Actions matrix build for
linux/amd64,
linux/arm64,darwin/amd64,darwin/arm64,windows/amd64. Version
injected via-ldflags "-X main.version=…". - Integration test. Committed multi-language fixture
(testdata/fixtures/sample) exercised end-to-end in CI: parsers, index,
all nine query methods. - CI. Lint + vet + race-enabled tests on Linux and macOS.
[v0.5.0] — 2026-04-21
Added
search_lexical— parallel 4-worker ripgrep-style regex scan over
indexed files. Fills the gap where semantic search misses exact strings.get_file_summary— structural summary per file: exports, imports,
LOC, symbol counts by kind. No LLM calls.get_neighborhood— local call graph around a symbol via recursive
CTE onrefs. Depth capped at 5; direction = out | in | both.- HTTP transport — loopback server on
127.0.0.1:<http_port>. Routes:
POST /rpcwith{method, params}and per-methodPOST /<method>. - Parallel initial scan — worker pool for parsing; single-writer
goroutine for DB commits. Threshold-gated (≥200 files) to avoid
goroutine overhead on small repos.
[v0.4.0] — 2026-04-21
Added
- Semantic search (
search_semantic) — embeds the query, brute-force
cosine similarity over stored float32 vectors. Top-k with snippet,
kind/path filtering. - Embedders.
Noop(default),Ollama(localhttp://localhost:11434),
Fake(test-only). Pluggable via.mycelium.yml. - Chunker. One chunk per symbol with qualified name + signature +
docstring + body; skips tiny const/var without docstrings. - Embed queue + worker. Background goroutine in the daemon; batches to
the embedder, writes tochunks.embedding+embed_cache. Rate-limit
circuit breaker (trailing 60s). - Model-switch invalidation. Changing
embedder.modelon daemon start
drops stale vectors automatically.
Changed
- Migrated chunks table to include
content,embedding,embed_model
columns (migration0002_embeddings.sql). Deferredsqlite-vec—
brute-force Go cosine is fast enough for typical repos.
[v0.3.0] — 2026-04-21
Added
- MCP stdio server (
myco mcp) — minimal JSON-RPC 2.0 over stdio, no
external MCP SDK. Exposes five tools:find_symbol,get_references,
list_files,get_file_outline,stats. myco init— writes.mycelium.yml, adds.mycelium/to
.gitignore, installs post-commit hook, prints Claude Code / Cursor MCP
config snippet via--mcp claude|cursor.- Post-commit git hook — reconciles the index after commits when the
daemon isn't running. - TypeScript/TSX parser —
smacker/go-tree-sittergrammar; extracts
function / class / interface / type / enum / var / method / field decls
plus import + call refs. Leading_heuristic for private. - Python parser — tree-sitter grammar; extracts function / class /
method decls with PEP-257 docstring detection._-prefix convention for
private; dunders are public. - Shared tree-sitter helpers (
internal/parser/tsutil) — slice, position,
walk, preceding-comment extraction.
[v0.2.0] — 2026-04-21
Added
- Daemon (
myco daemon) — long-running per-repo process that owns the
index. Thin clients (CLI, MCP, hook, HTTP later) talk to it via a unix
socket at.mycelium/daemon.sock. - fsnotify watcher — recursive watch with per-file debounce window;
auto-registers new directories. - Reference resolution pass. Two-step: exact qualified match, then
unique short-name match viarefs.dst_shortcolumn.ON DELETE SET NULL
cascades keep refs honest. get_references,list_files,get_file_outlinequery methods.
Refs flag each hit asresolvedvstextual.- Query package (
internal/query) — the single reader of the DB.
All transports call this package.
[v0.1.0] — 2026-04-21
Initial indexer. Go-only. One-shot CLI.
Added
- Go parser — stdlib
go/ast, no cgo. Extracts functions, methods,
types (struct / interface / alias), top-level vars / consts, imports,
call-site refs. - SQLite schema (
migrations/0001_init.sql) — files, symbols, refs,
chunks,symbols_fts(FTS5 trigram),embed_cache,embed_queue, meta. - Walker (
internal/repo) — doublestar-matching include/exclude, size
limits,.git/.myceliumskipping. - One-shot pipeline — hash-gated per-file transactions.
myco index,myco query find,myco statssubcommands.