@mthanh/vespercli 2.0.1 → 2.2.0

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.
Files changed (33) hide show
  1. package/README.md +54 -11
  2. package/assets/CATALOG.md +14 -0
  3. package/assets/claude/.claude-plugin/marketplace.json +21 -15
  4. package/assets/claude/.claude-plugin/plugin.json +1 -1
  5. package/assets/claude/agents/workspace-learner.md +1 -1
  6. package/assets/claude/orchestration.md.tpl +1 -1
  7. package/assets/claude/packs/backend/.claude-plugin/plugin.json +1 -1
  8. package/assets/claude/packs/docs/.claude-plugin/plugin.json +1 -1
  9. package/assets/claude/packs/frontend/.claude-plugin/plugin.json +1 -1
  10. package/assets/claude/packs/graphify/.claude-plugin/plugin.json +10 -0
  11. package/assets/claude/packs/graphify/skills/agent-fundamentals/SKILL.md +89 -0
  12. package/assets/claude/packs/graphify/skills/agent-fundamentals/references/codebase-query.md +95 -0
  13. package/assets/claude/packs/graphify/skills/agent-fundamentals/references/terminal-discipline.md +94 -0
  14. package/assets/claude/packs/graphify/skills/agent-fundamentals/references/tool-usage-patterns.md +67 -0
  15. package/assets/claude/packs/graphify/skills/graphify-research/SKILL.md +101 -0
  16. package/assets/claude/packs/integrations/.claude-plugin/plugin.json +1 -1
  17. package/assets/claude/packs/playwright/.claude-plugin/plugin.json +1 -1
  18. package/assets/claude/packs/polyglot/.claude-plugin/plugin.json +1 -1
  19. package/assets/claude/packs/styles/.claude-plugin/plugin.json +1 -1
  20. package/assets/claude/vesper-orchestrator.md +2 -2
  21. package/assets/copilot/agents/workspace-learner.agent.md +1 -1
  22. package/assets/copilot/packs/graphify/skills/graphify-research/SKILL.md +101 -0
  23. package/assets/hooks/pre-commit +1 -1
  24. package/assets/manifest.json +3 -0
  25. package/assets/opencode/vesper-plugin.js +120 -0
  26. package/dist/cli.min.js +134 -99
  27. package/dist/opencode-render.js +254 -0
  28. package/package.json +2 -2
  29. package/scripts/packs-layout.test.js +23 -2
  30. package/src/mcp/vesper-indexing/dist/indexer/chunker.js +83 -2
  31. package/src/mcp/vesper-indexing/dist/indexer/chunker.js.map +1 -1
  32. package/src/mcp/vesper-indexing/package-lock.json +2 -2
  33. package/src/mcp/vesper-indexing/package.json +1 -1
@@ -0,0 +1,94 @@
1
+ # Terminal Discipline — Expanded Rules
2
+
3
+ Non-negotiable rules for every `Bash` command a subagent executes.
4
+
5
+ ## Timeout Requirements
6
+
7
+ | Command Type | Timeout | Examples |
8
+ | -------------------- | ---------- | ---------------------------------------------------------- |
9
+ | Quick | 15s | `ls`, `cat`, `echo`, `which`, `git status` |
10
+ | Package management | 60s | `npm install`, `pip install`, `brew install` |
11
+ | Build | 120s | `npm run build`, `tsc`, `esbuild`, `webpack` |
12
+ | Test | 300s | `npm test`, `vitest run`, `pytest`, `playwright test` |
13
+ | Default | 30s | Anything not in the above categories |
14
+
15
+ **Every command gets a timeout. No exceptions.**
16
+
17
+ ## Background Processes
18
+
19
+ Commands that never terminate must be run with `run_in_background: true`:
20
+
21
+ - Dev servers: `npm run dev`, `next dev`, `vite dev`
22
+ - Watch modes: `tsc --watch`, `vitest --watch`, `nodemon`
23
+ - Log tailing: `tail -f`, `docker logs -f`
24
+ - Containers: `docker compose up`
25
+ - Any long-running process the subagent doesn't need to wait for
26
+
27
+ After starting a background process, check its output via the harness's background-task output mechanism rather than blocking on it.
28
+
29
+ ## Output Control
30
+
31
+ ```bash
32
+ # Cap output to prevent context overflow
33
+ command | head -30
34
+
35
+ # Capture both stdout and stderr
36
+ command 2>&1 | head -30
37
+
38
+ # Count lines before displaying
39
+ command | wc -l # check size first, then decide head limit
40
+
41
+ # Git: always limit
42
+ git log --oneline -20
43
+ git diff --stat
44
+
45
+ # Grep: files-only when appropriate
46
+ grep -rl "pattern" src/
47
+ ```
48
+
49
+ **Never** let unbounded output flood the context window. If output might exceed 50 lines, pipe through `head`.
50
+
51
+ ## Directory Navigation
52
+
53
+ ```bash
54
+ # ❌ WRONG — breaks permission-prompt matching (matching starts from `cd`)
55
+ cd /some/path && npm run build
56
+
57
+ # ✅ CORRECT — preserves working directory, approval matches `npm`
58
+ pushd /some/path && npm run build && popd
59
+
60
+ # ✅ ALSO CORRECT — if already in the right directory
61
+ npm run build
62
+ ```
63
+
64
+ The `cd && command` pattern breaks command-matching for permission prompts because matching starts from `cd`, not the actual command. Always use `pushd`/`popd` for temporary directory changes.
65
+
66
+ ## Runtime Version Guard (Node/nvm)
67
+
68
+ Projects that pin a Node version via nvm expect every command to run under that version — running under whatever `node` happens to be on PATH can produce misleading build/test failures or silently wrong behavior.
69
+
70
+ Before running any Node-based command (`npm`, `npx`, `yarn`, `pnpm`, `node`, or project scripts that wrap them):
71
+
72
+ 1. Check the project root for `.nvmrc` or `.node-version`.
73
+ 2. If found, chain `nvm use` into the **same** `Bash` call as the actual command — shell state (including nvm's shell function) does not persist between separate tool calls:
74
+ ```bash
75
+ nvm use > /dev/null 2>&1; npm test
76
+ ```
77
+ 3. If `nvm` isn't installed, no version file exists, or the switch fails — proceed anyway with whatever `node` is on PATH. **Never block the task on this.**
78
+
79
+ Do not run `nvm use` as its own command and expect it to carry over to the next one — it won't.
80
+
81
+ ## curl Rules
82
+
83
+ - Always set `--max-time 5` to prevent hanging on unresponsive hosts
84
+ - Localhost health checks only without explicit user approval
85
+ - Never curl external URLs without user awareness
86
+
87
+ ## Timeout Recovery
88
+
89
+ When a command times out:
90
+
91
+ 1. **Diagnose first** — `ps aux | grep <process>` to check if it's still running
92
+ 2. **Report to user** — Explain what happened and why
93
+ 3. **Don't blindly retry** — The same command will likely time out again
94
+ 4. **Suggest alternatives** — Smaller scope, different approach, or user intervention
@@ -0,0 +1,67 @@
1
+ # Tool Usage Patterns
2
+
3
+ Comprehensive guide for when to use which tools. Built-in tools trump `Bash` in every case where they're available.
4
+
5
+ ## File Reading
6
+
7
+ | Scenario | Tool | Notes |
8
+ | --------------------------------- | ------------------ | -------------------------------------------- |
9
+ | Read known file | `Read` | Specify `offset`/`limit` to limit token usage |
10
+ | Read file overview | `Grep` | Search for key patterns in the file |
11
+ | Read file existence | `Glob` | Glob pattern matching |
12
+ | Read directory listing | `Glob`/`Bash` (`ls`) | Lists immediate children |
13
+
14
+ **Anti-patterns:**
15
+ - `cat file.txt` — use `Read`
16
+ - `less file.txt` — blocks terminal, use `Read`
17
+ - `head -n 50 file.txt` — use `Read` with a line range
18
+ - `node -e "console.log(fs.readFileSync(...))"` — never
19
+
20
+ ## Code Search
21
+
22
+ | Scenario | Tool | Notes |
23
+ | ---------------------------------- | ------------------------------------------------- | ---------------------------------------------------- |
24
+ | Primary codebase search | `mcp__vesper-indexing__vesper_search` | Full-text, returns snippets with paths + line ranges |
25
+ | Find symbols (functions/classes)| `mcp__vesper-indexing__vesper_symbols` | Name-based lookup with locations |
26
+ | Exact text / regex match | `Grep` | Fast, supports regex, glob filters |
27
+ | Find files by name/glob | `Glob` | Filename/path patterns only |
28
+
29
+ **Anti-patterns:**
30
+ - `grep -r "pattern" .` — use `Grep`
31
+ - `find . -name "*.ts" -exec grep` — use `Glob` + `Grep`
32
+ - `rg "pattern"` — use `Grep`
33
+ - `tree` / `ls -R` — use `Glob`
34
+
35
+ ## File Writing
36
+
37
+ | Scenario | Tool | Notes |
38
+ | ----------------------------- | -------------------------- | ---------------------------------------- |
39
+ | Create new file | `Write` | Creates parent directories |
40
+ | Edit existing file | `Edit` | Include enough surrounding context to make the match unique |
41
+ | Multiple identical edits | `Edit` with `replace_all` | Batch identical replacements in one call |
42
+ | Rename / move file | `Bash` + `mv` | Only case where terminal is correct |
43
+
44
+ **Anti-patterns:**
45
+ - `echo "content" > file.txt` — use `Write`
46
+ - `python -c "open('f','w').write('...')"` — never
47
+ - `node -e "fs.writeFileSync(...)"` — never
48
+ - `sed -i 's/old/new/g' file` — use `Edit`
49
+ - `perl -pi -e 's/old/new/'` — use `Edit`
50
+
51
+ ## Error Checking
52
+
53
+ | Scenario | Tool | Notes |
54
+ | ------------------------ | --------------- | --------------------------------------------- |
55
+ | Compile/lint errors | `Bash` (project's own lint/typecheck script) | No dedicated "get errors" tool — run what CI runs |
56
+ | Test failures | `Bash` | For test execution |
57
+
58
+ ## When Terminal (`Bash`) IS Correct
59
+
60
+ Terminal is the right tool for:
61
+ - Running tests (`npm test`, `vitest run`, `pytest`)
62
+ - Package management (`npm install`, `pip install`)
63
+ - Build commands (`npm run build`)
64
+ - Git operations (`git status`, `git diff`)
65
+ - Starting dev servers (with `run_in_background: true`)
66
+ - Moving/renaming files (`mv`)
67
+ - Operations that have no built-in tool equivalent
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: graphify-research
3
+ description: Code knowledge graph for research-side roles (task-researcher, se-system-architecture-reviewer) when the opt-in graphify pack is enabled. Routes impact ("who calls X"), path ("how does A reach B"), architecture and onboarding questions to the graphify MCP tools (query_graph, get_node, get_neighbors, shortest_path, get_pr_impact) and keeps locate/edit questions on vesper_search / vesper_symbols. Covers qualified node names, the EXTRACTED / INFERRED / AMBIGUOUS edge tags, why GRAPH_REPORT.md is never read whole, MCP tools over the CLI, and the fallback when the server or graph is absent.
4
+ ---
5
+
6
+ # graphify research
7
+
8
+ The `graphify` pack gives the **research-side roles** a code knowledge graph built by
9
+ [graphify](https://github.com/Graphify-Labs/graphify) (an external tool; PyPI `graphifyy`, CLI
10
+ `graphify`). It complements vesper-indexing, it does not replace it: `vesper_search` /
11
+ `vesper_symbols` remain the default codebase tool for every role, and developers, testers and
12
+ reviewers keep using them unchanged. This skill is loaded only by `task-researcher` and
13
+ `se-system-architecture-reviewer`, and only when the Team Roster lists it (the pack is enabled).
14
+
15
+ ## When the graph beats the index (measured 2026-10-01, ~25k-LOC repo)
16
+
17
+ | Question shape | Use | Why |
18
+ | :-- | :-- | :-- |
19
+ | **Locate** — "where is `decideHostAction` defined", "which file holds the gitignore writer" | `vesper_search` / `vesper_symbols` | Tie with the graph; the index is already loaded and returns the snippet you edit |
20
+ | **Edit** — anything that ends in a code change | `vesper_search` / `vesper_symbols` | The developer roles never use the graph |
21
+ | **Impact** — "who calls `decideHostAction`", "what breaks if `packs.js` changes" | graphify `get_neighbors` / `get_pr_impact` | ~40% fewer tokens AND complete — FTS missed callers inside test callbacks |
22
+ | **Path** — "how does `utils/packs.js` reach `opencode-overlay.js`" | graphify `shortest_path` | 10–25× fewer tokens than reading files along the way |
23
+ | **Architecture / onboarding** — "what are the modules and how do they cluster", "what is central here" | graphify `query_graph` (communities, god nodes) | One query instead of a directory traversal |
24
+
25
+ Rule of thumb: a question whose answer is a **set of relationships** goes to the graph; a question
26
+ whose answer is **a location or a snippet** goes to the index. When unsure, ask the index first
27
+ (cheap, always present) and escalate to the graph for the relationship part.
28
+
29
+ ## Always pass QUALIFIED node names
30
+
31
+ graphify's name matching is **fuzzy**: an unqualified `packs.js` resolved to `packs.test.js`.
32
+ Every tool call names nodes by their **path-qualified** form:
33
+
34
+ - a file: `src/cli/utils/packs.js` (never `packs.js`);
35
+ - a symbol: `src/cli/utils/packs.js#enablePack` (file `#` symbol), or the `source_file` +
36
+ `label` pair `get_node` returns.
37
+
38
+ Resolve the qualified name once with `get_node` (or with `vesper_symbols`) before an impact or path
39
+ query, and quote it back in your report so the reader can repeat the query.
40
+
41
+ ## Use the MCP server, not the CLI
42
+
43
+ The `graphify` CLI reloads `graphify-out/graph.json` (tens of MB) on every call (~2 s each). The
44
+ pack registers the MCP server `graphify` (`vespercli mcp serve graphify`, which runs
45
+ `python -m graphify.serve graphify-out/graph.json` under the interpreter graphify recorded in
46
+ `graphify-out/.graphify_python`), which keeps the graph resident — its tools are the ones to call:
47
+
48
+ | Tool | Use |
49
+ | :-- | :-- |
50
+ | `query_graph` | free-form question over the graph (communities, central nodes, "how does X relate to Y") |
51
+ | `get_node` | one node's attributes (`label`, `source_file`, `source_location`, `community`) — the name resolver |
52
+ | `get_neighbors` | the edges around a node: callers, callees, imports — the impact tool |
53
+ | `shortest_path` | the chain from A to B — the path tool |
54
+ | `get_pr_impact` | the nodes a change set touches and what depends on them |
55
+
56
+ Never shell out to `graphify query` / `graphify path` from an agent when the server is available.
57
+ The one CLI command you may name (never run yourself unless asked) is `graphify update .`, which
58
+ builds or refreshes the graph.
59
+
60
+ ## Read the edge tags
61
+
62
+ Every edge carries a confidence tag. Weight your conclusions by it and say which tag backs each
63
+ claim:
64
+
65
+ - **EXTRACTED** — from the code via tree-sitter. Trust it.
66
+ - **INFERRED** — LLM-derived. Verify before acting on it: confirm with `get_node` on both ends or
67
+ a `vesper_search` for the relationship.
68
+ - **AMBIGUOUS** — an unresolved name (several candidates). Confirm with `vesper_search` /
69
+ `vesper_symbols` which candidate the code really means before you report it.
70
+
71
+ ## Never read GRAPH_REPORT.md whole
72
+
73
+ `graphify-out/GRAPH_REPORT.md` can be hundreds of KB. Query the specific node or section you need
74
+ (`get_node`, `get_community`, or a targeted `vesper_search` over the report for one heading) —
75
+ never load the file into context.
76
+
77
+ ## Cost and data flow
78
+
79
+ - Code never leaves the machine: a code-only `graphify update .` is tree-sitter extraction, **0
80
+ tokens**, ~35 s on a ~25k-LOC repo (tens of thousands of nodes/edges, graph.json ~56 MB).
81
+ - Doc/PDF extraction and community **labels** call an LLM. `--no-label` skips the labels; a
82
+ code-only run calls nothing.
83
+ - `graphify-out/graph.json` is **committed and shared** (`vespercli packs enable graphify` writes
84
+ the `.gitignore` block that ignores the rest of `graphify-out/`); the Vesper post-checkout hook
85
+ runs `graphify update .` after a branch checkout when the tool is on PATH, so the graph follows
86
+ the branch. `vespercli doctor` reports a graph built from a commit other than HEAD
87
+ (`graphify-stale`).
88
+
89
+ ## When the server or the graph is absent
90
+
91
+ If the `graphify` MCP tools are unavailable, or a call reports that `graphify-out/graph.json` does
92
+ not exist or is stale:
93
+
94
+ 1. **Fall back to `vesper_search` / `vesper_symbols`** for the whole question — answer it the way
95
+ the roles always have; do not stop, do not ask the user to install anything mid-task.
96
+ 2. In your report, add one line: "graphify graph unavailable — answered from vesper-indexing; run
97
+ `graphify update .` (tool: `uv tool install graphifyy`, or `pipx install graphifyy`, or
98
+ `python3 -m pip install --user graphifyy`) to build the graph", and, for an impact
99
+ question, note that FTS may miss callers inside callbacks.
100
+ 3. Never run `graphify update .`, `graphify install` or any graphify LLM path yourself unless the
101
+ user asks for exactly that.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vesper-integrations",
3
3
  "displayName": "Vesper integrations pack",
4
- "version": "2.0.1",
4
+ "version": "2.2.0",
5
5
  "description": "Vesper integrations pack — ClickUp task-board specialist, the GitLab MR guardrails skill, and the third-party GitLab CI skills (gitlab-ci-skill, github-actions-to-gitlab-ci; UPSTREAM.json beside each). Enabled per project by `vespercli packs sync` when the Team Roster names one of its assets.",
6
6
  "author": {
7
7
  "name": "Vesper Maintainers"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vesper-playwright",
3
3
  "displayName": "Vesper playwright pack",
4
- "version": "2.0.1",
4
+ "version": "2.2.0",
5
5
  "description": "Vesper playwright pack — Playwright end-to-end testing specialists and skills. Enabled per project by `vespercli packs sync` when the Team Roster names one of its assets.",
6
6
  "author": {
7
7
  "name": "Vesper Maintainers"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vesper-polyglot",
3
3
  "displayName": "Vesper polyglot pack",
4
- "version": "2.0.1",
4
+ "version": "2.2.0",
5
5
  "description": "Vesper polyglot pack — Java, Flutter and Google ADK (Python) skills. Enabled per project by `vespercli packs sync` when the Team Roster names one of its assets.",
6
6
  "author": {
7
7
  "name": "Vesper Maintainers"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vesper-styles",
3
3
  "displayName": "Vesper styles pack",
4
- "version": "2.0.1",
4
+ "version": "2.2.0",
5
5
  "description": "opt-in output styles: i-have-adhd, ponytail. Third-party skills (MIT) shipped as pack content, never written into a project; `vespercli packs enable styles` is the consent and writes the always-on pointer rule that applies them.",
6
6
  "author": {
7
7
  "name": "Vesper Maintainers"
@@ -56,7 +56,7 @@ For verified trivial edits, Vesper MAY perform the change directly instead of de
56
56
  - If no roster yet, or no role matches a task → delegate to `vesper:workspace-learner`.
57
57
  - **Namespaced delegation names (correctness invariant, not style):** every specialist is a plugin agent exposed under the `vesper:` namespace. Delegate to them by their **`vesper:<name>`** form (e.g. `vesper:code-reviewer`, `vesper:task-researcher`, `vesper:principal-software-engineer`). The Team Roster's "Agent Name" column carries these namespaced names verbatim — use them exactly as written. If a roster entry names a catalog specialist **without** the namespace (an older roster not yet revalidated), delegate to `vesper:<name>` anyway — unless that name is a project agent in `.claude/agents/`, which resolves bare — and suggest `/revalidate-team`. The ONE non-namespaced name is the committed main-thread persona `vesper-orchestrator` (the `.claude/settings.json` `agent` key value); it is a project agent, not a plugin agent, so it carries no namespace. The four maintenance skills are committed project skills invoked **unqualified** (`/reinit`, `/compact-knowledge`, `/revalidate-knowledge`, `/revalidate-team`) — never `/vesper:reinit`.
58
58
 
59
- - **Packs (core + packs):** the catalog is a **core** plugin (`vesper`) plus stack **packs** — separate plugins in the same marketplace (`vesper-frontend`, `vesper-playwright`, `vesper-backend`, `vesper-polyglot`, `vesper-docs`, `vesper-integrations`, the opt-in `vesper-styles`), enabled per project when the Team Roster names one of their assets. A packed specialist resolves as **`vesper-<pack>:<name>`** (e.g. `vesper-frontend:expert-react-frontend-engineer`, `vesper-playwright:playwright-tester`) and the roster carries that namespaced name verbatim; a packed skill is `/vesper-<pack>:<name>`. A roster row naming an asset of a pack that is **not enabled** here is a `vespercli doctor` finding (`pack-not-enabled`) — the fix is `vespercli packs sync`, which enables every pack the roster needs (it never disables). `init` and `update` run the same sync; `vespercli packs` lists what is shipped, enabled and needed.
59
+ - **Packs (core + packs):** the catalog is a **core** plugin (`vesper`) plus stack **packs** — separate plugins in the same marketplace (`vesper-frontend`, `vesper-playwright`, `vesper-backend`, `vesper-polyglot`, `vesper-docs`, `vesper-integrations`, the opt-in `vesper-styles`), enabled per project when the Team Roster names one of their assets. A packed specialist resolves as **`vesper-<pack>:<name>`** (e.g. `vesper-frontend:expert-react-frontend-engineer`, `vesper-playwright:playwright-tester`) and the roster carries that namespaced name verbatim; a packed skill is `/vesper-<pack>:<name>`. A roster row naming an asset of a pack that is **not enabled** here is a `vespercli doctor` finding (`pack-not-enabled`) — the fix is `vespercli packs sync`, which enables every pack the roster needs (it never disables). `init` and `update` run the same sync; `vespercli packs` lists what is shipped, enabled and needed. On OpenCode the same namespaced names resolve through the Vesper plugin.
60
60
 
61
61
  **What you MAY read** (routing files only — to decide WHO to delegate to, never to do the work yourself):
62
62
 
@@ -179,7 +179,7 @@ off your own inference about the host.
179
179
  - Because of direct collaboration, a `NEED_HELP` you receive means the subagent genuinely couldn't resolve it — its `Agent` tool was missing, the depth budget was spent, or the need is a full pipeline step (which is yours to route, never theirs to run). Route it per the roster and re-delegate with the answer.
180
180
  - Multiple `Agent` calls can run in parallel in a single turn — use this for independent tracks (see Orchestration Loop below). Nested spawns by your delegates count toward overall load: when delegating to agents likely to collaborate (planner, TPM, feature-wrap), keep your own parallel fan-out modest (≤ 3).
181
181
 
182
- **Codebase context**: Subagents MUST use `vesper-indexing` MCP tools (`mcp__vesper-indexing__vesper_search`, `mcp__vesper-indexing__vesper_symbols`) for codebase queries. If MCP is unavailable, STOP and ask the user — do NOT silently fall back. Avoid full directory traversals. If the index is empty or stale, delegate to `workspace-learner` to reindex.
182
+ **Codebase context**: Subagents MUST use `vesper-indexing` MCP tools (`mcp__vesper-indexing__vesper_search`, `mcp__vesper-indexing__vesper_symbols`) for codebase queries. If MCP is unavailable, STOP and ask the user — do NOT silently fall back. Avoid full directory traversals. If the index is empty or stale, delegate to `workspace-learner` to reindex. When the `graphify` pack is enabled, research-side roles additionally use the graphify MCP tools (`graphify-research` skill) for impact, path and architecture questions.
183
183
 
184
184
  **Delegation prompt framing**: When referencing codebase artifacts in delegation prompts, describe **what to investigate** (concepts, patterns, behaviors), not **which files to read**. Subagents own their own discovery workflow via vesper-indexing. File paths may appear as hints, never as read commands.
185
185
 
@@ -276,7 +276,7 @@ Before assuming any subagent is available, check whether `~/.vesper/skills/vespe
276
276
 
277
277
  Run `vesper list --target claude` via `Bash` and capture the full output. This lists every subagent, command, and skill currently installed in the plugin catalog.
278
278
 
279
- **Packs.** The catalog is a **core** plugin plus stack **packs** (`frontend`, `playwright`, `backend`, `polyglot`, `docs`, `integrations`), and a pack is enabled per project only when the roster needs it — so the installed listing may show a pack as *disabled*. Pick from the **whole catalog index** regardless: run `vespercli catalog` (the shipped `CATALOG.md`; always complete) and read each entry's `Pack:` line. A packed specialist is delegated to as `<name>`; a packed skill is `<name>`. You never enable a pack yourself — the orchestrator runs `vespercli packs sync` after you hand off (final step below).
279
+ **Packs.** The catalog is a **core** plugin plus stack **packs** (`frontend`, `playwright`, `backend`, `polyglot`, `docs`, `integrations`), and a pack is enabled per project only when the roster needs it — so the installed listing may show a pack as *disabled*. Pick from the **whole catalog index** regardless: run `vespercli catalog` (the shipped `CATALOG.md`; always complete) and read each entry's `Pack:` line. A packed specialist is delegated to as `<name>`; a packed skill is `<name>`. You never enable a pack yourself — the orchestrator runs `vespercli packs sync` after you hand off (final step below). Two packs are **opt-in** and never enabled by the sync: `styles` and `graphify`. When `vespercli packs` shows `graphify` as *enabled*, add the `graphify-research` skill to the Assigned Skills of the Research (`task-researcher`) and Architecture (`se-system-architecture-reviewer`) rows only — developers, testers and reviewers keep `vesper-indexing` as their codebase tool; when it is disabled, do not list the skill anywhere.
280
280
 
281
281
  #### Step 2 — Match Team Composition to Catalog
282
282
 
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: graphify-research
3
+ description: Code knowledge graph for research-side roles (task-researcher, se-system-architecture-reviewer) when the opt-in graphify pack is enabled. Routes impact ("who calls X"), path ("how does A reach B"), architecture and onboarding questions to the graphify MCP tools (query_graph, get_node, get_neighbors, shortest_path, get_pr_impact) and keeps locate/edit questions on vesper_search / vesper_symbols. Covers qualified node names, the EXTRACTED / INFERRED / AMBIGUOUS edge tags, why GRAPH_REPORT.md is never read whole, MCP tools over the CLI, and the fallback when the server or graph is absent.
4
+ ---
5
+
6
+ # graphify research
7
+
8
+ The `graphify` pack gives the **research-side roles** a code knowledge graph built by
9
+ [graphify](https://github.com/Graphify-Labs/graphify) (an external tool; PyPI `graphifyy`, CLI
10
+ `graphify`). It complements vesper-indexing, it does not replace it: `vesper_search` /
11
+ `vesper_symbols` remain the default codebase tool for every role, and developers, testers and
12
+ reviewers keep using them unchanged. This skill is loaded only by `task-researcher` and
13
+ `se-system-architecture-reviewer`, and only when the Team Roster lists it (the pack is enabled).
14
+
15
+ ## When the graph beats the index (measured 2026-10-01, ~25k-LOC repo)
16
+
17
+ | Question shape | Use | Why |
18
+ | :-- | :-- | :-- |
19
+ | **Locate** — "where is `decideHostAction` defined", "which file holds the gitignore writer" | `vesper_search` / `vesper_symbols` | Tie with the graph; the index is already loaded and returns the snippet you edit |
20
+ | **Edit** — anything that ends in a code change | `vesper_search` / `vesper_symbols` | The developer roles never use the graph |
21
+ | **Impact** — "who calls `decideHostAction`", "what breaks if `packs.js` changes" | graphify `get_neighbors` / `get_pr_impact` | ~40% fewer tokens AND complete — FTS missed callers inside test callbacks |
22
+ | **Path** — "how does `utils/packs.js` reach `opencode-overlay.js`" | graphify `shortest_path` | 10–25× fewer tokens than reading files along the way |
23
+ | **Architecture / onboarding** — "what are the modules and how do they cluster", "what is central here" | graphify `query_graph` (communities, god nodes) | One query instead of a directory traversal |
24
+
25
+ Rule of thumb: a question whose answer is a **set of relationships** goes to the graph; a question
26
+ whose answer is **a location or a snippet** goes to the index. When unsure, ask the index first
27
+ (cheap, always present) and escalate to the graph for the relationship part.
28
+
29
+ ## Always pass QUALIFIED node names
30
+
31
+ graphify's name matching is **fuzzy**: an unqualified `packs.js` resolved to `packs.test.js`.
32
+ Every tool call names nodes by their **path-qualified** form:
33
+
34
+ - a file: `src/cli/utils/packs.js` (never `packs.js`);
35
+ - a symbol: `src/cli/utils/packs.js#enablePack` (file `#` symbol), or the `source_file` +
36
+ `label` pair `get_node` returns.
37
+
38
+ Resolve the qualified name once with `get_node` (or with `vesper_symbols`) before an impact or path
39
+ query, and quote it back in your report so the reader can repeat the query.
40
+
41
+ ## Use the MCP server, not the CLI
42
+
43
+ The `graphify` CLI reloads `graphify-out/graph.json` (tens of MB) on every call (~2 s each). The
44
+ pack registers the MCP server `graphify` (`vespercli mcp serve graphify`, which runs
45
+ `python -m graphify.serve graphify-out/graph.json` under the interpreter graphify recorded in
46
+ `graphify-out/.graphify_python`), which keeps the graph resident — its tools are the ones to call:
47
+
48
+ | Tool | Use |
49
+ | :-- | :-- |
50
+ | `query_graph` | free-form question over the graph (communities, central nodes, "how does X relate to Y") |
51
+ | `get_node` | one node's attributes (`label`, `source_file`, `source_location`, `community`) — the name resolver |
52
+ | `get_neighbors` | the edges around a node: callers, callees, imports — the impact tool |
53
+ | `shortest_path` | the chain from A to B — the path tool |
54
+ | `get_pr_impact` | the nodes a change set touches and what depends on them |
55
+
56
+ Never shell out to `graphify query` / `graphify path` from an agent when the server is available.
57
+ The one CLI command you may name (never run yourself unless asked) is `graphify update .`, which
58
+ builds or refreshes the graph.
59
+
60
+ ## Read the edge tags
61
+
62
+ Every edge carries a confidence tag. Weight your conclusions by it and say which tag backs each
63
+ claim:
64
+
65
+ - **EXTRACTED** — from the code via tree-sitter. Trust it.
66
+ - **INFERRED** — LLM-derived. Verify before acting on it: confirm with `get_node` on both ends or
67
+ a `vesper_search` for the relationship.
68
+ - **AMBIGUOUS** — an unresolved name (several candidates). Confirm with `vesper_search` /
69
+ `vesper_symbols` which candidate the code really means before you report it.
70
+
71
+ ## Never read GRAPH_REPORT.md whole
72
+
73
+ `graphify-out/GRAPH_REPORT.md` can be hundreds of KB. Query the specific node or section you need
74
+ (`get_node`, `get_community`, or a targeted `vesper_search` over the report for one heading) —
75
+ never load the file into context.
76
+
77
+ ## Cost and data flow
78
+
79
+ - Code never leaves the machine: a code-only `graphify update .` is tree-sitter extraction, **0
80
+ tokens**, ~35 s on a ~25k-LOC repo (tens of thousands of nodes/edges, graph.json ~56 MB).
81
+ - Doc/PDF extraction and community **labels** call an LLM. `--no-label` skips the labels; a
82
+ code-only run calls nothing.
83
+ - `graphify-out/graph.json` is **committed and shared** (`vespercli packs enable graphify` writes
84
+ the `.gitignore` block that ignores the rest of `graphify-out/`); the Vesper post-checkout hook
85
+ runs `graphify update .` after a branch checkout when the tool is on PATH, so the graph follows
86
+ the branch. `vespercli doctor` reports a graph built from a commit other than HEAD
87
+ (`graphify-stale`).
88
+
89
+ ## When the server or the graph is absent
90
+
91
+ If the `graphify` MCP tools are unavailable, or a call reports that `graphify-out/graph.json` does
92
+ not exist or is stale:
93
+
94
+ 1. **Fall back to `vesper_search` / `vesper_symbols`** for the whole question — answer it the way
95
+ the roles always have; do not stop, do not ask the user to install anything mid-task.
96
+ 2. In your report, add one line: "graphify graph unavailable — answered from vesper-indexing; run
97
+ `graphify update .` (tool: `uv tool install graphifyy`, or `pipx install graphifyy`, or
98
+ `python3 -m pip install --user graphifyy`) to build the graph", and, for an impact
99
+ question, note that FTS may miss callers inside callbacks.
100
+ 3. Never run `graphify update .`, `graphify install` or any graphify LLM path yourself unless the
101
+ user asks for exactly that.
@@ -28,7 +28,7 @@ if [ "$(git config --type=bool --get vesper.hooks 2>/dev/null)" != "false" ]; th
28
28
  # No `exit 0` inside this block: it may sit inside a user's or a hook manager's
29
29
  # hook with lines after it, and those must always run. Only the mr.json refusal
30
30
  # exits (non-zero, to abort the commit on purpose).
31
- vesper_git_paths=".claude/vesper/context .claude/agents .claude/commands .claude/skills .claude/rules .vesper/skills .vesper/instructions .vesper/agents .vesper/context .vesper/.tracking .vesper/memory.jsonl .vesper/gates.json CLAUDE.md"
31
+ vesper_git_paths=".claude/vesper/context .claude/agents .claude/commands .claude/skills .claude/rules .vesper/skills .vesper/instructions .vesper/agents .vesper/context .vesper/.tracking .vesper/memory.jsonl .vesper/gates.json CLAUDE.md opencode.json .opencode"
32
32
 
33
33
  vesper_staged=$(git diff --cached --name-only)
34
34
 
@@ -5,6 +5,7 @@
5
5
  "backend",
6
6
  "docs",
7
7
  "frontend",
8
+ "graphify",
8
9
  "integrations",
9
10
  "playwright",
10
11
  "polyglot",
@@ -82,6 +83,7 @@
82
83
  "gitlab-ci-skill",
83
84
  "gitlab-guardrails",
84
85
  "google-adk-python",
86
+ "graphify-research",
85
87
  "i-have-adhd",
86
88
  "java",
87
89
  "mcp-builder",
@@ -170,6 +172,7 @@
170
172
  "gitlab-ci-skill": "integrations",
171
173
  "gitlab-guardrails": "integrations",
172
174
  "google-adk-python": "polyglot",
175
+ "graphify-research": "graphify",
173
176
  "i-have-adhd": "styles",
174
177
  "java": "polyglot",
175
178
  "mcp-builder": "core",
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Vesper OpenCode plugin — TEMPLATE. `vespercli init --host claude` copies it to
3
+ * `.opencode/plugins/vesper.js` (committed). Plain ESM, no dependencies, runs under Bun.
4
+ *
5
+ * What it does (every `vespercli` call is guarded; nothing here ever throws into OpenCode):
6
+ * 1. Locates the installed package: `vespercli --print-assets-dir` (fallback: `VESPER_ASSETS_DIR`).
7
+ * 2. Reads Vesper's keys in `opencode.json`: `vesper.packs` (the enabled packs) and
8
+ * `vesper.agentsMode` (`"plugin"` default | `"files"`).
9
+ * 3. `config` hook (plugin mode): registers every core + enabled-pack specialist as an OpenCode
10
+ * subagent (`vesper:<name>` / `vesper-<pack>:<name>`) by adding them to the loaded config's
11
+ * `agent` map — converted from the Claude catalog by the package's own renderer
12
+ * (`dist/opencode-render.js`), never a second converter here.
13
+ * 4. `session.created` → `vespercli guard --running-host opencode`; a `VESPER_HOST_MISMATCH` on
14
+ * its stdout is shown as a TUI toast and appended to the session's first message so the
15
+ * playbook's host-mismatch warning fires.
16
+ * 5. `tool.execute.after` on the `task` tool → `vespercli log delegated` (run-ledger enrichment).
17
+ *
18
+ * OpenCode loads every export of this module as a plugin function, so this file exports exactly one.
19
+ */
20
+ import { execFileSync } from 'node:child_process';
21
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
22
+ import { join, resolve } from 'node:path';
23
+ import { pathToFileURL } from 'node:url';
24
+
25
+ const TOKEN = 'VESPER_HOST_MISMATCH';
26
+
27
+ function vespercli(args, cwd) {
28
+ try {
29
+ const stdout = execFileSync('vespercli', args, { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 60000 });
30
+ return { ok: true, stdout: String(stdout) };
31
+ } catch (err) {
32
+ return { ok: false, stdout: String(err?.stdout || ''), stderr: String(err?.stderr || err?.message || '') };
33
+ }
34
+ }
35
+
36
+ function readVesperSettings(dir) {
37
+ const out = { packs: [], agentsMode: 'plugin' };
38
+ try {
39
+ const v = JSON.parse(readFileSync(join(dir, 'opencode.json'), 'utf-8'))?.vesper;
40
+ if (Array.isArray(v?.packs)) out.packs = v.packs.filter((p) => typeof p === 'string');
41
+ if (v?.agentsMode === 'files') out.agentsMode = 'files';
42
+ } catch { /* no opencode.json / no vesper key → defaults */ }
43
+ return out;
44
+ }
45
+
46
+ function agentFiles(dir, plugin) {
47
+ try {
48
+ return readdirSync(dir).filter((f) => f.endsWith('.md')).sort().map((f) => ({ name: f.slice(0, -3), plugin, path: join(dir, f) }));
49
+ } catch { return []; }
50
+ }
51
+
52
+ async function loadRenderer(assetsDir) {
53
+ const root = resolve(assetsDir, '..');
54
+ for (const rel of ['dist/opencode-render.js', 'src/cli/utils/opencode-render.js']) {
55
+ const abs = join(root, rel);
56
+ if (existsSync(abs)) return import(pathToFileURL(abs).href);
57
+ }
58
+ return null;
59
+ }
60
+
61
+ export const VesperPlugin = async ({ client, directory, worktree }) => {
62
+ const dir = directory || worktree || process.cwd();
63
+ const settings = readVesperSettings(dir);
64
+ const log = async (level, message) => {
65
+ try { await client?.app?.log?.({ body: { service: 'vesper', level, message } }); } catch { /* best effort */ }
66
+ };
67
+ const available = vespercli(['--version'], dir).ok;
68
+ const located = available ? vespercli(['--print-assets-dir'], dir) : { ok: false };
69
+ const assetsDir = located.ok ? located.stdout.trim() : (process.env.VESPER_ASSETS_DIR || '');
70
+ const render = assetsDir ? await loadRenderer(assetsDir).catch(() => null) : null;
71
+ if (settings.agentsMode === 'plugin' && !render) {
72
+ await log('warn', 'Vesper: the @mthanh/vespercli package was not found (vespercli on PATH, or VESPER_ASSETS_DIR) — specialists not registered. Install it, or run `vespercli opencode render-agents` and set `vesper.agentsMode: "files"` in opencode.json.');
73
+ }
74
+ const pending = new Map(); // sessionID → the mismatch banner to append once
75
+
76
+ return {
77
+ config: async (config) => {
78
+ if (settings.agentsMode !== 'plugin' || !render) return;
79
+ config.agent ??= {};
80
+ const sources = agentFiles(join(assetsDir, 'claude', 'agents'), 'vesper');
81
+ for (const pack of settings.packs) sources.push(...agentFiles(join(assetsDir, 'claude', 'packs', pack, 'agents'), `vesper-${pack}`));
82
+ for (const src of sources) {
83
+ try {
84
+ const { frontmatter, body } = render.parseAgentMarkdown(readFileSync(src.path, 'utf-8'));
85
+ const { id, config: agent } = render.convertClaudeAgent({ name: src.name, frontmatter, body, plugin: src.plugin });
86
+ if (!config.agent[id]) config.agent[id] = agent;
87
+ } catch (err) { await log('warn', `Vesper: could not register ${src.plugin}:${src.name}: ${err?.message || err}`); }
88
+ }
89
+ },
90
+
91
+ event: async ({ event }) => {
92
+ if (event?.type !== 'session.created' || !available) return;
93
+ const info = event.properties?.info;
94
+ if (info?.parentID) return; // a subagent session — the guard ran for its parent
95
+ const res = vespercli(['guard', '--running-host', 'opencode', '--path', dir], dir);
96
+ const lines = res.stdout.split('\n');
97
+ const at = lines.indexOf(TOKEN);
98
+ if (at === -1) return;
99
+ const banner = lines.slice(at).filter(Boolean).join('\n');
100
+ pending.set(info?.id ?? '*', banner);
101
+ try { await client?.tui?.showToast?.({ body: { title: 'Vesper', message: lines[at + 1] || TOKEN, variant: 'warning' } }); } catch { /* headless */ }
102
+ },
103
+
104
+ 'chat.message': async (input, output) => {
105
+ const banner = pending.get(input?.sessionID) ?? pending.get('*');
106
+ if (!banner) return;
107
+ pending.delete(input?.sessionID); pending.delete('*');
108
+ const part = (output?.parts || []).find((p) => p?.type === 'text' && typeof p.text === 'string');
109
+ if (part) part.text = `${part.text}\n\n${banner}`;
110
+ },
111
+
112
+ 'tool.execute.after': async (input) => {
113
+ if (input?.tool !== 'task' || !available) return;
114
+ const agent = input.args?.subagent_type || input.args?.agent || 'unknown';
115
+ vespercli(['log', 'delegated', '--pipeline', 'hook', '--agent', String(agent), '--note', 'opencode', '--session', String(input.sessionID || ''), '--path', dir], dir);
116
+ },
117
+ };
118
+ };
119
+
120
+ export default VesperPlugin;