@nerviq/cli 1.8.9 → 1.10.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 +32 -24
- package/bin/cli.js +173 -104
- package/package.json +59 -59
- package/src/activity.js +68 -12
- package/src/aider/freshness.js +168 -168
- package/src/anti-patterns.js +13 -11
- package/src/audit.js +16 -14
- package/src/auto-suggest.js +62 -9
- package/src/benchmark.js +52 -41
- package/src/codex/freshness.js +167 -167
- package/src/copilot/freshness.js +197 -197
- package/src/cursor/freshness.js +214 -214
- package/src/dashboard.js +36 -14
- package/src/freshness.js +19 -19
- package/src/gemini/freshness.js +204 -204
- package/src/i18n.js +63 -0
- package/src/instruction-surfaces.js +185 -0
- package/src/locales/en.json +34 -0
- package/src/locales/es.json +34 -0
- package/src/mcp-server.js +1 -1
- package/src/opencode/freshness.js +158 -158
- package/src/stack-checks.js +1 -1
- package/src/synergy/report.js +1 -0
- package/src/techniques.js +174 -58
- package/src/windsurf/freshness.js +215 -215
- package/src/workspace.js +51 -6
package/src/cursor/freshness.js
CHANGED
|
@@ -1,214 +1,214 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cursor Freshness Operationalization
|
|
3
|
-
*
|
|
4
|
-
* Release gates, recurring probes, propagation checklists,
|
|
5
|
-
* and staleness blocking for Cursor surfaces.
|
|
6
|
-
*
|
|
7
|
-
* P0 sources from docs.cursor.com, propagation for rule format changes.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const { version } = require('../../package.json');
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* P0 sources that must be fresh before any Cursor release claim.
|
|
14
|
-
*/
|
|
15
|
-
const P0_SOURCES = [
|
|
16
|
-
{
|
|
17
|
-
key: 'cursor-rules-docs',
|
|
18
|
-
label: 'Cursor Rules Documentation',
|
|
19
|
-
url: 'https://
|
|
20
|
-
stalenessThresholdDays: 30,
|
|
21
|
-
verifiedAt:
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
key: 'cursor-mdc-format',
|
|
25
|
-
label: 'MDC Format Documentation',
|
|
26
|
-
url: 'https://
|
|
27
|
-
stalenessThresholdDays: 30,
|
|
28
|
-
verifiedAt:
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
key: 'cursor-mcp-docs',
|
|
32
|
-
label: 'Cursor MCP Documentation',
|
|
33
|
-
url: 'https://
|
|
34
|
-
stalenessThresholdDays: 30,
|
|
35
|
-
verifiedAt:
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
key: 'cursor-background-agents',
|
|
39
|
-
label: '
|
|
40
|
-
url: 'https://
|
|
41
|
-
stalenessThresholdDays: 14,
|
|
42
|
-
verifiedAt:
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
key: 'cursor-automations',
|
|
46
|
-
label: 'Automations Documentation',
|
|
47
|
-
url: 'https://
|
|
48
|
-
stalenessThresholdDays: 14,
|
|
49
|
-
verifiedAt:
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
key: 'cursor-bugbot',
|
|
53
|
-
label: 'BugBot Documentation',
|
|
54
|
-
url: 'https://
|
|
55
|
-
stalenessThresholdDays: 30,
|
|
56
|
-
verifiedAt:
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
key: 'cursor-privacy-mode',
|
|
60
|
-
label: 'Cursor Privacy
|
|
61
|
-
url: 'https://
|
|
62
|
-
stalenessThresholdDays: 30,
|
|
63
|
-
verifiedAt:
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
key: 'cursor-changelog',
|
|
67
|
-
label: 'Cursor Changelog',
|
|
68
|
-
url: 'https://
|
|
69
|
-
stalenessThresholdDays: 14,
|
|
70
|
-
verifiedAt:
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
key: 'cursor-security',
|
|
74
|
-
label: 'Cursor Security
|
|
75
|
-
url: 'https://
|
|
76
|
-
stalenessThresholdDays: 30,
|
|
77
|
-
verifiedAt:
|
|
78
|
-
},
|
|
79
|
-
];
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Propagation checklist: when a Cursor source changes, these must update.
|
|
83
|
-
*/
|
|
84
|
-
const PROPAGATION_CHECKLIST = [
|
|
85
|
-
{
|
|
86
|
-
trigger: 'MDC rule format change (new frontmatter fields, type behavior change)',
|
|
87
|
-
targets: [
|
|
88
|
-
'src/cursor/config-parser.js — update VALID_MDC_FIELDS, detectRuleType, parseSimpleYaml',
|
|
89
|
-
'src/cursor/techniques.js — update rule validation checks (CU-A01..CU-A09)',
|
|
90
|
-
'src/cursor/context.js — update cursorRules() parsing and type detection',
|
|
91
|
-
'src/cursor/setup.js — update rule template generation',
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
trigger: 'Background agent behavior change (environment.json format, VM config)',
|
|
96
|
-
targets: [
|
|
97
|
-
'src/cursor/techniques.js — update background agent checks (CU-G01..CU-G05)',
|
|
98
|
-
'src/cursor/setup.js — update environment.json template',
|
|
99
|
-
'src/cursor/governance.js — update background-agent permission profile',
|
|
100
|
-
],
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
trigger: 'Automation trigger format or behavior change',
|
|
104
|
-
targets: [
|
|
105
|
-
'src/cursor/techniques.js — update automation checks (CU-H01..CU-H05)',
|
|
106
|
-
'src/cursor/context.js — update automationsConfig() parsing',
|
|
107
|
-
'src/cursor/governance.js — update automation permission profile and caveats',
|
|
108
|
-
],
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
trigger: 'MCP configuration format change in .cursor/mcp.json',
|
|
112
|
-
targets: [
|
|
113
|
-
'src/cursor/mcp-packs.js — update pack JSON projections and merge logic',
|
|
114
|
-
'src/cursor/techniques.js — update MCP checks (CU-E01..CU-E05)',
|
|
115
|
-
'src/cursor/context.js — update mcpConfig() parsing',
|
|
116
|
-
'src/cursor/config-parser.js — update validateMcpEnvVars',
|
|
117
|
-
],
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
trigger: 'MCP tool limit change (currently ~40)',
|
|
121
|
-
targets: [
|
|
122
|
-
'src/cursor/techniques.js — update CU-B02 threshold',
|
|
123
|
-
'src/cursor/governance.js — update mcp-tool-limit caveat',
|
|
124
|
-
'src/cursor/mcp-packs.js — update recommendation logic',
|
|
125
|
-
],
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
trigger: 'BugBot feature update or autofix behavior change',
|
|
129
|
-
targets: [
|
|
130
|
-
'src/cursor/techniques.js — update BugBot checks (CU-J01..CU-J04)',
|
|
131
|
-
'src/cursor/setup.js — update BugBot guide template',
|
|
132
|
-
'src/cursor/governance.js — update bugbot-review hook',
|
|
133
|
-
],
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
trigger: 'Privacy Mode or security model change',
|
|
137
|
-
targets: [
|
|
138
|
-
'src/cursor/techniques.js — update trust checks (CU-C01..CU-C09)',
|
|
139
|
-
'src/cursor/governance.js — update caveats and permission profiles',
|
|
140
|
-
'src/cursor/deep-review.js — update trust class detection',
|
|
141
|
-
],
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
trigger: 'Design Mode feature update',
|
|
145
|
-
targets: [
|
|
146
|
-
'src/cursor/setup.js — update Design Mode guide template',
|
|
147
|
-
'src/cursor/techniques.js — update CU-L01 modern features check',
|
|
148
|
-
],
|
|
149
|
-
},
|
|
150
|
-
];
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Release gate: check if all P0 sources are within staleness threshold.
|
|
154
|
-
*/
|
|
155
|
-
function checkReleaseGate(sourceVerifications = {}) {
|
|
156
|
-
const now = new Date();
|
|
157
|
-
const results = P0_SOURCES.map(source => {
|
|
158
|
-
const verifiedAt = sourceVerifications[source.key]
|
|
159
|
-
? new Date(sourceVerifications[source.key])
|
|
160
|
-
: source.verifiedAt ? new Date(source.verifiedAt) : null;
|
|
161
|
-
|
|
162
|
-
if (!verifiedAt) {
|
|
163
|
-
return { ...source, status: 'unverified', daysStale: null };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const daysSince = Math.floor((now - verifiedAt) / (1000 * 60 * 60 * 24));
|
|
167
|
-
const isStale = daysSince > source.stalenessThresholdDays;
|
|
168
|
-
|
|
169
|
-
return { ...source, verifiedAt: verifiedAt.toISOString(), daysStale: daysSince, status: isStale ? 'stale' : 'fresh' };
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
ready: results.every(r => r.status === 'fresh'),
|
|
174
|
-
stale: results.filter(r => r.status === 'stale' || r.status === 'unverified'),
|
|
175
|
-
fresh: results.filter(r => r.status === 'fresh'),
|
|
176
|
-
results,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function formatReleaseGate(gateResult) {
|
|
181
|
-
const lines = [
|
|
182
|
-
`Cursor Freshness Gate (nerviq v${version})`,
|
|
183
|
-
'═══════════════════════════════════════',
|
|
184
|
-
'',
|
|
185
|
-
`Status: ${gateResult.ready ? 'READY' : 'BLOCKED'}`,
|
|
186
|
-
`Fresh: ${gateResult.fresh.length}/${gateResult.results.length}`,
|
|
187
|
-
'',
|
|
188
|
-
];
|
|
189
|
-
|
|
190
|
-
for (const result of gateResult.results) {
|
|
191
|
-
const icon = result.status === 'fresh' ? '✓' : result.status === 'stale' ? '✗' : '?';
|
|
192
|
-
const age = result.daysStale !== null ? ` (${result.daysStale}d ago)` : ' (unverified)';
|
|
193
|
-
lines.push(` ${icon} ${result.label}${age} — threshold: ${result.stalenessThresholdDays}d`);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (!gateResult.ready) {
|
|
197
|
-
lines.push('', 'Action required: verify stale/unverified sources before claiming release freshness.');
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return lines.join('\n');
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function getPropagationTargets(triggerKeyword) {
|
|
204
|
-
const keyword = triggerKeyword.toLowerCase();
|
|
205
|
-
return PROPAGATION_CHECKLIST.filter(item => item.trigger.toLowerCase().includes(keyword));
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
module.exports = {
|
|
209
|
-
P0_SOURCES,
|
|
210
|
-
PROPAGATION_CHECKLIST,
|
|
211
|
-
checkReleaseGate,
|
|
212
|
-
formatReleaseGate,
|
|
213
|
-
getPropagationTargets,
|
|
214
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Cursor Freshness Operationalization
|
|
3
|
+
*
|
|
4
|
+
* Release gates, recurring probes, propagation checklists,
|
|
5
|
+
* and staleness blocking for Cursor surfaces.
|
|
6
|
+
*
|
|
7
|
+
* P0 sources from docs.cursor.com, propagation for rule format changes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { version } = require('../../package.json');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* P0 sources that must be fresh before any Cursor release claim.
|
|
14
|
+
*/
|
|
15
|
+
const P0_SOURCES = [
|
|
16
|
+
{
|
|
17
|
+
key: 'cursor-rules-docs',
|
|
18
|
+
label: 'Cursor Rules Documentation',
|
|
19
|
+
url: 'https://cursor.com/docs/rules',
|
|
20
|
+
stalenessThresholdDays: 30,
|
|
21
|
+
verifiedAt: '2026-04-07',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
key: 'cursor-mdc-format',
|
|
25
|
+
label: 'MDC Format Documentation',
|
|
26
|
+
url: 'https://cursor.com/docs/rules',
|
|
27
|
+
stalenessThresholdDays: 30,
|
|
28
|
+
verifiedAt: '2026-04-07',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
key: 'cursor-mcp-docs',
|
|
32
|
+
label: 'Cursor MCP Documentation',
|
|
33
|
+
url: 'https://cursor.com/docs/context/mcp',
|
|
34
|
+
stalenessThresholdDays: 30,
|
|
35
|
+
verifiedAt: '2026-04-07',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: 'cursor-background-agents',
|
|
39
|
+
label: 'Cloud Agents Documentation',
|
|
40
|
+
url: 'https://cursor.com/docs/cloud-agent',
|
|
41
|
+
stalenessThresholdDays: 14,
|
|
42
|
+
verifiedAt: '2026-04-07',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
key: 'cursor-automations',
|
|
46
|
+
label: 'Automations Documentation',
|
|
47
|
+
url: 'https://cursor.com/docs/cloud-agent/automations',
|
|
48
|
+
stalenessThresholdDays: 14,
|
|
49
|
+
verifiedAt: '2026-04-07',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: 'cursor-bugbot',
|
|
53
|
+
label: 'BugBot Documentation',
|
|
54
|
+
url: 'https://cursor.com/docs/bugbot',
|
|
55
|
+
stalenessThresholdDays: 30,
|
|
56
|
+
verifiedAt: '2026-04-07',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
key: 'cursor-privacy-mode',
|
|
60
|
+
label: 'Cursor Privacy & Data Governance',
|
|
61
|
+
url: 'https://cursor.com/docs/enterprise/privacy-and-data-governance',
|
|
62
|
+
stalenessThresholdDays: 30,
|
|
63
|
+
verifiedAt: '2026-04-07',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: 'cursor-changelog',
|
|
67
|
+
label: 'Cursor Changelog',
|
|
68
|
+
url: 'https://cursor.com/changelog',
|
|
69
|
+
stalenessThresholdDays: 14,
|
|
70
|
+
verifiedAt: '2026-04-07',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
key: 'cursor-security',
|
|
74
|
+
label: 'Cursor Agent Security',
|
|
75
|
+
url: 'https://cursor.com/docs/agent/security',
|
|
76
|
+
stalenessThresholdDays: 30,
|
|
77
|
+
verifiedAt: '2026-04-07',
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Propagation checklist: when a Cursor source changes, these must update.
|
|
83
|
+
*/
|
|
84
|
+
const PROPAGATION_CHECKLIST = [
|
|
85
|
+
{
|
|
86
|
+
trigger: 'MDC rule format change (new frontmatter fields, type behavior change)',
|
|
87
|
+
targets: [
|
|
88
|
+
'src/cursor/config-parser.js — update VALID_MDC_FIELDS, detectRuleType, parseSimpleYaml',
|
|
89
|
+
'src/cursor/techniques.js — update rule validation checks (CU-A01..CU-A09)',
|
|
90
|
+
'src/cursor/context.js — update cursorRules() parsing and type detection',
|
|
91
|
+
'src/cursor/setup.js — update rule template generation',
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
trigger: 'Background agent behavior change (environment.json format, VM config)',
|
|
96
|
+
targets: [
|
|
97
|
+
'src/cursor/techniques.js — update background agent checks (CU-G01..CU-G05)',
|
|
98
|
+
'src/cursor/setup.js — update environment.json template',
|
|
99
|
+
'src/cursor/governance.js — update background-agent permission profile',
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
trigger: 'Automation trigger format or behavior change',
|
|
104
|
+
targets: [
|
|
105
|
+
'src/cursor/techniques.js — update automation checks (CU-H01..CU-H05)',
|
|
106
|
+
'src/cursor/context.js — update automationsConfig() parsing',
|
|
107
|
+
'src/cursor/governance.js — update automation permission profile and caveats',
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
trigger: 'MCP configuration format change in .cursor/mcp.json',
|
|
112
|
+
targets: [
|
|
113
|
+
'src/cursor/mcp-packs.js — update pack JSON projections and merge logic',
|
|
114
|
+
'src/cursor/techniques.js — update MCP checks (CU-E01..CU-E05)',
|
|
115
|
+
'src/cursor/context.js — update mcpConfig() parsing',
|
|
116
|
+
'src/cursor/config-parser.js — update validateMcpEnvVars',
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
trigger: 'MCP tool limit change (currently ~40)',
|
|
121
|
+
targets: [
|
|
122
|
+
'src/cursor/techniques.js — update CU-B02 threshold',
|
|
123
|
+
'src/cursor/governance.js — update mcp-tool-limit caveat',
|
|
124
|
+
'src/cursor/mcp-packs.js — update recommendation logic',
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
trigger: 'BugBot feature update or autofix behavior change',
|
|
129
|
+
targets: [
|
|
130
|
+
'src/cursor/techniques.js — update BugBot checks (CU-J01..CU-J04)',
|
|
131
|
+
'src/cursor/setup.js — update BugBot guide template',
|
|
132
|
+
'src/cursor/governance.js — update bugbot-review hook',
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
trigger: 'Privacy Mode or security model change',
|
|
137
|
+
targets: [
|
|
138
|
+
'src/cursor/techniques.js — update trust checks (CU-C01..CU-C09)',
|
|
139
|
+
'src/cursor/governance.js — update caveats and permission profiles',
|
|
140
|
+
'src/cursor/deep-review.js — update trust class detection',
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
trigger: 'Design Mode feature update',
|
|
145
|
+
targets: [
|
|
146
|
+
'src/cursor/setup.js — update Design Mode guide template',
|
|
147
|
+
'src/cursor/techniques.js — update CU-L01 modern features check',
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Release gate: check if all P0 sources are within staleness threshold.
|
|
154
|
+
*/
|
|
155
|
+
function checkReleaseGate(sourceVerifications = {}) {
|
|
156
|
+
const now = new Date();
|
|
157
|
+
const results = P0_SOURCES.map(source => {
|
|
158
|
+
const verifiedAt = sourceVerifications[source.key]
|
|
159
|
+
? new Date(sourceVerifications[source.key])
|
|
160
|
+
: source.verifiedAt ? new Date(source.verifiedAt) : null;
|
|
161
|
+
|
|
162
|
+
if (!verifiedAt) {
|
|
163
|
+
return { ...source, status: 'unverified', daysStale: null };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const daysSince = Math.floor((now - verifiedAt) / (1000 * 60 * 60 * 24));
|
|
167
|
+
const isStale = daysSince > source.stalenessThresholdDays;
|
|
168
|
+
|
|
169
|
+
return { ...source, verifiedAt: verifiedAt.toISOString(), daysStale: daysSince, status: isStale ? 'stale' : 'fresh' };
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
ready: results.every(r => r.status === 'fresh'),
|
|
174
|
+
stale: results.filter(r => r.status === 'stale' || r.status === 'unverified'),
|
|
175
|
+
fresh: results.filter(r => r.status === 'fresh'),
|
|
176
|
+
results,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function formatReleaseGate(gateResult) {
|
|
181
|
+
const lines = [
|
|
182
|
+
`Cursor Freshness Gate (nerviq v${version})`,
|
|
183
|
+
'═══════════════════════════════════════',
|
|
184
|
+
'',
|
|
185
|
+
`Status: ${gateResult.ready ? 'READY' : 'BLOCKED'}`,
|
|
186
|
+
`Fresh: ${gateResult.fresh.length}/${gateResult.results.length}`,
|
|
187
|
+
'',
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
for (const result of gateResult.results) {
|
|
191
|
+
const icon = result.status === 'fresh' ? '✓' : result.status === 'stale' ? '✗' : '?';
|
|
192
|
+
const age = result.daysStale !== null ? ` (${result.daysStale}d ago)` : ' (unverified)';
|
|
193
|
+
lines.push(` ${icon} ${result.label}${age} — threshold: ${result.stalenessThresholdDays}d`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!gateResult.ready) {
|
|
197
|
+
lines.push('', 'Action required: verify stale/unverified sources before claiming release freshness.');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return lines.join('\n');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function getPropagationTargets(triggerKeyword) {
|
|
204
|
+
const keyword = triggerKeyword.toLowerCase();
|
|
205
|
+
return PROPAGATION_CHECKLIST.filter(item => item.trigger.toLowerCase().includes(keyword));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = {
|
|
209
|
+
P0_SOURCES,
|
|
210
|
+
PROPAGATION_CHECKLIST,
|
|
211
|
+
checkReleaseGate,
|
|
212
|
+
formatReleaseGate,
|
|
213
|
+
getPropagationTargets,
|
|
214
|
+
};
|
package/src/dashboard.js
CHANGED
|
@@ -68,7 +68,7 @@ function buildScoreOverTimeSvg(history) {
|
|
|
68
68
|
|
|
69
69
|
return `
|
|
70
70
|
<div class="card">
|
|
71
|
-
<h2>Score Over Time</h2>
|
|
71
|
+
<h2>Audit Snapshot Score Over Time</h2>
|
|
72
72
|
<svg viewBox="0 0 ${w} ${h}" width="100%" style="max-width:${w}px">
|
|
73
73
|
${yLabels}
|
|
74
74
|
<polyline points="${polyline}" fill="none" stroke="${COLORS.blue}" stroke-width="2"/>
|
|
@@ -119,11 +119,28 @@ function buildCategoryBreakdownSvg(results) {
|
|
|
119
119
|
</div>`;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
function getDashboardScoreMeta(history) {
|
|
123
|
+
if (history && history.length > 0) {
|
|
124
|
+
return {
|
|
125
|
+
label: 'Latest audit snapshot score',
|
|
126
|
+
note: 'Dashboard is anchored to the most recent saved audit snapshot. Trend and drift sections use audit snapshots only.',
|
|
127
|
+
consoleSource: 'latest audit snapshot',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
label: 'Live audit score',
|
|
133
|
+
note: 'No saved audit snapshots found, so this dashboard ran a live audit of the current repo. Run `nerviq audit --snapshot` to build history.',
|
|
134
|
+
consoleSource: 'live audit (no snapshots yet)',
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
122
138
|
function buildHtml(projectName, auditPayload, history) {
|
|
123
139
|
const score = auditPayload.score ?? 0;
|
|
124
140
|
const platform = auditPayload.platform || 'unknown';
|
|
125
141
|
const results = auditPayload.results || [];
|
|
126
142
|
const timestamp = new Date().toISOString();
|
|
143
|
+
const scoreMeta = getDashboardScoreMeta(history);
|
|
127
144
|
|
|
128
145
|
// Top 5 failed checks sorted by impact severity
|
|
129
146
|
const impactOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
@@ -184,7 +201,8 @@ function buildHtml(projectName, auditPayload, history) {
|
|
|
184
201
|
|
|
185
202
|
<div class="card score-card">
|
|
186
203
|
<div class="score-number" style="color:${scoreColor(score)}">${score}</div>
|
|
187
|
-
<div class="score-label"
|
|
204
|
+
<div class="score-label">${escapeHtml(scoreMeta.label)}</div>
|
|
205
|
+
<div style="color:${COLORS.textDim};font-size:.85rem;margin-top:.75rem;max-width:520px;margin-left:auto;margin-right:auto">${escapeHtml(scoreMeta.note)}</div>
|
|
188
206
|
</div>
|
|
189
207
|
|
|
190
208
|
<div class="card">
|
|
@@ -238,6 +256,7 @@ async function generateDashboard(dir, flags = {}) {
|
|
|
238
256
|
auditPayload = await audit({ dir, silent: true, platform: flags.platform || 'claude' });
|
|
239
257
|
}
|
|
240
258
|
|
|
259
|
+
const scoreMeta = getDashboardScoreMeta(history);
|
|
241
260
|
const html = buildHtml(projectName, auditPayload, history);
|
|
242
261
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
243
262
|
fs.writeFileSync(outputPath, html, 'utf8');
|
|
@@ -247,8 +266,9 @@ async function generateDashboard(dir, flags = {}) {
|
|
|
247
266
|
console.log('');
|
|
248
267
|
console.log(' nerviq dashboard');
|
|
249
268
|
console.log(' \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');
|
|
250
|
-
console.log(`
|
|
251
|
-
console.log(`
|
|
269
|
+
console.log(` Dashboard score: ${auditPayload.score ?? '?'}/100`);
|
|
270
|
+
console.log(` Score source: ${scoreMeta.consoleSource}`);
|
|
271
|
+
console.log(` Audit snapshots: ${history.length}`);
|
|
252
272
|
console.log(` Output: ${relPath}`);
|
|
253
273
|
console.log('');
|
|
254
274
|
}
|
|
@@ -261,7 +281,7 @@ async function generateDashboard(dir, flags = {}) {
|
|
|
261
281
|
exec(cmd);
|
|
262
282
|
}
|
|
263
283
|
|
|
264
|
-
return { outputPath, relativePath: relPath, score: auditPayload.score };
|
|
284
|
+
return { outputPath, relativePath: relPath, score: auditPayload.score, scoreSource: scoreMeta.consoleSource };
|
|
265
285
|
}
|
|
266
286
|
|
|
267
287
|
/**
|
|
@@ -274,13 +294,15 @@ function detectDrifts(history, threshold = 5) {
|
|
|
274
294
|
for (let i = 0; i < history.length - 1; i++) {
|
|
275
295
|
const current = history[i];
|
|
276
296
|
const previous = history[i + 1];
|
|
277
|
-
|
|
278
|
-
|
|
297
|
+
const currentScore = current.summary?.score;
|
|
298
|
+
const previousScore = previous.summary?.score;
|
|
299
|
+
if (currentScore != null && previousScore != null) {
|
|
300
|
+
const delta = currentScore - previousScore;
|
|
279
301
|
if (Math.abs(delta) >= threshold) {
|
|
280
302
|
drifts.push({
|
|
281
|
-
date: current.date || current.timestamp,
|
|
282
|
-
from:
|
|
283
|
-
to:
|
|
303
|
+
date: current.createdAt || current.date || current.timestamp,
|
|
304
|
+
from: previousScore,
|
|
305
|
+
to: currentScore,
|
|
284
306
|
delta,
|
|
285
307
|
});
|
|
286
308
|
}
|
|
@@ -307,8 +329,8 @@ function buildDriftAlertsHtml(drifts) {
|
|
|
307
329
|
|
|
308
330
|
return `
|
|
309
331
|
<div style="margin-top:32px">
|
|
310
|
-
<h2 style="color:${COLORS.text};font-size:18px;margin-bottom:12px">⚠
|
|
311
|
-
<p style="color:${COLORS.textDim};font-size:13px;margin-bottom:12px">Changes of 5+ points between consecutive snapshots</p>
|
|
332
|
+
<h2 style="color:${COLORS.text};font-size:18px;margin-bottom:12px">⚠ Audit Snapshot Drift Alerts</h2>
|
|
333
|
+
<p style="color:${COLORS.textDim};font-size:13px;margin-bottom:12px">Changes of 5+ points between consecutive audit snapshots</p>
|
|
312
334
|
<table style="width:100%;border-collapse:collapse;background:${COLORS.surface};border-radius:8px;overflow:hidden">
|
|
313
335
|
<thead><tr style="background:${COLORS.border}">
|
|
314
336
|
<th style="padding:8px 12px;text-align:left;color:${COLORS.textDim};font-size:12px">Date</th>
|
|
@@ -375,7 +397,7 @@ function buildPortfolioHtml(repoResults) {
|
|
|
375
397
|
|
|
376
398
|
<div class="card score-card">
|
|
377
399
|
<div class="score-number" style="color:${scoreColor(avgScore)}">${avgScore}</div>
|
|
378
|
-
<div class="score-label">average score across ${repoResults.length} repos</div>
|
|
400
|
+
<div class="score-label">average live audit score across ${repoResults.length} repos</div>
|
|
379
401
|
</div>
|
|
380
402
|
|
|
381
403
|
<div class="highlights">
|
|
@@ -452,7 +474,7 @@ async function generatePortfolioDashboard(repoPaths, flags = {}) {
|
|
|
452
474
|
console.log(' nerviq portfolio dashboard');
|
|
453
475
|
console.log(' \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');
|
|
454
476
|
console.log(` Repos: ${repoResults.length}`);
|
|
455
|
-
console.log(` Average score: ${avgScore}/100`);
|
|
477
|
+
console.log(` Average live audit score: ${avgScore}/100`);
|
|
456
478
|
console.log(` Output: ${outputPath}`);
|
|
457
479
|
console.log('');
|
|
458
480
|
}
|
package/src/freshness.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Release gates, recurring probes, propagation checklists,
|
|
5
5
|
* and staleness blocking for Claude Code surfaces.
|
|
6
6
|
*
|
|
7
|
-
* P0 sources from
|
|
7
|
+
* P0 sources from code.claude.com/docs, propagation for CLAUDE.md format changes.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const { version } = require('../package.json');
|
|
@@ -16,58 +16,58 @@ const P0_SOURCES = [
|
|
|
16
16
|
{
|
|
17
17
|
key: 'claude-code-docs',
|
|
18
18
|
label: 'Claude Code Official Docs',
|
|
19
|
-
url: 'https://
|
|
19
|
+
url: 'https://code.claude.com/docs',
|
|
20
20
|
stalenessThresholdDays: 30,
|
|
21
|
-
verifiedAt:
|
|
21
|
+
verifiedAt: '2026-04-07',
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
key: 'claude-md-format',
|
|
25
|
-
label: 'CLAUDE.md
|
|
26
|
-
url: 'https://
|
|
25
|
+
label: 'CLAUDE.md / Memory Documentation',
|
|
26
|
+
url: 'https://code.claude.com/docs/en/memory',
|
|
27
27
|
stalenessThresholdDays: 30,
|
|
28
|
-
verifiedAt:
|
|
28
|
+
verifiedAt: '2026-04-07',
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
key: 'claude-mcp-docs',
|
|
32
32
|
label: 'Claude Code MCP Documentation',
|
|
33
|
-
url: 'https://
|
|
33
|
+
url: 'https://code.claude.com/docs/en/mcp',
|
|
34
34
|
stalenessThresholdDays: 30,
|
|
35
|
-
verifiedAt:
|
|
35
|
+
verifiedAt: '2026-04-07',
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
key: 'claude-hooks-docs',
|
|
39
39
|
label: 'Claude Code Hooks Documentation',
|
|
40
|
-
url: 'https://
|
|
40
|
+
url: 'https://code.claude.com/docs/en/hooks',
|
|
41
41
|
stalenessThresholdDays: 14,
|
|
42
|
-
verifiedAt:
|
|
42
|
+
verifiedAt: '2026-04-07',
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
key: 'claude-security-docs',
|
|
46
46
|
label: 'Claude Code Security Documentation',
|
|
47
|
-
url: 'https://
|
|
47
|
+
url: 'https://code.claude.com/docs/en/security',
|
|
48
48
|
stalenessThresholdDays: 30,
|
|
49
|
-
verifiedAt:
|
|
49
|
+
verifiedAt: '2026-04-07',
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
key: 'claude-permissions-docs',
|
|
53
53
|
label: 'Claude Code Permissions Documentation',
|
|
54
|
-
url: 'https://
|
|
54
|
+
url: 'https://code.claude.com/docs/en/permissions',
|
|
55
55
|
stalenessThresholdDays: 14,
|
|
56
|
-
verifiedAt:
|
|
56
|
+
verifiedAt: '2026-04-07',
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
key: 'claude-settings-docs',
|
|
60
60
|
label: 'Claude Code Settings Documentation',
|
|
61
|
-
url: 'https://
|
|
61
|
+
url: 'https://code.claude.com/docs/en/settings',
|
|
62
62
|
stalenessThresholdDays: 30,
|
|
63
|
-
verifiedAt:
|
|
63
|
+
verifiedAt: '2026-04-07',
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
key: 'anthropic-changelog',
|
|
67
|
-
label: '
|
|
68
|
-
url: 'https://
|
|
67
|
+
label: 'Claude Code Changelog',
|
|
68
|
+
url: 'https://code.claude.com/docs/en/changelog',
|
|
69
69
|
stalenessThresholdDays: 14,
|
|
70
|
-
verifiedAt:
|
|
70
|
+
verifiedAt: '2026-04-07',
|
|
71
71
|
},
|
|
72
72
|
];
|
|
73
73
|
|