Reference
The documentation, unabridged.
The landing page argues a case. This is the README itself — every command, every flag and every captured run, in the order the repository keeps them.
Install and start
Requires Bun 1.4+ and git. From this checkout:
shbun install
bun run build # standalone binary: dist/sgx, with WASM, workers and UI embedded
bun link # registers sgx -> src/cli.ts; requires this checkout and Bun
cd /path/to/your/repository
sgx init --claude # index and merge repository-local Claude Code configuration
sgx init --codex # the same, for Codex (.codex/config.toml)
sgx init --fx # the same, for fx (.mcp.json and AGENTS.md)
sgx find login
sgx source add https://docs.example.com/api.md --covers 'src/api/**'
sgx uiWhile working on sgx itself, run the working tree directly — no build, no
bun link, and the source you just edited:
shbun run --silent sgx find login # from this checkout (`--silent` drops bun's banner)
./scripts/sgx find login # the same
cd /other/repo && /path/to/sgx/scripts/sgx source list # against another repositoryscripts/sgx execs bun <checkout>/src/cli.ts "$@" and changes nothing else, so
--root still defaults to the caller's repository.
Use the absolute path to dist/sgx instead of sgx to run the compiled binary.
The package name is sgx.sh; the executable is sgx. This private package is
installed from source, not from a published registry release. init creates
.sgx/graph.db and .sgx/.gitignore; it does not edit your root .gitignore.
--claude, --codex and --fx keep existing MCP servers, hooks and
instructions, adding only missing sgx entries. All three reject configuration
paths that resolve outside the repository, and any of them may be passed on its
own or together.
CLI reference
All commands accept --root <dir> and --json. The default root is the nearest
ancestor containing .git, otherwise the working directory. Scope flags
(--repo, --project, --workspace, --all) widen a command past one
repository; see Workspaces. Read commands refresh
the index first; --no-refresh skips that pass. --help lists commands and
--version reports the version. Watch/UI use JSON readiness/events; MCP always
speaks the MCP protocol and hooks retain their Claude Code output contract.
These samples were captured from the compiled binary on 2026-09-19 in a temporary
copy of test/fixtures/mini with git history. Paths, ports and timings are the
actual run's values; handles remain stable across body edits. Where described as
an excerpt, lines were omitted without rewriting the shown output. All ~ token
counts are estimates at roughly four characters per token.
init, index, watch, stats
init [--claude] [--codex] creates the graph and runs its first index:
sgx init --claudecaptured outputinitialised .sgx/ in /var/folders/k3/bvwcdygn053bktwwbzm9c8vc0000gn/T/sgx-mini-HIiPLr · indexed 23 of 23 files (+23 ~0 -0) · 23 files · 44 symbols · 68 edges · epoch 1 · 66 ms · history mined
.mcp.json: added mcpServers.sgx → /Users/tim/workspace/sgx/dist/sgx mcp
.claude/settings.json: added hooks.SessionStart → /Users/tim/workspace/sgx/dist/sgx hook session-start
.claude/settings.json: added hooks.PostToolUse (Edit|Write|MultiEdit) → /Users/tim/workspace/sgx/dist/sgx hook post-editindex [--force] refreshes changed files; --force reparses everything. stats reports graph size, resolution confidence and timings.
sgx indexcaptured outputindexed 2 of 25 files (+2 ~0 -0) · 25 files · 52 symbols · 68 edges · epoch 2 · 18 mssgx statscaptured outputepoch 2 · 25 files · 293 lines · 52 symbols · 68 edges · 82 refs
languages typescript 9 · json 5 · python 4 · go 3 · javascript 1 · markdown 1 · other 1 · tsx 1
symbols function 17 · key 16 · method 6 · interface 5 · class 2 · const 2 · enum 1 · section 1 · struct 1 · type 1
edges calls 21 · imports 20 · tests 15 · references 12
confidence 44 at 1.0 · 15 at 0.9 · 9 at 0.5 · 0 at 0.2
git HEAD 388aa7f · 23 files with history · 1 co-change pairs
last index 18 ms (parse 1 ms) · 2 of 25 files parsed · +2 ~0 -0 · epoch 2watch keeps indexing until Ctrl-C. Edits are debounced into one epoch; failed batches retry with capped backoff. Startup from this run:
sgx watchcaptured outputwatching /var/folders/k3/bvwcdygn053bktwwbzm9c8vc0000gn/T/sgx-mini-HIiPLr · up to date · 25 files · 52 symbols · 68 edges · epoch 3 · 14 msfind, outline, show, map, flavors, context
find <query> [--limit 20] [--kind <kind>] searches names, paths and docs,
boosting exact and prefix names before applying the result limit.
sgx find login --limit 3captured output@twtp1 fn login(user: User, opts: LoginOpts = {}): Session src/auth/login.ts:17
@89nj2 iface interface LoginOpts src/auth/types.ts:6
@jwcqd fn testLogin(): void tests/login.ts:3outline <file|dir> prints signatures, handles and anchored notes, eliding bodies:
sgx outline src/auth/store.tscaptured outputsrc/auth/store.ts · 23 lines · outline ~53 tok (full ~112)
@atvdc export class SessionStore {
@rtj1r get(id: string): Session | undefined { … 3 lines }
@f5zfk set(session: Session): void { … 3 lines }
@8czjn clear(): void { … 3 lines }
}
@hwz8t export const storeshow <handle|path#qualname|unique-name>... [--level 0-3] [--lines]
uses the fidelity ladder: L0 location/name, L1 signature, L2 documentation and
member outline, L3 full source (default). --lines adds source line numbers.
Use --session <id> to deduplicate L3 bodies across CLI calls and --fresh to
resend them. Without a session, CLI output is self-contained.
sgx show validate --level 1captured output@f0daw fn validate(user: User): boolean src/auth/login.ts:9map [--budget 1500] ranks central exported symbols and entry points. First lines of a 200-token-estimate map:
sgx map --budget 200captured outputrepo map · ~193/200 tok · 25 files · 12 symbols · epoch 2
entry: go/cmd/main.go · py/app/service.py · src/cli.ts
go/greeter/ (1 file)
@bpayd type Greeter struct greeter.go:4
@b32tr fn New() *Greeter greeter.go:14
flavors [--evidence] lists detected ecosystems by package. The small mini fixture has none:
sgx flavorscaptured outputno flavors detectedcontext "<task>" [--budget 4000] [--working-set] [--session <id>] packs
code into an estimated token budget. Explicit handles, paths, identifier terms
and optionally the git working set seed personalized PageRank. The strongest
seeds receive source before remaining space goes to neighbours. Generic/common
terms carry less weight. JSON and YAML keys are background: a task word landing on
a config key is a coincidence, not a request, so the packer skips them unless the
caller names one by handle or path — and still falls back to them when a question
about configuration has nothing else to answer with. They stay indexed either way,
because find and docs want them. Symbols are grouped by path and line; the footer lists
what was dropped. Output is deterministic for the same graph, sources and ledger.
bun run eval:context runs the relevance evaluation set.
sgx context login --budget 200captured outputcontext · ~197/200 tok · 7 symbols in 3 files · epoch 2
src/auth/login.ts
@f0daw fn validate :9
@twtp1 fn login :14-25
/**
* Log a user in and remember the session.
*/
export function login(user: User, opts: LoginOpts = {}): Session {
if (!validate(user)) {
throw new Error("invalid user");
}
const token = `${formatName(user.name)}-${opts.remember ? "long" : "short"}`;
const session: Session = { token, user };
store.set(session);
return session;
}
@5kx7a fn logout :27
src/auth/types.ts
@rxqer iface User :1
@89nj2 iface LoginOpts :6-8
export interface LoginOpts {
remember?: boolean;
}
@sn2v2 iface Session :10
tests/login.ts
@jwcqd export function testLogin(): void { … 5 lines }
not shown (12): @atvdc 0.32 · @b0q5q 0.29 · … — expand: sgx show <handle>impact, tests, check, hotspots
impact <target|--diff> [--depth 3] combines static callers/importers, tests
and git co-change evidence. Inferred relationships print ~; syntactic
resolution is not a complete, type-checked call graph.
sgx impact login --depth 1captured outputimpact @twtp1 login · src/auth/login.ts:17
2 callers in 3 files · 1 test · 1 co-change partner
direct:
@jb7pz module src/cli.ts:1 imports
@eqdyv function main src/cli.ts:3 calls
@2pnq2 module src/index.ts:1 imports
@kd19m module tests/login.ts:1 imports
@jwcqd function testLogin tests/login.ts:3 calls
tests:
tests/login.ts
co-change:
src/auth/store.ts support 1.00 (4 commits)tests <target|--diff> prints one test path per line. For example, sgx tests --diff | xargs bun test selects tests reachable from a working diff.
sgx tests logincaptured outputtests/login.tscheck [--diff|--file <path>|--all] reports removed exports, stale callers
of changed signatures, cycles, architecture-rule violations, untested changes
and (with --all) dead exports. It exits 1 for errors, 0 for warnings/info.
The first line from the fixture's --all report:
sgx check --allcaptured outputwarn cycle src/core/a.ts import cycle src/core/a.ts -> src/core/b.ts -> src/core/a.tsArchitecture rules and file/test globs live in repository-local sgx.json:
json{
"exclude": ["generated/**"],
"tests": ["tests/**", "**/*.test.ts"],
"rules": [{"name": "core-no-ui", "from": "src/core/**", "to": "src/ui/**"}]
}docs [<path>] [--stale] reports which documents cover which code; see
Documents. It exits 1 when --stale finds anything behind.
hotspots [--limit 20] ranks normalized 90-day churn × complexity × log(1 +
fan-in). This fixture's January commits are outside the run's 90-day window,
so its scores are zero:
sgx hotspots --limit 3captured output0.00 src/auth/login.ts churn 0 in 90d (4 total) · complexity 6 · fan-in 2
0.00 .claude/settings.json churn 0 in 90d (0 total) · complexity 5 · fan-in 0
0.00 go/greeter/greeter.go churn 0 in 90d (1 total) · complexity 5 · fan-in 1changes and note
changes [--since <epoch>] [--limit 500] prints committed symbol changes.
When truncated, text and JSON report omitted rows and text gives a larger-limit
command. After adding a parameter to login:
sgx changes --since 2captured outputchanges since epoch 2 · 1 symbol change in 1 file · epoch 3
M sig @twtp1 login(user: User, opts: LoginOpts = {}): Session → login(user: User, opts: LoginOpts = {}, required: boolean): Session src/auth/login.ts:17note add <ref> <text> [--author <name>], note list [<ref>], and
note rm <id> manage anchored notes. Notes become stale after body edits;
list without a ref also shows notes whose symbol no longer resolves. Put global
flags before -- to preserve flag-like note text, for example
sgx note add login -- --json is an argument, not an output mode.
sgx note add login "Retries twice on 503" --author developercaptured output#1 @twtp1 login src/auth/login.ts:17: Retries twice on 503sgx note list logincaptured output#1 @twtp1 login src/auth/login.ts:17: Retries twice on 503sgx note rm 1captured outputremoved note #1git
sgx git [status|log|show|authors|branches|remotes|owners] reads the history
mined into the graph, plus the live working tree for status. History is mined
from one git log --raw --numstat pass over the last 2,000 commits when HEAD
moves; renames are not followed, so history belongs to the path it was made under.
sgx git statuscaptured outputmain · 388aa7f · 5 commits · 1 authors
working tree cleansgx git log --limit 4captured output388aa7f 2026-01-06 Test Author +1/-0 1f format note
d0ac0ba 2026-01-04 Test Author +2/-0 2f auth tweak 3
e496837 2026-01-03 Test Author +2/-0 2f auth tweak 2
e99edba 2026-01-02 Test Author +2/-0 2f auth tweak 1sgx git authorscaptured output 5 Test Author <author@example.com> · 23 files · +257/-0 · 2026-01-01…2026-01-06sgx git owners src/auth/login.tscaptured outputsrc/auth/login.ts · 1 author
100% Test Author <author@example.com> · 4 commits · +30/-0 · last 2026-01-04log takes --limit, --path and --author; show <sha> accepts an
abbreviated sha and lists the commit's files with their line counts.
github
sgx github [sync|repo|prs|issues|pr <n>|review <n>] over the real GitHub GraphQL
API.
Token, first hit wins: $SGX_GITHUB_TOKEN, $GITHUB_TOKEN, $GH_TOKEN,
then gh auth token. So if you already use gh, there is nothing to configure.
The token is never stored, logged or printed, and the only header sgx sends about
itself is User-Agent: sgx.
When it goes to the network: sync always; any other github read (and the
UI's GitHub screen) when the cache is empty or older than 15 minutes. Never
during indexing, watching, the post-edit hook, check or context. --offline
or SGX_OFFLINE=1 keeps it off the network entirely and answers from the cache.
A failed sync keeps the cache and tells you what failed.
sgx github synccaptured outputcli/cli · api · 94 pull requests (63 open) · 57 issues (40 open) · 4824 API calls left until 2026-09-20 16:16
file lists truncated at 100 by the API for #13017Each sync makes four passes at once: every open pull request — "what is in
flight" has to be complete — a recent window of any state (--limit, default
100), and the same two for issues. Rows are upserted, so syncing repeatedly
accumulates history rather than truncating it. files(first: 100) is a hard API
page, so a bigger pull request is reported as truncated rather than quietly
analysed short.
sgx github prs --state allcaptured output#14475 open Add a skill for chaptered CLI recordings BagToad 76f +16765/-0 2026-09-19
#14462 merged Revert "Use internal Apple codesign action" williammartin 2f +3/-3 2026-09-15
· cli/cli · api · synced 2026-09-20 15:45pr <n> is the point of the integration: the pull request's changed paths are
resolved into the graph and answered with the same blast radius sgx impact
gives, plus the reviewers the file history suggests and any downstream
repositories from the registry.
sgx github pr 42captured output#42 Retry login twice on 503
open · dana · login-retry → main · REVIEW_REQUIRED · 2026-01-01
checks test failure
files 2 files · +12/-3 · 8 symbols · 3 callers in 4 files · 1 tests
symbols
@twtp1 function login src/auth/login.ts:17
@atvdc class SessionStore src/auth/store.ts:7
impact 3 callers in 4 files · 1 test · 0 co-change partners
@eqdyv main src/cli.ts:3 (calls, depth 1)
@jwcqd testLogin tests/login.ts:3 (calls, depth 1)
tests tests/login.ts
reviewers Test Author (2 files, 100%)review <n> prints the same report followed by check findings scoped to the
changed files, and exits 1 on an error finding. And check itself now warns when
a file you are editing is also changed by an open pull request — the conflict you
would otherwise meet at merge time:
sgx checkcaptured outputwarn open-pr src/auth/login.ts also changed by open pull request #42 (dana)That finding reads only the cache, so it costs nothing on the post-edit hook.
Testing and pinning. $SGX_GITHUB_FIXTURE, else <root>/.sgx/github.json,
replays recorded responses and wins over the API. The test suite and bun run e2e
use it and never touch the network; bun run scripts/record-github.ts <owner/name>
re-records the API payloads in test/fixtures/github/ that the mapping is tested
against.
enrich and where
The graph is syntactic, so sgx find auth misses SessionStore and
validateCredentials. sgx enrich judges every eligible symbol into a concept —
one request per symbol to TypeSafe's System One model, three questions each
(concept, public API, change risk), each answer carrying the model's own
confidence.
sgx enrich --vocabularycaptured output40 concepts
auth role authentication, authorization, sessions, tokens, credentials
resilience role retries, timeouts, backoff, circuit breaking and error recovery
…
ui repo code under the ui directory (42 files)
nextjs repo Next.js framework codeHalf the vocabulary is universal roles; half is derived from the repository's own directories, package names and detected flavors, so nothing is generated and the list is reproducible from the graph. Docs, tests and hidden trees contribute nothing.
sgx enrichcaptured outputjev-latest · judged 1,694 of 1,694 sent (1,714 eligible) · 40 concepts, 37 in use · 20944 ms · 2,311,847 input tokensThat is the whole of sgx: 1,694 symbols in 21 seconds, about ten cents. It is
incremental by body hash, so a second run sends only the symbols whose bodies
changed, and --limit enriches a large repository in slices.
sgx where "caching"captured outputcache (10) · 4 symbols
@h8kcy 100% cache perEpoch src/ui/api.ts:155
@frevc 99% cache getCsr src/context/csr.ts:39
@hkymv 88% cache ensureSynced src/github/sync.ts:88
@m0s0e 87% cache REFRESH_MS src/github/sync.ts:14None of those four say "cache" in their name. sgx find --concept cache filters
lexical search the same way, and sgx enrich --concepts and --status show the
vocabulary in use and how much of the repository has been judged.
Concept matching is lexical, and a word is weighted by how many concept
descriptions share it — "sessions" routes to auth, "service" routes nowhere. A
question that matches nothing says so rather than guessing:
sgx where "where do we prove who the caller is before talking to a hosted service"2 commandsno concept matches '…' — try `--route` to ask the model
$ sgx where "where do we prove who the caller is before talking to a hosted service" --route
auth (7) · 3 symbols
@fzx1a 100% auth fromGhCli src/github/token.ts:18
@7nxv1 100% auth requireToken src/github/token.ts:35
@8hrb7 98% auth TokenSource src/github/token.ts:9Two confidences, never merged. The percentage on a symbol line is the judge's
confidence that the symbol belongs to the concept. Whether the question is
about that concept is a separate thing, and it is the one to read first, so it
goes in the header. Every description word unique to one concept scores the same,
which means the score alone cannot tell a good detail match from a bad one:
sessions and session ledger resending bodies both reach auth with exactly
1.5. What separates them is how much of the question the match explains, so a
lone word inside a longer question is reported as weak rather than rendered like
a verdict:
sgx where "session ledger resending bodies"captured outputauth (7) · 7 symbols
weak match: 1 of 4 question words reached 'auth' — `--route` asks the model instead
@fzx1a 100% auth fromGhCli src/github/token.ts:18Lexical matching genuinely cannot decide that case, and --route is what can.
A routed answer is never weak: the model judged the whole question, and its
choice probability is calibrated. Routing is also allowed to answer nothing —
the vocabulary it is given carries an explicit "none of these" option, because a
choice question always picks something, and without somewhere to put "the
repository has no concept for this" the model is forced to name one anyway.
Key: $SGX_TYPESAFE_API_KEY or $TYPESAFE_API_KEY. Network: sgx enrich
and sgx where --route are the only commands that reach the model; SGX_OFFLINE=1
stops both. Recorded judgements in .sgx/semantic.json replace the model entirely
and are how the test suite and bun run e2e run — regenerate them with
bun run scripts/record-semantic.ts.
Precision, not just recall. sgx enrich --edges asks about the edges the
graph already admits it is guessing at. On this repository that is 1,366 ambiguous
edges across 513 call sites and 4,673 tests edges — the latter being three-hop
reachability, which is generous. In a sample of 25, 18 tests edges were rejected
and the rejections were right: src/github/api.test.ts does not exercise
findToken or Sources.get, and the edges it kept (ApiProvider.fetch,
ApiProvider.page) are the ones it really does. Verdicts are stored by handle, so
they survive re-indexing. sgx enrich --precision shows how much is still a guess.
sgx enrich --precisioncaptured output1,325 ambiguous edges in 494 call sites · 4,655 tests edges · 92 judged (25 confirmed, 42 rejected, 18 tests edges dropped)Across a workspace. sgx where --all answers from every repository, and
sgx where --all --shared lists the concepts two or more of them share — two
repositories both doing auth are related whether or not one imports the other,
which is the thing package-name links cannot see.
Before you spend anything. sgx enrich --estimate prices a pass first, and
the /concepts screen's button carries the same number.
sgx enrich --estimatecaptured output~1,245 symbols × 40 concepts ≈ 1,718,100 input tokens ≈ $0.07 and ~39s (estimates)What it is worth, measured. bun run eval:context --no-semantic packs context
without concept seeds. On this repository the semantic layer moves overall recall
from 75% to 78% and flips one case outright: "caching computed results so they are
not recomputed" finds perEpoch, which the lexical packer never reached. That is
a modest gain on the packer; the larger one is sgx where, which answers a
question the graph previously could not.
Workspaces, projects and repositories
A workspace holds projects; a project holds repositories. Every repository keeps
its own .sgx/graph.db, so nothing about indexing, watching or the ledger
changes and a repository still works entirely on its own. The registry that
records the structure is one SQLite file outside them all — $SGX_HOME/registry.db,
default ~/.sgx — holding no symbols. Delete it and every repository still works.
sgx never creates it as a side effect of an ordinary single-repo run.
sgx workspace add acme3 commandsworkspace acme · active
$ sgx repo add ~/work/ui --project platform --name ui
added acme/platform/ui · /Users/you/work/ui
$ sgx repo add ~/work/app --project platform --name app
added acme/platform/app · /Users/you/work/appsgx repo scan <dir> [--depth 3] registers every git checkout it finds and does
not descend into one. sgx repo link re-derives the cross-repo links: a
repository publishes the package names in its manifests and consumes the import
sources that resolved to nothing inside it, and a consumed source that equals, or
is a subpath of, another repository's published name is a link. Matching is
exact — @acme/ui matches @acme/ui/button and never @acme/ui-kit.
sgx index --all3 commandsapp indexed 2 of 2 files (+2 ~0 -0) · 2 files · 1 symbols · 0 edges · epoch 1 · 58 ms · history mined
ui indexed 2 of 2 files (+2 ~0 -0) · 2 files · 1 symbols · 0 edges · epoch 1 · 57 ms · history mined
$ sgx repo link
2 repositories · 2 published packages · 1 cross-repo links
$ sgx workspace show
workspace acme · 1 project · 2 repositories · 4 files · 2 symbols · 0 edges
platform/
app 2 files 1 symbols main epoch 1
publishes @acme/app
ui 2 files 1 symbols main epoch 1
publishes @acme/ui
cross-repo imports
app → ui @acme/ui 1 fileScope flags decide what a command runs over:
| Flag | Scope |
|---|---|
| (none) | the repository containing the working directory, registered or not |
--repo <slug|project/slug|path> | that one repository |
--project <slug> | every repository in the project |
--workspace <slug> | every repository in the workspace |
--all | every repository in the active workspace |
One repository always takes the ordinary single-repo code path, so behaviour with
no scope flag is unchanged. A wider scope is federated by index, find,
stats, hotspots, changes, impact, context and ui; every other command
says it works on one repository at a time rather than quietly answering for one.
sgx find renderButton --all3 commandsui @aq0dz fn renderButton(label: string): string src/button.ts:1
$ sgx impact renderButton --all
ui · 0 callers in 0 files · 0 tests · 0 co-change partners
downstream app imports @acme/ui in 1 file
$ sgx stats --all
workspace acme · 2 repositories · 4 files · 16 lines · 2 symbols · 0 edges
app epoch 1 · 2 files · 1 symbols · 0 edges
ui epoch 1 · 2 files · 1 symbols · 0 edges
languages json 2 · typescript 2Federated answers merge per-repository results; no query joins across SQLite
files. find merges by score, hotspots and changes by score and epoch, and
context splits the budget by how well each repository matches the task — every
repository packs a small probe first and the budget is shared in proportion to the
probes' seed scores, so an unrelated repository costs a header and nothing more.
impact adds the downstream repositories, which is the reason multi-repo exists:
package-level, not type-accurate, and reported as such.
sgx workspace list|add|use|remove|show, sgx project list|add|remove and
sgx repo list|add|scan|remove|link manage the registry. $SGX_WORKSPACE
overrides the active workspace for one run.
Language support
sgx deeply indexes TypeScript/TSX, JavaScript/JSX, Python, Go, Rust, C#,
Java, PHP, and Ruby. It also builds compact structural outlines for Lua,
Racket, SQL (.sql/.ddl/.psql), Markdown, JSON, YAML, HTML/HTMX, CSS, and
GraphQL (.graphql/.gql). Local imports, module paths,
document links, resources, and schema references become graph edges where
their repository targets can be resolved. Other files remain searchable by
path and participate in churn and co-change analysis.
SQL has no tree-sitter grammar in @vscode/tree-sitter-wasm, so it takes the same
text-extractor path as Lua, Racket and GraphQL, and is dialect-agnostic on purpose.
CREATE TABLE becomes a table symbol with a column child per definition;
views, functions, procedures, triggers, types and indexes each become a symbol; and
REFERENCES, ALTER TABLE, FROM and JOIN become ordinary references, so a
foreign key resolves through the same rules as a function call. Comments and string
literals are blanked before scanning, so a CREATE TABLE inside a default value
declares nothing.
sgx outline db/schema.sqlcaptured outputdb/schema.sql · 17 lines · outline ~116 tok (full ~118)
@a7f10 table accounts
@8ccay id BIGSERIAL PRIMARY KEY
@hd786 email TEXT NOT NULL UNIQUE
@m0ddr created_at TIMESTAMPTZ NOT NULL DEFAULT now()
@3984n table sessions
@excsk id UUID PRIMARY KEY
@2stvy account BIGINT NOT NULL REFERENCES accounts (id) ON DELETE CASCADE
@w1ry2 index sessions_account INDEX sessions_account ON sessions (account)
@73vxx view active_sessions VIEW active_sessions AS SELECT s.id FROM sessions s JOIN accounts a ON a.id = s.accountThe foreign key is an edge, so a table has a blast radius like anything else:
sgx impact accounts --depth 1captured outputimpact @a7f10 accounts · db/schema.sql:1
2 callers in 1 file · 0 tests · 0 co-change partners
direct:
@2stvy column sessions.account db/schema.sql:11 ~0.90 references
@73vxx view active_sessions db/schema.sql:16 ~0.90 references
documented by:
docs/auth.mdYAML keys with scalar values are indexed to two levels of nesting, so runs-on: ubuntu-latest and image: postgres:16 are searchable; deeper leaves are left out
rather than flooding the graph. Markdown files carry a real module doc for the
first time — front-matter title/description, else the opening paragraph — which
is what lets search and context rank a document at all.
Extension mappings and display metadata live in
src/parse/language-meta.ts; parser implementations live behind the registry
in src/parse/registry.ts.
Documents
A repository is not only code, and the prose beside it is what goes stale silently.
A document declares what it governs in its front matter; the graph carries
documents edges from it to the files it covers; and check reports the ones a
change has left behind. The design is in docs/documents.md.
Front matter is the --- block at the top of a Markdown file. Every top-level key
becomes a searchable key symbol, scalars included, and five keys mean something
to sgx:
| Key | Meaning |
|---|---|
title | Display name; becomes the module doc, so find and context can rank the document |
description | Appended to that doc |
owner | Who answers for it; printed on every finding |
status | deprecated, obsolete, archived or superseded never raises a staleness finding |
covers | Globs or paths: what this document governs |
Edges come from three sources, each with its own confidence, and nothing else:
| Source | Confidence |
|---|---|
A front-matter covers: glob that matches the file | 1.0 |
A Markdown link that resolves to a repository file, optionally #symbol | 0.9 |
| A fenced block whose info string names a repository path | 0.7 |
A path or a name merely mentioned in prose is deliberately not a source.
Prose-to-code inference is noisy, and a finding is only worth having if it is
trusted. covers: is the answer for anything a link cannot express.
sgx docs lists every document, what it covers and whether the code has moved on:
sgx docscaptured outputdocs/auth.md covers 4 up to date current · platform
1 document · 0 behindsgx docs <path> answers in both directions — what documents a file, and what a
document covers:
sgx docs src/auth/login.tscaptured outputsrc/auth/login.ts is documented by:
docs/auth.md → login How login workssgx docs --stale is the repository-wide sweep, and exits 1 when anything is
behind, so it works as a CI gate. "Behind" means commits that touched a covered
file after the document itself last moved:
sgx docs --stalecaptured outputdocs/auth.md 2 commits behind src/auth/login.ts owner platform
1 stale documentThe point of all of it is the finding. check raises stale-doc when you change a
covered file and leave its document alone, which puts it in the post-edit hook,
where an agent that has just edited login is told what it has invalidated:
sgx checkcaptured outputwarn stale-doc docs/auth.md documents src/auth/login.ts, which you changed; the document is 2 commits behind it (owner: platform)
0 errors · 1 warning · 0 infocheck --all sweeps every documents edge instead of the change set. Both read
only tables already in the graph — no network, no extra git process — so the guard
costs the hook nothing. sgx impact gains a documented by section for the same
reason, and /api/symbol/:handle and /api/file carry documents.
Coverage is a claim the repository makes about itself, not a measurement. A
covers: glob can be wrong, a link can point at the wrong file, and a document can
be current while its git timestamp is old. The finding says what it saw — which
document, which file, how many commits — and lets the reader judge.
Remote sources
A repository depends on things that are not in it: a vendor's API reference, a
runbook, a sibling checkout you read constantly. sgx source fetches those into
.sgx/sources/<name>/, indexes them with the ordinary pipeline, and gives them
the same treatment documents get — symbols, handles, search, covers: and
staleness. The design, and what it deliberately refuses, is in
docs/remote-sources.md.
sgx source add ../vendor-docs --as vendor --covers 'src/billing/**' --ttl 7d2 commandsvendor: fetched · 1 file · 131 B
$ sgx source list
1 source
vendor ../vendor-docs
1 file · 131 B · synced 0s ago · trusted · ttl 1w
covers src/billing/**Once it is in, a mirrored page is ordinary graph content:
sgx find charge --limit 52 commands@qdsrj fn chargeCustomer(id: string, cents: number) src/billing/charge.ts:1
@y5mzv section ## charge.failed .sgx/sources/vendor/webhooks.md:7
@3kekz section ## charge.succeeded .sgx/sources/vendor/webhooks.md:3
$ sgx source show vendor
vendor · ../vendor-docs
trusted · text · ttl 1w · synced 3m ago · changed 3m ago
1 file · 131 B
.sgx/sources/vendor/webhooks.md · 9 lines
covers src/billing/charge.tsThe last line is the point of the feature. The manifest's covers: becomes a
documents edge, so the vendor's reference governs billing code exactly as
docs/auth.md would: when the code moves on and the reference does not, it is
reported by sgx docs --stale and warned about by sgx check as a stale-doc
finding, naming the mirrored page and how many commits it is behind. A mirrored
file has no commits of its own, so its last fetch stands in for the timestamp.
What it fetches
| URI | What happens |
|---|---|
a path, or file://… | a file, or a directory of text files, is mirrored (.md, .txt, .sql, .graphql, .json, .yaml, .toml, .csv, …) |
https://… serving Markdown or plain text | the body is mirrored, with ETag/Last-Modified for the next sync |
https://… serving HTML | refused, naming tier 2 as the change that would support it |
git+https://… | refused, naming tier 4 |
sgx source add|sync are the only commands in sgx that touch the network.
check, context, find, the MCP server and the hooks all read the mirror, so a
source that is unreachable today still answers, and a flaky CDN can never block
an edit-time guard. A failed sync keeps the previous snapshot and records why.
Refreshing, on purpose
--ttl is not a scheduler. It marks a source due in sgx source list, and
sgx source sync --stale fetches those and nothing else — an unchanged page is a
conditional request answered 304, so a due source that has not moved costs one
round trip and no re-index. Three ways to run it, none of them on by default:
shsgx source sync --stale # by hand, or from cron / CI
sgx watch --sync-sources [--sync-every 15m] # while the watcher is already running
sgx init --claude --sync-sources # a SessionStart hook, for Claude Code, Codex or fx--sync-sources on init installs exactly one extra hook:
sgx source sync --stale --quiet --deadline 10. Bounded and silent by
construction — ten seconds for the whole run, whatever it does not reach is
reported as skipped and waits for next time, and it prints nothing unless
something actually changed or failed. The watcher's timer is the same shape: a
check every 15 minutes, a 60-second deadline per tick, and no output when nothing
was due. Without the flag, none of sgx's wiring ever reaches the network.
The manifest, and what is committed
.sgx/sources.toml is written by sgx source add and is meant to be committed —
it is how a team shares sources. .sgx/.gitignore is rewritten to keep it and
ignore everything else, so the mirror and the graph stay out of git. sgx source sync reconciles the graph with the manifest first: a source someone added in a
pull request is fetched, and one they removed is dropped.
toml[[source]]
name = "vendor"
uri = "../vendor-docs"
covers = ["src/billing/**"]
ttl = "1w"Trust
Mirrored text is the one input to sgx that a third party writes, and it is treated that way:
- Data, never instructions. Any MCP result containing mirrored content is prefixed with a notice naming the sources it came from and saying exactly that. Nothing fetched is ever executed, and no JavaScript is run to fetch it.
contextleaves it out by default.--remoteopts in, and even then remote text may take at most 25% of the budget, so a documentation page cannot evict the function you are editing.- Nothing identifying goes out. The request carries
User-Agent: sgxand conditional-request headers. No repository name, no user, no token, no cookie. - Bounded. 2 MB per body, 500 files per directory, at most 3 redirects, and a redirect that leaves the original host is refused rather than followed.
- The worktree pass never reaches the network, and never deletes a mirrored file:
files.source_idmarks them, and only the sync that owns a source removes one.
Ecosystem flavors
sgx detects package-scoped framework and ecosystem flavors independently of language parsing: React, Next.js, Vite, Svelte, SvelteKit, FastAPI, GraphQL, and HTMX. A package can have several flavors at once, and monorepo packages are reported separately. Detection combines dependencies, config files, file conventions, imports, and syntax evidence; every result retains confidence, version, traits, and its supporting evidence.
Next.js enrichment recognizes App and Pages Router files, route handlers,
layouts, modern proxy files, and backward-compatible middleware files.
FastAPI routes, GraphQL declarations and operations, and HTMX endpoint
references are added to the same symbol graph. Add simple detectors in
src/flavors/registry.ts; richer graph adapters live in src/flavors/.
MCP: agent tools and schema cost
Run sgx mcp --root /path/to/repo as a stdio MCP server. It starts a watcher;
stdout contains only protocol messages. Any MCP client can use:
json{
"mcpServers": {
"sgx": {"command": "/absolute/path/to/dist/sgx", "args": ["mcp", "--root", "/path/to/repo"]}
}
}On initialize the server sends instructions (src/mcp/instructions.ts),
which the client puts in the agent's system prompt. They route one habit per
tool — where/find instead of grep, outline instead of opening a file to see
what is in it, show instead of reading around a symbol, context instead of
exploring file by file, impact before editing a shared symbol, check after
editing — because an agent that is merely offered the tools keeps reaching for
grep and whole-file reads, which it always has. The same first line is prepended
to the SessionStart hook output, for clients that ignore server instructions.
The original eight text tools are preserved. Framework flavors add one text tool,
Jev adds two structured lookup tools, git/GitHub add two, the semantic layer adds
where and documents add docs, for 15 current tools:
| Tool | Inputs | Result |
|---|---|---|
find | query, limit=20 (1–200) | L1 symbol lines |
outline | path | File/directory skeleton |
show | handles[], fresh=false | L3 source with session deduplication |
context | task, budget=4000 (200–50000), working_set=false | Packed text, estimates and dropped handles |
impact | target or diff=true, depth=3 (1–6) | Callers, tests and co-change |
changes | none | Feed since this session's last call |
check | scope="diff", "all", or a path | Guard findings |
docs | target?, stale=false | Which documents cover which code, or what has fallen behind |
note | handle, text | Stored anchored note |
flavors | evidence=false | Ecosystems by package |
where | query, limit=20, route=false | Symbols by concept with calibrated confidence; route spends one model call |
git | what="status", "log" or "owners", path, limit=20 | Working tree, commit log, or a file's authors |
pr | number?, state="open" | Pull request list, or one with its changed symbols, callers, tests and downstream repositories |
lookup_candidates | query, kind, limit, budget, expand | Structured versioned candidates and provenance |
lookup_materialize | expected, handle, budget | Source for the selected snapshot, or stale result |
sgx mcp --schema-cost measures the actual SDK tools/list response. JSON
array punctuation is included in the total; estimates are not tokenizer counts.
sgx mcp --schema-costcaptured outputsgx mcp schema · 15 tools · 6,049 bytes · ~1,513 tok (est.)
lookup_candidates 532 B ~133 tok
lookup_materialize 697 B ~175 tok
find 360 B ~90 tok
flavors 301 B ~76 tok
outline 291 B ~73 tok
show 403 B ~101 tok
context 422 B ~106 tok
impact 404 B ~101 tok
docs 389 B ~98 tok
changes 224 B ~56 tok
check 310 B ~78 tok
where 488 B ~122 tok
git 425 B ~107 tok
pr 434 B ~109 tok
note 353 B ~89 tokText-tool responses end with · epoch N. show and context remember sent
body hashes for this server session: unchanged source becomes a one-line
≡ unchanged since sent reference; changed bodies are resent and marked changed.
show's fresh=true bypasses deduplication. Compaction resets the ledger.
Separate clients/server processes have separate ledgers.
Usage telemetry: how agents use sgx
Every tool call is recorded locally, in the repository's own .sgx/graph.db
(docs/telemetry.md): which tool, with what arguments, how long it took, how
many tokens came back, whether the session ledger kept anything out of the
response, and what the agent called next. MCP calls are recorded in the one
handler every agent call passes through; CLI runs are recorded the same way, in a
session shared by one shell ($SGX_SESSION, else cli:<ppid>). SGX_USAGE=0
turns it off; nothing is ever sent anywhere; retention is 50,000 calls.
sgx usagecaptured outputsgx usage · 8 calls · 2 sessions · 0s ago → 0s ago
~371 tok returned · 87 tok saved by the ledger · 25.0% errors · 1 finding · p50 15ms · p95 75ms
tool calls err find p50 p95 tok share saved last
find 2 — — 17ms 18ms 212 57% — 0s ago
show 2 — — 0ms 1ms 114 31% 87 0s ago
impact 1 1 — 15ms 15ms 0 0% — 0s ago
index 1 — — 75ms 75ms 23 6% — 0s ago
outline 1 1 — 0ms 0ms 11 3% — 0s ago
where 1 — 1 15ms 15ms 11 3% — 0s ago
what follows what:
find → where ×1
find → show ×1
index → find ×1
show → show ×1
show → outline ×1
where → impact ×1
most repeated arguments:
2× show {"handles":["@twtp1"],"fresh":false}
1× outline {"path":"nope/nope.ts"}
1× find {"query":"login","limit":20}
1× where how do we log in
1× find login
errors:
1× outline: nothing indexed at 'nope/nope.ts'
1× impact: sgx impact: needs a target (handle, path#name, name or file) or --diff
sessions:
session client calls err find tok last tools
e4132ffc mcp/claude-code 4 1 — 232 0s ago show outline find
cli:3420 cli 4 1 1 139 0s ago where index impact findA finding is not a failure. check exits 1 because it found drift, where
because there is no semantic layer yet: the tool ran, and the answer is negative.
Those get their own column and stay out of the error rate, which is reserved for
misuse, missing preconditions and crashes. The reason is recorded either way —
MCP tools return their message, and a CLI run's is taken from what it printed.
share is the tool's share of every token sgx returned in the window: the fastest
way to see which tool is spending the agent's context.
--trace prints the calls themselves, newest first, errors marked ! and
findings ~, each carrying the message the agent received:
sgx usage --trace --limit 6captured outputwhen session # tool ms tok args
0s ago e4132ffc 4 !outline 0ms 11 {"path":"nope/nope.ts"} → nothing indexed at 'nope/nope.ts'
0s ago e4132ffc 3 show 0ms 14 {"handles":["@twtp1"],"fresh":false}
0s ago e4132ffc 2 show 1ms 100 {"handles":["@twtp1"],"fresh":false}
0s ago e4132ffc 1 find 18ms 107 {"query":"login","limit":20}
0s ago cli:3420 4 !impact 15ms 0 → sgx impact: needs a target (handle, path#name, name or file) or --diff
0s ago cli:3420 3 ~where 15ms 11 how do we log in → no semantic layer yet — run `sgx enrich`--sessions lists sessions, --clear forgets everything, and --json emits the
whole summary. Filters: --since 30m|24h|7d|2w, --tool <name>,
--session <id>, --surface mcp|cli, --limit <n>. The same numbers are on the
/usage screen of the web UI, with charts and a live trace.
The readings worth acting on: which tools an agent actually reaches for, which
ones it fails against and with what message, what each one costs its context in
tokens and as a share of the total, where latency is high enough that it would rather grep, what the ledger
saved, which pairs of calls follow one another (where then show), and which
questions it asked twice.
Self-improvement: sgx improve
sgx usage answers how sgx was used. It cannot answer what the agent did
instead — an agent that never calls sgx looks perfect there. That answer is in
the agent's own transcript, which Claude Code and Codex both write to disk: every
grep -rn, every cat src/foo.ts, with the size of the result it received.
sgx improve joins the two (docs/improve.md), ranks what is worth changing,
and with --apply rewrites the guidance the next session is given.
sgx improvecaptured outputsgx improve · 52 sgx calls · 948 agent tool calls across 10 sessions
361 of them were questions sgx answers, costing ~195k tok of the agent's context
! displaced: the agent read a whole file 196× here, costing ~152k tok (~774 a call) · `show` cost ~1.4k tok a call here
→ route it to `show` / `outline`
cat docs/telemetry.md && ls src/telemetry/
git diff --stat && sed -n '1,80p' src/telemetry/record.ts
! displaced: the agent searched with grep 145× here, costing ~36k tok (~251 a call) · `where` cost ~145 tok a call here
→ route it to `where` / `find`
! stale: `where` was called 5× with no semantic layer built (1,532 symbols eligible)
→ `sgx enrich`
· costly: `check` returned 62% of every token sgx gave the agent (~37k tok over 14 calls)
→ tune its defaults, or give it a smaller budget when the agent calls itA call counts as displaced only when sgx indexes the file and has a tool for
the question. A grep outside the repository, a read of something nothing indexed,
a | grep filtering another command's output: all left alone, because a wrong
accusation teaches an agent to distrust the report.
--apply writes .sgx/instructions.md — one generated block, regenerated in
full each time, capped at 1,200 characters:
text<!-- generated by `sgx improve --apply` · 2026-09-20 · do not edit -->
Learned in this repository from 10 sessions:
- Do not read a source file whole to find something in it: 196 whole-file reads here cost ~152k tok. `show` / `outline` answer the same question.
- Do not grep this repository: 145 searches here cost ~37k tok. `where` / `find` answer the same question.
- `impact` keeps failing with: impact needs a target (handle, path#name, name or file) or diff=trueTwo readers pick it up, both already places an agent is told about sgx: the MCP
server appends it to its instructions, and sgx hook session-start prints it
under the repo map. That is the loop — measure the workaround, then change the
instruction that allowed it.
Flags: --since 24h|7d, --apply, --no-transcripts (telemetry only), --json.
Transcripts are read, never written, and nothing read ever leaves the machine.
Structured lookup for Jev loops
sgx mcp --root <worktree> also exposes lookup_candidates and
lookup_materialize. These read-only tools return version 1 JSON in both
structuredContent and the text content, without an epoch footer. Existing
agent tools keep their text formats.
Candidates accept query, kind (symbol or concept), limit (0–32),
budget (1–50000 estimated tokens), and expand. A zero limit refreshes and
returns provenance only. Exact symbols preserve ambiguity; concept lookup
uses indexed terms and graph ranking, and may not find unrelated synonyms.
Materialization accepts the returned expected snapshot, a selected handle,
and budget. A changed generation or epoch returns status: "stale".
Every result includes the canonical worktree root, generation, epoch, parser coverage, source hashes, and truncation information. Source buffers are checked against their indexed hashes. Lookup refresh hashes every indexed file, including files whose size and mtime are unchanged, so new competing symbols invalidate previous mappings. Results are self-contained across sessions; these tools never suppress a body because another agent saw it. Candidate evidence budgets include serialized candidate metadata; output budgets apply to rendered source text. Cold indexing and freshness scans still cost time: this interface does not promise constant-time search.
Claude Code, Codex and fx
sgx init --claude merges .mcp.json and .claude/settings.json in the
indexed repository, using the absolute running binary path (or Bun plus the
source entry). Existing entries survive; running it again is idempotent. No
home-directory Claude configuration is written.
sgx hook session-startis installed forSessionStartfrom every source. It emits the routing line, then the repo map at ≤1,500 estimated tokens plus open errors. Onsource: "compact", it resets the ledger because the previous bodies were removed from the model's context.sgx hook post-editis installed forPostToolUsewith matcherEdit|Write|MultiEdit. It reads hook JSON on stdin, reindexes the given file, checks it against HEAD, and prints nothing when clean. Paths outside the indexed repository are ignored.
Both read stdin the way the agents write it: the JSON arrives and stdin closes. A caller that leaves stdin open instead — a shell pipeline, or running the hook by hand — is waited on for two seconds and then treated as sending no input, so a hook never hangs the turn it was meant to inform.
First lines of the session-start output:
sgx hook session-startcaptured outputsgx indexes this repo — prefer its MCP tools (where, find, outline, show, context, impact) over grep, find and whole-file reads.
repo map · ~363/1,500 tok · 25 files · 25 symbols · epoch 2
entry: go/cmd/main.go · py/app/service.py · src/cli.tsAfter the signature edit above, post-edit received
{"tool_name":"Edit","tool_input":{"file_path":"<fixture>/src/auth/login.ts"}}
on stdin and produced this real response:
sgx hook post-editcaptured output{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"sgx check src/auth/login.ts\nwarn signature-changed src/auth/login.ts:17 @twtp1 login: signature changed (was login(user: User, opts: LoginOpts = {}): Session; now login(user: User, opts: LoginOpts = {}, required: boolean): Session); 2 call sites in unchanged files: src/cli.ts:5, tests/login.ts:4\n0 errors · 1 warning · 0 info"}}Codex
sgx init --codex writes the same wiring to .codex/config.toml, the
project-scoped configuration Codex reads after ~/.codex/config.toml — which sgx
never touches. Codex uses the same hook contract as Claude Code: the same stdin
JSON, the same hookSpecificOutput.additionalContext on stdout. One hook
implementation serves both, so Codex gets the orientation and the edit-time guard,
not only the tools.
Three things differ, and the installer handles all three: the file is TOML, MCP
servers live under [mcp_servers.<id>], and Codex's edit tool is apply_patch,
so the PostToolUse matcher names it alongside Claude's Edit|Write|MultiEdit.
Merging appends the missing blocks rather than reformatting the file, so comments
and ordering survive; an entry already running sgx hook <sub> is left alone
however it was written.
sgx init --codexcaptured outputinitialised .sgx/ in /tmp/sgx-mini-0kPqa2 · indexed 23 of 23 files (+23 ~0 -0) · 23 files · 44 symbols · 68 edges · epoch 1 · 98 ms · history mined
.codex/config.toml: added mcp_servers.sgx → /path/to/dist/sgx mcp
.codex/config.toml: added hooks.SessionStart → /path/to/dist/sgx hook session-start
.codex/config.toml: added hooks.PostToolUse (apply_patch|Edit|Write|MultiEdit) → /path/to/dist/sgx hook post-edittoml[mcp_servers.sgx]
command = "/path/to/dist/sgx"
args = ["mcp"]
startup_timeout_sec = 30
[[hooks.PostToolUse]]
matcher = "apply_patch|Edit|Write|MultiEdit"
[[hooks.PostToolUse.hooks]]
type = "command"
command = "/path/to/dist/sgx hook post-edit"
statusMessage = "sgx check"fx
sgx init --fx wires fx 0.0.7 or newer, which reads the repository's own
.mcp.json — the same file --claude writes, under the same mcpServers key,
which fx takes as an alias of its canonical mcp map. So the tools need no second
entry: sgx init --claude --fx writes one server and both agents use it.
fx has no command hooks. Its lifecycle hooks — PreToolUse, Stop,
PostTurnEnd, AttentionRequired — are in-process handlers registered through
libfx, not commands named in a configuration file, so there is nowhere to install
sgx hook session-start or sgx hook post-edit. What those hooks do
automatically becomes an instruction instead: --fx appends a marked block to
AGENTS.md, fx's project instructions, carrying the routing line and the two
commands that stand in for the hooks. That depends on the model following the
line, which is weaker than a hook, and it is the most fx allows today.
sgx init --fxcaptured outputinitialised .sgx/ in /tmp/sgx-mini-0kPqa2 · indexed 23 of 23 files (+23 ~0 -0) · 23 files · 44 symbols · 68 edges · epoch 1 · 96 ms · history mined
.mcp.json: added mcpServers.sgx → /path/to/dist/sgx mcp
AGENTS.md: added the sgx block (repo map and check, which fx has no hook for)
note: fx (0.0.7+) leaves workspace .mcp.json servers untrusted — approve sgx once with `fx mcp trust`, or /mcp inside fxmarkdown<!-- sgx:start -->
## sgx
sgx indexes this repo — prefer its MCP tools (where, find, outline, show, context, impact) over grep, find and whole-file reads.
fx has no command hooks, so two things sgx does by itself under other agents are shell calls here:
run `/path/to/dist/sgx map` when a session starts, for the repo map, and `/path/to/dist/sgx check --file <path>` after editing a file.
<!-- sgx:end -->The note matters: fx marks every workspace .mcp.json server pending, and a
pending server does not connect. Trust it once with fx mcp trust or /mcp
inside fx, or set
enabledMcpjsonServers in ~/.fx/settings.json — which sgx never writes, the
same rule it follows for Claude Code and Codex. Once connected, fx puts the
server's initialize.instructions in front of the model, so the per-tool detail
arrives the same way it does everywhere else. docs/fx.md records what was
verified against the fx source.
Any other MCP client works from the generic form above; without hooks you get the tools but not the automatic orientation or the edit-time guard.
Web UI: ui and demo
sgx ui [--root <dir>] [--port <n>] indexes, watches and serves the current
repository; with a scope flag (--all, --project, --workspace) it indexes,
watches and serves every repository in that scope behind one switcher. Port precedence is --port, then $PORT, then 4817, with fallback
to the next free port. --port 0 lets the OS choose. It binds only 127.0.0.1.
The compiled binary embeds the UI; bun run dev runs from source with HMR.
A bounded fixture test captured:
sgx ui --port 0captured outputsgx ui · /var/folders/k3/bvwcdygn053bktwwbzm9c8vc0000gn/T/sgx-mini-HIiPLr · epoch 3
listening on http://127.0.0.1:52232sgx demo indexes sgx's own checkout and serves those results. From this
checkout, bun run demo builds first if the binary is absent and honours $PORT.
Rebuild explicitly after source edits when a binary already exists.
sgx demo --root /path/to/sgx --port 4817 can locate the checkout explicitly.
The command's real help entry is:
text demo index sgx's own repository and serve the UI on $PORT| Screen | What it does |
|---|---|
/ | Counts, languages, package flavors/framework symbols, confidence, epoch/history, findings, ledger savings and live feed |
/search?q=context | Instant symbol search with links to symbols and files |
/symbol/:handle | Signature/docs, L0–L3 text and estimates, editable anchored notes, source, callers/callees with confidence, tests and co-change |
/file/* | File outline and source ladder, symbol links, imports/importers, the pull requests in flight on the file, tests and notes |
/graph | Bounded Cosmos.gl symbol graph; seed, depth, limit and relationship filters in the URL; selection shows visible neighbours and file links |
/flow | xyflow lenses over the same graph: an architecture map of module containers with imports and cycles, and a feature-flow pipeline staged from entry points; lens, expansion, seed and depth in the URL |
/modules | Directory dependency matrix; rows depend on columns, cycles highlighted, click a count to see contributing edges |
/hotspots | TanStack scatter chart and table; churn versus square-root complexity, square-root fan-in radius, directory colors and filters |
/usage | How agents are using sgx: calls over time, latency against tokens returned, the per-tool table, what follows what, repeated arguments, errors, sessions and the live trace |
/context | Task and budget (200–64,000 estimated tokens), exact agent-facing output, budget usage and dropped candidates |
/concepts | The concept vocabulary, how many symbols carry each, a question box answered from the index, and every assignment with its confidence |
/workspace | Every served repository with counts, branch and published packages, and the cross-repo import links between them |
/git | Working tree, the commit stream with a selected commit's files, and the author table |
/github | Pull requests and issues from the API; /github/:number opens one pull request's files, symbols, blast radius, tests, downstream repositories and suggested reviewers |
All pages refetch on SSE epoch events. The sidebar offers Auto/Light/Dark;
mobile navigation becomes a horizontal strip. / opens search; ⌘K focuses
the jump box, and g followed by a navigation hint changes screens.
Hotspot bubbles can spread horizontally up to 0.32 commits to separate overlaps;
tooltips and the table retain exact values.
The typed UI client calls local /api/* endpoints for repos, workspace,
stats, overview, feed, changes, search/find, symbol/:handle,
file?path=, graph, flow, modules, hotspots, context, impact,
check, notes, usage, usage/trace, usage/sessions, git, git/commits, git/commit/:sha, git/authors,
git/owners, concepts, concept, where, github, github/pr/:number and github/touching; syncing is a
POST to github/sync, so no read ever triggers one by accident. SSE uses /api/events. Request paths cannot escape the
repository; notes render as text.
Every endpoint takes ?repo=<slug>, defaulting to the first repository served;
/api/repos lists them. The sidebar shows a repository picker when more than one
is served, and every repository's epochs stream on the one SSE connection, so the
screens refetch only for the repository they are showing.
/flow loads @xyflow/react and elkjs only when the screen is opened, so no
other screen pays for them; they add 5.1 MB to dist/sgx (77.3 MB to 82.4 MB,
measured on this checkout). The architecture lens groups directories with the
same two-level rule as /modules, so the two screens always agree. The feature
lens takes framework routes as its entry points where a repository has them and
otherwise falls back to source roots: exported callables that call something and
that no other file reaches.
End-to-end checks and benchmark
bun run e2e builds dist/sgx if missing, then drives that binary in temporary
copies of the fixtures with isolated git history. Each step prints PASS:
init with Claude integration, index, find, outline, show, context, impact,
tests, git status/log/owners, enrichment and concept lookup from recorded
judgements, GitHub sync/prs from recorded responses and a pull request's blast
radius,
a signature-change guard, post-edit hook JSON, an edit under watch and
its persisted feed, MCP initialize/list/call (including the structured lookup
tool registrations), UI /api/stats on an OS-assigned port, a two-repository
workspace with federated find/stats and the UI serving both, and flavor
regressions. The run uses its own $SGX_HOME and sets SGX_OFFLINE=1, so it
touches neither your registry nor the network. Children have bounded waits and are stopped before temp repos
are removed. Rebuild first when changing application code; an existing binary
is used as-is. On macOS, run watcher checks in an environment that permits
filesystem events; a sandbox that suppresses those events causes timeouts.
bun run bench generates a deterministic temporary git repo with 2,000 source
files and 100,000 lines: 1,600 TypeScript, 200 Python, 200 Go, including 160
TypeScript test files, classes and cross-file calls. It also indexes go.mod.
bun run bench --check exits nonzero if any latency reaches twice the spec's
target. Core queries run over one opened graph handle; the hook uses the
compiled binary and includes process startup. Single-file timings include
writing the edit and committing the new epoch, without the watch debounce.
Repeated measurements report p50 without discarding warmup samples. Cold
indexing includes graph creation, parsing, insertion, resolution and git mining,
with a fresh database (not a flushed OS disk cache).
Run the complete command checks in order:
shbun run typecheck
bun test
bun run build
bun run e2e
bun run eval:context --min-recall 0.75
bun run bench --check.github/workflows/ci.yml runs all of these on every push except the benchmark
gate: the targets are wall-clock on an M-series Mac, and a shared runner measured
the post-edit hook at 416 ms against 140 ms locally. CI records the numbers so a
catastrophic regression is visible in the log; --check stays the gate on hardware
we control.
Real run, 2026-09-20; machine, runtime and all measurements below are copied
from bun run bench --check:
textmachine: Tims-MacBook-Pro-2.local · Apple M5 Pro · darwin/arm64 · Bun 1.4.2
corpus: 2,000 source files · 100,000 lines · TS 1,600 / Python 200 / Go 200 · 160 TS test files · cross-file calls and classes
timings: core library over one graph handle; cold includes parsing, inserts, resolution and git; hook includes binary startup; no warmup samples discarded
cold index: 522.45 ms · target 4000 ms · check < 8000 ms PASS
graph: 2001 files · 5200 symbols · 5500 edges
no-op refresh: 40.64 ms p50 (n=21) · target 150 ms · check < 300 ms PASS
one-file edit → committed epoch: 28.20 ms p50 (n=21) · target 25 ms · check < 50 ms PASS
find: 0.16 ms p50 (n=21) · target 5 ms · check < 10 ms PASS
outline: 0.05 ms p50 (n=21) · target 5 ms · check < 10 ms PASS
show: 0.03 ms p50 (n=21) · target 5 ms · check < 10 ms PASS
impact depth 3: 0.10 ms p50 (n=21) · target 20 ms · check < 40 ms PASS
context 4k: 1.66 ms p50 (n=21) · target 60 ms · check < 120 ms PASS
full files vs outlines (entire corpus): 3637333 → 398650 bytes · saved 3238683 bytes (89.0%) · estimated tokens 910381 → 97890 (~4 chars/token)
naive target + callers vs context (20 targets, depth 3, 4k budget): 145331 → 150653 bytes · saved -5322 bytes (-3.7%) · estimated tokens 36337 → 37393 (~4 chars/token)
30-call show replay (10 targets, one body edit), without vs with ledger: 18933 → 7945 bytes · saved 10988 bytes (58.0%) · estimated tokens 4749 → 1988 (~4 chars/token)
post-edit hook process start → exit: 140.20 ms p50 (n=9) · target 150 ms · check < 300 ms PASSTwo of these were over their nominal targets for a long time, and profiling found
one cause rather than many. workingChanges — what check, impact --diff,
tests --diff and the post-edit hook all start with — ran four git subprocesses
in series at roughly 13 ms each. HEAD now comes from reading .git instead of
spawning git rev-parse, and the three remaining commands are independent, so
they run together: 53 ms to 13 ms, and check --file from 68 ms to 26 ms. The
post-edit hook went from 243 ms to 140 ms and is now inside its 150 ms target.
The one-file edit is 28 ms against a 25 ms target, down from 43 ms. What is left
is almost entirely one git ls-files spawn on the full-refresh path; the watcher,
which passes the changed paths directly, skips it and indexes in about 3 ms.
Token counts are estimates (~4 characters/token). On this synthetic corpus, outlines save 89.0% of bytes and the replayed ledger saves 58.0%. The 4k context pack is 3.7% larger than the naive caller-file read: it includes additional related context, so savings are not guaranteed. Cold indexing also writes the commit log and per-file line counts now, which this corpus (one commit of 2,000 files) absorbs without leaving its target.
Landing page
bun run site writes the site into site/. site/index.html is the landing
page: headlines and section prose are landing-page copy and live in
scripts/site-content.ts. Examples never do. Every terminal block on it is
resolved out of this README by a command that appears inside it:
tsterminal("sgx context login --budget 200")An unresolved or ambiguous reference throws, so a command shown on the page is a command that was really run here, and deleting or editing an example above breaks the build instead of leaving the page stale. Headline figures work the same way — the four latency numbers are read out of the benchmark block by regex, not retyped.
site/reference.html is this README in full: every ## section, in order, with a
sticky contents column. The two pages share one header, footer and stylesheet, and
link to each other.
Beside them the build writes llms.txt (the project in Markdown: one ## per
capability with its captured example), AGENTS.md (install, the smallest real
run, the tool table and the three mistakes that waste a context window),
index.md and reference.md (each page as Markdown), sitemap.xml, robots.txt
and a committed 1200×630 og.png. Both pages carry a canonical URL, Open Graph and
Twitter tags and a SoftwareSourceCode JSON-LD block written from the same model.
Neither page has network fonts, a build step to serve it, or any script but that
JSON-LD, which executes nothing. bun test scripts/site.test.ts fails if a
reference no longer resolves, if a section's prose stops naming a real option, if
a README section is missing from the reference page, or if any committed file is
out of date with this README. docs/landing-page.md records
the composition.
Boundaries and spec reconciliation
Not in v0.1: type-accurate LSP/SCIP resolution, external-package nodes,
embeddings, hosted/team servers, or UI authentication. Two things leave the
machine and nothing else does: the GitHub sync and sgx enrich (with
where --route). Both are explicit, both are stopped by SGX_OFFLINE=1, and both
replay from recorded files in tests. Concept assignments are one model's judgement
with its own confidence, reported as such — not ground truth. Cross-repo links are package-name matches, not resolution: sgx says
which repository imports the package, never which symbol in it resolves where. The UI is local only. Unknown languages remain file nodes;
structural extractors do not imply deep semantic analysis. Edges are syntactic
with explicit confidence, and false positives/negatives remain possible.
Session-ledger rows and savings counters currently have no automatic retention policy. Handles lengthen on hash collisions; in that rare case allocation can depend on insertion order. Renames/moves create new handles. A parser-engine failure retains the previous graph until a successful retry rather than deleting known symbols.
The current contract is docs/spec.md. Its final exclusion paragraph
still says languages beyond TS/JS/Python/Go are absent and Rust has no extractor;
that conflicts with its updated §2 and the shipped parsers. The language list
above documents the implementation. The older task wording asks for eight MCP
tools; current §8 adds flavors, the shipped Jev interface adds two more, and
git/GitHub add git and pr.
Go same-package cross-file calls resolve at 0.9, extending §3's literal
same-file/same-class rule to Go's package scope. These differences are reported
explicitly; the completion pass preserves the newer capabilities. §11's command
list now also carries docs.
Document coverage is a claim, not a measurement: a covers: glob is whatever the
document says, a documents edge is never inferred from prose, and "behind" is a
commit count, not a judgement that the text is wrong. SQL is extracted by a
dialect-agnostic text scanner, not a grammar, so an exotic dialect degrades to
fewer symbols rather than wrong ones.
Spec section ownership: T1 §1; T2 §2; T3 §3; T4 §§4–5; T5 §6; T6 §7; T7 §§8–9; T10 §10; T14 §11; T13 the benchmark. T8/T9 supply visual designs, T11/T12 complete the UI screens, and T15 refines §5 relevance. T14's acceptance covers the CLI audit and integration/documentation checks.