@nerviq/cli 1.29.0 → 1.30.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/CHANGELOG.md +1764 -1493
- package/README.md +568 -538
- package/SECURITY.md +78 -82
- package/bin/cli.js +2838 -2558
- package/docs/api-reference.md +356 -356
- package/docs/audit-fix.md +109 -0
- package/docs/autofix.md +3 -62
- package/docs/getting-started.md +1 -1
- package/docs/index.html +592 -592
- package/docs/integration-contracts.md +287 -287
- package/docs/maintenance.md +128 -128
- package/docs/new-platform-guide.md +202 -202
- package/docs/release-process.md +63 -0
- package/docs/shallow-risk.md +244 -244
- package/docs/why-nerviq.md +82 -82
- package/package.json +75 -67
- package/sdk/README.md +12 -3
- package/sdk/examples/langchain-integration.md +128 -0
- package/sdk/examples/self-governing-agent.js +135 -0
- package/sdk/index.d.ts +115 -0
- package/sdk/index.js +94 -0
- package/sdk/package.json +11 -0
- package/src/activity.js +13 -0
- package/src/aider/activity.js +226 -226
- package/src/aider/context.js +162 -162
- package/src/aider/freshness.js +123 -123
- package/src/aider/techniques.js +3465 -3465
- package/src/audit/layers.js +180 -180
- package/src/audit.js +1133 -1032
- package/src/auto-suggest.js +9 -2
- package/src/behavioral-drift.js +37 -2
- package/src/benchmark.js +299 -299
- package/src/codex/activity.js +324 -324
- package/src/codex/freshness.js +149 -142
- package/src/codex/techniques.js +4895 -4895
- package/src/context.js +326 -326
- package/src/continuous-ops.js +11 -1
- package/src/convert.js +340 -340
- package/src/copilot/config-parser.js +280 -280
- package/src/copilot/context.js +218 -218
- package/src/copilot/freshness.js +184 -177
- package/src/copilot/patch.js +238 -238
- package/src/copilot/techniques.js +3578 -3578
- package/src/cursor/freshness.js +194 -194
- package/src/cursor/patch.js +243 -243
- package/src/cursor/techniques.js +3735 -3735
- package/src/doctor.js +201 -201
- package/src/fix-engine.js +511 -8
- package/src/formatters/csv.js +86 -86
- package/src/formatters/junit.js +123 -123
- package/src/formatters/markdown.js +164 -164
- package/src/formatters/otel.js +151 -151
- package/src/freshness.js +163 -156
- package/src/gemini/activity.js +402 -402
- package/src/gemini/context.js +290 -290
- package/src/gemini/freshness.js +188 -188
- package/src/gemini/patch.js +229 -229
- package/src/gemini/techniques.js +3811 -3811
- package/src/governance.js +533 -533
- package/src/harmony/audit.js +306 -306
- package/src/i18n.js +63 -63
- package/src/insights.js +119 -119
- package/src/integrations.js +134 -134
- package/src/locales/en.json +33 -33
- package/src/locales/es.json +33 -33
- package/src/migrate.js +354 -354
- package/src/opencode/activity.js +286 -286
- package/src/opencode/freshness.js +137 -137
- package/src/opencode/techniques.js +3450 -3450
- package/src/safe-glyph.js +97 -0
- package/src/setup/analysis.js +12 -12
- package/src/setup.js +13 -6
- package/src/shallow-risk/index.js +113 -56
- package/src/shallow-risk/patterns/agent-config-cross-platform-drift.js +51 -50
- package/src/shallow-risk/patterns/agent-config-dangerous-autoapprove.js +47 -46
- package/src/shallow-risk/patterns/agent-config-deprecated-keys.js +47 -46
- package/src/shallow-risk/patterns/agent-config-framework-version-mismatch.js +138 -0
- package/src/shallow-risk/patterns/agent-config-missing-file.js +318 -317
- package/src/shallow-risk/patterns/agent-config-script-not-in-package-json.js +108 -0
- package/src/shallow-risk/patterns/agent-config-secret-literal.js +52 -49
- package/src/shallow-risk/patterns/agent-config-stack-contradiction.js +35 -34
- package/src/shallow-risk/patterns/hook-script-missing.js +71 -70
- package/src/shallow-risk/patterns/mcp-server-no-allowlist.js +53 -52
- package/src/shallow-risk/shared.js +653 -648
- package/src/source-urls.js +295 -295
- package/src/state-paths.js +85 -85
- package/src/supplemental-checks.js +805 -805
- package/src/telemetry.js +160 -160
- package/src/watch.js +46 -0
- package/src/windsurf/context.js +359 -359
- package/src/windsurf/freshness.js +194 -194
- package/src/windsurf/patch.js +231 -231
- package/src/windsurf/techniques.js +3779 -3779
package/src/telemetry.js
CHANGED
|
@@ -1,160 +1,160 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Nerviq Opt-In Telemetry Foundation
|
|
3
|
-
*
|
|
4
|
-
* Collects anonymous usage events ONLY when NERVIQ_TELEMETRY=1 is set.
|
|
5
|
-
* No PII, no file contents, no absolute paths are ever stored.
|
|
6
|
-
* Events are stored locally in <projectDir>/.nerviq/telemetry.json.
|
|
7
|
-
*
|
|
8
|
-
* This module is the foundation layer — actual transmission to a dashboard
|
|
9
|
-
* is an explicit opt-in step configured separately.
|
|
10
|
-
*
|
|
11
|
-
* Privacy guarantees:
|
|
12
|
-
* - No usernames, emails, or identifiers
|
|
13
|
-
* - No file contents or code
|
|
14
|
-
* - No absolute paths (only hashed project fingerprint)
|
|
15
|
-
* - Stored only on local disk
|
|
16
|
-
* - Never sent anywhere without additional explicit configuration
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
'use strict';
|
|
20
|
-
|
|
21
|
-
const fs = require('fs');
|
|
22
|
-
const path = require('path');
|
|
23
|
-
const os = require('os');
|
|
24
|
-
const crypto = require('crypto');
|
|
25
|
-
|
|
26
|
-
const TELEMETRY_FILE = path.join(os.homedir(), '.nerviq', 'telemetry.json');
|
|
27
|
-
const MAX_EVENTS = 500; // cap file size at ~500 events
|
|
28
|
-
|
|
29
|
-
// ─── Opt-in check ─────────────────────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Returns true only when the user has explicitly set NERVIQ_TELEMETRY=1.
|
|
33
|
-
* Telemetry is opt-IN, not opt-out.
|
|
34
|
-
* @returns {boolean}
|
|
35
|
-
*/
|
|
36
|
-
function shouldCollectTelemetry() {
|
|
37
|
-
return process.env.NERVIQ_TELEMETRY === '1';
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ─── Anonymous fingerprinting ─────────────────────────────────────────────────
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Creates a one-way hash of the project directory.
|
|
44
|
-
* This allows grouping events by project without exposing the path.
|
|
45
|
-
* @param {string} dir
|
|
46
|
-
* @returns {string} 8-char hex fingerprint
|
|
47
|
-
*/
|
|
48
|
-
function hashProject(dir) {
|
|
49
|
-
try {
|
|
50
|
-
return crypto.createHash('sha256').update(dir).digest('hex').slice(0, 8);
|
|
51
|
-
} catch {
|
|
52
|
-
return 'unknown';
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ─── Event collection ────────────────────────────────────────────────────────
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Collect an anonymous usage event and append it to the local telemetry file.
|
|
60
|
-
* Does nothing unless shouldCollectTelemetry() returns true.
|
|
61
|
-
*
|
|
62
|
-
* @param {string} event - Event name (e.g. 'audit', 'setup', 'convert')
|
|
63
|
-
* @param {object} [data] - Additional anonymous data
|
|
64
|
-
* @param {string} [data.platform] - Platform name (claude, codex, etc.)
|
|
65
|
-
* @param {number} [data.score] - Audit score
|
|
66
|
-
* @param {number} [data.checkCount] - Total checks evaluated
|
|
67
|
-
* @param {number} [data.durationMs] - Execution time in ms
|
|
68
|
-
* @param {string} [data.dir] - Project dir (hashed before storage)
|
|
69
|
-
* @returns {object|null} The recorded event object, or null if telemetry is off
|
|
70
|
-
*/
|
|
71
|
-
function collectAnonymousEvent(event, data = {}) {
|
|
72
|
-
if (!shouldCollectTelemetry()) return null;
|
|
73
|
-
|
|
74
|
-
const record = {
|
|
75
|
-
event: String(event),
|
|
76
|
-
platform: data.platform || null,
|
|
77
|
-
score: typeof data.score === 'number' ? data.score : null,
|
|
78
|
-
checkCount: typeof data.checkCount === 'number' ? data.checkCount : null,
|
|
79
|
-
durationMs: typeof data.durationMs === 'number' ? Math.round(data.durationMs) : null,
|
|
80
|
-
timestamp: new Date().toISOString(),
|
|
81
|
-
nodeVersion: process.version,
|
|
82
|
-
os: `${os.platform()}-${os.arch()}`,
|
|
83
|
-
projectFingerprint: data.dir ? hashProject(data.dir) : null,
|
|
84
|
-
// Explicitly omit: paths, file contents, usernames, email, tokens
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
const telemetryDir = path.dirname(TELEMETRY_FILE);
|
|
89
|
-
fs.mkdirSync(telemetryDir, { recursive: true });
|
|
90
|
-
|
|
91
|
-
let events = [];
|
|
92
|
-
if (fs.existsSync(TELEMETRY_FILE)) {
|
|
93
|
-
try {
|
|
94
|
-
const raw = fs.readFileSync(TELEMETRY_FILE, 'utf8');
|
|
95
|
-
const parsed = JSON.parse(raw);
|
|
96
|
-
events = Array.isArray(parsed.events) ? parsed.events : [];
|
|
97
|
-
} catch {
|
|
98
|
-
events = [];
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
events.push(record);
|
|
103
|
-
|
|
104
|
-
// Cap at MAX_EVENTS to prevent unbounded growth
|
|
105
|
-
if (events.length > MAX_EVENTS) {
|
|
106
|
-
events = events.slice(events.length - MAX_EVENTS);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const payload = {
|
|
110
|
-
version: 1,
|
|
111
|
-
telemetryOptIn: true,
|
|
112
|
-
note: 'Local telemetry only. Set NERVIQ_TELEMETRY=0 or unset to disable.',
|
|
113
|
-
events,
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
fs.writeFileSync(TELEMETRY_FILE, JSON.stringify(payload, null, 2), 'utf8');
|
|
117
|
-
} catch {
|
|
118
|
-
// Telemetry failures are always silent — never block main flow
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return record;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ─── Read local telemetry ─────────────────────────────────────────────────────
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Read the local telemetry file.
|
|
128
|
-
* @returns {{ version: number, events: object[] } | null}
|
|
129
|
-
*/
|
|
130
|
-
function readLocalTelemetry() {
|
|
131
|
-
try {
|
|
132
|
-
if (!fs.existsSync(TELEMETRY_FILE)) return null;
|
|
133
|
-
return JSON.parse(fs.readFileSync(TELEMETRY_FILE, 'utf8'));
|
|
134
|
-
} catch {
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Clear all local telemetry events.
|
|
141
|
-
* @returns {boolean} true if cleared successfully
|
|
142
|
-
*/
|
|
143
|
-
function clearLocalTelemetry() {
|
|
144
|
-
try {
|
|
145
|
-
if (fs.existsSync(TELEMETRY_FILE)) {
|
|
146
|
-
fs.writeFileSync(TELEMETRY_FILE, JSON.stringify({ version: 1, events: [] }, null, 2), 'utf8');
|
|
147
|
-
}
|
|
148
|
-
return true;
|
|
149
|
-
} catch {
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
module.exports = {
|
|
155
|
-
shouldCollectTelemetry,
|
|
156
|
-
collectAnonymousEvent,
|
|
157
|
-
readLocalTelemetry,
|
|
158
|
-
clearLocalTelemetry,
|
|
159
|
-
TELEMETRY_FILE,
|
|
160
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Nerviq Opt-In Telemetry Foundation
|
|
3
|
+
*
|
|
4
|
+
* Collects anonymous usage events ONLY when NERVIQ_TELEMETRY=1 is set.
|
|
5
|
+
* No PII, no file contents, no absolute paths are ever stored.
|
|
6
|
+
* Events are stored locally in <projectDir>/.nerviq/telemetry.json.
|
|
7
|
+
*
|
|
8
|
+
* This module is the foundation layer — actual transmission to a dashboard
|
|
9
|
+
* is an explicit opt-in step configured separately.
|
|
10
|
+
*
|
|
11
|
+
* Privacy guarantees:
|
|
12
|
+
* - No usernames, emails, or identifiers
|
|
13
|
+
* - No file contents or code
|
|
14
|
+
* - No absolute paths (only hashed project fingerprint)
|
|
15
|
+
* - Stored only on local disk
|
|
16
|
+
* - Never sent anywhere without additional explicit configuration
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const os = require('os');
|
|
24
|
+
const crypto = require('crypto');
|
|
25
|
+
|
|
26
|
+
const TELEMETRY_FILE = path.join(os.homedir(), '.nerviq', 'telemetry.json');
|
|
27
|
+
const MAX_EVENTS = 500; // cap file size at ~500 events
|
|
28
|
+
|
|
29
|
+
// ─── Opt-in check ─────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Returns true only when the user has explicitly set NERVIQ_TELEMETRY=1.
|
|
33
|
+
* Telemetry is opt-IN, not opt-out.
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
function shouldCollectTelemetry() {
|
|
37
|
+
return process.env.NERVIQ_TELEMETRY === '1';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Anonymous fingerprinting ─────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Creates a one-way hash of the project directory.
|
|
44
|
+
* This allows grouping events by project without exposing the path.
|
|
45
|
+
* @param {string} dir
|
|
46
|
+
* @returns {string} 8-char hex fingerprint
|
|
47
|
+
*/
|
|
48
|
+
function hashProject(dir) {
|
|
49
|
+
try {
|
|
50
|
+
return crypto.createHash('sha256').update(dir).digest('hex').slice(0, 8);
|
|
51
|
+
} catch {
|
|
52
|
+
return 'unknown';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Event collection ────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Collect an anonymous usage event and append it to the local telemetry file.
|
|
60
|
+
* Does nothing unless shouldCollectTelemetry() returns true.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} event - Event name (e.g. 'audit', 'setup', 'convert')
|
|
63
|
+
* @param {object} [data] - Additional anonymous data
|
|
64
|
+
* @param {string} [data.platform] - Platform name (claude, codex, etc.)
|
|
65
|
+
* @param {number} [data.score] - Audit score
|
|
66
|
+
* @param {number} [data.checkCount] - Total checks evaluated
|
|
67
|
+
* @param {number} [data.durationMs] - Execution time in ms
|
|
68
|
+
* @param {string} [data.dir] - Project dir (hashed before storage)
|
|
69
|
+
* @returns {object|null} The recorded event object, or null if telemetry is off
|
|
70
|
+
*/
|
|
71
|
+
function collectAnonymousEvent(event, data = {}) {
|
|
72
|
+
if (!shouldCollectTelemetry()) return null;
|
|
73
|
+
|
|
74
|
+
const record = {
|
|
75
|
+
event: String(event),
|
|
76
|
+
platform: data.platform || null,
|
|
77
|
+
score: typeof data.score === 'number' ? data.score : null,
|
|
78
|
+
checkCount: typeof data.checkCount === 'number' ? data.checkCount : null,
|
|
79
|
+
durationMs: typeof data.durationMs === 'number' ? Math.round(data.durationMs) : null,
|
|
80
|
+
timestamp: new Date().toISOString(),
|
|
81
|
+
nodeVersion: process.version,
|
|
82
|
+
os: `${os.platform()}-${os.arch()}`,
|
|
83
|
+
projectFingerprint: data.dir ? hashProject(data.dir) : null,
|
|
84
|
+
// Explicitly omit: paths, file contents, usernames, email, tokens
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const telemetryDir = path.dirname(TELEMETRY_FILE);
|
|
89
|
+
fs.mkdirSync(telemetryDir, { recursive: true });
|
|
90
|
+
|
|
91
|
+
let events = [];
|
|
92
|
+
if (fs.existsSync(TELEMETRY_FILE)) {
|
|
93
|
+
try {
|
|
94
|
+
const raw = fs.readFileSync(TELEMETRY_FILE, 'utf8');
|
|
95
|
+
const parsed = JSON.parse(raw);
|
|
96
|
+
events = Array.isArray(parsed.events) ? parsed.events : [];
|
|
97
|
+
} catch {
|
|
98
|
+
events = [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
events.push(record);
|
|
103
|
+
|
|
104
|
+
// Cap at MAX_EVENTS to prevent unbounded growth
|
|
105
|
+
if (events.length > MAX_EVENTS) {
|
|
106
|
+
events = events.slice(events.length - MAX_EVENTS);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const payload = {
|
|
110
|
+
version: 1,
|
|
111
|
+
telemetryOptIn: true,
|
|
112
|
+
note: 'Local telemetry only. Set NERVIQ_TELEMETRY=0 or unset to disable.',
|
|
113
|
+
events,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
fs.writeFileSync(TELEMETRY_FILE, JSON.stringify(payload, null, 2), 'utf8');
|
|
117
|
+
} catch {
|
|
118
|
+
// Telemetry failures are always silent — never block main flow
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return record;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ─── Read local telemetry ─────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Read the local telemetry file.
|
|
128
|
+
* @returns {{ version: number, events: object[] } | null}
|
|
129
|
+
*/
|
|
130
|
+
function readLocalTelemetry() {
|
|
131
|
+
try {
|
|
132
|
+
if (!fs.existsSync(TELEMETRY_FILE)) return null;
|
|
133
|
+
return JSON.parse(fs.readFileSync(TELEMETRY_FILE, 'utf8'));
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Clear all local telemetry events.
|
|
141
|
+
* @returns {boolean} true if cleared successfully
|
|
142
|
+
*/
|
|
143
|
+
function clearLocalTelemetry() {
|
|
144
|
+
try {
|
|
145
|
+
if (fs.existsSync(TELEMETRY_FILE)) {
|
|
146
|
+
fs.writeFileSync(TELEMETRY_FILE, JSON.stringify({ version: 1, events: [] }, null, 2), 'utf8');
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
} catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = {
|
|
155
|
+
shouldCollectTelemetry,
|
|
156
|
+
collectAnonymousEvent,
|
|
157
|
+
readLocalTelemetry,
|
|
158
|
+
clearLocalTelemetry,
|
|
159
|
+
TELEMETRY_FILE,
|
|
160
|
+
};
|
package/src/watch.js
CHANGED
|
@@ -144,6 +144,40 @@ async function watch(options) {
|
|
|
144
144
|
console.log(c(' Press Ctrl+C to stop', 'dim'));
|
|
145
145
|
console.log('');
|
|
146
146
|
|
|
147
|
+
// LOOP-01: alert state — track which named alerts fired last cycle so we
|
|
148
|
+
// can emit "new alert / cleared alert" lines per change rather than the
|
|
149
|
+
// full list every save. The user-lab's "spellcheck for prompts" framing
|
|
150
|
+
// wants action-on-change, not score-deltas alone.
|
|
151
|
+
const alertsEnabled = options.alerts !== false; // default-on; pass --no-alerts to disable
|
|
152
|
+
const lastAlerts = new Set();
|
|
153
|
+
function buildAlertSet(result) {
|
|
154
|
+
const set = new Set();
|
|
155
|
+
if (result && result.staleReferences && result.staleReferences.byKey) {
|
|
156
|
+
for (const [key, count] of Object.entries(result.staleReferences.byKey)) {
|
|
157
|
+
set.add(`stale:${key}:${count}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (Array.isArray(result && result.shallowRiskHints)) {
|
|
161
|
+
for (const h of result.shallowRiskHints) {
|
|
162
|
+
if (h && h.key && h.severity === 'critical') {
|
|
163
|
+
set.add(`critical:${h.key}:${h.file || ''}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return set;
|
|
168
|
+
}
|
|
169
|
+
function emitAlertDiff(prev, curr) {
|
|
170
|
+
if (!alertsEnabled) return;
|
|
171
|
+
const newAlerts = [...curr].filter((a) => !prev.has(a));
|
|
172
|
+
const cleared = [...prev].filter((a) => !curr.has(a));
|
|
173
|
+
for (const a of newAlerts) {
|
|
174
|
+
console.log(c(` 🔔 NEW: ${a}`, 'yellow'));
|
|
175
|
+
}
|
|
176
|
+
for (const a of cleared) {
|
|
177
|
+
console.log(c(` ✓ CLEARED: ${a}`, 'green'));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
147
181
|
// Initial audit
|
|
148
182
|
let lastScore = null;
|
|
149
183
|
try {
|
|
@@ -151,6 +185,9 @@ async function watch(options) {
|
|
|
151
185
|
lastScore = result.score;
|
|
152
186
|
console.log(` ${c('Initial score:', 'bold')} ${scoreColor(result.score)}`);
|
|
153
187
|
console.log(` ${result.passed} / ${result.passed + result.failed} checks passing`);
|
|
188
|
+
if (alertsEnabled && result.staleReferences && result.staleReferences.count > 0) {
|
|
189
|
+
console.log(c(` 📌 Initial alerts: ${result.staleReferences.count} stale reference(s)`, 'yellow'));
|
|
190
|
+
}
|
|
154
191
|
const continuousStatus = buildContinuousStatus({
|
|
155
192
|
dir: options.dir,
|
|
156
193
|
auditResult: result,
|
|
@@ -159,6 +196,8 @@ async function watch(options) {
|
|
|
159
196
|
});
|
|
160
197
|
console.log(formatContinuousStatus(continuousStatus, { compact: true }));
|
|
161
198
|
console.log('');
|
|
199
|
+
const initialAlerts = buildAlertSet(result);
|
|
200
|
+
for (const a of initialAlerts) lastAlerts.add(a);
|
|
162
201
|
} catch (e) {
|
|
163
202
|
console.log(c(` Initial audit failed: ${e.message}`, 'dim'));
|
|
164
203
|
}
|
|
@@ -205,6 +244,13 @@ async function watch(options) {
|
|
|
205
244
|
console.log(` Score: ${scoreColor(result.score)} ${arrow} (${result.passed}/${result.passed + result.failed} passing)`);
|
|
206
245
|
console.log(formatContinuousStatus(continuousStatus, { compact: true }));
|
|
207
246
|
|
|
247
|
+
// LOOP-01: emit named alert diff (NEW / CLEARED) per change, so the
|
|
248
|
+
// developer gets action-on-change feedback, not just score deltas.
|
|
249
|
+
const currentAlerts = buildAlertSet(result);
|
|
250
|
+
emitAlertDiff(lastAlerts, currentAlerts);
|
|
251
|
+
lastAlerts.clear();
|
|
252
|
+
for (const a of currentAlerts) lastAlerts.add(a);
|
|
253
|
+
|
|
208
254
|
if (lastScore !== null && result.score > lastScore) {
|
|
209
255
|
console.log(c(' Nice improvement!', 'green'));
|
|
210
256
|
} else if (lastScore !== null && result.score < lastScore) {
|