@nerviq/cli 1.11.0 → 1.13.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/README.md +216 -124
- package/bin/cli.js +620 -183
- package/package.json +3 -2
- package/src/activity.js +49 -9
- package/src/adoption-advisor.js +299 -0
- package/src/aider/freshness.js +65 -20
- package/src/aider/techniques.js +16 -11
- package/src/analyze.js +128 -0
- package/src/anti-patterns.js +13 -0
- package/src/audit/instruction-files.js +180 -0
- package/src/audit/recommendations.js +531 -0
- package/src/audit.js +53 -681
- package/src/behavioral-drift.js +801 -0
- package/src/codex/freshness.js +84 -25
- package/src/continuous-ops.js +681 -0
- package/src/copilot/freshness.js +57 -20
- package/src/cost-tracking.js +61 -0
- package/src/cursor/freshness.js +65 -20
- package/src/cursor/techniques.js +17 -12
- package/src/deep-review.js +83 -0
- package/src/diff-only.js +280 -0
- package/src/doctor.js +118 -55
- package/src/freshness.js +74 -21
- package/src/gemini/freshness.js +66 -21
- package/src/governance.js +59 -43
- package/src/hook-validation.js +342 -0
- package/src/index.js +5 -0
- package/src/integrations.js +42 -5
- package/src/mcp-server.js +95 -59
- package/src/mcp-validation.js +337 -0
- package/src/opencode/freshness.js +66 -21
- package/src/opencode/techniques.js +12 -7
- package/src/operating-profile.js +574 -0
- package/src/org.js +97 -13
- package/src/plans.js +192 -8
- package/src/platform-change-manifest.js +86 -0
- package/src/policy-layers.js +210 -0
- package/src/profiles.js +4 -1
- package/src/prompt-injection.js +74 -0
- package/src/repo-archetype.js +386 -0
- package/src/setup/analysis.js +619 -0
- package/src/setup/runtime.js +172 -0
- package/src/setup.js +62 -748
- package/src/source-urls.js +132 -132
- package/src/supplemental-checks.js +13 -12
- package/src/techniques/api.js +407 -0
- package/src/techniques/automation.js +316 -0
- package/src/techniques/compliance.js +257 -0
- package/src/techniques/hygiene.js +294 -0
- package/src/techniques/instructions.js +243 -0
- package/src/techniques/observability.js +226 -0
- package/src/techniques/optimization.js +142 -0
- package/src/techniques/quality.js +317 -0
- package/src/techniques/security.js +237 -0
- package/src/techniques/shared.js +443 -0
- package/src/techniques/stacks.js +2294 -0
- package/src/techniques/tools.js +106 -0
- package/src/techniques/workflow.js +413 -0
- package/src/techniques.js +78 -5607
- package/src/watch.js +18 -0
- package/src/windsurf/freshness.js +36 -21
- package/src/windsurf/techniques.js +17 -12
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { buildSettingsForProfile } = require('../governance');
|
|
4
|
+
|
|
5
|
+
function snapshotSettingsBeforeSetup(dir) {
|
|
6
|
+
const settingsPath = path.join(dir, '.claude/settings.json');
|
|
7
|
+
if (!fs.existsSync(settingsPath)) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
return fs.readFileSync(settingsPath, 'utf8');
|
|
12
|
+
} catch (_) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function collectFailedSetupTemplates(ctx, techniques, only) {
|
|
18
|
+
let failedWithTemplates = [];
|
|
19
|
+
for (const [key, technique] of Object.entries(techniques)) {
|
|
20
|
+
if (technique.passed || technique.check(ctx)) continue;
|
|
21
|
+
if (!technique.template) continue;
|
|
22
|
+
failedWithTemplates.push({ key, technique });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (only && only.length > 0) {
|
|
26
|
+
failedWithTemplates = failedWithTemplates.filter(item => only.includes(item.key));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return failedWithTemplates;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function applyTemplateResults({ dir, failedWithTemplates, stacks, ctx, templates, log }) {
|
|
33
|
+
const writtenFiles = [];
|
|
34
|
+
const preservedFiles = [];
|
|
35
|
+
let created = 0;
|
|
36
|
+
let skipped = 0;
|
|
37
|
+
|
|
38
|
+
for (const { key, technique } of failedWithTemplates) {
|
|
39
|
+
const template = templates[technique.template];
|
|
40
|
+
if (!template) continue;
|
|
41
|
+
|
|
42
|
+
const result = template(stacks, ctx);
|
|
43
|
+
|
|
44
|
+
if (typeof result === 'string') {
|
|
45
|
+
const filePathMap = {
|
|
46
|
+
claudeMd: 'CLAUDE.md',
|
|
47
|
+
mermaidArchitecture: 'CLAUDE.md',
|
|
48
|
+
};
|
|
49
|
+
if (key === 'mermaidArchitecture') continue;
|
|
50
|
+
const filePath = filePathMap[key] || key;
|
|
51
|
+
const fullPath = path.join(dir, filePath);
|
|
52
|
+
|
|
53
|
+
if (!fs.existsSync(fullPath)) {
|
|
54
|
+
fs.writeFileSync(fullPath, result, 'utf8');
|
|
55
|
+
writtenFiles.push(filePath);
|
|
56
|
+
log(` \x1b[32mג…\x1b[0m Created ${filePath}`);
|
|
57
|
+
created++;
|
|
58
|
+
} else {
|
|
59
|
+
preservedFiles.push(filePath);
|
|
60
|
+
log(` \x1b[2mגן¸ Skipped ${filePath} (already exists ג€” your version is kept)\x1b[0m`);
|
|
61
|
+
skipped++;
|
|
62
|
+
}
|
|
63
|
+
} else if (typeof result === 'object') {
|
|
64
|
+
const dirMap = {
|
|
65
|
+
hooks: '.claude/hooks',
|
|
66
|
+
commands: '.claude/commands',
|
|
67
|
+
skills: '.claude/skills',
|
|
68
|
+
rules: '.claude/rules',
|
|
69
|
+
agents: '.claude/agents',
|
|
70
|
+
};
|
|
71
|
+
const targetDir = dirMap[technique.template] || `.claude/${technique.template}`;
|
|
72
|
+
const fullDir = path.join(dir, targetDir);
|
|
73
|
+
|
|
74
|
+
if (!fs.existsSync(fullDir)) {
|
|
75
|
+
fs.mkdirSync(fullDir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const [fileName, content] of Object.entries(result)) {
|
|
79
|
+
const filePath = path.join(fullDir, fileName);
|
|
80
|
+
const fileDir = path.dirname(filePath);
|
|
81
|
+
if (!fs.existsSync(fileDir)) {
|
|
82
|
+
fs.mkdirSync(fileDir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
if (!fs.existsSync(filePath)) {
|
|
85
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
86
|
+
writtenFiles.push(path.relative(dir, filePath));
|
|
87
|
+
log(` \x1b[32mג…\x1b[0m Created ${path.relative(dir, filePath)}`);
|
|
88
|
+
created++;
|
|
89
|
+
} else {
|
|
90
|
+
preservedFiles.push(path.relative(dir, filePath));
|
|
91
|
+
skipped++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
created,
|
|
99
|
+
skipped,
|
|
100
|
+
writtenFiles,
|
|
101
|
+
preservedFiles,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function mergeGeneratedHookSettings({ dir, profile, mcpPacks, writtenFiles, preservedFiles, log }) {
|
|
106
|
+
const hooksDir = path.join(dir, '.claude/hooks');
|
|
107
|
+
const settingsPath = path.join(dir, '.claude/settings.json');
|
|
108
|
+
let created = 0;
|
|
109
|
+
|
|
110
|
+
if (!fs.existsSync(hooksDir)) {
|
|
111
|
+
return { created, writtenFiles, preservedFiles };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const hookFiles = fs.readdirSync(hooksDir).filter(file => file.endsWith('.sh') || file.endsWith('.js'));
|
|
115
|
+
if (hookFiles.length === 0) {
|
|
116
|
+
return { created, writtenFiles, preservedFiles };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const newSettings = buildSettingsForProfile({
|
|
120
|
+
profileKey: profile || 'safe-write',
|
|
121
|
+
hookFiles,
|
|
122
|
+
mcpPackKeys: mcpPacks || [],
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
let existingSettings = {};
|
|
126
|
+
if (fs.existsSync(settingsPath)) {
|
|
127
|
+
try {
|
|
128
|
+
existingSettings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
129
|
+
} catch (_) {
|
|
130
|
+
existingSettings = {};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (newSettings.hooks) existingSettings.hooks = newSettings.hooks;
|
|
135
|
+
if (newSettings.permissions) {
|
|
136
|
+
existingSettings.permissions = existingSettings.permissions || {};
|
|
137
|
+
const existingDeny = existingSettings.permissions.deny || [];
|
|
138
|
+
const newDeny = newSettings.permissions.deny || [];
|
|
139
|
+
existingSettings.permissions.deny = [...new Set([...existingDeny, ...newDeny])];
|
|
140
|
+
if (!existingSettings.permissions.defaultMode && newSettings.permissions.defaultMode) {
|
|
141
|
+
existingSettings.permissions.defaultMode = newSettings.permissions.defaultMode;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (newSettings.mcpServers) {
|
|
145
|
+
existingSettings.mcpServers = { ...existingSettings.mcpServers, ...newSettings.mcpServers };
|
|
146
|
+
}
|
|
147
|
+
if (newSettings.nerviqSetup) {
|
|
148
|
+
existingSettings.nerviqSetup = { ...existingSettings.nerviqSetup, ...newSettings.nerviqSetup };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
fs.writeFileSync(settingsPath, JSON.stringify(existingSettings, null, 2), 'utf8');
|
|
152
|
+
if (!writtenFiles.includes('.claude/settings.json') && !preservedFiles.includes('.claude/settings.json')) {
|
|
153
|
+
writtenFiles.push('.claude/settings.json');
|
|
154
|
+
log(` \x1b[32mג…\x1b[0m Updated .claude/settings.json (hooks registered)`);
|
|
155
|
+
created++;
|
|
156
|
+
} else {
|
|
157
|
+
log(` \x1b[32mג…\x1b[0m Merged hooks into existing .claude/settings.json`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
created,
|
|
162
|
+
writtenFiles,
|
|
163
|
+
preservedFiles,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = {
|
|
168
|
+
applyTemplateResults,
|
|
169
|
+
collectFailedSetupTemplates,
|
|
170
|
+
mergeGeneratedHookSettings,
|
|
171
|
+
snapshotSettingsBeforeSetup,
|
|
172
|
+
};
|