mirror of
https://github.com/NotAShelf/nvf.git
synced 2025-11-08 22:45:30 +00:00
Merge 53f8755c1b into 3d3cd87978
This commit is contained in:
commit
9df682b3c3
10 changed files with 488 additions and 1 deletions
|
|
@ -246,6 +246,7 @@ isMaximal: {
|
||||||
};
|
};
|
||||||
|
|
||||||
assistant = {
|
assistant = {
|
||||||
|
mcphub-nvim.enable = isMaximal;
|
||||||
chatgpt.enable = false;
|
chatgpt.enable = false;
|
||||||
copilot = {
|
copilot = {
|
||||||
enable = false;
|
enable = false;
|
||||||
|
|
|
||||||
|
|
@ -386,6 +386,10 @@
|
||||||
- Fix [render-markdown.nvim] file_types option type to list, to accept merging.
|
- Fix [render-markdown.nvim] file_types option type to list, to accept merging.
|
||||||
- Add [avante.nvim] plugin under `vim.assistant.avante-nvim`.
|
- Add [avante.nvim] plugin under `vim.assistant.avante-nvim`.
|
||||||
|
|
||||||
|
[mcphub-nvim]: https://github.com/ravitemer/mcphub.nvim
|
||||||
|
|
||||||
|
- Add [mcphub.nvim] plugin under `vim.assistant.mcphub-nvim`.
|
||||||
|
|
||||||
[poz](https://poz.pet):
|
[poz](https://poz.pet):
|
||||||
|
|
||||||
[everforest]: https://github.com/sainnhe/everforest
|
[everforest]: https://github.com/sainnhe/everforest
|
||||||
|
|
|
||||||
25
flake/pkgs/by-name/mcphub-nvim/bin.nix
Normal file
25
flake/pkgs/by-name/mcphub-nvim/bin.nix
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
pins,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
pin = pins.mcp-hub;
|
||||||
|
src = pkgs.fetchFromGitHub {
|
||||||
|
inherit (pin.repository) owner repo;
|
||||||
|
tag = pin.version;
|
||||||
|
sha256 = pin.hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
inherit (pkgs) nodejs;
|
||||||
|
in
|
||||||
|
pkgs.buildNpmPackage {
|
||||||
|
pname = "mcp-hub";
|
||||||
|
inherit (pin) version;
|
||||||
|
inherit src nodejs;
|
||||||
|
|
||||||
|
nativeBuildInputs = [nodejs];
|
||||||
|
npmDeps = pkgs.importNpmLock {
|
||||||
|
npmRoot = src;
|
||||||
|
};
|
||||||
|
inherit (pkgs.importNpmLock) npmConfigHook;
|
||||||
|
}
|
||||||
26
flake/pkgs/by-name/mcphub-nvim/package.nix
Normal file
26
flake/pkgs/by-name/mcphub-nvim/package.nix
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
pins,
|
||||||
|
vimUtils,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
mcp-hub = import ./bin.nix {inherit pins pkgs;};
|
||||||
|
pin = pins.mcphub-nvim;
|
||||||
|
version = pin.branch;
|
||||||
|
src = pkgs.fetchFromGitHub {
|
||||||
|
inherit (pin.repository) owner repo;
|
||||||
|
rev = pin.revision;
|
||||||
|
sha256 = pin.hash;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
vimUtils.buildVimPlugin {
|
||||||
|
pname = "mcphub-nvim";
|
||||||
|
inherit src version;
|
||||||
|
|
||||||
|
doCheck = false;
|
||||||
|
|
||||||
|
postInstall = ''
|
||||||
|
mkdir -p $out/bundled/mcp-hub/node_modules/.bin
|
||||||
|
ln -s ${mcp-hub}/bin/mcp-hub $out/bundled/mcp-hub/node_modules/.bin/mcp-hub
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
|
@ -5,5 +5,6 @@
|
||||||
./codecompanion
|
./codecompanion
|
||||||
./supermaven-nvim
|
./supermaven-nvim
|
||||||
./avante
|
./avante
|
||||||
|
./mcphub
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
104
modules/plugins/assistant/mcphub/config.nix
Normal file
104
modules/plugins/assistant/mcphub/config.nix
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
inherit (lib.modules) mkIf;
|
||||||
|
|
||||||
|
cfg = config.vim.assistant.mcphub-nvim;
|
||||||
|
in {
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
vim = {
|
||||||
|
startPlugins = [
|
||||||
|
"plenary-nvim"
|
||||||
|
];
|
||||||
|
|
||||||
|
lazy.plugins = {
|
||||||
|
mcphub-nvim = {
|
||||||
|
package = "mcphub-nvim";
|
||||||
|
setupModule = "mcphub";
|
||||||
|
inherit (cfg) setupOpts;
|
||||||
|
event = ["DeferredUIEnter"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# avante-nvim
|
||||||
|
assistant.avante-nvim.setupOpts = {
|
||||||
|
system_prompt = lib.generators.mkLuaInline ''
|
||||||
|
function()
|
||||||
|
local hub = require("mcphub").get_hub_instance()
|
||||||
|
return hub and hub:get_active_servers_prompt() or ""
|
||||||
|
end
|
||||||
|
'';
|
||||||
|
custom_tools = lib.generators.mkLuaInline ''
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
require("mcphub.extensions.avante").mcp_tool(),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# codecompanion-nvim
|
||||||
|
assistant.codecompanion-nvim.setupOpts.extensions.mcphub = {
|
||||||
|
callback = "mcphub.extensions.codecompanion";
|
||||||
|
opts = {
|
||||||
|
## MCP Tools
|
||||||
|
make_tools = true;
|
||||||
|
show_server_tools_in_chat = true;
|
||||||
|
add_mcp_prefix_to_tool_names = false;
|
||||||
|
show_result_in_chat = true;
|
||||||
|
format_tool = null;
|
||||||
|
## MCP Resources
|
||||||
|
make_vars = true;
|
||||||
|
## MCP Prompts
|
||||||
|
make_slash_commands = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# lualine
|
||||||
|
statusline.lualine.setupOpts.sections.lualine_x = lib.generators.mkLuaInline ''
|
||||||
|
{{
|
||||||
|
function()
|
||||||
|
-- Check if MCPHub is loaded
|
||||||
|
if not vim.g.loaded_mcphub then
|
||||||
|
return " -"
|
||||||
|
end
|
||||||
|
|
||||||
|
local count = vim.g.mcphub_servers_count or 0
|
||||||
|
local status = vim.g.mcphub_status or "stopped"
|
||||||
|
local executing = vim.g.mcphub_executing
|
||||||
|
|
||||||
|
-- Show "-" when stopped
|
||||||
|
if status == "stopped" then
|
||||||
|
return " -"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Show spinner when executing, starting, or restarting
|
||||||
|
if executing or status == "starting" or status == "restarting" then
|
||||||
|
local frames = { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" }
|
||||||
|
local frame = math.floor(vim.loop.now() / 100) % #frames + 1
|
||||||
|
return " " .. frames[frame]
|
||||||
|
end
|
||||||
|
|
||||||
|
return " " .. count
|
||||||
|
end,
|
||||||
|
color = function()
|
||||||
|
if not vim.g.loaded_mcphub then
|
||||||
|
return { fg = "#6c7086" } -- Gray for not loaded
|
||||||
|
end
|
||||||
|
|
||||||
|
local status = vim.g.mcphub_status or "stopped"
|
||||||
|
if status == "ready" or status == "restarted" then
|
||||||
|
return { fg = "#50fa7b" } -- Green for connected
|
||||||
|
elseif status == "starting" or status == "restarting" then
|
||||||
|
return { fg = "#ffb86c" } -- Orange for connecting
|
||||||
|
else
|
||||||
|
return { fg = "#ff5555" } -- Red for error/stopped
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
6
modules/plugins/assistant/mcphub/default.nix
Normal file
6
modules/plugins/assistant/mcphub/default.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./config.nix
|
||||||
|
./mcphub-nvim.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
291
modules/plugins/assistant/mcphub/mcphub-nvim.nix
Normal file
291
modules/plugins/assistant/mcphub/mcphub-nvim.nix
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
{lib, ...}: let
|
||||||
|
inherit (lib.options) mkOption mkEnableOption literalMD;
|
||||||
|
inherit (lib.types) int bool enum str nullOr attrs listOf either attrsOf anything;
|
||||||
|
inherit (lib.strings) toUpper;
|
||||||
|
inherit (lib.nvim.types) mkPluginSetupOption luaInline;
|
||||||
|
inherit (lib.generators) mkLuaInline;
|
||||||
|
in {
|
||||||
|
options.vim.assistant = {
|
||||||
|
mcphub-nvim = {
|
||||||
|
enable = mkEnableOption "MCPHub";
|
||||||
|
|
||||||
|
setupOpts = mkPluginSetupOption "mcphub-nvim" {
|
||||||
|
use_bundled_binary =
|
||||||
|
mkEnableOption "Use local `mcp-hub` binary."
|
||||||
|
// {
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
port = mkOption {
|
||||||
|
type = int;
|
||||||
|
default = 37373;
|
||||||
|
description = "The port for the mcp-hub Express server.";
|
||||||
|
};
|
||||||
|
|
||||||
|
server_url = mkOption {
|
||||||
|
type = nullOr str;
|
||||||
|
default = null;
|
||||||
|
description = "The URL for the mcp-hub server in cases where it is hosted somewhere else.";
|
||||||
|
example = "http://mydomain.com:8080";
|
||||||
|
};
|
||||||
|
|
||||||
|
config_path = mkOption {
|
||||||
|
type = str;
|
||||||
|
default = "~/.config/mcphub/servers.json";
|
||||||
|
description = "The absolute path to your mcpservers.json configuration file. Defaults to ~/.config/mcphub/servers.json in Lua.";
|
||||||
|
example = "~/.config/nvim/mcpservers.json";
|
||||||
|
};
|
||||||
|
|
||||||
|
shutdown_delay = mkOption {
|
||||||
|
type = int;
|
||||||
|
default = 300000; # 5 minutes in milliseconds
|
||||||
|
description = "The delay in milliseconds before the server shuts down when the last client disconnects.";
|
||||||
|
};
|
||||||
|
|
||||||
|
request_timeout = mkOption {
|
||||||
|
type = int;
|
||||||
|
default = 60000;
|
||||||
|
description = "Timeout for MCP requests in milliseconds.";
|
||||||
|
};
|
||||||
|
|
||||||
|
auto_approve = mkOption {
|
||||||
|
type = either luaInline bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
How to approve MCP calls.
|
||||||
|
|
||||||
|
The system checks auto-approval in this order:
|
||||||
|
1. Function: Custom auto_approve function (if provided)
|
||||||
|
1. Server-specific: autoApprove field in server config
|
||||||
|
1. Default: Show confirmation dialog
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
auto_toggle_servers =
|
||||||
|
mkEnableOption "Let LLMs start and stop MCP servers automatically."
|
||||||
|
// {
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
cmd = mkOption {
|
||||||
|
type = nullOr str;
|
||||||
|
default = null;
|
||||||
|
description = "Custom command to start the mcp-hub binary.";
|
||||||
|
};
|
||||||
|
|
||||||
|
cmd_args = mkOption {
|
||||||
|
type = nullOr (listOf str);
|
||||||
|
default = null;
|
||||||
|
description = "Custom arguments for the mcp-hub command.";
|
||||||
|
};
|
||||||
|
|
||||||
|
global_env = mkOption {
|
||||||
|
type = nullOr (either luaInline (attrsOf anything));
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Global environment variables available to all MCP servers.
|
||||||
|
You can use either a table or a function that returns a table.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
log = {
|
||||||
|
level = mkOption {
|
||||||
|
description = "Logging level, e.g., 'vim.log.levels.WARN'.";
|
||||||
|
type = enum ["debug" "info" "warn" "error" "trace"];
|
||||||
|
default = "info";
|
||||||
|
apply = filter: mkLuaInline "vim.log.levels.${toUpper filter}";
|
||||||
|
};
|
||||||
|
to_file = mkEnableOption "log to a file.";
|
||||||
|
file_path = mkOption {
|
||||||
|
type = nullOr str;
|
||||||
|
default = null;
|
||||||
|
description = "Path to the log file.";
|
||||||
|
};
|
||||||
|
prefix = mkOption {
|
||||||
|
type = str;
|
||||||
|
default = "MCPHub";
|
||||||
|
description = "The prefix for log messages.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
ui = {
|
||||||
|
window = mkOption {
|
||||||
|
type = attrs;
|
||||||
|
default = {
|
||||||
|
width = 0.8;
|
||||||
|
height = 0.8;
|
||||||
|
align = "center";
|
||||||
|
relative = "editor";
|
||||||
|
zindex = 50;
|
||||||
|
border = "rounded";
|
||||||
|
};
|
||||||
|
description = "Options for the UI window.";
|
||||||
|
};
|
||||||
|
wo = mkOption {
|
||||||
|
type = attrs;
|
||||||
|
default = {
|
||||||
|
winhl = "Normal:MCPHubNormal,FloatBorder:MCPHubBorder";
|
||||||
|
};
|
||||||
|
description = "Window options.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
extensions = {
|
||||||
|
avante = {
|
||||||
|
enabled =
|
||||||
|
mkEnableOption "the Avante extension."
|
||||||
|
// {
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
make_slash_commands =
|
||||||
|
mkEnableOption "create slash commands for Avante."
|
||||||
|
// {
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
copilotchat = {
|
||||||
|
enabled =
|
||||||
|
mkEnableOption "the CopilotChat extension."
|
||||||
|
// {
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
convert_tools_to_functions =
|
||||||
|
mkEnableOption "convert tools to functions."
|
||||||
|
// {
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
convert_resources_to_functions =
|
||||||
|
mkEnableOption "convert resources to functions."
|
||||||
|
// {
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
add_mcp_prefix = mkEnableOption "add an mcp prefix.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
builtin_tools = {
|
||||||
|
edit_file = {
|
||||||
|
parser = {
|
||||||
|
track_issues =
|
||||||
|
mkEnableOption "track issues during parsing."
|
||||||
|
// {
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
extract_inline_content =
|
||||||
|
mkEnableOption "extract inline content."
|
||||||
|
// {
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
locator = {
|
||||||
|
fuzzy_threshold = mkOption {
|
||||||
|
type = nullOr int;
|
||||||
|
default = null;
|
||||||
|
description = "Fuzzy matching threshold.";
|
||||||
|
};
|
||||||
|
enable_fuzzy_matching =
|
||||||
|
mkEnableOption "fuzzy matching."
|
||||||
|
// {
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
ui = {
|
||||||
|
go_to_origin_on_complete =
|
||||||
|
mkEnableOption "go to the origin on complete."
|
||||||
|
// {
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
keybindings = mkOption {
|
||||||
|
type = attrs;
|
||||||
|
default = {
|
||||||
|
accept = ".";
|
||||||
|
reject = ",";
|
||||||
|
next = "n";
|
||||||
|
prev = "p";
|
||||||
|
accept_all = "ga";
|
||||||
|
reject_all = "gr";
|
||||||
|
};
|
||||||
|
description = "Keybindings for the edit file UI.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
workspace = {
|
||||||
|
enabled =
|
||||||
|
mkEnableOption "workspace-specific hubs."
|
||||||
|
// {
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
look_for = mkOption {
|
||||||
|
type = listOf str;
|
||||||
|
default = [".mcphub/servers.json" ".vscode/mcp.json" ".cursor/mcp.json"];
|
||||||
|
description = "Files to search for in order.";
|
||||||
|
};
|
||||||
|
reload_on_dir_changed =
|
||||||
|
mkEnableOption "listen to DirChanged events to reload workspace config."
|
||||||
|
// {
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
port_range = mkOption {
|
||||||
|
type = attrs;
|
||||||
|
default = {
|
||||||
|
min = 40000;
|
||||||
|
max = 41000;
|
||||||
|
};
|
||||||
|
description = "Port range for workspace hubs, with `min` and `max` attributes.";
|
||||||
|
};
|
||||||
|
get_port = mkOption {
|
||||||
|
type = nullOr luaInline;
|
||||||
|
default = null;
|
||||||
|
description = "Function that returns the port.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
on_ready = mkOption {
|
||||||
|
type = luaInline;
|
||||||
|
default = mkLuaInline ''
|
||||||
|
function() end
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
A Lua function to be executed once the mcp-hub server is ready.
|
||||||
|
It receives the hub object as an argument.
|
||||||
|
'';
|
||||||
|
example = literalMD ''
|
||||||
|
```lua
|
||||||
|
function(hub)
|
||||||
|
vim.notify('MCPHub is ready', vim.log.levels.INFO)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
on_error = mkOption {
|
||||||
|
type = luaInline;
|
||||||
|
default = mkLuaInline ''
|
||||||
|
function(msg) end
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
A Lua function to be executed when an error occurs.
|
||||||
|
It receives the error message as an argument.
|
||||||
|
'';
|
||||||
|
example = literalMD ''
|
||||||
|
```lua
|
||||||
|
function(msg)
|
||||||
|
vim.notify('An error occurred in MCPHub: ' .. msg, vim.log.levels.ERROR)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
json_decode = mkOption {
|
||||||
|
type = nullOr luaInline;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Custom JSON parser function for configuration files.
|
||||||
|
|
||||||
|
This is particularly useful for supporting JSON5 syntax (comments and trailing commas).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
inherit (inputs.self.packages.${pkgs.stdenv.system}) blink-cmp avante-nvim;
|
inherit (inputs.self.packages.${pkgs.stdenv.system}) blink-cmp avante-nvim mcphub-nvim;
|
||||||
};
|
};
|
||||||
|
|
||||||
buildConfigPlugins = plugins:
|
buildConfigPlugins = plugins:
|
||||||
|
|
|
||||||
|
|
@ -961,6 +961,35 @@
|
||||||
"url": "https://github.com/OXY2DEV/markview.nvim/archive/de79a7626d54d7785436105ef72f37ee8fe8fa16.tar.gz",
|
"url": "https://github.com/OXY2DEV/markview.nvim/archive/de79a7626d54d7785436105ef72f37ee8fe8fa16.tar.gz",
|
||||||
"hash": "032i6m9pld1zyhd7lq49dg4qh98w6vmmzqp2f46drhq0ds26hs4h"
|
"hash": "032i6m9pld1zyhd7lq49dg4qh98w6vmmzqp2f46drhq0ds26hs4h"
|
||||||
},
|
},
|
||||||
|
"mcp-hub": {
|
||||||
|
"type": "GitRelease",
|
||||||
|
"repository": {
|
||||||
|
"type": "GitHub",
|
||||||
|
"owner": "ravitemer",
|
||||||
|
"repo": "mcp-hub"
|
||||||
|
},
|
||||||
|
"pre_releases": false,
|
||||||
|
"version_upper_bound": null,
|
||||||
|
"release_prefix": null,
|
||||||
|
"submodules": false,
|
||||||
|
"version": "v4.2.1",
|
||||||
|
"revision": "aac67ba3e163712d07674c154532a71965015e57",
|
||||||
|
"url": "https://api.github.com/repos/ravitemer/mcp-hub/tarball/v4.2.1",
|
||||||
|
"hash": "127d91xnfqaxqvk76682n8cghjhw1b02xzi4rxm3ggpljxfjza99"
|
||||||
|
},
|
||||||
|
"mcphub-nvim": {
|
||||||
|
"type": "Git",
|
||||||
|
"repository": {
|
||||||
|
"type": "GitHub",
|
||||||
|
"owner": "ravitemer",
|
||||||
|
"repo": "mcphub.nvim"
|
||||||
|
},
|
||||||
|
"branch": "main",
|
||||||
|
"submodules": false,
|
||||||
|
"revision": "8ff40b5edc649959bb7e89d25ae18e055554859a",
|
||||||
|
"url": "https://github.com/ravitemer/mcphub.nvim/archive/8ff40b5edc649959bb7e89d25ae18e055554859a.tar.gz",
|
||||||
|
"hash": "1saw3xfrbnwpjklcffp144q2y100kd51yrhvmxnhgc7niy0ip893"
|
||||||
|
},
|
||||||
"mind-nvim": {
|
"mind-nvim": {
|
||||||
"type": "Git",
|
"type": "Git",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue