nurosys-agents 2.0.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.
- package/.agent/INSTRUCTIONS.md +82 -0
- package/.agent/README.md +483 -0
- package/.agent/backend/skills/architect/SKILL.md +436 -0
- package/.agent/backend/skills/auth-and-permissions/SKILL.md +168 -0
- package/.agent/backend/skills/brainstorm/SKILL.md +127 -0
- package/.agent/backend/skills/code-reviewer/SKILL.md +324 -0
- package/.agent/backend/skills/create-blueprint/SKILL.md +650 -0
- package/.agent/backend/skills/debug-issue/SKILL.md +53 -0
- package/.agent/backend/skills/explore-codebase/SKILL.md +45 -0
- package/.agent/backend/skills/quick-execute/SKILL.md +99 -0
- package/.agent/backend/skills/refactor-safely/SKILL.md +46 -0
- package/.agent/backend/skills/security-assessment/SKILL.md +174 -0
- package/.agent/backend/workflows/module-runner.claude.md +226 -0
- package/.agent/backend/workflows/module-runner.codex.md +155 -0
- package/.agent/backend/workflows/module-runner.cursor.md +212 -0
- package/.agent/frontend/skills/architect/SKILL.md +644 -0
- package/.agent/frontend/skills/auth-and-permissions/SKILL.md +43 -0
- package/.agent/frontend/skills/create-blueprint/SKILL.md +635 -0
- package/.agent/frontend/skills/debug-issue/SKILL.md +28 -0
- package/.agent/frontend/skills/explore-codebase/SKILL.md +29 -0
- package/.agent/frontend/skills/feature-workflow/SKILL.md +61 -0
- package/.agent/frontend/skills/react-quality-review/SKILL.md +126 -0
- package/.agent/frontend/skills/react-quality-review/examples.md +24 -0
- package/.agent/frontend/skills/react-quality-review/rules/_sections.md +26 -0
- package/.agent/frontend/skills/react-quality-review/rules/_template.md +28 -0
- package/.agent/frontend/skills/react-quality-review/rules/advanced-event-handler-refs.md +55 -0
- package/.agent/frontend/skills/react-quality-review/rules/advanced-init-once.md +42 -0
- package/.agent/frontend/skills/react-quality-review/rules/react-rules-calling.md +66 -0
- package/.agent/frontend/skills/react-quality-review/rules/react-rules-hooks.md +91 -0
- package/.agent/frontend/skills/react-quality-review/rules/react-rules-purity.md +121 -0
- package/.agent/frontend/skills/react-quality-review/rules/rendering-activity.md +26 -0
- package/.agent/frontend/skills/react-quality-review/rules/rendering-conditional-render.md +40 -0
- package/.agent/frontend/skills/react-quality-review/rules/rendering-content-visibility.md +38 -0
- package/.agent/frontend/skills/react-quality-review/rules/rendering-hoist-jsx.md +46 -0
- package/.agent/frontend/skills/react-quality-review/rules/rendering-usetransition-loading.md +75 -0
- package/.agent/frontend/skills/react-quality-review/rules/rerender-defer-reads.md +39 -0
- package/.agent/frontend/skills/react-quality-review/rules/rerender-dependencies.md +45 -0
- package/.agent/frontend/skills/react-quality-review/rules/rerender-derived-state-no-effect.md +40 -0
- package/.agent/frontend/skills/react-quality-review/rules/rerender-derived-state.md +29 -0
- package/.agent/frontend/skills/react-quality-review/rules/rerender-functional-setstate.md +74 -0
- package/.agent/frontend/skills/react-quality-review/rules/rerender-lazy-state-init.md +58 -0
- package/.agent/frontend/skills/react-quality-review/rules/rerender-memo-with-default-value.md +38 -0
- package/.agent/frontend/skills/react-quality-review/rules/rerender-memo.md +44 -0
- package/.agent/frontend/skills/react-quality-review/rules/rerender-move-effect-to-event.md +45 -0
- package/.agent/frontend/skills/react-quality-review/rules/rerender-no-inline-components.md +82 -0
- package/.agent/frontend/skills/react-quality-review/rules/rerender-simple-expression-in-memo.md +35 -0
- package/.agent/frontend/skills/react-quality-review/rules/rerender-transitions.md +40 -0
- package/.agent/frontend/skills/react-quality-review/rules/rerender-use-ref-transient-values.md +73 -0
- package/.agent/frontend/skills/refactor-safely/SKILL.md +29 -0
- package/.agent/frontend/skills/vuexy-component-guide/SKILL.md +369 -0
- package/.agent/frontend/skills/vuexy-component-guide/examples.md +662 -0
- package/.agent/frontend/skills/vuexy-component-guide/reference.md +1036 -0
- package/.agent/frontend/workflows/build-feature-react.workflow.md +82 -0
- package/.agent/frontend/workflows/feature-module-runner.md +101 -0
- package/.agent/monolith/skills/architect/SKILL.md +648 -0
- package/.agent/monolith/skills/auth-and-permissions/SKILL.md +43 -0
- package/.agent/monolith/skills/code-reviewer/SKILL.md +281 -0
- package/.agent/monolith/skills/create-blueprint/SKILL.md +635 -0
- package/.agent/monolith/skills/debug-issue/SKILL.md +28 -0
- package/.agent/monolith/skills/explore-codebase/SKILL.md +29 -0
- package/.agent/monolith/skills/feature-workflow/SKILL.md +61 -0
- package/.agent/monolith/skills/react-quality-review/SKILL.md +126 -0
- package/.agent/monolith/skills/react-quality-review/examples.md +24 -0
- package/.agent/monolith/skills/react-quality-review/rules/_sections.md +26 -0
- package/.agent/monolith/skills/react-quality-review/rules/_template.md +28 -0
- package/.agent/monolith/skills/react-quality-review/rules/advanced-event-handler-refs.md +55 -0
- package/.agent/monolith/skills/react-quality-review/rules/advanced-init-once.md +42 -0
- package/.agent/monolith/skills/react-quality-review/rules/react-rules-calling.md +66 -0
- package/.agent/monolith/skills/react-quality-review/rules/react-rules-hooks.md +91 -0
- package/.agent/monolith/skills/react-quality-review/rules/react-rules-purity.md +121 -0
- package/.agent/monolith/skills/react-quality-review/rules/rendering-activity.md +26 -0
- package/.agent/monolith/skills/react-quality-review/rules/rendering-conditional-render.md +40 -0
- package/.agent/monolith/skills/react-quality-review/rules/rendering-content-visibility.md +38 -0
- package/.agent/monolith/skills/react-quality-review/rules/rendering-hoist-jsx.md +46 -0
- package/.agent/monolith/skills/react-quality-review/rules/rendering-usetransition-loading.md +75 -0
- package/.agent/monolith/skills/react-quality-review/rules/rerender-defer-reads.md +39 -0
- package/.agent/monolith/skills/react-quality-review/rules/rerender-dependencies.md +45 -0
- package/.agent/monolith/skills/react-quality-review/rules/rerender-derived-state-no-effect.md +40 -0
- package/.agent/monolith/skills/react-quality-review/rules/rerender-derived-state.md +29 -0
- package/.agent/monolith/skills/react-quality-review/rules/rerender-functional-setstate.md +74 -0
- package/.agent/monolith/skills/react-quality-review/rules/rerender-lazy-state-init.md +58 -0
- package/.agent/monolith/skills/react-quality-review/rules/rerender-memo-with-default-value.md +38 -0
- package/.agent/monolith/skills/react-quality-review/rules/rerender-memo.md +44 -0
- package/.agent/monolith/skills/react-quality-review/rules/rerender-move-effect-to-event.md +45 -0
- package/.agent/monolith/skills/react-quality-review/rules/rerender-no-inline-components.md +82 -0
- package/.agent/monolith/skills/react-quality-review/rules/rerender-simple-expression-in-memo.md +35 -0
- package/.agent/monolith/skills/react-quality-review/rules/rerender-transitions.md +40 -0
- package/.agent/monolith/skills/react-quality-review/rules/rerender-use-ref-transient-values.md +73 -0
- package/.agent/monolith/skills/refactor-safely/SKILL.md +29 -0
- package/.agent/monolith/skills/vuexy-component-guide/SKILL.md +369 -0
- package/.agent/monolith/skills/vuexy-component-guide/examples.md +662 -0
- package/.agent/monolith/skills/vuexy-component-guide/reference.md +1036 -0
- package/.agent/monolith/workflows/add-new-api-feature-module.md +63 -0
- package/.agent/monolith/workflows/backend-quality-review.md +27 -0
- package/.agent/monolith/workflows/build-feature-backend.workflow.md +91 -0
- package/.agent/monolith/workflows/build-feature-react.workflow.md +82 -0
- package/.agent/monolith/workflows/feature-module-runner.md +97 -0
- package/.agent/templates/FEATURE_PLAN.md +42 -0
- package/.agent/templates/MODULE.md +45 -0
- package/.agent/templates/REVIEW_REPORT.md +44 -0
- package/.agent/templates/SECURITY_REPORT.md +70 -0
- package/.agent/templates/TEST_PLAN.md +49 -0
- package/README.md +131 -0
- package/package.json +42 -0
- package/scripts/setup-rules.js +224 -0
- package/scripts/setup.js +518 -0
package/scripts/setup.js
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnSync, execSync } = require('child_process');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
|
|
8
|
+
const { runSetupRules } = require('./setup-rules.js');
|
|
9
|
+
|
|
10
|
+
const projectRoot = process.cwd();
|
|
11
|
+
|
|
12
|
+
function resolvePackagePath() {
|
|
13
|
+
try { return path.dirname(require.resolve('nurosys-agents/package.json')); }
|
|
14
|
+
catch { return path.resolve(__dirname, '..'); }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const packagePath = resolvePackagePath();
|
|
18
|
+
|
|
19
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function ask(q) {
|
|
22
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
23
|
+
return new Promise(res => rl.question(q, a => { rl.close(); res(a.trim().toLowerCase()); }));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Interactive checkbox multiselect — arrow keys to move, space to toggle, enter to confirm
|
|
27
|
+
function multiselect(title, options) {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
let cursor = 0;
|
|
30
|
+
const selected = new Set();
|
|
31
|
+
|
|
32
|
+
function render() {
|
|
33
|
+
if (render.drawn) process.stdout.write(`\x1B[${options.length + 2}A`);
|
|
34
|
+
render.drawn = true;
|
|
35
|
+
|
|
36
|
+
process.stdout.write(`\n ${title}\n`);
|
|
37
|
+
options.forEach((opt, i) => {
|
|
38
|
+
const check = selected.has(i) ? '◉' : '○';
|
|
39
|
+
const active = i === cursor ? '\x1B[36m›\x1B[0m' : ' ';
|
|
40
|
+
const label = i === cursor ? `\x1B[36m${opt.label}\x1B[0m` : opt.label;
|
|
41
|
+
process.stdout.write(` ${active} ${check} ${label}\n`);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
render.drawn = false;
|
|
46
|
+
render();
|
|
47
|
+
|
|
48
|
+
readline.emitKeypressEvents(process.stdin);
|
|
49
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
50
|
+
|
|
51
|
+
process.stdin.on('keypress', function handler(_, key) {
|
|
52
|
+
if (!key) return;
|
|
53
|
+
|
|
54
|
+
if (key.name === 'up') cursor = (cursor - 1 + options.length) % options.length;
|
|
55
|
+
if (key.name === 'down') cursor = (cursor + 1) % options.length;
|
|
56
|
+
if (key.name === 'space') selected.has(cursor) ? selected.delete(cursor) : selected.add(cursor);
|
|
57
|
+
|
|
58
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
59
|
+
process.stdin.removeListener('keypress', handler);
|
|
60
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
61
|
+
process.stdout.write('\n');
|
|
62
|
+
resolve(options.filter((_, i) => selected.has(i)).map(o => o.value));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (key.ctrl && key.name === 'c') process.exit(0);
|
|
67
|
+
|
|
68
|
+
render();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function commandExists(cmd) {
|
|
74
|
+
return spawnSync(cmd, ['--version'], { stdio: 'pipe', shell: true }).status === 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function run(cmd, opts = {}) {
|
|
78
|
+
execSync(cmd, { stdio: 'inherit', ...opts });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isSymlink(p) {
|
|
82
|
+
try { return fs.lstatSync(p).isSymbolicLink(); } catch { return false; }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function symlink(source, target, label, type = 'dir') {
|
|
86
|
+
const exists = fs.existsSync(target) || isSymlink(target);
|
|
87
|
+
if (exists) {
|
|
88
|
+
if (isSymlink(target)) { console.log(` ✅ ${label} (already set)`); return; }
|
|
89
|
+
console.warn(` ⚠️ ${label} — exists as real ${type}, skipping`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const parent = path.dirname(target);
|
|
93
|
+
if (!fs.existsSync(parent)) fs.mkdirSync(parent, { recursive: true });
|
|
94
|
+
fs.symlinkSync(source, target, type);
|
|
95
|
+
console.log(` ✅ ${label}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function writeJSON(filePath, data, label) {
|
|
99
|
+
const content = JSON.stringify(data, null, 2);
|
|
100
|
+
if (fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf8') === content) {
|
|
101
|
+
console.log(` ✅ ${label} (already up to date)`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
105
|
+
fs.writeFileSync(filePath, content);
|
|
106
|
+
console.log(` ✅ ${label}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function writeFile(filePath, content, label) {
|
|
110
|
+
if (fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf8') === content) {
|
|
111
|
+
console.log(` ✅ ${label} (already up to date)`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
115
|
+
fs.writeFileSync(filePath, content);
|
|
116
|
+
console.log(` ✅ ${label}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ─── Stack detection ──────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
const FRONTEND_DEPS = ['react', 'react-dom', 'next', 'vite', 'vue', 'nuxt', 'svelte', '@angular/core', 'gatsby', '@remix-run/react'];
|
|
122
|
+
const BACKEND_DEPS = ['@nestjs/core', 'express', 'fastify', 'koa', '@hapi/hapi', 'restify'];
|
|
123
|
+
|
|
124
|
+
function detectStack() {
|
|
125
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
126
|
+
if (!fs.existsSync(pkgPath)) return 'unknown';
|
|
127
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
128
|
+
const deps = Object.keys({ ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) });
|
|
129
|
+
const fe = FRONTEND_DEPS.some(d => deps.includes(d));
|
|
130
|
+
const be = BACKEND_DEPS.some(d => deps.includes(d));
|
|
131
|
+
if (fe && be) return 'monolith';
|
|
132
|
+
if (fe) return 'frontend';
|
|
133
|
+
if (be) return 'backend';
|
|
134
|
+
return 'unknown';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const STACK_LABEL = { backend: 'Backend (Node/NestJS)', frontend: 'Frontend (React/Vue/Vite)', monolith: 'Monolith (full-stack)', unknown: 'Unknown' };
|
|
138
|
+
|
|
139
|
+
// ─── Source paths (in package) ────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
const pkgAgent = path.join(packagePath, '.agent');
|
|
142
|
+
const pkgInstructions = path.join(pkgAgent, 'INSTRUCTIONS.md');
|
|
143
|
+
|
|
144
|
+
function pkgSkills(stack) { return path.join(pkgAgent, stack, 'skills'); }
|
|
145
|
+
function pkgWorkflows(stack) { return path.join(pkgAgent, stack, 'workflows'); }
|
|
146
|
+
|
|
147
|
+
// Relative path helper — avoids absolute paths in symlinks so they survive moves
|
|
148
|
+
function rel(from, to) { return path.relative(path.dirname(from), to); }
|
|
149
|
+
|
|
150
|
+
// Pick the IDE-specific module-runner variant
|
|
151
|
+
function moduleRunnerSource(stack, ide) {
|
|
152
|
+
const variant = { claude: 'module-runner.claude.md', cursor: 'module-runner.cursor.md', codex: 'module-runner.codex.md' }[ide];
|
|
153
|
+
if (!variant) return null;
|
|
154
|
+
const candidate = path.join(pkgWorkflows(stack), variant);
|
|
155
|
+
return fs.existsSync(candidate) ? candidate : null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ─── Serena MCP config ────────────────────────────────────────────────────────
|
|
159
|
+
// Serena replaces code-review-graph as the symbolic code intelligence MCP.
|
|
160
|
+
// Install: uvx --from git+https://github.com/oraios/serena serena start-mcp-server --project=.
|
|
161
|
+
|
|
162
|
+
const SERENA_MCP = {
|
|
163
|
+
command: 'uvx',
|
|
164
|
+
args: ['--from', 'git+https://github.com/oraios/serena', 'serena', 'start-mcp-server', '--project=.'],
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// ─── IDE setup ────────────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
function setupClaudeCode(stack) {
|
|
170
|
+
console.log('\n── Claude Code ───────────────────────────────────────────────');
|
|
171
|
+
// Symlink each skill DIRECTORY so supporting files come along.
|
|
172
|
+
const skillsDir = pkgSkills(stack);
|
|
173
|
+
const claudeSkillsDir = path.join(projectRoot, '.claude/skills');
|
|
174
|
+
if (fs.existsSync(skillsDir)) {
|
|
175
|
+
fs.mkdirSync(claudeSkillsDir, { recursive: true });
|
|
176
|
+
for (const skillName of fs.readdirSync(skillsDir).sort()) {
|
|
177
|
+
const skillSrc = path.join(skillsDir, skillName);
|
|
178
|
+
const skillMd = path.join(skillSrc, 'SKILL.md');
|
|
179
|
+
if (fs.existsSync(skillMd) && fs.statSync(skillSrc).isDirectory()) {
|
|
180
|
+
const target = path.join(claudeSkillsDir, skillName);
|
|
181
|
+
symlink(rel(target, skillSrc), target, `.claude/skills/${skillName}/`, 'dir');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Symlink only the Claude Code variant of module-runner as the canonical module-runner.md
|
|
187
|
+
const runnerSrc = moduleRunnerSource(stack, 'claude');
|
|
188
|
+
const claudeAgentsDir = path.join(projectRoot, '.claude/agents');
|
|
189
|
+
if (runnerSrc) {
|
|
190
|
+
fs.mkdirSync(claudeAgentsDir, { recursive: true });
|
|
191
|
+
const target = path.join(claudeAgentsDir, 'module-runner.md');
|
|
192
|
+
symlink(rel(target, runnerSrc), target, '.claude/agents/module-runner.md (Claude variant)', 'file');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
symlink(rel(path.join(projectRoot, 'CLAUDE.md'), pkgInstructions), path.join(projectRoot, 'CLAUDE.md'), 'CLAUDE.md → INSTRUCTIONS.md', 'file');
|
|
196
|
+
symlink(rel(path.join(projectRoot, 'AGENTS.md'), pkgInstructions), path.join(projectRoot, 'AGENTS.md'), 'AGENTS.md → INSTRUCTIONS.md', 'file');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Parse a single field from YAML frontmatter (--- ... ---)
|
|
200
|
+
function parseFrontmatterField(content, field) {
|
|
201
|
+
const m = content.match(/^---\n([\s\S]*?)\n---/);
|
|
202
|
+
if (!m) return null;
|
|
203
|
+
const line = m[1].split('\n').find(l => l.startsWith(`${field}:`));
|
|
204
|
+
if (!line) return null;
|
|
205
|
+
return line.slice(field.length + 1).trim().replace(/^["']|["']$/g, '');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Write a .mdc file; skips if already up to date (safe to re-run after package update)
|
|
209
|
+
function writeMdc(rulesDir, filename, { description, alwaysApply, body }) {
|
|
210
|
+
const content = [
|
|
211
|
+
'---',
|
|
212
|
+
`description: "${description}"`,
|
|
213
|
+
`alwaysApply: ${alwaysApply}`,
|
|
214
|
+
'---',
|
|
215
|
+
'',
|
|
216
|
+
body,
|
|
217
|
+
].join('\n');
|
|
218
|
+
const dest = path.join(rulesDir, filename);
|
|
219
|
+
if (fs.existsSync(dest) && fs.readFileSync(dest, 'utf8') === content) {
|
|
220
|
+
console.log(` ✅ .cursor/rules/${filename} (already up to date)`);
|
|
221
|
+
} else {
|
|
222
|
+
fs.writeFileSync(dest, content);
|
|
223
|
+
console.log(` ✅ .cursor/rules/${filename}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function setupCursor(stack) {
|
|
228
|
+
console.log('\n── Cursor ────────────────────────────────────────────────────');
|
|
229
|
+
|
|
230
|
+
// Project-level MCP config for Serena
|
|
231
|
+
writeJSON(
|
|
232
|
+
path.join(projectRoot, '.cursor/mcp.json'),
|
|
233
|
+
{ mcpServers: { serena: SERENA_MCP } },
|
|
234
|
+
'.cursor/mcp.json → Serena MCP'
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const rulesDir = path.join(projectRoot, '.cursor', 'rules');
|
|
238
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
239
|
+
|
|
240
|
+
// MDC rule — Serena (always apply: prefer symbol-level analysis over grep/read)
|
|
241
|
+
writeMdc(rulesDir, 'serena.mdc', {
|
|
242
|
+
description: 'Symbolic code analysis via Serena — prefer get_symbols_overview / find_symbol / find_referencing_symbols over grep/read',
|
|
243
|
+
alwaysApply: true,
|
|
244
|
+
body: fs.readFileSync(pkgInstructions, 'utf8'),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// MDC rules — one per skill (description-triggered, not always-on)
|
|
248
|
+
const skillsDir = pkgSkills(stack);
|
|
249
|
+
if (fs.existsSync(skillsDir)) {
|
|
250
|
+
for (const skillName of fs.readdirSync(skillsDir).sort()) {
|
|
251
|
+
const skillFile = path.join(skillsDir, skillName, 'SKILL.md');
|
|
252
|
+
if (!fs.existsSync(skillFile)) continue;
|
|
253
|
+
const skillContent = fs.readFileSync(skillFile, 'utf8');
|
|
254
|
+
const description = parseFrontmatterField(skillContent, 'description') || `${skillName} skill`;
|
|
255
|
+
writeMdc(rulesDir, `${skillName}.mdc`, { description, alwaysApply: false, body: skillContent });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Module-runner — Cursor variant. Also wire Cursor subagents the runner depends on.
|
|
260
|
+
setupCursorSubagents(stack);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Cursor subagents (.cursor/subagents/*.md) used by /module-runner (Cursor variant).
|
|
264
|
+
// Each is a thin wrapper that delegates to a SKILL.md in this package.
|
|
265
|
+
function setupCursorSubagents(stack) {
|
|
266
|
+
const subagentsDir = path.join(projectRoot, '.cursor', 'subagents');
|
|
267
|
+
fs.mkdirSync(subagentsDir, { recursive: true });
|
|
268
|
+
|
|
269
|
+
const SUBAGENTS = [
|
|
270
|
+
{ name: 'implementer', delegateTo: null, purpose: 'Implement a single module from its prompt file. Read documentation/features/<feature>/<MODULE>.md and produce code per its specification. Return JSON { files_changed, summary, blockers }.' },
|
|
271
|
+
{ name: 'code-reviewer', delegateTo: 'code-reviewer', purpose: 'Run /code-reviewer in sub-agent mode on the diff. Return findings JSON per the skill\'s sub-agent contract.' },
|
|
272
|
+
{ name: 'code-fixer', delegateTo: null, purpose: 'Apply BLOCKER/HIGH/required-MED findings from a /code-reviewer JSON. Return JSON { fixed, skipped, summary }.' },
|
|
273
|
+
{ name: 'security-auditor', delegateTo: 'security-assessment', purpose: 'Run /security-assessment in sub-agent mode on the diff. Return findings JSON.' },
|
|
274
|
+
{ name: 'security-fixer', delegateTo: null, purpose: 'Apply CRITICAL/HIGH findings from a /security-assessment JSON. Return JSON { fixed, skipped, summary }.' },
|
|
275
|
+
{ name: 'build-test-fixer', delegateTo: null, purpose: 'Fix build or test failures given failure output and the recently changed files. Return JSON { fixed, summary }.' },
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
for (const sa of SUBAGENTS) {
|
|
279
|
+
const file = path.join(subagentsDir, `${sa.name}.md`);
|
|
280
|
+
const delegateNote = sa.delegateTo
|
|
281
|
+
? `\nDelegate to the \`${sa.delegateTo}\` skill (.cursor/rules/${sa.delegateTo}.mdc). Follow its sub-agent mode contract.\n`
|
|
282
|
+
: '';
|
|
283
|
+
const content = [
|
|
284
|
+
'---',
|
|
285
|
+
`name: ${sa.name}`,
|
|
286
|
+
`description: "${sa.purpose}"`,
|
|
287
|
+
'---',
|
|
288
|
+
'',
|
|
289
|
+
`# Subagent: ${sa.name}`,
|
|
290
|
+
'',
|
|
291
|
+
sa.purpose,
|
|
292
|
+
delegateNote,
|
|
293
|
+
'## Constraints',
|
|
294
|
+
'- Independent context — the parent agent has provided everything you need in the prompt.',
|
|
295
|
+
'- Return structured JSON output when the parent requests it.',
|
|
296
|
+
'- Do not commit, push, or change branches.',
|
|
297
|
+
'- Use Serena for symbol-level edits where possible (`rename_symbol`, `replace_symbol_body`, `safe_delete_symbol`).',
|
|
298
|
+
'',
|
|
299
|
+
].join('\n');
|
|
300
|
+
writeFile(file, content, `.cursor/subagents/${sa.name}.md`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function setupAntigravity(stack) {
|
|
305
|
+
console.log('\n── Antigravity ───────────────────────────────────────────────');
|
|
306
|
+
symlink(rel(path.join(projectRoot, 'GEMINI.md'), pkgInstructions), path.join(projectRoot, 'GEMINI.md'), 'GEMINI.md → INSTRUCTIONS.md', 'file');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function setupCodexDefaultRules() {
|
|
310
|
+
const rulesDir = path.join(projectRoot, '.codex', 'rules');
|
|
311
|
+
const rulesFile = path.join(rulesDir, 'default.rules');
|
|
312
|
+
|
|
313
|
+
const content = [
|
|
314
|
+
'# Codex execution policy — generated by nurosys-agent-setup',
|
|
315
|
+
'# Docs: https://developers.openai.com/codex/rules',
|
|
316
|
+
'',
|
|
317
|
+
'prefix_rule(',
|
|
318
|
+
' pattern = "npm",',
|
|
319
|
+
' decision = "allow",',
|
|
320
|
+
' justification = "Allow npm package management",',
|
|
321
|
+
')',
|
|
322
|
+
'',
|
|
323
|
+
'prefix_rule(',
|
|
324
|
+
' pattern = "git",',
|
|
325
|
+
' decision = "allow",',
|
|
326
|
+
' justification = "Allow git version control",',
|
|
327
|
+
')',
|
|
328
|
+
'',
|
|
329
|
+
'prefix_rule(',
|
|
330
|
+
' pattern = "uvx",',
|
|
331
|
+
' decision = "allow",',
|
|
332
|
+
' justification = "Allow uvx (for Serena MCP server)",',
|
|
333
|
+
')',
|
|
334
|
+
'',
|
|
335
|
+
'prefix_rule(',
|
|
336
|
+
' pattern = "node",',
|
|
337
|
+
' decision = "allow",',
|
|
338
|
+
' justification = "Allow node script execution",',
|
|
339
|
+
')',
|
|
340
|
+
].join('\n') + '\n';
|
|
341
|
+
|
|
342
|
+
writeFile(rulesFile, content, '.codex/rules/default.rules');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function setupCodex(stack) {
|
|
346
|
+
console.log('\n── Codex ─────────────────────────────────────────────────');
|
|
347
|
+
symlink(rel(path.join(projectRoot, 'AGENTS.md'), pkgInstructions), path.join(projectRoot, 'AGENTS.md'), 'AGENTS.md → INSTRUCTIONS.md', 'file');
|
|
348
|
+
setupCodexDefaultRules();
|
|
349
|
+
|
|
350
|
+
// Symlink Codex variant of module-runner into a Codex-discoverable location.
|
|
351
|
+
// Codex reads AGENTS.md for project-level instructions; we also drop the runner doc
|
|
352
|
+
// alongside so users can reference it explicitly.
|
|
353
|
+
const runnerSrc = moduleRunnerSource(stack, 'codex');
|
|
354
|
+
if (runnerSrc) {
|
|
355
|
+
const target = path.join(projectRoot, '.codex', 'workflows', 'module-runner.md');
|
|
356
|
+
symlink(rel(target, runnerSrc), target, '.codex/workflows/module-runner.md (Codex variant)', 'file');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ─── Serena ──────────────────────────────────────────────────────────────────
|
|
361
|
+
|
|
362
|
+
function checkUvx() {
|
|
363
|
+
return commandExists('uvx');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function setupSerena(ides) {
|
|
367
|
+
console.log('\n── Serena (MCP for symbolic code analysis) ───────────────────');
|
|
368
|
+
console.log(' ℹ️ Skills use Serena for symbol-level navigation and edits.');
|
|
369
|
+
console.log(' https://github.com/oraios/serena\n');
|
|
370
|
+
|
|
371
|
+
if (!checkUvx()) {
|
|
372
|
+
console.log(' ⚠️ `uvx` not found. Serena runs via uvx (part of the `uv` Python toolchain).');
|
|
373
|
+
console.log(' Install: https://docs.astral.sh/uv/getting-started/installation/');
|
|
374
|
+
const ans = await ask(' ❓ Continue setup without verifying Serena? [y/N] ');
|
|
375
|
+
if (ans !== 'y' && ans !== 'yes') return;
|
|
376
|
+
} else {
|
|
377
|
+
console.log(' ✅ uvx available — Serena will run on demand via the configured MCP entry');
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Cursor MCP already written by setupCursor()
|
|
381
|
+
if (ides.includes('cursor')) {
|
|
382
|
+
console.log(' ✅ Cursor MCP configured via .cursor/mcp.json');
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Claude Code — write or merge into project-level .mcp.json
|
|
386
|
+
if (ides.includes('claude')) {
|
|
387
|
+
const claudeMcp = path.join(projectRoot, '.mcp.json');
|
|
388
|
+
let existing = {};
|
|
389
|
+
if (fs.existsSync(claudeMcp)) {
|
|
390
|
+
try { existing = JSON.parse(fs.readFileSync(claudeMcp, 'utf8')); } catch {}
|
|
391
|
+
}
|
|
392
|
+
existing.mcpServers = existing.mcpServers || {};
|
|
393
|
+
if (!existing.mcpServers.serena) {
|
|
394
|
+
existing.mcpServers.serena = SERENA_MCP;
|
|
395
|
+
fs.writeFileSync(claudeMcp, JSON.stringify(existing, null, 2));
|
|
396
|
+
console.log(' ✅ Claude Code MCP configured (.mcp.json)');
|
|
397
|
+
} else {
|
|
398
|
+
console.log(' ✅ Claude Code MCP already configured (.mcp.json)');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Codex — write to ~/.codex/config.toml
|
|
403
|
+
if (ides.includes('codex')) {
|
|
404
|
+
const configDir = path.join(process.env.HOME || '', '.codex');
|
|
405
|
+
const configFile = path.join(configDir, 'config.toml');
|
|
406
|
+
const existing = fs.existsSync(configFile) ? fs.readFileSync(configFile, 'utf8') : '';
|
|
407
|
+
if (existing.includes('[mcp_servers.serena]')) {
|
|
408
|
+
console.log(' ✅ Codex MCP already configured (~/.codex/config.toml)');
|
|
409
|
+
} else {
|
|
410
|
+
const ans = await ask(' ❓ Add Serena MCP to ~/.codex/config.toml? [y/N] ');
|
|
411
|
+
if (ans === 'y' || ans === 'yes') {
|
|
412
|
+
const entry = `\n[mcp_servers.serena]\ncommand = "${SERENA_MCP.command}"\nargs = ${JSON.stringify(SERENA_MCP.args)}\n`;
|
|
413
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
414
|
+
fs.writeFileSync(configFile, existing + entry);
|
|
415
|
+
console.log(' ✅ Codex MCP configured (~/.codex/config.toml)');
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ─── .gitignore ───────────────────────────────────────────────────────────────
|
|
422
|
+
|
|
423
|
+
function setupGitignore(ides) {
|
|
424
|
+
console.log('\n── .gitignore ────────────────────────────────────────────────');
|
|
425
|
+
|
|
426
|
+
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
427
|
+
const entries = ['# nurosys-agents (generated — do not commit)'];
|
|
428
|
+
|
|
429
|
+
entries.push('.agents/rules');
|
|
430
|
+
|
|
431
|
+
if (ides.includes('claude')) {
|
|
432
|
+
entries.push('.claude/skills', '.claude/agents', '.mcp.json', 'CLAUDE.md', 'AGENTS.md');
|
|
433
|
+
}
|
|
434
|
+
if (ides.includes('cursor')) {
|
|
435
|
+
entries.push('.cursor/rules', '.cursor/subagents', '.cursor/mcp.json');
|
|
436
|
+
}
|
|
437
|
+
if (ides.includes('antigravity')) {
|
|
438
|
+
entries.push('GEMINI.md');
|
|
439
|
+
}
|
|
440
|
+
if (ides.includes('codex')) {
|
|
441
|
+
entries.push('.codex/rules', '.codex/workflows');
|
|
442
|
+
if (!ides.includes('claude')) entries.push('AGENTS.md');
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const existing = fs.existsSync(gitignorePath)
|
|
446
|
+
? fs.readFileSync(gitignorePath, 'utf8')
|
|
447
|
+
: '';
|
|
448
|
+
|
|
449
|
+
const toAdd = entries.filter(e => e.startsWith('#') || !existing.split('\n').includes(e));
|
|
450
|
+
|
|
451
|
+
if (toAdd.length <= 1) {
|
|
452
|
+
console.log(' ✅ .gitignore already up to date');
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const block = '\n' + toAdd.join('\n') + '\n';
|
|
457
|
+
fs.writeFileSync(gitignorePath, existing + block);
|
|
458
|
+
console.log(` ✅ Added ${toAdd.length - 1} entries to .gitignore`);
|
|
459
|
+
toAdd.filter(e => !e.startsWith('#')).forEach(e => console.log(` ${e}`));
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
463
|
+
|
|
464
|
+
async function main() {
|
|
465
|
+
console.log('🚀 nurosys-agents — setup\n');
|
|
466
|
+
|
|
467
|
+
// 1. Stack
|
|
468
|
+
let stack = detectStack();
|
|
469
|
+
console.log(`🔍 Detected stack: ${STACK_LABEL[stack]}`);
|
|
470
|
+
if (stack === 'unknown') {
|
|
471
|
+
const ans = await ask(' Choose: (b)ackend (f)rontend (m)onolith: ');
|
|
472
|
+
stack = ans.startsWith('f') ? 'frontend' : ans.startsWith('m') ? 'monolith' : 'backend';
|
|
473
|
+
console.log(` Using: ${STACK_LABEL[stack]}`);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// 2. IDEs — interactive multiselect
|
|
477
|
+
const ideChoices = await multiselect('Which IDE(s) do you use? ↑↓ move space toggle enter confirm', [
|
|
478
|
+
{ label: 'Claude Code', value: 'claude' },
|
|
479
|
+
{ label: 'Cursor', value: 'cursor' },
|
|
480
|
+
{ label: 'Antigravity', value: 'antigravity' },
|
|
481
|
+
{ label: 'Codex', value: 'codex' },
|
|
482
|
+
]);
|
|
483
|
+
|
|
484
|
+
const ides = ideChoices.length > 0 ? ideChoices : ['claude'];
|
|
485
|
+
console.log(` Setting up: ${ides.join(', ')}`);
|
|
486
|
+
|
|
487
|
+
// 3. IDE-specific wiring (includes per-IDE module-runner variant)
|
|
488
|
+
if (ides.includes('claude')) setupClaudeCode(stack);
|
|
489
|
+
if (ides.includes('cursor')) setupCursor(stack);
|
|
490
|
+
if (ides.includes('antigravity')) setupAntigravity(stack);
|
|
491
|
+
if (ides.includes('codex')) setupCodex(stack);
|
|
492
|
+
|
|
493
|
+
// 4. Serena MCP
|
|
494
|
+
await setupSerena(ides);
|
|
495
|
+
|
|
496
|
+
// 5. Gitignore
|
|
497
|
+
setupGitignore(ides);
|
|
498
|
+
|
|
499
|
+
// 6. Project-memory rules (if project-memory/ exists)
|
|
500
|
+
runSetupRules();
|
|
501
|
+
|
|
502
|
+
// 7. Summary
|
|
503
|
+
console.log('\n' + '─'.repeat(60));
|
|
504
|
+
console.log('✨ Setup complete!\n');
|
|
505
|
+
console.log(` Stack : ${STACK_LABEL[stack]}`);
|
|
506
|
+
console.log(` IDEs : ${ides.join(', ')}`);
|
|
507
|
+
console.log('\n Skills available:');
|
|
508
|
+
const skillsDir = pkgSkills(stack);
|
|
509
|
+
if (fs.existsSync(skillsDir)) fs.readdirSync(skillsDir).forEach(s => console.log(` /${s}`));
|
|
510
|
+
console.log('\n Next steps:');
|
|
511
|
+
console.log(' 1. Bootstrap project-memory → /create-blueprint all');
|
|
512
|
+
console.log(' 2. Brainstorm a feature → /brainstorm <problem>');
|
|
513
|
+
console.log(' 3. Plan it → /architect <feature>');
|
|
514
|
+
console.log(' 4. Build it autonomously → /module-runner documentation/features/<feature>/');
|
|
515
|
+
console.log('─'.repeat(60) + '\n');
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
main().catch(err => { console.error('\n❌ Setup failed:', err.message); process.exit(1); });
|