@geml/dsh-plugin 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +27 -28
- package/README.md +69 -69
- package/README.zh.md +60 -60
- package/package.json +1 -1
- package/skills/geml/SKILL.md +170 -167
- package/skills/geml/references/authoring.geml +397 -369
- package/skills/geml/references/markdown-writes.md +116 -0
- package/skills/geml-code-graph/SKILL.md +222 -222
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Writing to a Markdown file with `geml set`
|
|
2
|
+
|
|
3
|
+
`geml set` and `geml add` edit one block of a `.md` in place. Nothing is
|
|
4
|
+
converted: the file stays the Markdown it was, and a reader who does not have
|
|
5
|
+
`geml` sees no trace of it.
|
|
6
|
+
|
|
7
|
+
Everything below was measured against real documents, not read off the help
|
|
8
|
+
text. Two of the rules are **silent** when broken — no error, exit code 0 — and
|
|
9
|
+
they are the reason to read this before the first write rather than after.
|
|
10
|
+
|
|
11
|
+
## What holds
|
|
12
|
+
|
|
13
|
+
**A write is surgical.** After `geml set <file> '#id' --body --in -`, the YAML
|
|
14
|
+
frontmatter and every block other than `#id` are byte-for-byte what they were.
|
|
15
|
+
The verb splices; it does not re-serialize the file.
|
|
16
|
+
|
|
17
|
+
**The body lands verbatim.** Whatever you write goes in as typed — a
|
|
18
|
+
`> [!tip]` callout, a `[[wikilink]]`, a `` ```dataview `` fence, a table. No
|
|
19
|
+
escaping, no reflowing, no normalization of your Markdown to anyone else's
|
|
20
|
+
taste.
|
|
21
|
+
|
|
22
|
+
**A broken result is refused.** The file is re-parsed before the write lands;
|
|
23
|
+
if the result would not parse, nothing is written.
|
|
24
|
+
|
|
25
|
+
**Heading addresses are stable.** `## Entities` is `#entities` on every run and
|
|
26
|
+
every platform, and it survives anything happening above it. That is the whole
|
|
27
|
+
reason to address a file this way instead of by line number.
|
|
28
|
+
|
|
29
|
+
One exception, and it is GitHub's too: **a repeated heading is numbered by
|
|
30
|
+
position.** The second `## Added` is `#added-1`, the third `#added-2` — the same
|
|
31
|
+
anchors GitHub gives them — so a Keep-a-Changelog file is writable section by
|
|
32
|
+
section. But a new `## Added` inserted above one shifts it: `#added-1` becomes
|
|
33
|
+
`#added-2`. Take a repeated heading's address from a fresh `geml list`, never
|
|
34
|
+
from memory.
|
|
35
|
+
|
|
36
|
+
**The file is read as Markdown, not as GEML.** Where the two disagree, a `.md`
|
|
37
|
+
gets Markdown's reading:
|
|
38
|
+
|
|
39
|
+
- `[[Note]]`, `[[Note#Heading]]`, `[[Note#Heading|alias]]`, `[[Note#^block]]`
|
|
40
|
+
and `![[Note#Heading]]` are Obsidian links. The note is found by name
|
|
41
|
+
anywhere under the resolution root, `.md` implied; one that does not exist
|
|
42
|
+
yet is a **warning**, never a refusal — a vault plans notes that way.
|
|
43
|
+
Within the page, `[[#Heading Text]]` may name a heading by its text.
|
|
44
|
+
- `[^label]` is a GFM footnote. With a `[^label]:` line it is a footnote;
|
|
45
|
+
without one it is plain text — so `[^0-9]` in a sentence about a regex is
|
|
46
|
+
nothing to worry about.
|
|
47
|
+
- `{{title}}` is text: a template engine's placeholder, not a reference.
|
|
48
|
+
- `~~~` fences and indented code blocks are code, like ``` ones. Nothing inside
|
|
49
|
+
is a link, a heading or a footnote.
|
|
50
|
+
|
|
51
|
+
The resolution root is the file's own directory unless you pass `--root`. A
|
|
52
|
+
note in a subfolder that links across its vault needs `--root <vault>`, and
|
|
53
|
+
`geml check` says so when it sees the vault's `.obsidian/` above you. It never
|
|
54
|
+
looks above the root on its own.
|
|
55
|
+
|
|
56
|
+
## The three that bite
|
|
57
|
+
|
|
58
|
+
### 1. Never `set` the frontmatter block — it destroys the frontmatter
|
|
59
|
+
|
|
60
|
+
Frontmatter is not a typed block in Markdown; it is an anonymous prose block,
|
|
61
|
+
and its **closing `---` is part of that block's body**. Replace the body and the
|
|
62
|
+
closer is gone, leaving an opener with nothing to close it — no properties at
|
|
63
|
+
all, for every tool that reads them.
|
|
64
|
+
|
|
65
|
+
Change frontmatter with an ordinary editor. This is silent: exit code 0.
|
|
66
|
+
|
|
67
|
+
### 2. `--body` is for a heading. On a prose block it APPENDS
|
|
68
|
+
|
|
69
|
+
A heading's block is a heading line plus a body. A prose block is body all the
|
|
70
|
+
way down, so it has no separate body to set — `get '#x' --body` on one comes
|
|
71
|
+
back empty, and `set --body` writes into that emptiness, which lands **after**
|
|
72
|
+
the prose already there. Nothing is removed.
|
|
73
|
+
|
|
74
|
+
| target | `set '#id'` | `set '#id' --body` |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| a heading block | refused: *content is prose, not a block — use --body* | replaces the section body, keeps the heading line |
|
|
77
|
+
| a prose block | replaces it | **appends to it, silently** |
|
|
78
|
+
|
|
79
|
+
The rule is the opposite of what one habit would give you, and picking wrong
|
|
80
|
+
fails loudly one way and quietly the other. `geml list` prints the kind in its
|
|
81
|
+
second column — read it before choosing. This is the other silent one.
|
|
82
|
+
|
|
83
|
+
### 3. `--in <file>` is not "read this text"
|
|
84
|
+
|
|
85
|
+
`--in F` means *take block `#id` from file F*. Raw text goes in on **stdin**:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
printf '…' | geml set page.md '#id' --body --in - # right
|
|
89
|
+
geml set page.md '#id' --body --in fragment.txt # looks for #id INSIDE fragment.txt
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Loud, at least: when F has no such block the refusal names the stdin form.
|
|
93
|
+
|
|
94
|
+
## Smaller things worth knowing
|
|
95
|
+
|
|
96
|
+
- `find` is a **literal substring**, not a pattern. `Hot.Cache` does not match
|
|
97
|
+
"Hot Cache". Case-insensitive unless `--case`.
|
|
98
|
+
- `find --head` shows **one line per block**. A block with thirteen matches
|
|
99
|
+
reports one; `find` locates, `get` reads.
|
|
100
|
+
- `set --body` replaces a trailing `---` rule too, if the section ends with one.
|
|
101
|
+
- The blank line after a heading is not re-inserted. Cosmetic; renderers do not
|
|
102
|
+
care, a diff does.
|
|
103
|
+
- A directory walk skips hidden directories. A tree that hides sources in
|
|
104
|
+
`.raw/` must name that directory: `geml find 'x' notes .raw`.
|
|
105
|
+
- `@…` addresses are content hashes and change when the content does. Fine to
|
|
106
|
+
read from a fresh `list`; never store one, and never write to one.
|
|
107
|
+
|
|
108
|
+
Before a first write to a page, `geml check <file>` — exit 0 means every block is
|
|
109
|
+
writable; warnings (a note not yet written) never block one.
|
|
110
|
+
|
|
111
|
+
## Do not convert the file
|
|
112
|
+
|
|
113
|
+
`geml <page>.md --to geml` and back is lossy for anything beyond plain
|
|
114
|
+
Markdown — frontmatter lists collapse, `[[…]]` comes back escaped, thematic
|
|
115
|
+
breaks are dropped. The point of addressing a Markdown file is that it **stays
|
|
116
|
+
Markdown**.
|
|
@@ -1,222 +1,222 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: geml-code-graph
|
|
3
|
-
description: >-
|
|
4
|
-
Build, view, update, and navigate a project's call graph as GEML codemap
|
|
5
|
-
documents. Use when asked to see/update/build a project's code graph or
|
|
6
|
-
codemap (看下/更新下 code-graph), when asked "who calls X" / "what does X
|
|
7
|
-
call" / to trace a call chain or impact path, or whenever a
|
|
8
|
-
.geml-code-graph/ directory with index.geml and _index/name-lookup.json
|
|
9
|
-
exists.
|
|
10
|
-
Detects the project's languages itself — never asks the user; viewing ends
|
|
11
|
-
with the browser OPEN on the graph.
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
# Code-graph navigation (codemap profile)
|
|
15
|
-
|
|
16
|
-
The call graph lives as **text documents, not a database**
|
|
17
|
-
([profile](https://github.com/geml-spec/geml/blob/main/
|
|
18
|
-
one GEML document per container (module / dir /
|
|
19
|
-
file), each with ONE meta (`module`, `src`, `entry`, `resolution-default`),
|
|
20
|
-
empty-body `code` blocks per method, and up to three CSV edge tables —
|
|
21
|
-
`#calls` (out), `#called-by` (in), `#unresolved` (blind spots). The build's
|
|
22
|
-
`verify` has checked that every edge reference resolves.
|
|
23
|
-
|
|
24
|
-
## The moves
|
|
25
|
-
|
|
26
|
-
```sh
|
|
27
|
-
# 1. resolve a name — where does a symbol live
|
|
28
|
-
node -e "console.log(JSON.stringify(require('./.geml-code-graph/_index/name-lookup.json')['hashtableFind'],null,1))"
|
|
29
|
-
# → [{"anchor":"c:hashtable.c#hashtableFind(…)","doc":"hashtable.c.geml","id":"hashtableFind"}, …]
|
|
30
|
-
# Multiple entries = real ambiguity (e.g. a .c definition and a .h inline) — inspect each.
|
|
31
|
-
|
|
32
|
-
# 2. container overview — the module's surface, one glance
|
|
33
|
-
head -8 .geml-code-graph/hashtable.c.geml # meta: entry = the externally-called methods
|
|
34
|
-
|
|
35
|
-
# 3. open the method block (src= tells you exactly where the code is)
|
|
36
|
-
geml get .geml-code-graph/hashtable.c.geml '#hashtableFind'
|
|
37
|
-
|
|
38
|
-
# 4. forward: what it calls (grep your method's rows; follow doc.geml#id refs)
|
|
39
|
-
geml get .geml-code-graph/hashtable.c.geml '#calls'
|
|
40
|
-
|
|
41
|
-
# 5. reverse: who calls it (aggregated, with file:line sites)
|
|
42
|
-
geml get .geml-code-graph/hashtable.c.geml '#called-by'
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
A reference is `#id` (same document) or `sibling.geml#id` (that document, that
|
|
46
|
-
block) — `geml get` it the same way. `index.geml` holds the repo-level view:
|
|
47
|
-
app entries in its meta, `#modules` / `#module-edges` aggregate tables.
|
|
48
|
-
|
|
49
|
-
## Reading the tables
|
|
50
|
-
|
|
51
|
-
| Line | Meaning |
|
|
52
|
-
|---|---|
|
|
53
|
-
| `#calls` row, empty confidence | resolved at the document's `resolution-default`, high confidence |
|
|
54
|
-
| `#calls` row `kind=candidate` | dispatch ambiguity: one of several implementations, right after its main `call` row. Treat the SET as the answer, never just the first |
|
|
55
|
-
| `#calls` row confidence `medium`/`low` | the extractor is less sure — say so when reporting |
|
|
56
|
-
| `#unresolved` rows (hidden table) | calls the extractor could NOT resolve — **blind spots, not evidence of absence**; fall back to grep when one matters |
|
|
57
|
-
| `#called-by` absent for a method | no *resolved* callers. Under `resolution-default = heuristic` that means little; under `cpg` it is strong (but pointer/dynamic dispatch still lands in `#unresolved`) |
|
|
58
|
-
|
|
59
|
-
Symbol classes: `.accessor` (bean get/set/is leaves — the graph view hides
|
|
60
|
-
them by default, tables keep them) · `.leaf` (calls nothing, only called — usually skippable when
|
|
61
|
-
tracing logic) · `.test` (test territory) · `.flow-entry` (critical-flow start).
|
|
62
|
-
|
|
63
|
-
## "看下/更新下 X 项目的 code-graph" — the end-to-end move
|
|
64
|
-
|
|
65
|
-
The toolkit ships inside the `@geml/geml` package: `geml codemap …`
|
|
66
|
-
(without a global install: `npx -y @geml/geml codemap …`).
|
|
67
|
-
|
|
68
|
-
### Dispatch first — generation is slow, the conversation must not block on it
|
|
69
|
-
|
|
70
|
-
Indexers take real time (scip: seconds–minutes; Joern on a repo: minutes).
|
|
71
|
-
Pick the executor BEFORE starting:
|
|
72
|
-
|
|
73
|
-
- **Codemap exists, user wants to look** → inline, seconds:
|
|
74
|
-
`serve --background` + open the browser. No subagent.
|
|
75
|
-
- **Update asked and `_index/refresh.json` exists** → no subagent either:
|
|
76
|
-
`geml codemap refresh <dir> --background` (detached process, costs the
|
|
77
|
-
conversation nothing). Open the CURRENT graph immediately — serve renders
|
|
78
|
-
live, so when the refresh lands, F5 shows it; say exactly that.
|
|
79
|
-
- **geml files must be (re)generated agentically** — first build, no recipe
|
|
80
|
-
recorded, adapters change, or a refresh failed → hand the WHOLE generation
|
|
81
|
-
to ONE subagent (Agent tool; `run_in_background: true` so the user can keep
|
|
82
|
-
working). Its prompt must be self-contained: project root; detect the
|
|
83
|
-
languages per the table below (never ask); the exact indexer +
|
|
84
|
-
`geml codemap build --history` + `geml codemap verify` commands; verify
|
|
85
|
-
MUST exit 0; write `_index/refresh.json` with the exact commands used;
|
|
86
|
-
return container/method/entry counts, verify result, and any language
|
|
87
|
-
gaps. The MAIN conversation does the last mile itself when the subagent
|
|
88
|
-
reports: `serve --background`, open the browser (if an older codemap was
|
|
89
|
-
already on screen, telling the user to F5 is the whole move).
|
|
90
|
-
|
|
91
|
-
1. **Have a codemap?** `<proj>/.geml-code-graph/index.geml` exists → skip to
|
|
92
|
-
step 4 (view) or step 3 (update was asked). An older `codemap/`/`graph/`
|
|
93
|
-
tree from before the rename is not special: regenerate into
|
|
94
|
-
`.geml-code-graph/` (one build; carry the `*.gemlhistory` sidecars over
|
|
95
|
-
first if they matter) and remove the old directory.
|
|
96
|
-
2. **Detect the language(s) — NEVER ask the user.** (Steps 2–3 are the
|
|
97
|
-
generation work — per Dispatch above they normally run inside the
|
|
98
|
-
subagent.) Judge from manifests
|
|
99
|
-
first, then source-file counts (`Glob`/`ls`). Multiple languages with
|
|
100
|
-
real code (≥ a handful of files each) → one build with REPEATED
|
|
101
|
-
`--adapter` groups; the codemap merges them (Java+TS validated).
|
|
102
|
-
|
|
103
|
-
| Signal | Indexer → adapter |
|
|
104
|
-
|---|---|
|
|
105
|
-
| `tsconfig.json` / mostly `.ts` `.tsx` `.js` | `npx --yes @sourcegraph/scip-typescript index --output index.scip` (run IN the target repo/subproject) → `--adapter scip --raw index.scip` |
|
|
106
|
-
| React / JSX (`.tsx` `.jsx`) | same scip route, verified tier: `<Child />` render edges, custom-hook calls, and `useReducer(reducer, …)` wiring all resolve high — arrow components (`const Foo = () =>`) included. Indirect dispatch is **absent, not `#unresolved`**: callback-prop calls (`onToggle(…)`), `dispatch()`→reducer case handling, and context-injected functions ride scip locals/members and leave NO edge — grep when one matters. Also invisible: `memo()`/`forwardRef()`-wrapped components (const = call, inner fn is a local) and module-scope `render(<App />)` callers |
|
|
107
|
-
| `Cargo.toml` / `.rs` | `rust-analyzer scip . --output rust.scip` (run IN the crate/workspace root; missing → `rustup component add rust-analyzer` or the rust-analyzer GitHub releases page) → `--adapter scip --raw rust.scip`. Precise tier: rust-analyzer-resolved, cross-file/cross-crate calls included; calls into std/external crates land in `#unresolved` |
|
|
108
|
-
| `pom.xml` / `build.gradle` / `.java` | Joern (locate per **Locating Joern** below; JDK required): `GEML_SRC=<abs-src> GEML_OUT=<abs-raw> GEML_LANG=JAVASRC joern --script <pkg>/codemap/joern-export.sc` → `--adapter joern --raw <raw>`. GEML_LANG takes Joern's `--language` names, UPPERCASE — lowercase `javasrc` fails with "No CPG generator exists" |
|
|
109
|
-
| `.c` / `.h` | same Joern route, `GEML_LANG=NEWC` (valkey-validated) |
|
|
110
|
-
| `.py` / `go.mod` / `.kt` | Joern frontends, `GEML_LANG=PYTHONSRC` etc. (usable tier — SAY SO in your report) |
|
|
111
|
-
| only a code-review-graph `graph.db` | `--db <graph.db>` (heuristic tier — say so) |
|
|
112
|
-
| none of the above | report honestly which languages are unsupported; do not guess |
|
|
113
|
-
|
|
114
|
-
`.vue` / `.svelte` SFCs: covered — use the AUTO build (`geml codemap
|
|
115
|
-
build --root <proj>`), not the manual per-indexer route. It virtualizes
|
|
116
|
-
each SFC project (Volar / svelte2tsx, fetched hermetically via npx) into
|
|
117
|
-
shadow TS with line-map sidecars, runs one scip pass over shadows + the
|
|
118
|
-
project's real TS/JS, and attributes every symbol back to the original
|
|
119
|
-
file and line. Template event handlers surface as edges from a synthetic
|
|
120
|
-
`<Component>.template` node (`@click="save"` → `#App-template, #save`;
|
|
121
|
-
mustapi-validated across three Vue apps, 85/85 SFCs). Honest residuals —
|
|
122
|
-
say them when reporting: component-TAG usage (`<Child/>`) is not a call
|
|
123
|
-
edge; Nuxt auto-imports (unimported `ref`, auto-registered components)
|
|
124
|
-
don't resolve, so those references drop; top-level `<script setup>`
|
|
125
|
-
calls, including `computed(() => …)` bodies, drop exactly like
|
|
126
|
-
module-level calls in plain TS; a failed virtualization falls back to
|
|
127
|
-
plain TS indexing and says so.
|
|
128
|
-
|
|
129
|
-
Vendored source trees explode the job list — next.js's
|
|
130
|
-
`packages/next/src/compiled/` carries ~140 checked-in package.json bundles,
|
|
131
|
-
each becoming its own scip job. Prune them at build time:
|
|
132
|
-
`geml codemap build --root <proj> --exclude "src/compiled/**"` (repeatable;
|
|
133
|
-
the exclusion also keeps their symbols out of the graph).
|
|
134
|
-
|
|
135
|
-
**Locating Joern — never hardcode a path.** Resolve it fresh on each run,
|
|
136
|
-
in this order: (1) `joern` on PATH — if `joern --version` works, use it;
|
|
137
|
-
(2) else read `~/.claude/skills/geml-code-graph/config.json` (`{"joern": "<launcher-or-dir>"}`)
|
|
138
|
-
and pass it as `geml codemap build … --joern <path>` (or export `GEML_JOERN`);
|
|
139
|
-
(3) else ASK the user for the joern-cli location (Windows: the folder unzipped
|
|
140
|
-
from joern-cli.zip; macOS/Linux: the joern-install.sh install dir), WRITE it
|
|
141
|
-
into that JSON file, then reuse it. `<path>` may be the launcher itself or the
|
|
142
|
-
directory holding it (`joern.bat` on Windows, `joern` on unix). Ask at most
|
|
143
|
-
once per machine — after that the JSON answers. Mirrors the CLI's own
|
|
144
|
-
`--joern` / `GEML_JOERN` resolution.
|
|
145
|
-
3. **Build + verify** (also the "更新" path — builds are deterministic,
|
|
146
|
-
only changed documents are rewritten):
|
|
147
|
-
|
|
148
|
-
```sh
|
|
149
|
-
geml codemap build --adapter scip --raw index.scip --root <proj> \
|
|
150
|
-
--out <proj>/.geml-code-graph --history # --container module|dir|file: match
|
|
151
|
-
# the layout (default dir; flat C repo → file)
|
|
152
|
-
geml codemap verify <proj>/.geml-code-graph # MUST exit 0 before showing anyone
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
**First successful build: record the recipe** so `refresh` (and the
|
|
156
|
-
commit hook) can replay it — write `<proj>/.geml-code-graph/_index/refresh.json`
|
|
157
|
-
with the EXACT commands you ran:
|
|
158
|
-
|
|
159
|
-
```json
|
|
160
|
-
{ "root": "..",
|
|
161
|
-
"steps": ["npx --yes @sourcegraph/scip-typescript index --output index.scip",
|
|
162
|
-
"geml codemap build --adapter scip --raw index.scip --root . --out .geml-code-graph --history",
|
|
163
|
-
"geml codemap verify .geml-code-graph"] }
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
From then on, "更新下" = `geml codemap refresh <proj>/.geml-code-graph` (skips
|
|
167
|
-
itself when git HEAD hasn't moved; log at `_index/refresh.log`).
|
|
168
|
-
4. **View — finish with the browser OPEN, not with instructions.**
|
|
169
|
-
|
|
170
|
-
```sh
|
|
171
|
-
geml codemap serve <proj>/.geml-code-graph --background # detached: SURVIVES the agent session;
|
|
172
|
-
# http://localhost:8140, pages render live
|
|
173
|
-
# from .geml — rebuild + F5, never stale.
|
|
174
|
-
# already-running port → reused, not stacked.
|
|
175
|
-
geml codemap serve <proj>/.geml-code-graph --stop # stop it (pid: .geml-code-graph/_index/serve.pid)
|
|
176
|
-
geml codemap render <proj>/.geml-code-graph # serverless alternative: bake .html next to
|
|
177
|
-
# each doc; open file:///…/.geml-code-graph/index.html
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
Always `--background` (a viewer must not die with the session). Then open
|
|
181
|
-
it for the user: Windows `start "" <url>` (or `Start-Process <url>`),
|
|
182
|
-
macOS `open <url>`, Linux `xdg-open <url>`. Port taken by something
|
|
183
|
-
else → pick another (`--port`), open that one.
|
|
184
|
-
|
|
185
|
-
`index.html` is the module overview; clicking a module opens its page inside
|
|
186
|
-
the graph area (nested view). Method pages: click = callee chain, ⊕ on an
|
|
187
|
-
entry = full caller chain, breadcrumb walks back up.
|
|
188
|
-
|
|
189
|
-
## Keep it in sync on every commit (optional per-project hook)
|
|
190
|
-
|
|
191
|
-
With the recipe recorded (step 3), a Claude Code PostToolUse hook makes any
|
|
192
|
-
`git commit` Claude runs in that project refresh the codemap in the
|
|
193
|
-
BACKGROUND (never blocks the commit; non-commit commands exit instantly;
|
|
194
|
-
projects without `refresh.json` are silently skipped). Add to the project's
|
|
195
|
-
`.claude/settings.json`:
|
|
196
|
-
|
|
197
|
-
```json
|
|
198
|
-
{ "hooks": { "PostToolUse": [ { "matcher": "Bash", "hooks": [
|
|
199
|
-
{ "type": "command", "command": "geml codemap refresh .geml-code-graph --hook --commit" }
|
|
200
|
-
] } ] } }
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
(`.geml-code-graph` = the codemap dir relative to the project root; use an absolute
|
|
204
|
-
path if the hook cwd differs.) With `--commit`, the refreshed documents land
|
|
205
|
-
as their own follow-up commit — `chore(codemap): refresh for <sha>`, codemap
|
|
206
|
-
dir only — so the next push carries code + graph together. It is loop-safe
|
|
207
|
-
(the follow-up commit changes no source file, so the refresh it triggers
|
|
208
|
-
skips) and it stands down when HEAD moved during the refresh or a merge is in
|
|
209
|
-
progress. Drop `--commit` to keep the old behavior: refreshed files stay in
|
|
210
|
-
the working tree for you to include in a later commit.
|
|
211
|
-
|
|
212
|
-
Between commits (editing-time sync), `geml codemap serve <dir> --watch`
|
|
213
|
-
re-runs the recipe after 30s of quiet whenever an indexed source file
|
|
214
|
-
changes — pages render live, so a browser reload shows the new graph.
|
|
215
|
-
|
|
216
|
-
Add `--history [-m msg]` to build to snapshot changed documents into
|
|
217
|
-
`.gemlhistory` sidecars — then `geml history get .geml-code-graph/<doc>.geml` shows
|
|
218
|
-
the graph's evolution and `geml revert .geml-code-graph/<doc>.geml '#method' --rev -1`
|
|
219
|
-
rolls one method's edges back. Language maturity tiers and the smoke-test
|
|
220
|
-
gate: [DESIGN-geml-code-graph.md](https://github.com/geml-spec/geml/blob/main/docs/design/specs/codemap/DESIGN-geml-code-graph.md) §3.4. An MCP wrapper exists (`geml mcp --root <dir>`, which serves the four
|
|
221
|
-
read-only `geml_codemap_*` tools next to the document tools when the root holds
|
|
222
|
-
a graph); the CLI path works without it.
|
|
1
|
+
---
|
|
2
|
+
name: geml-code-graph
|
|
3
|
+
description: >-
|
|
4
|
+
Build, view, update, and navigate a project's call graph as GEML codemap
|
|
5
|
+
documents. Use when asked to see/update/build a project's code graph or
|
|
6
|
+
codemap (看下/更新下 code-graph), when asked "who calls X" / "what does X
|
|
7
|
+
call" / to trace a call chain or impact path, or whenever a
|
|
8
|
+
.geml-code-graph/ directory with index.geml and _index/name-lookup.json
|
|
9
|
+
exists.
|
|
10
|
+
Detects the project's languages itself — never asks the user; viewing ends
|
|
11
|
+
with the browser OPEN on the graph.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Code-graph navigation (codemap profile)
|
|
15
|
+
|
|
16
|
+
The call graph lives as **text documents, not a database**
|
|
17
|
+
([profile](https://github.com/geml-spec/geml/blob/main/spec/profiles/geml-codemap/geml-codemap-profile.md)):
|
|
18
|
+
one GEML document per container (module / dir /
|
|
19
|
+
file), each with ONE meta (`module`, `src`, `entry`, `resolution-default`),
|
|
20
|
+
empty-body `code` blocks per method, and up to three CSV edge tables —
|
|
21
|
+
`#calls` (out), `#called-by` (in), `#unresolved` (blind spots). The build's
|
|
22
|
+
`verify` has checked that every edge reference resolves.
|
|
23
|
+
|
|
24
|
+
## The moves
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
# 1. resolve a name — where does a symbol live
|
|
28
|
+
node -e "console.log(JSON.stringify(require('./.geml-code-graph/_index/name-lookup.json')['hashtableFind'],null,1))"
|
|
29
|
+
# → [{"anchor":"c:hashtable.c#hashtableFind(…)","doc":"hashtable.c.geml","id":"hashtableFind"}, …]
|
|
30
|
+
# Multiple entries = real ambiguity (e.g. a .c definition and a .h inline) — inspect each.
|
|
31
|
+
|
|
32
|
+
# 2. container overview — the module's surface, one glance
|
|
33
|
+
head -8 .geml-code-graph/hashtable.c.geml # meta: entry = the externally-called methods
|
|
34
|
+
|
|
35
|
+
# 3. open the method block (src= tells you exactly where the code is)
|
|
36
|
+
geml get .geml-code-graph/hashtable.c.geml '#hashtableFind'
|
|
37
|
+
|
|
38
|
+
# 4. forward: what it calls (grep your method's rows; follow doc.geml#id refs)
|
|
39
|
+
geml get .geml-code-graph/hashtable.c.geml '#calls'
|
|
40
|
+
|
|
41
|
+
# 5. reverse: who calls it (aggregated, with file:line sites)
|
|
42
|
+
geml get .geml-code-graph/hashtable.c.geml '#called-by'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A reference is `#id` (same document) or `sibling.geml#id` (that document, that
|
|
46
|
+
block) — `geml get` it the same way. `index.geml` holds the repo-level view:
|
|
47
|
+
app entries in its meta, `#modules` / `#module-edges` aggregate tables.
|
|
48
|
+
|
|
49
|
+
## Reading the tables
|
|
50
|
+
|
|
51
|
+
| Line | Meaning |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `#calls` row, empty confidence | resolved at the document's `resolution-default`, high confidence |
|
|
54
|
+
| `#calls` row `kind=candidate` | dispatch ambiguity: one of several implementations, right after its main `call` row. Treat the SET as the answer, never just the first |
|
|
55
|
+
| `#calls` row confidence `medium`/`low` | the extractor is less sure — say so when reporting |
|
|
56
|
+
| `#unresolved` rows (hidden table) | calls the extractor could NOT resolve — **blind spots, not evidence of absence**; fall back to grep when one matters |
|
|
57
|
+
| `#called-by` absent for a method | no *resolved* callers. Under `resolution-default = heuristic` that means little; under `cpg` it is strong (but pointer/dynamic dispatch still lands in `#unresolved`) |
|
|
58
|
+
|
|
59
|
+
Symbol classes: `.accessor` (bean get/set/is leaves — the graph view hides
|
|
60
|
+
them by default, tables keep them) · `.leaf` (calls nothing, only called — usually skippable when
|
|
61
|
+
tracing logic) · `.test` (test territory) · `.flow-entry` (critical-flow start).
|
|
62
|
+
|
|
63
|
+
## "看下/更新下 X 项目的 code-graph" — the end-to-end move
|
|
64
|
+
|
|
65
|
+
The toolkit ships inside the `@geml/geml` package: `geml codemap …`
|
|
66
|
+
(without a global install: `npx -y @geml/geml codemap …`).
|
|
67
|
+
|
|
68
|
+
### Dispatch first — generation is slow, the conversation must not block on it
|
|
69
|
+
|
|
70
|
+
Indexers take real time (scip: seconds–minutes; Joern on a repo: minutes).
|
|
71
|
+
Pick the executor BEFORE starting:
|
|
72
|
+
|
|
73
|
+
- **Codemap exists, user wants to look** → inline, seconds:
|
|
74
|
+
`serve --background` + open the browser. No subagent.
|
|
75
|
+
- **Update asked and `_index/refresh.json` exists** → no subagent either:
|
|
76
|
+
`geml codemap refresh <dir> --background` (detached process, costs the
|
|
77
|
+
conversation nothing). Open the CURRENT graph immediately — serve renders
|
|
78
|
+
live, so when the refresh lands, F5 shows it; say exactly that.
|
|
79
|
+
- **geml files must be (re)generated agentically** — first build, no recipe
|
|
80
|
+
recorded, adapters change, or a refresh failed → hand the WHOLE generation
|
|
81
|
+
to ONE subagent (Agent tool; `run_in_background: true` so the user can keep
|
|
82
|
+
working). Its prompt must be self-contained: project root; detect the
|
|
83
|
+
languages per the table below (never ask); the exact indexer +
|
|
84
|
+
`geml codemap build --history` + `geml codemap verify` commands; verify
|
|
85
|
+
MUST exit 0; write `_index/refresh.json` with the exact commands used;
|
|
86
|
+
return container/method/entry counts, verify result, and any language
|
|
87
|
+
gaps. The MAIN conversation does the last mile itself when the subagent
|
|
88
|
+
reports: `serve --background`, open the browser (if an older codemap was
|
|
89
|
+
already on screen, telling the user to F5 is the whole move).
|
|
90
|
+
|
|
91
|
+
1. **Have a codemap?** `<proj>/.geml-code-graph/index.geml` exists → skip to
|
|
92
|
+
step 4 (view) or step 3 (update was asked). An older `codemap/`/`graph/`
|
|
93
|
+
tree from before the rename is not special: regenerate into
|
|
94
|
+
`.geml-code-graph/` (one build; carry the `*.gemlhistory` sidecars over
|
|
95
|
+
first if they matter) and remove the old directory.
|
|
96
|
+
2. **Detect the language(s) — NEVER ask the user.** (Steps 2–3 are the
|
|
97
|
+
generation work — per Dispatch above they normally run inside the
|
|
98
|
+
subagent.) Judge from manifests
|
|
99
|
+
first, then source-file counts (`Glob`/`ls`). Multiple languages with
|
|
100
|
+
real code (≥ a handful of files each) → one build with REPEATED
|
|
101
|
+
`--adapter` groups; the codemap merges them (Java+TS validated).
|
|
102
|
+
|
|
103
|
+
| Signal | Indexer → adapter |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `tsconfig.json` / mostly `.ts` `.tsx` `.js` | `npx --yes @sourcegraph/scip-typescript index --output index.scip` (run IN the target repo/subproject) → `--adapter scip --raw index.scip` |
|
|
106
|
+
| React / JSX (`.tsx` `.jsx`) | same scip route, verified tier: `<Child />` render edges, custom-hook calls, and `useReducer(reducer, …)` wiring all resolve high — arrow components (`const Foo = () =>`) included. Indirect dispatch is **absent, not `#unresolved`**: callback-prop calls (`onToggle(…)`), `dispatch()`→reducer case handling, and context-injected functions ride scip locals/members and leave NO edge — grep when one matters. Also invisible: `memo()`/`forwardRef()`-wrapped components (const = call, inner fn is a local) and module-scope `render(<App />)` callers |
|
|
107
|
+
| `Cargo.toml` / `.rs` | `rust-analyzer scip . --output rust.scip` (run IN the crate/workspace root; missing → `rustup component add rust-analyzer` or the rust-analyzer GitHub releases page) → `--adapter scip --raw rust.scip`. Precise tier: rust-analyzer-resolved, cross-file/cross-crate calls included; calls into std/external crates land in `#unresolved` |
|
|
108
|
+
| `pom.xml` / `build.gradle` / `.java` | Joern (locate per **Locating Joern** below; JDK required): `GEML_SRC=<abs-src> GEML_OUT=<abs-raw> GEML_LANG=JAVASRC joern --script <pkg>/codemap/joern-export.sc` → `--adapter joern --raw <raw>`. GEML_LANG takes Joern's `--language` names, UPPERCASE — lowercase `javasrc` fails with "No CPG generator exists" |
|
|
109
|
+
| `.c` / `.h` | same Joern route, `GEML_LANG=NEWC` (valkey-validated) |
|
|
110
|
+
| `.py` / `go.mod` / `.kt` | Joern frontends, `GEML_LANG=PYTHONSRC` etc. (usable tier — SAY SO in your report) |
|
|
111
|
+
| only a code-review-graph `graph.db` | `--db <graph.db>` (heuristic tier — say so) |
|
|
112
|
+
| none of the above | report honestly which languages are unsupported; do not guess |
|
|
113
|
+
|
|
114
|
+
`.vue` / `.svelte` SFCs: covered — use the AUTO build (`geml codemap
|
|
115
|
+
build --root <proj>`), not the manual per-indexer route. It virtualizes
|
|
116
|
+
each SFC project (Volar / svelte2tsx, fetched hermetically via npx) into
|
|
117
|
+
shadow TS with line-map sidecars, runs one scip pass over shadows + the
|
|
118
|
+
project's real TS/JS, and attributes every symbol back to the original
|
|
119
|
+
file and line. Template event handlers surface as edges from a synthetic
|
|
120
|
+
`<Component>.template` node (`@click="save"` → `#App-template, #save`;
|
|
121
|
+
mustapi-validated across three Vue apps, 85/85 SFCs). Honest residuals —
|
|
122
|
+
say them when reporting: component-TAG usage (`<Child/>`) is not a call
|
|
123
|
+
edge; Nuxt auto-imports (unimported `ref`, auto-registered components)
|
|
124
|
+
don't resolve, so those references drop; top-level `<script setup>`
|
|
125
|
+
calls, including `computed(() => …)` bodies, drop exactly like
|
|
126
|
+
module-level calls in plain TS; a failed virtualization falls back to
|
|
127
|
+
plain TS indexing and says so.
|
|
128
|
+
|
|
129
|
+
Vendored source trees explode the job list — next.js's
|
|
130
|
+
`packages/next/src/compiled/` carries ~140 checked-in package.json bundles,
|
|
131
|
+
each becoming its own scip job. Prune them at build time:
|
|
132
|
+
`geml codemap build --root <proj> --exclude "src/compiled/**"` (repeatable;
|
|
133
|
+
the exclusion also keeps their symbols out of the graph).
|
|
134
|
+
|
|
135
|
+
**Locating Joern — never hardcode a path.** Resolve it fresh on each run,
|
|
136
|
+
in this order: (1) `joern` on PATH — if `joern --version` works, use it;
|
|
137
|
+
(2) else read `~/.claude/skills/geml-code-graph/config.json` (`{"joern": "<launcher-or-dir>"}`)
|
|
138
|
+
and pass it as `geml codemap build … --joern <path>` (or export `GEML_JOERN`);
|
|
139
|
+
(3) else ASK the user for the joern-cli location (Windows: the folder unzipped
|
|
140
|
+
from joern-cli.zip; macOS/Linux: the joern-install.sh install dir), WRITE it
|
|
141
|
+
into that JSON file, then reuse it. `<path>` may be the launcher itself or the
|
|
142
|
+
directory holding it (`joern.bat` on Windows, `joern` on unix). Ask at most
|
|
143
|
+
once per machine — after that the JSON answers. Mirrors the CLI's own
|
|
144
|
+
`--joern` / `GEML_JOERN` resolution.
|
|
145
|
+
3. **Build + verify** (also the "更新" path — builds are deterministic,
|
|
146
|
+
only changed documents are rewritten):
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
geml codemap build --adapter scip --raw index.scip --root <proj> \
|
|
150
|
+
--out <proj>/.geml-code-graph --history # --container module|dir|file: match
|
|
151
|
+
# the layout (default dir; flat C repo → file)
|
|
152
|
+
geml codemap verify <proj>/.geml-code-graph # MUST exit 0 before showing anyone
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**First successful build: record the recipe** so `refresh` (and the
|
|
156
|
+
commit hook) can replay it — write `<proj>/.geml-code-graph/_index/refresh.json`
|
|
157
|
+
with the EXACT commands you ran:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{ "root": "..",
|
|
161
|
+
"steps": ["npx --yes @sourcegraph/scip-typescript index --output index.scip",
|
|
162
|
+
"geml codemap build --adapter scip --raw index.scip --root . --out .geml-code-graph --history",
|
|
163
|
+
"geml codemap verify .geml-code-graph"] }
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
From then on, "更新下" = `geml codemap refresh <proj>/.geml-code-graph` (skips
|
|
167
|
+
itself when git HEAD hasn't moved; log at `_index/refresh.log`).
|
|
168
|
+
4. **View — finish with the browser OPEN, not with instructions.**
|
|
169
|
+
|
|
170
|
+
```sh
|
|
171
|
+
geml codemap serve <proj>/.geml-code-graph --background # detached: SURVIVES the agent session;
|
|
172
|
+
# http://localhost:8140, pages render live
|
|
173
|
+
# from .geml — rebuild + F5, never stale.
|
|
174
|
+
# already-running port → reused, not stacked.
|
|
175
|
+
geml codemap serve <proj>/.geml-code-graph --stop # stop it (pid: .geml-code-graph/_index/serve.pid)
|
|
176
|
+
geml codemap render <proj>/.geml-code-graph # serverless alternative: bake .html next to
|
|
177
|
+
# each doc; open file:///…/.geml-code-graph/index.html
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Always `--background` (a viewer must not die with the session). Then open
|
|
181
|
+
it for the user: Windows `start "" <url>` (or `Start-Process <url>`),
|
|
182
|
+
macOS `open <url>`, Linux `xdg-open <url>`. Port taken by something
|
|
183
|
+
else → pick another (`--port`), open that one.
|
|
184
|
+
|
|
185
|
+
`index.html` is the module overview; clicking a module opens its page inside
|
|
186
|
+
the graph area (nested view). Method pages: click = callee chain, ⊕ on an
|
|
187
|
+
entry = full caller chain, breadcrumb walks back up.
|
|
188
|
+
|
|
189
|
+
## Keep it in sync on every commit (optional per-project hook)
|
|
190
|
+
|
|
191
|
+
With the recipe recorded (step 3), a Claude Code PostToolUse hook makes any
|
|
192
|
+
`git commit` Claude runs in that project refresh the codemap in the
|
|
193
|
+
BACKGROUND (never blocks the commit; non-commit commands exit instantly;
|
|
194
|
+
projects without `refresh.json` are silently skipped). Add to the project's
|
|
195
|
+
`.claude/settings.json`:
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{ "hooks": { "PostToolUse": [ { "matcher": "Bash", "hooks": [
|
|
199
|
+
{ "type": "command", "command": "geml codemap refresh .geml-code-graph --hook --commit" }
|
|
200
|
+
] } ] } }
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
(`.geml-code-graph` = the codemap dir relative to the project root; use an absolute
|
|
204
|
+
path if the hook cwd differs.) With `--commit`, the refreshed documents land
|
|
205
|
+
as their own follow-up commit — `chore(codemap): refresh for <sha>`, codemap
|
|
206
|
+
dir only — so the next push carries code + graph together. It is loop-safe
|
|
207
|
+
(the follow-up commit changes no source file, so the refresh it triggers
|
|
208
|
+
skips) and it stands down when HEAD moved during the refresh or a merge is in
|
|
209
|
+
progress. Drop `--commit` to keep the old behavior: refreshed files stay in
|
|
210
|
+
the working tree for you to include in a later commit.
|
|
211
|
+
|
|
212
|
+
Between commits (editing-time sync), `geml codemap serve <dir> --watch`
|
|
213
|
+
re-runs the recipe after 30s of quiet whenever an indexed source file
|
|
214
|
+
changes — pages render live, so a browser reload shows the new graph.
|
|
215
|
+
|
|
216
|
+
Add `--history [-m msg]` to build to snapshot changed documents into
|
|
217
|
+
`.gemlhistory` sidecars — then `geml history get .geml-code-graph/<doc>.geml` shows
|
|
218
|
+
the graph's evolution and `geml revert .geml-code-graph/<doc>.geml '#method' --rev -1`
|
|
219
|
+
rolls one method's edges back. Language maturity tiers and the smoke-test
|
|
220
|
+
gate: [DESIGN-geml-code-graph.md](https://github.com/geml-spec/geml/blob/main/docs/design/specs/geml-codemap/DESIGN-geml-code-graph.md) §3.4. An MCP wrapper exists (`geml mcp --root <dir>`, which serves the four
|
|
221
|
+
read-only `geml_codemap_*` tools next to the document tools when the root holds
|
|
222
|
+
a graph); the CLI path works without it.
|