nexus-dev-toolkit 3.1.0__tar.gz → 3.1.2__tar.gz
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.
- {nexus_dev_toolkit-3.1.0/nexus_dev_toolkit.egg-info → nexus_dev_toolkit-3.1.2}/PKG-INFO +30 -15
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/README.md +29 -14
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_cli.py +257 -16
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2/nexus_dev_toolkit.egg-info}/PKG-INFO +30 -15
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/pyproject.toml +1 -1
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tests/test_cli.py +47 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/LICENSE +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_dev_toolkit.egg-info/SOURCES.txt +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_dev_toolkit.egg-info/dependency_links.txt +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_dev_toolkit.egg-info/entry_points.txt +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_dev_toolkit.egg-info/requires.txt +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_dev_toolkit.egg-info/top_level.txt +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_server.py +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/setup.cfg +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tests/test_server.py +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/__init__.py +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/agents/__init__.py +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/agents/code-reviewer.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/agents/database-reviewer.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/agents/deployment-reviewer.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/agents/monitoring-reviewer.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/agents/performance-reviewer.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/__init__.py +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/arch_ingest.py +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/package_resolver.py +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/project_rules.py +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/__init__.py +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/apply.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/code-review.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/database-review.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/deployment-review.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/epav.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/evaluate.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/monitoring-review.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/performance-review.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/plan.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/scaffold.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/validate.md +0 -0
- {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/task_loader.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nexus-dev-toolkit
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.2
|
|
4
4
|
Summary: Developer workflow toolkit for Claude Code — Day 0 scaffold + Day 1 EPAV
|
|
5
5
|
Author-email: Ronald dela Cruz <rcdelacruz@users.noreply.github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -80,7 +80,8 @@ uv tool install nexus-dev-toolkit
|
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
82
|
cd my-project
|
|
83
|
-
nexus init .
|
|
83
|
+
nexus init . # Claude Code (default)
|
|
84
|
+
nexus init . --tool opencode # OpenCode
|
|
84
85
|
```
|
|
85
86
|
|
|
86
87
|
### 4. Place your reference docs
|
|
@@ -137,15 +138,11 @@ Each step is a built-in skill in `.claude/commands/`. Every task starts from the
|
|
|
137
138
|
|
|
138
139
|
## What `nexus init` Creates
|
|
139
140
|
|
|
141
|
+
**Claude Code** (`nexus init .`):
|
|
140
142
|
```
|
|
141
143
|
.claude/
|
|
142
|
-
├── commands/
|
|
143
|
-
|
|
144
|
-
│ ├── evaluate.md ← /evaluate — orient on a task
|
|
145
|
-
│ ├── plan.md ← /plan — blueprint, no code
|
|
146
|
-
│ ├── apply.md ← /apply — implement the plan
|
|
147
|
-
│ ├── validate.md ← /validate — verify acceptance criteria
|
|
148
|
-
│ └── epav.md ← /epav — full cycle guide
|
|
144
|
+
├── commands/ ← EPAV skills + 5 reviewer skills (/code-review, etc.)
|
|
145
|
+
├── agents/ ← 5 reviewer subagents (code-reviewer, database-reviewer, etc.)
|
|
149
146
|
└── settings.json ← PostToolUse: graphify auto-updates after every file edit
|
|
150
147
|
knowledge/
|
|
151
148
|
├── rules/ ← coding standards, arch decisions
|
|
@@ -155,17 +152,35 @@ knowledge/
|
|
|
155
152
|
.mcp.json ← MCP server config
|
|
156
153
|
```
|
|
157
154
|
|
|
155
|
+
**OpenCode** (`nexus init . --tool opencode`):
|
|
156
|
+
```
|
|
157
|
+
.opencode/
|
|
158
|
+
├── commands/ ← same EPAV skills + reviewer skills
|
|
159
|
+
├── agents/ ← same reviewer subagents (adapted for OpenCode)
|
|
160
|
+
└── plugins/
|
|
161
|
+
└── graphify.js ← tool.execute.after: graphify auto-update
|
|
162
|
+
knowledge/ ← same structure
|
|
163
|
+
opencode.json ← MCP server config
|
|
164
|
+
```
|
|
165
|
+
|
|
158
166
|
---
|
|
159
167
|
|
|
160
168
|
## Commands
|
|
161
169
|
|
|
162
170
|
```bash
|
|
163
|
-
nexus init .
|
|
164
|
-
nexus
|
|
165
|
-
nexus
|
|
166
|
-
nexus
|
|
167
|
-
|
|
168
|
-
nexus
|
|
171
|
+
nexus init . # Claude Code — set up .claude/ + knowledge/ + .mcp.json
|
|
172
|
+
nexus init . --tool opencode # OpenCode — set up .opencode/ + knowledge/ + opencode.json
|
|
173
|
+
nexus --version # show installed version
|
|
174
|
+
nexus update # update to latest version
|
|
175
|
+
|
|
176
|
+
nexus skill list # list all skills in .claude/commands/
|
|
177
|
+
nexus skill add my-review # create a custom skill
|
|
178
|
+
|
|
179
|
+
nexus agent list # list all subagents in .claude/agents/
|
|
180
|
+
nexus agent add my-agent # create a custom subagent
|
|
181
|
+
|
|
182
|
+
nexus rule list # list all rules in knowledge/rules/
|
|
183
|
+
nexus rule add api-standards # create a project rule
|
|
169
184
|
```
|
|
170
185
|
|
|
171
186
|
---
|
|
@@ -59,7 +59,8 @@ uv tool install nexus-dev-toolkit
|
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
61
|
cd my-project
|
|
62
|
-
nexus init .
|
|
62
|
+
nexus init . # Claude Code (default)
|
|
63
|
+
nexus init . --tool opencode # OpenCode
|
|
63
64
|
```
|
|
64
65
|
|
|
65
66
|
### 4. Place your reference docs
|
|
@@ -116,15 +117,11 @@ Each step is a built-in skill in `.claude/commands/`. Every task starts from the
|
|
|
116
117
|
|
|
117
118
|
## What `nexus init` Creates
|
|
118
119
|
|
|
120
|
+
**Claude Code** (`nexus init .`):
|
|
119
121
|
```
|
|
120
122
|
.claude/
|
|
121
|
-
├── commands/
|
|
122
|
-
|
|
123
|
-
│ ├── evaluate.md ← /evaluate — orient on a task
|
|
124
|
-
│ ├── plan.md ← /plan — blueprint, no code
|
|
125
|
-
│ ├── apply.md ← /apply — implement the plan
|
|
126
|
-
│ ├── validate.md ← /validate — verify acceptance criteria
|
|
127
|
-
│ └── epav.md ← /epav — full cycle guide
|
|
123
|
+
├── commands/ ← EPAV skills + 5 reviewer skills (/code-review, etc.)
|
|
124
|
+
├── agents/ ← 5 reviewer subagents (code-reviewer, database-reviewer, etc.)
|
|
128
125
|
└── settings.json ← PostToolUse: graphify auto-updates after every file edit
|
|
129
126
|
knowledge/
|
|
130
127
|
├── rules/ ← coding standards, arch decisions
|
|
@@ -134,17 +131,35 @@ knowledge/
|
|
|
134
131
|
.mcp.json ← MCP server config
|
|
135
132
|
```
|
|
136
133
|
|
|
134
|
+
**OpenCode** (`nexus init . --tool opencode`):
|
|
135
|
+
```
|
|
136
|
+
.opencode/
|
|
137
|
+
├── commands/ ← same EPAV skills + reviewer skills
|
|
138
|
+
├── agents/ ← same reviewer subagents (adapted for OpenCode)
|
|
139
|
+
└── plugins/
|
|
140
|
+
└── graphify.js ← tool.execute.after: graphify auto-update
|
|
141
|
+
knowledge/ ← same structure
|
|
142
|
+
opencode.json ← MCP server config
|
|
143
|
+
```
|
|
144
|
+
|
|
137
145
|
---
|
|
138
146
|
|
|
139
147
|
## Commands
|
|
140
148
|
|
|
141
149
|
```bash
|
|
142
|
-
nexus init .
|
|
143
|
-
nexus
|
|
144
|
-
nexus
|
|
145
|
-
nexus
|
|
146
|
-
|
|
147
|
-
nexus
|
|
150
|
+
nexus init . # Claude Code — set up .claude/ + knowledge/ + .mcp.json
|
|
151
|
+
nexus init . --tool opencode # OpenCode — set up .opencode/ + knowledge/ + opencode.json
|
|
152
|
+
nexus --version # show installed version
|
|
153
|
+
nexus update # update to latest version
|
|
154
|
+
|
|
155
|
+
nexus skill list # list all skills in .claude/commands/
|
|
156
|
+
nexus skill add my-review # create a custom skill
|
|
157
|
+
|
|
158
|
+
nexus agent list # list all subagents in .claude/agents/
|
|
159
|
+
nexus agent add my-agent # create a custom subagent
|
|
160
|
+
|
|
161
|
+
nexus rule list # list all rules in knowledge/rules/
|
|
162
|
+
nexus rule add api-standards # create a project rule
|
|
148
163
|
```
|
|
149
164
|
|
|
150
165
|
---
|
|
@@ -11,12 +11,14 @@ from rich.table import Table
|
|
|
11
11
|
app = typer.Typer(name="nexus", no_args_is_help=False, help="nexus-dev-toolkit — Day 0 scaffold + Day 1 EPAV workflow for Claude Code")
|
|
12
12
|
skill_app = typer.Typer(name="skill", no_args_is_help=True, help="Manage skills in .claude/commands/")
|
|
13
13
|
rule_app = typer.Typer(name="rule", no_args_is_help=True, help="Manage rules in knowledge/rules/")
|
|
14
|
+
agent_app = typer.Typer(name="agent", no_args_is_help=True, help="Manage subagents in .claude/agents/")
|
|
14
15
|
app.add_typer(skill_app, name="skill")
|
|
15
16
|
app.add_typer(rule_app, name="rule")
|
|
17
|
+
app.add_typer(agent_app, name="agent")
|
|
16
18
|
|
|
17
19
|
console = Console()
|
|
18
20
|
|
|
19
|
-
_VERSION = "3.1.
|
|
21
|
+
_VERSION = "3.1.2"
|
|
20
22
|
|
|
21
23
|
_LOGO = """\
|
|
22
24
|
[cyan]███╗ ██╗███████╗██╗ ██╗██╗ ██╗███████╗[/cyan]
|
|
@@ -35,8 +37,17 @@ def _print_logo() -> None:
|
|
|
35
37
|
console.print()
|
|
36
38
|
|
|
37
39
|
|
|
40
|
+
def _version_callback(value: bool) -> None:
|
|
41
|
+
if value:
|
|
42
|
+
console.print(f"nexus-dev-toolkit v{_VERSION}")
|
|
43
|
+
raise typer.Exit()
|
|
44
|
+
|
|
45
|
+
|
|
38
46
|
@app.callback(invoke_without_command=True)
|
|
39
|
-
def _callback(
|
|
47
|
+
def _callback(
|
|
48
|
+
ctx: typer.Context,
|
|
49
|
+
version: bool = typer.Option(None, "--version", "-V", callback=_version_callback, is_eager=True, help="Show version and exit."),
|
|
50
|
+
) -> None:
|
|
40
51
|
if ctx.invoked_subcommand is None:
|
|
41
52
|
_print_logo()
|
|
42
53
|
console.print(ctx.get_help())
|
|
@@ -106,6 +117,29 @@ _MCP_BLOCK = {
|
|
|
106
117
|
}
|
|
107
118
|
}
|
|
108
119
|
|
|
120
|
+
_OPENCODE_MCP_BLOCK = {
|
|
121
|
+
"nexus-mcp": {
|
|
122
|
+
"type": "local",
|
|
123
|
+
"command": ["uvx", "--refresh", "--from", "nexus-dev-toolkit", "nexus-mcp"],
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
_OPENCODE_GRAPHIFY_PLUGIN = """\
|
|
128
|
+
// .opencode/plugins/graphify.js
|
|
129
|
+
// Auto-updates the graphify knowledge graph after every tool execution.
|
|
130
|
+
import { execSync } from "node:child_process"
|
|
131
|
+
|
|
132
|
+
export const GraphifyPlugin = async ({ directory }) => {
|
|
133
|
+
return {
|
|
134
|
+
"tool.execute.after": async () => {
|
|
135
|
+
try {
|
|
136
|
+
execSync("graphify update . --force", { cwd: directory, stdio: "ignore" })
|
|
137
|
+
} catch (_) {}
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
"""
|
|
142
|
+
|
|
109
143
|
|
|
110
144
|
def _init_project(project_dir: Path) -> list[str]:
|
|
111
145
|
"""
|
|
@@ -153,6 +187,81 @@ def _init_project(project_dir: Path) -> list[str]:
|
|
|
153
187
|
return created
|
|
154
188
|
|
|
155
189
|
|
|
190
|
+
def _strip_claude_frontmatter(content: str) -> str:
|
|
191
|
+
"""Remove Claude Code-only frontmatter fields (tools, model) for OpenCode compatibility."""
|
|
192
|
+
if not content.startswith("---"):
|
|
193
|
+
return content
|
|
194
|
+
parts = content.split("---", 2)
|
|
195
|
+
if len(parts) < 3:
|
|
196
|
+
return content
|
|
197
|
+
lines = [
|
|
198
|
+
line for line in parts[1].splitlines()
|
|
199
|
+
if not line.startswith("tools:") and not line.startswith("model:")
|
|
200
|
+
]
|
|
201
|
+
return "---" + "\n".join(lines) + "---" + parts[2]
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _init_opencode(project_dir: Path) -> list[str]:
|
|
205
|
+
"""
|
|
206
|
+
nexus init --tool opencode — sets up:
|
|
207
|
+
.opencode/commands/ ← built-in skills
|
|
208
|
+
.opencode/agents/ ← built-in subagents
|
|
209
|
+
.opencode/plugins/ ← graphify PostToolUse hook
|
|
210
|
+
opencode.json ← MCP server config
|
|
211
|
+
knowledge/ ← empty scaffold
|
|
212
|
+
"""
|
|
213
|
+
created = []
|
|
214
|
+
|
|
215
|
+
# .opencode/commands/ — copy built-in skills
|
|
216
|
+
commands_dir = project_dir / ".opencode" / "commands"
|
|
217
|
+
commands_dir.mkdir(parents=True, exist_ok=True)
|
|
218
|
+
for skill_name in _BUILTIN_SKILLS:
|
|
219
|
+
src = _SKILLS_SRC / skill_name
|
|
220
|
+
dest = commands_dir / skill_name
|
|
221
|
+
if src.exists() and not dest.exists():
|
|
222
|
+
shutil.copy2(src, dest)
|
|
223
|
+
created.append(f".opencode/commands/{skill_name}")
|
|
224
|
+
|
|
225
|
+
# .opencode/agents/ — copy built-in subagents, stripping Claude Code-only frontmatter
|
|
226
|
+
agents_dir = project_dir / ".opencode" / "agents"
|
|
227
|
+
agents_dir.mkdir(parents=True, exist_ok=True)
|
|
228
|
+
for agent_name in _BUILTIN_AGENTS:
|
|
229
|
+
src = _AGENTS_SRC / agent_name
|
|
230
|
+
dest = agents_dir / agent_name
|
|
231
|
+
if src.exists() and not dest.exists():
|
|
232
|
+
dest.write_text(_strip_claude_frontmatter(src.read_text()), encoding="utf-8")
|
|
233
|
+
created.append(f".opencode/agents/{agent_name}")
|
|
234
|
+
|
|
235
|
+
# .opencode/plugins/graphify.js — PostToolUse hook
|
|
236
|
+
plugins_dir = project_dir / ".opencode" / "plugins"
|
|
237
|
+
plugins_dir.mkdir(parents=True, exist_ok=True)
|
|
238
|
+
plugin_path = plugins_dir / "graphify.js"
|
|
239
|
+
if not plugin_path.exists():
|
|
240
|
+
plugin_path.write_text(_OPENCODE_GRAPHIFY_PLUGIN, encoding="utf-8")
|
|
241
|
+
created.append(".opencode/plugins/graphify.js")
|
|
242
|
+
|
|
243
|
+
# opencode.json — MCP server config
|
|
244
|
+
opencode_json = project_dir / "opencode.json"
|
|
245
|
+
existing: dict = {}
|
|
246
|
+
if opencode_json.exists():
|
|
247
|
+
try:
|
|
248
|
+
existing = json.loads(opencode_json.read_text())
|
|
249
|
+
except Exception:
|
|
250
|
+
pass
|
|
251
|
+
already_has_mcp = "nexus-mcp" in existing.get("mcp", {})
|
|
252
|
+
existing.setdefault("$schema", "https://opencode.ai/config.json")
|
|
253
|
+
existing.setdefault("mcp", {}).update(_OPENCODE_MCP_BLOCK)
|
|
254
|
+
opencode_json.write_text(json.dumps(existing, indent=2))
|
|
255
|
+
if not already_has_mcp:
|
|
256
|
+
created.append("opencode.json")
|
|
257
|
+
|
|
258
|
+
# knowledge/ scaffold
|
|
259
|
+
for d in _KNOWLEDGE_DIRS:
|
|
260
|
+
(project_dir / d).mkdir(parents=True, exist_ok=True)
|
|
261
|
+
|
|
262
|
+
return created
|
|
263
|
+
|
|
264
|
+
|
|
156
265
|
def _write_mcp_config(project_dir: Path) -> str:
|
|
157
266
|
mcp_path = project_dir / ".mcp.json"
|
|
158
267
|
existing: dict = {}
|
|
@@ -168,26 +277,80 @@ def _write_mcp_config(project_dir: Path) -> str:
|
|
|
168
277
|
|
|
169
278
|
# ── Commands ──────────────────────────────────────────────────────────────────
|
|
170
279
|
|
|
280
|
+
_SUPPORTED_TOOLS = ["claude", "opencode"]
|
|
281
|
+
|
|
282
|
+
_TOOL_CHECK = {
|
|
283
|
+
"claude": {
|
|
284
|
+
"bin": "claude",
|
|
285
|
+
"name": "Claude Code",
|
|
286
|
+
"install": "npm install -g @anthropic-ai/claude-code && claude login",
|
|
287
|
+
"install_cmd": ["npm", "install", "-g", "@anthropic-ai/claude-code"],
|
|
288
|
+
},
|
|
289
|
+
"opencode": {
|
|
290
|
+
"bin": "opencode",
|
|
291
|
+
"name": "OpenCode",
|
|
292
|
+
"install": "curl -fsSL https://opencode.ai/install | bash",
|
|
293
|
+
"install_cmd": None, # pipe install — run via shell
|
|
294
|
+
},
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _check_and_offer_install(tool: str) -> None:
|
|
299
|
+
info = _TOOL_CHECK[tool]
|
|
300
|
+
if shutil.which(info["bin"]):
|
|
301
|
+
return
|
|
302
|
+
|
|
303
|
+
console.print(f" [yellow]⚠[/yellow] {info['name']} not found — you need it to use nexus.\n")
|
|
304
|
+
answer = console.input(f" Install {info['name']} now? [y/N] ").strip().lower()
|
|
305
|
+
if answer != "y":
|
|
306
|
+
console.print(f" [dim]Skipping. Install manually: {info['install']}[/dim]\n")
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
console.print(f" [cyan]▶[/cyan] Installing {info['name']}…\n")
|
|
310
|
+
if info["install_cmd"]:
|
|
311
|
+
result = subprocess.run(info["install_cmd"])
|
|
312
|
+
else:
|
|
313
|
+
result = subprocess.run(info["install"], shell=True)
|
|
314
|
+
|
|
315
|
+
if result.returncode == 0:
|
|
316
|
+
console.print(f" [green]✓[/green] {info['name']} installed.\n")
|
|
317
|
+
else:
|
|
318
|
+
console.print(f" [red]✗[/red] Install failed. Run manually: {info['install']}\n")
|
|
319
|
+
|
|
320
|
+
|
|
171
321
|
@app.command()
|
|
172
322
|
def init(
|
|
173
323
|
project_dir: str = typer.Argument(".", help="Project directory to initialize"),
|
|
324
|
+
tool: str = typer.Option("claude", "--tool", "-t", help=f"AI coding tool to set up for ({', '.join(_SUPPORTED_TOOLS)})"),
|
|
174
325
|
) -> None:
|
|
175
|
-
"""Initialize
|
|
176
|
-
|
|
177
|
-
|
|
326
|
+
"""Initialize nexus in a project directory. Defaults to Claude Code."""
|
|
327
|
+
if tool not in _SUPPORTED_TOOLS:
|
|
328
|
+
console.print(f" [red]✗[/red] Unknown tool '{tool}'. Supported: {', '.join(_SUPPORTED_TOOLS)}")
|
|
329
|
+
raise typer.Exit(1)
|
|
178
330
|
|
|
179
|
-
|
|
180
|
-
for f in created:
|
|
181
|
-
console.print(f" [green]✓[/green] {f}")
|
|
182
|
-
|
|
183
|
-
if not created:
|
|
184
|
-
console.print(" [yellow]·[/yellow] Already initialized — nothing to do")
|
|
185
|
-
return
|
|
331
|
+
_check_and_offer_install(tool)
|
|
186
332
|
|
|
187
|
-
|
|
188
|
-
console.print(f" [
|
|
189
|
-
|
|
190
|
-
|
|
333
|
+
root = Path(project_dir).resolve()
|
|
334
|
+
console.print(f" [cyan]▶[/cyan] Initializing nexus for [bold]{tool}[/bold] in [bold]{root}[/bold]\n")
|
|
335
|
+
|
|
336
|
+
if tool == "opencode":
|
|
337
|
+
created = _init_opencode(root)
|
|
338
|
+
for f in created:
|
|
339
|
+
console.print(f" [green]✓[/green] {f}")
|
|
340
|
+
if not created:
|
|
341
|
+
console.print(" [yellow]·[/yellow] Already initialized — nothing to do")
|
|
342
|
+
return
|
|
343
|
+
console.print(f"\n [bold green]Done.[/bold green] Open [bold]{root}[/bold] in OpenCode and type [cyan]/scaffold[/cyan]\n")
|
|
344
|
+
else:
|
|
345
|
+
created = _init_project(root)
|
|
346
|
+
for f in created:
|
|
347
|
+
console.print(f" [green]✓[/green] {f}")
|
|
348
|
+
if not created:
|
|
349
|
+
console.print(" [yellow]·[/yellow] Already initialized — nothing to do")
|
|
350
|
+
return
|
|
351
|
+
mcp = _write_mcp_config(root)
|
|
352
|
+
console.print(f" [green]✓[/green] {mcp}")
|
|
353
|
+
console.print(f"\n [bold green]Done.[/bold green] Open [bold]{root}[/bold] in Claude Code and type [cyan]/scaffold[/cyan]\n")
|
|
191
354
|
|
|
192
355
|
|
|
193
356
|
@app.command()
|
|
@@ -337,6 +500,84 @@ def rule_list(
|
|
|
337
500
|
console.print(table)
|
|
338
501
|
|
|
339
502
|
|
|
503
|
+
# ── agent subcommands ─────────────────────────────────────────────────────────
|
|
504
|
+
|
|
505
|
+
_AGENT_TEMPLATE = """\
|
|
506
|
+
---
|
|
507
|
+
name: {name}
|
|
508
|
+
description: Describe when Claude should spawn this agent (role, not action — e.g. "reviews X for Y")
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
# {name}
|
|
512
|
+
|
|
513
|
+
## Role
|
|
514
|
+
|
|
515
|
+
Describe what this agent does.
|
|
516
|
+
|
|
517
|
+
## Steps
|
|
518
|
+
|
|
519
|
+
### 1 — Discover context
|
|
520
|
+
|
|
521
|
+
Read CLAUDE.md, project manifest, and relevant source files.
|
|
522
|
+
|
|
523
|
+
### 2 — Perform task
|
|
524
|
+
|
|
525
|
+
What the agent should do.
|
|
526
|
+
|
|
527
|
+
### 3 — Output
|
|
528
|
+
|
|
529
|
+
What the agent should return when done.
|
|
530
|
+
"""
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
@agent_app.command("add")
|
|
534
|
+
def agent_add(
|
|
535
|
+
name: str = typer.Argument(..., help="Agent name (e.g. 'security-reviewer')"),
|
|
536
|
+
project_dir: str = typer.Option(".", "--dir", "-d"),
|
|
537
|
+
) -> None:
|
|
538
|
+
"""Create a new subagent in .claude/agents/."""
|
|
539
|
+
root = Path(project_dir).resolve()
|
|
540
|
+
dest = root / ".claude" / "agents" / f"{name}.md"
|
|
541
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
542
|
+
|
|
543
|
+
if dest.exists():
|
|
544
|
+
console.print(f" [yellow]·[/yellow] .claude/agents/{name}.md already exists")
|
|
545
|
+
return
|
|
546
|
+
|
|
547
|
+
dest.write_text(_AGENT_TEMPLATE.format(name=name), encoding="utf-8")
|
|
548
|
+
console.print(f" [green]✓[/green] Created .claude/agents/{name}.md")
|
|
549
|
+
console.print(f" [dim]Edit the description so Claude knows when to spawn it.[/dim]")
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
@agent_app.command("list")
|
|
553
|
+
def agent_list(
|
|
554
|
+
project_dir: str = typer.Option(".", "--dir", "-d"),
|
|
555
|
+
) -> None:
|
|
556
|
+
"""List all subagents in .claude/agents/."""
|
|
557
|
+
root = Path(project_dir).resolve()
|
|
558
|
+
agents_dir = root / ".claude" / "agents"
|
|
559
|
+
|
|
560
|
+
if not agents_dir.exists():
|
|
561
|
+
console.print(" [yellow]·[/yellow] No .claude/agents/ found. Run [cyan]nexus init[/cyan] first.")
|
|
562
|
+
return
|
|
563
|
+
|
|
564
|
+
agents = sorted(agents_dir.glob("*.md"))
|
|
565
|
+
if not agents:
|
|
566
|
+
console.print(" [yellow]·[/yellow] No agents yet. Run [cyan]nexus agent add <name>[/cyan]")
|
|
567
|
+
return
|
|
568
|
+
|
|
569
|
+
table = Table(show_header=True, header_style="dim")
|
|
570
|
+
table.add_column("Agent", style="cyan")
|
|
571
|
+
table.add_column("Source")
|
|
572
|
+
|
|
573
|
+
builtins = set(_BUILTIN_AGENTS)
|
|
574
|
+
for a in agents:
|
|
575
|
+
source = "built-in" if a.name in builtins else "custom"
|
|
576
|
+
table.add_row(a.stem, source)
|
|
577
|
+
|
|
578
|
+
console.print(table)
|
|
579
|
+
|
|
580
|
+
|
|
340
581
|
def main() -> None:
|
|
341
582
|
app()
|
|
342
583
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nexus-dev-toolkit
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.2
|
|
4
4
|
Summary: Developer workflow toolkit for Claude Code — Day 0 scaffold + Day 1 EPAV
|
|
5
5
|
Author-email: Ronald dela Cruz <rcdelacruz@users.noreply.github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -80,7 +80,8 @@ uv tool install nexus-dev-toolkit
|
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
82
|
cd my-project
|
|
83
|
-
nexus init .
|
|
83
|
+
nexus init . # Claude Code (default)
|
|
84
|
+
nexus init . --tool opencode # OpenCode
|
|
84
85
|
```
|
|
85
86
|
|
|
86
87
|
### 4. Place your reference docs
|
|
@@ -137,15 +138,11 @@ Each step is a built-in skill in `.claude/commands/`. Every task starts from the
|
|
|
137
138
|
|
|
138
139
|
## What `nexus init` Creates
|
|
139
140
|
|
|
141
|
+
**Claude Code** (`nexus init .`):
|
|
140
142
|
```
|
|
141
143
|
.claude/
|
|
142
|
-
├── commands/
|
|
143
|
-
|
|
144
|
-
│ ├── evaluate.md ← /evaluate — orient on a task
|
|
145
|
-
│ ├── plan.md ← /plan — blueprint, no code
|
|
146
|
-
│ ├── apply.md ← /apply — implement the plan
|
|
147
|
-
│ ├── validate.md ← /validate — verify acceptance criteria
|
|
148
|
-
│ └── epav.md ← /epav — full cycle guide
|
|
144
|
+
├── commands/ ← EPAV skills + 5 reviewer skills (/code-review, etc.)
|
|
145
|
+
├── agents/ ← 5 reviewer subagents (code-reviewer, database-reviewer, etc.)
|
|
149
146
|
└── settings.json ← PostToolUse: graphify auto-updates after every file edit
|
|
150
147
|
knowledge/
|
|
151
148
|
├── rules/ ← coding standards, arch decisions
|
|
@@ -155,17 +152,35 @@ knowledge/
|
|
|
155
152
|
.mcp.json ← MCP server config
|
|
156
153
|
```
|
|
157
154
|
|
|
155
|
+
**OpenCode** (`nexus init . --tool opencode`):
|
|
156
|
+
```
|
|
157
|
+
.opencode/
|
|
158
|
+
├── commands/ ← same EPAV skills + reviewer skills
|
|
159
|
+
├── agents/ ← same reviewer subagents (adapted for OpenCode)
|
|
160
|
+
└── plugins/
|
|
161
|
+
└── graphify.js ← tool.execute.after: graphify auto-update
|
|
162
|
+
knowledge/ ← same structure
|
|
163
|
+
opencode.json ← MCP server config
|
|
164
|
+
```
|
|
165
|
+
|
|
158
166
|
---
|
|
159
167
|
|
|
160
168
|
## Commands
|
|
161
169
|
|
|
162
170
|
```bash
|
|
163
|
-
nexus init .
|
|
164
|
-
nexus
|
|
165
|
-
nexus
|
|
166
|
-
nexus
|
|
167
|
-
|
|
168
|
-
nexus
|
|
171
|
+
nexus init . # Claude Code — set up .claude/ + knowledge/ + .mcp.json
|
|
172
|
+
nexus init . --tool opencode # OpenCode — set up .opencode/ + knowledge/ + opencode.json
|
|
173
|
+
nexus --version # show installed version
|
|
174
|
+
nexus update # update to latest version
|
|
175
|
+
|
|
176
|
+
nexus skill list # list all skills in .claude/commands/
|
|
177
|
+
nexus skill add my-review # create a custom skill
|
|
178
|
+
|
|
179
|
+
nexus agent list # list all subagents in .claude/agents/
|
|
180
|
+
nexus agent add my-agent # create a custom subagent
|
|
181
|
+
|
|
182
|
+
nexus rule list # list all rules in knowledge/rules/
|
|
183
|
+
nexus rule add api-standards # create a project rule
|
|
169
184
|
```
|
|
170
185
|
|
|
171
186
|
---
|
|
@@ -103,3 +103,50 @@ def test_rule_list(tmp_project):
|
|
|
103
103
|
result = runner.invoke(app, ["rule", "list", "--dir", str(tmp_project)])
|
|
104
104
|
assert result.exit_code == 0
|
|
105
105
|
assert "api-standards" in result.output
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_init_opencode_creates_commands(tmp_project):
|
|
109
|
+
result = runner.invoke(app, ["init", str(tmp_project), "--tool", "opencode"])
|
|
110
|
+
assert result.exit_code == 0
|
|
111
|
+
commands_dir = tmp_project / ".opencode" / "commands"
|
|
112
|
+
assert commands_dir.exists()
|
|
113
|
+
for skill in _BUILTIN_SKILLS:
|
|
114
|
+
assert (commands_dir / skill).exists(), f"Missing skill: {skill}"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_init_opencode_creates_agents(tmp_project):
|
|
118
|
+
result = runner.invoke(app, ["init", str(tmp_project), "--tool", "opencode"])
|
|
119
|
+
assert result.exit_code == 0
|
|
120
|
+
agents_dir = tmp_project / ".opencode" / "agents"
|
|
121
|
+
assert agents_dir.exists()
|
|
122
|
+
for agent in _BUILTIN_AGENTS:
|
|
123
|
+
assert (agents_dir / agent).exists(), f"Missing agent: {agent}"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_init_opencode_creates_plugin(tmp_project):
|
|
127
|
+
runner.invoke(app, ["init", str(tmp_project), "--tool", "opencode"])
|
|
128
|
+
plugin = tmp_project / ".opencode" / "plugins" / "graphify.js"
|
|
129
|
+
assert plugin.exists()
|
|
130
|
+
assert "tool.execute.after" in plugin.read_text()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_init_opencode_creates_opencode_json(tmp_project):
|
|
134
|
+
runner.invoke(app, ["init", str(tmp_project), "--tool", "opencode"])
|
|
135
|
+
config = tmp_project / "opencode.json"
|
|
136
|
+
assert config.exists()
|
|
137
|
+
data = json.loads(config.read_text())
|
|
138
|
+
assert "nexus-mcp" in data["mcp"]
|
|
139
|
+
assert data["mcp"]["nexus-mcp"]["type"] == "local"
|
|
140
|
+
assert data["mcp"]["nexus-mcp"]["command"][0] == "uvx"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_init_opencode_idempotent(tmp_project):
|
|
144
|
+
runner.invoke(app, ["init", str(tmp_project), "--tool", "opencode"])
|
|
145
|
+
result = runner.invoke(app, ["init", str(tmp_project), "--tool", "opencode"])
|
|
146
|
+
assert result.exit_code == 0
|
|
147
|
+
assert "Already initialized" in result.output
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_init_unknown_tool(tmp_project):
|
|
151
|
+
result = runner.invoke(app, ["init", str(tmp_project), "--tool", "cursor"])
|
|
152
|
+
assert result.exit_code == 1
|
|
File without changes
|
|
File without changes
|
{nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_dev_toolkit.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_dev_toolkit.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_dev_toolkit.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|