diff --git a/docs/features/user-plugins/authoring.md b/docs/features/user-plugins/authoring.md index 1c928d308..fa9711802 100644 --- a/docs/features/user-plugins/authoring.md +++ b/docs/features/user-plugins/authoring.md @@ -22,6 +22,10 @@ For anonymous web search and page fetching, see uses Streamable HTTP with no account or API key required. Package it as a small ZIP and explicitly enable it after installation; anonymous service rate limits apply. +For local code-entity search and structural diffs, see +[Sem](../../../examples/user-plugins/sem/README.md). This optional stdio example asks for an explicit +Git repository path and keeps normal tool approvals; its validation tools can execute repository commands. + ## A complete skills-and-hooks package ```text diff --git a/examples/user-plugins/sem/.codex-plugin/plugin.json b/examples/user-plugins/sem/.codex-plugin/plugin.json new file mode 100644 index 000000000..df7964a6a --- /dev/null +++ b/examples/user-plugins/sem/.codex-plugin/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "sem-code-review", + "version": "0.1.0", + "description": "Local code-entity search, caller inspection and structural diffs with Sem.", + "author": { "name": "Ataraxy Labs" }, + "license": "Apache-2.0", + "mcpServers": "./.mcp.json" +} diff --git a/examples/user-plugins/sem/.mcp.json b/examples/user-plugins/sem/.mcp.json new file mode 100644 index 000000000..75f22861d --- /dev/null +++ b/examples/user-plugins/sem/.mcp.json @@ -0,0 +1,11 @@ +{ + "sem": { + "command": "npx", + "args": ["-y", "@ataraxy-labs/sem@0.27.0", "mcp"], + "env": { + "SEM_REPO": "${SEM_REPO}", + "SEM_NO_NETWORK": "1", + "SEM_NO_TELEMETRY": "1" + } + } +} diff --git a/examples/user-plugins/sem/README.md b/examples/user-plugins/sem/README.md new file mode 100644 index 000000000..4339104f3 --- /dev/null +++ b/examples/user-plugins/sem/README.md @@ -0,0 +1,64 @@ +# Sem local code review for DeepChat + +An optional MCP-only plugin for [Sem](https://github.com/Ataraxy-Labs/sem): inspect +code entities, callers, dependencies and structural diffs in a local Git repository. +No Sem account or API key is required. This example adds no hooks, Skills or bundled binaries. +It leaves existing model settings and MCP servers unchanged. + +## Install + +Use a DeepChat version with user plugins (v1.1.2-beta.5 or later), Git, and Node.js/npm +available through DeepChat's Toolchains settings. This example targets macOS/Linux on ARM64/x64. +Enabling the server invokes npx; first installation downloads the pinned npm package and native +Sem binary. Only connect a repository whose build/test scripts you trust. + +1. Obtain a checkout containing this example. Until its PR merges, use the PR's exact commit. +2. From this directory, package the hidden files as well as the README: + + ```sh + python3 -m zipfile -c /tmp/sem-code-review.zip .codex-plugin .mcp.json README.md + ``` + + Choose a suitable output path on your platform. Do not package the whole repository, + credentials, `.git` or `node_modules`. +3. Open **Plugins → Install from ZIP** and inspect the archive. Expect one stdio server, + the pinned command above, and one required variable, `SEM_REPO`. Select MCP and install. + Installation leaves the plugin disabled. +4. Explicitly enable the plugin and use its MCP setup form to supply `SEM_REPO`: an absolute + path to an existing local Git repository. The generic setup form masks variable values; + this value is a directory path, not an API key. It is not inherited from your shell. +5. Check the plugin's MCP connection status and discovered tool schemas. Plugin-owned servers + are separate from the ordinary MCP settings list. Discovery is not proof that a tool call works. + +## Try it + +Substitute a symbol that exists in the selected repository: + +> Use Sem to locate `parse_config`, inspect its callers and explain the code. Do not edit files or run tests. + +For a checkout with tracked local changes: + +> Inspect the structural diff with Sem and explain the changes. Do not execute validation commands. + +The graph's coverage depends on the language and source constructs. Missing results do not prove +there are no references. Retain ordinary file tools for configuration and unresolved code. +This plugin does not configure external ACP agents or provide Weave editing/merging tools. + +## Permissions and privacy + +- `sem_check` can run repository build/test commands. Keep normal tool approvals and only + authorize execution intentionally; this is not a read-only server. +- `SEM_REPO` selects a repository; it is not an operating-system sandbox. +- The preset disables Sem cloud access and telemetry recording. npm/native-binary installation + still uses the network. Those flags do not sandbox commands run by repository checks. +- Returned source code enters the conversation and may reach the configured model provider. +- Sem builds a local index. Structural results are not proof that code compiles or passes tests. + +If setup fails, check Node/npm in Toolchains, Git availability, install-script permissions and the +absolute repository path. Change the path through MCP setup, not your shell environment. +Use **Review update** to change an installed package; editing the source checkout does not update +its installed snapshot. Disable or uninstall the plugin to remove its active integration. +Uninstalling does not erase earlier conversations, npm downloads or Sem's local cache. + +The example files use DeepChat's Apache-2.0 license; Sem is distributed separately under its own +license. This example is contributed by Sem's maintainer at Ataraxy Labs. diff --git a/test/main/plugin/userPluginLifecycle.test.ts b/test/main/plugin/userPluginLifecycle.test.ts index 259366f92..5c225935e 100644 --- a/test/main/plugin/userPluginLifecycle.test.ts +++ b/test/main/plugin/userPluginLifecycle.test.ts @@ -507,6 +507,49 @@ it('clears interrupted-update state even when restoring MCP settings fails', asy expect(f.store.pending()).toBeNull() }) +it('installs Sem disabled and retains its explicit repository binding through enable and removal', async () => { + const f = fixture() + const existing = await f.mcpSettings.getMcpServers() + const inspected = await f.service.inspect( + { kind: 'directory', path: path.resolve('examples/user-plugins/sem') }, + randomUUID() + ) + expect(inspected.package).toMatchObject({ + name: 'sem-code-review', + skills: [], + hooks: [], + findings: [], + mcpServers: [{ name: 'sem', type: 'stdio', requiredVariables: ['SEM_REPO'] }] + }) + const installed = await f.service.install({ + operationId: inspected.operationId, + selection: { skills: false, hooks: false, mcp: true } + }) + expect(installed.ok).toBe(true) + expect(installed.status).toMatchObject({ enabled: false }) + expect(await f.mcpSettings.getMcpServers()).toEqual(existing) + const id = installed.status!.id + const key = `${id}.sem` + expect((await f.service.enable(id)).ok).toBe(true) + expect((await f.service.get(id)).userPlugin?.setup[key]).toEqual(['SEM_REPO']) + expect((await f.service.configureMcp(id, key, { SEM_REPO: '' })).ok).toBe(false) + const repository = path.join(f.root, 'repository with spaces') + expect((await f.service.configureMcp(id, key, { SEM_REPO: repository })).ok).toBe(true) + const config = (await f.mcpSettings.getMcpServers())[key] + expect(config).toMatchObject({ + type: 'stdio', + command: 'npx', + args: ['-y', '@ataraxy-labs/sem@0.27.0', 'mcp'], + env: { SEM_REPO: '${SEM_REPO}', SEM_NO_NETWORK: '1', SEM_NO_TELEMETRY: '1' }, + environmentVariables: ['SEM_REPO'] + }) + expect(f.mcpSettings.getMcpVariableBindings(config)).toEqual({ SEM_REPO: repository }) + expect((await f.service.disable(id)).ok).toBe(true) + expect((await f.mcpSettings.getMcpServers())[key].enabled).toBe(false) + expect((await f.service.uninstall(id)).ok).toBe(true) + expect(await f.mcpSettings.getMcpServers()).toEqual(existing) +}) + it('installs the anonymous Parallel ZIP without changing existing MCP settings', async () => { const f = fixture() const source = path.resolve('examples/user-plugins/parallel-search')