@nerviq/cli 1.12.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 +224 -210
- package/bin/cli.js +2 -1
- package/package.json +3 -2
- package/src/aider/freshness.js +65 -20
- package/src/audit/instruction-files.js +180 -0
- package/src/audit/recommendations.js +531 -0
- package/src/audit.js +19 -722
- package/src/codex/freshness.js +84 -25
- package/src/copilot/freshness.js +57 -20
- package/src/cursor/freshness.js +65 -20
- package/src/freshness.js +74 -21
- package/src/gemini/freshness.js +66 -21
- package/src/mcp-server.js +95 -59
- package/src/opencode/freshness.js +66 -21
- package/src/setup/analysis.js +619 -0
- package/src/setup/runtime.js +172 -0
- package/src/setup.js +28 -748
- package/src/windsurf/freshness.js +36 -21
package/src/aider/freshness.js
CHANGED
|
@@ -32,18 +32,39 @@ const P0_SOURCES = [
|
|
|
32
32
|
stalenessThresholdDays: 14,
|
|
33
33
|
verifiedAt: '2026-04-08',
|
|
34
34
|
},
|
|
35
|
-
{
|
|
36
|
-
key: 'aider-model-docs',
|
|
37
|
-
label: 'Aider Model Documentation',
|
|
38
|
-
url: 'https://aider.chat/docs/llms.html',
|
|
39
|
-
stalenessThresholdDays: 30,
|
|
40
|
-
verifiedAt: '2026-04-08',
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
key: 'aider-
|
|
44
|
-
label: 'Aider
|
|
45
|
-
url: 'https://
|
|
46
|
-
stalenessThresholdDays: 14,
|
|
35
|
+
{
|
|
36
|
+
key: 'aider-model-docs',
|
|
37
|
+
label: 'Aider Model Documentation',
|
|
38
|
+
url: 'https://aider.chat/docs/llms.html',
|
|
39
|
+
stalenessThresholdDays: 30,
|
|
40
|
+
verifiedAt: '2026-04-08',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
key: 'aider-chat-modes',
|
|
44
|
+
label: 'Aider Chat Modes',
|
|
45
|
+
url: 'https://aider.chat/docs/usage/modes.html',
|
|
46
|
+
stalenessThresholdDays: 14,
|
|
47
|
+
verifiedAt: '2026-04-10',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
key: 'aider-git-integration',
|
|
51
|
+
label: 'Aider Git Integration',
|
|
52
|
+
url: 'https://aider.chat/docs/git.html',
|
|
53
|
+
stalenessThresholdDays: 14,
|
|
54
|
+
verifiedAt: '2026-04-10',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
key: 'aider-conventions',
|
|
58
|
+
label: 'Aider Coding Conventions',
|
|
59
|
+
url: 'https://aider.chat/docs/usage/conventions.html',
|
|
60
|
+
stalenessThresholdDays: 30,
|
|
61
|
+
verifiedAt: '2026-04-10',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: 'aider-pypi',
|
|
65
|
+
label: 'Aider PyPI Package',
|
|
66
|
+
url: 'https://pypi.org/project/aider-chat/',
|
|
67
|
+
stalenessThresholdDays: 14,
|
|
47
68
|
verifiedAt: '2026-04-08',
|
|
48
69
|
},
|
|
49
70
|
];
|
|
@@ -83,14 +104,38 @@ const PROPAGATION_CHECKLIST = [
|
|
|
83
104
|
'src/aider/interactive.js — update wizard options',
|
|
84
105
|
],
|
|
85
106
|
},
|
|
86
|
-
{
|
|
87
|
-
trigger: 'Aider domain pack definitions change',
|
|
88
|
-
targets: [
|
|
89
|
-
'src/aider/domain-packs.js — update pack registry',
|
|
90
|
-
'src/aider/governance.js — governance export picks up changes',
|
|
91
|
-
],
|
|
92
|
-
},
|
|
93
|
-
|
|
107
|
+
{
|
|
108
|
+
trigger: 'Aider domain pack definitions change',
|
|
109
|
+
targets: [
|
|
110
|
+
'src/aider/domain-packs.js — update pack registry',
|
|
111
|
+
'src/aider/governance.js — governance export picks up changes',
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
trigger: 'Aider chat-mode or architect/editor behavior change',
|
|
116
|
+
targets: [
|
|
117
|
+
'src/aider/techniques.js — update mode/architect checks',
|
|
118
|
+
'src/aider/interactive.js — update mode-selection guidance',
|
|
119
|
+
'src/source-urls.js — refresh Aider mode source mappings',
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
trigger: 'Aider git integration / undo / commit behavior change',
|
|
124
|
+
targets: [
|
|
125
|
+
'src/aider/techniques.js — update git-safety and undo assumptions',
|
|
126
|
+
'src/aider/setup.js — update git-related starter guidance',
|
|
127
|
+
'src/source-urls.js — refresh Aider git source mappings',
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
trigger: 'Aider conventions loading or persistent guidance behavior change',
|
|
132
|
+
targets: [
|
|
133
|
+
'src/aider/techniques.js — update conventions and always-read guidance checks',
|
|
134
|
+
'src/aider/setup.js — update conventions starter comments',
|
|
135
|
+
'src/source-urls.js — refresh Aider conventions source mappings',
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
];
|
|
94
139
|
|
|
95
140
|
/**
|
|
96
141
|
* Check release gate — are all P0 sources fresh?
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
const { hasWorkspaceConfig, detectWorkspaceGlobs, detectWorkspaces } = require('../workspace');
|
|
4
|
+
const { estimateTokenCount } = require('../token-estimate');
|
|
5
|
+
|
|
6
|
+
const LARGE_INSTRUCTION_WARN_TOKENS = 12000;
|
|
7
|
+
const LARGE_INSTRUCTION_SKIP_TOKENS = 240000;
|
|
8
|
+
|
|
9
|
+
function normalizeRelativePath(filePath) {
|
|
10
|
+
return String(filePath || '').replace(/\\/g, '/').replace(/^\.\//, '');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function formatCount(value) {
|
|
14
|
+
return Number(value || 0).toLocaleString('en-US');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function addPath(target, filePath) {
|
|
18
|
+
if (!filePath || typeof filePath !== 'string') return;
|
|
19
|
+
target.add(normalizeRelativePath(filePath));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function addDirFiles(ctx, target, dirPath, filter) {
|
|
23
|
+
if (typeof ctx.dirFiles !== 'function') return;
|
|
24
|
+
for (const file of ctx.dirFiles(dirPath)) {
|
|
25
|
+
if (filter && !filter.test(file)) continue;
|
|
26
|
+
addPath(target, path.join(dirPath, file));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function instructionFileCandidates(spec, ctx) {
|
|
31
|
+
const candidates = new Set();
|
|
32
|
+
|
|
33
|
+
if (spec.platform === 'claude') {
|
|
34
|
+
addPath(candidates, 'CLAUDE.md');
|
|
35
|
+
addPath(candidates, '.claude/CLAUDE.md');
|
|
36
|
+
addDirFiles(ctx, candidates, '.claude/rules', /\.md$/i);
|
|
37
|
+
addDirFiles(ctx, candidates, '.claude/commands', /\.md$/i);
|
|
38
|
+
addDirFiles(ctx, candidates, '.claude/agents', /\.md$/i);
|
|
39
|
+
if (typeof ctx.dirFiles === 'function') {
|
|
40
|
+
for (const skillDir of ctx.dirFiles('.claude/skills')) {
|
|
41
|
+
addPath(candidates, path.join('.claude', 'skills', skillDir, 'SKILL.md'));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (spec.platform === 'codex') {
|
|
47
|
+
addPath(candidates, 'AGENTS.md');
|
|
48
|
+
addPath(candidates, 'AGENTS.override.md');
|
|
49
|
+
addPath(candidates, typeof ctx.agentsMdPath === 'function' ? ctx.agentsMdPath() : null);
|
|
50
|
+
addDirFiles(ctx, candidates, 'codex/rules');
|
|
51
|
+
addDirFiles(ctx, candidates, '.codex/rules');
|
|
52
|
+
if (typeof ctx.skillDirs === 'function') {
|
|
53
|
+
for (const skillDir of ctx.skillDirs()) {
|
|
54
|
+
addPath(candidates, path.join('.agents', 'skills', skillDir, 'SKILL.md'));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (spec.platform === 'gemini') {
|
|
60
|
+
addPath(candidates, 'GEMINI.md');
|
|
61
|
+
addPath(candidates, '.gemini/GEMINI.md');
|
|
62
|
+
addDirFiles(ctx, candidates, '.gemini/agents', /\.md$/i);
|
|
63
|
+
if (typeof ctx.skillDirs === 'function') {
|
|
64
|
+
for (const skillDir of ctx.skillDirs()) {
|
|
65
|
+
addPath(candidates, path.join('.gemini', 'skills', skillDir, 'SKILL.md'));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (spec.platform === 'copilot') {
|
|
71
|
+
addPath(candidates, '.github/copilot-instructions.md');
|
|
72
|
+
addDirFiles(ctx, candidates, '.github/instructions', /\.instructions\.md$/i);
|
|
73
|
+
addDirFiles(ctx, candidates, '.github/prompts', /\.prompt\.md$/i);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (spec.platform === 'cursor') {
|
|
77
|
+
addPath(candidates, '.cursorrules');
|
|
78
|
+
addDirFiles(ctx, candidates, '.cursor/rules', /\.mdc$/i);
|
|
79
|
+
addDirFiles(ctx, candidates, '.cursor/commands', /\.md$/i);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (spec.platform === 'windsurf') {
|
|
83
|
+
addPath(candidates, '.windsurfrules');
|
|
84
|
+
addDirFiles(ctx, candidates, '.windsurf/rules', /\.md$/i);
|
|
85
|
+
addDirFiles(ctx, candidates, '.windsurf/workflows', /\.md$/i);
|
|
86
|
+
addDirFiles(ctx, candidates, '.windsurf/memories', /\.(md|json)$/i);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (spec.platform === 'aider' && typeof ctx.conventionFiles === 'function') {
|
|
90
|
+
for (const file of ctx.conventionFiles()) {
|
|
91
|
+
addPath(candidates, file);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (spec.platform === 'opencode') {
|
|
96
|
+
addPath(candidates, 'AGENTS.md');
|
|
97
|
+
addPath(candidates, 'CLAUDE.md');
|
|
98
|
+
addDirFiles(ctx, candidates, '.opencode/commands', /\.(md|markdown|ya?ml)$/i);
|
|
99
|
+
if (typeof ctx.skillDirs === 'function') {
|
|
100
|
+
for (const skillDir of ctx.skillDirs()) {
|
|
101
|
+
addPath(candidates, path.join('.opencode', 'commands', skillDir, 'SKILL.md'));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return [...candidates];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function inspectInstructionFiles(spec, ctx) {
|
|
110
|
+
const warnings = [];
|
|
111
|
+
|
|
112
|
+
for (const filePath of instructionFileCandidates(spec, ctx)) {
|
|
113
|
+
const content = typeof ctx.fileContent === 'function' ? ctx.fileContent(filePath) : null;
|
|
114
|
+
const byteCount = typeof ctx.fileSizeBytes === 'function' ? ctx.fileSizeBytes(filePath) : null;
|
|
115
|
+
const tokenCount = typeof content === 'string' ? estimateTokenCount(content) : null;
|
|
116
|
+
if (!Number.isFinite(tokenCount) || tokenCount <= LARGE_INSTRUCTION_WARN_TOKENS) continue;
|
|
117
|
+
|
|
118
|
+
warnings.push({
|
|
119
|
+
file: normalizeRelativePath(filePath),
|
|
120
|
+
byteCount,
|
|
121
|
+
tokenCount,
|
|
122
|
+
lineCount: typeof content === 'string' ? content.split(/\r?\n/).length : null,
|
|
123
|
+
skipped: tokenCount > LARGE_INSTRUCTION_SKIP_TOKENS,
|
|
124
|
+
severity: tokenCount > LARGE_INSTRUCTION_SKIP_TOKENS ? 'critical' : 'warning',
|
|
125
|
+
message: tokenCount > LARGE_INSTRUCTION_SKIP_TOKENS
|
|
126
|
+
? 'Instruction file exceeds ~240,000 tokens and will be skipped during audit.'
|
|
127
|
+
: 'Instruction file exceeds ~12,000 tokens. Audit will continue, but this file may reduce runtime clarity.',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return warnings;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function guardSkippedInstructionFiles(ctx, warnings) {
|
|
135
|
+
const skippedFiles = new Set(
|
|
136
|
+
warnings.filter((item) => item.skipped).map((item) => normalizeRelativePath(item.file))
|
|
137
|
+
);
|
|
138
|
+
if (skippedFiles.size === 0) return;
|
|
139
|
+
|
|
140
|
+
const originalFileContent = typeof ctx.fileContent === 'function' ? ctx.fileContent.bind(ctx) : null;
|
|
141
|
+
const originalLineNumber = typeof ctx.lineNumber === 'function' ? ctx.lineNumber.bind(ctx) : null;
|
|
142
|
+
|
|
143
|
+
if (originalFileContent) {
|
|
144
|
+
ctx.fileContent = (filePath) => {
|
|
145
|
+
if (skippedFiles.has(normalizeRelativePath(filePath))) return null;
|
|
146
|
+
return originalFileContent(filePath);
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (originalLineNumber) {
|
|
151
|
+
ctx.lineNumber = (filePath, matcher) => {
|
|
152
|
+
if (skippedFiles.has(normalizeRelativePath(filePath))) return null;
|
|
153
|
+
return originalLineNumber(filePath, matcher);
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function buildWorkspaceHint(dir) {
|
|
159
|
+
if (!hasWorkspaceConfig(dir)) return null;
|
|
160
|
+
|
|
161
|
+
const patterns = detectWorkspaceGlobs(dir);
|
|
162
|
+
const workspaces = detectWorkspaces(dir);
|
|
163
|
+
if (patterns.length === 0 && workspaces.length === 0) return null;
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
detected: true,
|
|
167
|
+
patterns,
|
|
168
|
+
workspaces,
|
|
169
|
+
suggestedCommand: patterns.length > 0
|
|
170
|
+
? `npx nerviq audit --workspace ${patterns.join(',')}`
|
|
171
|
+
: `npx nerviq audit --workspace ${workspaces.join(',')}`,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = {
|
|
176
|
+
buildWorkspaceHint,
|
|
177
|
+
formatCount,
|
|
178
|
+
guardSkippedInstructionFiles,
|
|
179
|
+
inspectInstructionFiles,
|
|
180
|
+
};
|