@nerviq/cli 1.10.0 → 1.11.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 +80 -29
- package/bin/cli.js +229 -110
- package/package.json +1 -1
- package/src/activity.js +185 -59
- package/src/aider/freshness.js +28 -25
- package/src/analyze.js +3 -1
- package/src/anti-patterns.js +4 -2
- package/src/audit.js +100 -74
- package/src/benchmark.js +15 -10
- package/src/governance.js +13 -7
- package/src/index.js +2 -1
- package/src/integrations.js +102 -55
- package/src/permission-rules.js +218 -0
- package/src/secret-patterns.js +9 -0
- package/src/server.js +398 -3
- package/src/setup.js +2 -2
- package/src/techniques.js +41 -45
- package/src/terminology.js +73 -0
- package/src/token-estimate.js +35 -0
- package/src/workspace.js +105 -8
package/src/activity.js
CHANGED
|
@@ -136,6 +136,37 @@ function summarizeSnapshot(snapshotKind, payload) {
|
|
|
136
136
|
return {};
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
function normalizeSnapshotTags(input) {
|
|
140
|
+
const values = Array.isArray(input) ? input : (input ? [input] : []);
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
const tags = [];
|
|
143
|
+
|
|
144
|
+
for (const value of values) {
|
|
145
|
+
const parts = `${value || ''}`
|
|
146
|
+
.split(',')
|
|
147
|
+
.map((item) => item.replace(/\s+/g, ' ').trim())
|
|
148
|
+
.filter(Boolean);
|
|
149
|
+
|
|
150
|
+
for (const part of parts) {
|
|
151
|
+
const key = part.toLowerCase();
|
|
152
|
+
if (seen.has(key)) continue;
|
|
153
|
+
seen.add(key);
|
|
154
|
+
tags.push(part.slice(0, 48));
|
|
155
|
+
if (tags.length >= 8) {
|
|
156
|
+
return tags;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return tags;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function formatSnapshotTags(tags = []) {
|
|
165
|
+
const normalized = normalizeSnapshotTags(tags);
|
|
166
|
+
if (normalized.length === 0) return '';
|
|
167
|
+
return ` [${normalized.join(', ')}]`;
|
|
168
|
+
}
|
|
169
|
+
|
|
139
170
|
function updateSnapshotIndex(snapshotDir, record) {
|
|
140
171
|
const indexPath = path.join(snapshotDir, 'index.json');
|
|
141
172
|
let entries = [];
|
|
@@ -173,6 +204,11 @@ function writeSnapshotArtifact(dir, snapshotKind, payload, meta = {}) {
|
|
|
173
204
|
const { snapshotDir } = ensureArtifactDirs(dir);
|
|
174
205
|
const filePath = path.join(snapshotDir, `${id}-${snapshotKind}.json`);
|
|
175
206
|
const summary = summarizeSnapshot(snapshotKind, payload);
|
|
207
|
+
const metaTags = normalizeSnapshotTags([
|
|
208
|
+
...(Array.isArray(meta.tags) ? meta.tags : (meta.tags ? [meta.tags] : [])),
|
|
209
|
+
...(meta.tag ? [meta.tag] : []),
|
|
210
|
+
]);
|
|
211
|
+
const { tags: _ignoredTags, tag: _ignoredTag, ...restMeta } = meta;
|
|
176
212
|
const envelope = {
|
|
177
213
|
schemaVersion: 1,
|
|
178
214
|
artifactType: 'snapshot',
|
|
@@ -183,7 +219,8 @@ function writeSnapshotArtifact(dir, snapshotKind, payload, meta = {}) {
|
|
|
183
219
|
generatedBy: `nerviq@${version}`,
|
|
184
220
|
directory: dir,
|
|
185
221
|
summary,
|
|
186
|
-
|
|
222
|
+
tags: metaTags,
|
|
223
|
+
...restMeta,
|
|
187
224
|
payload,
|
|
188
225
|
};
|
|
189
226
|
|
|
@@ -194,6 +231,7 @@ function writeSnapshotArtifact(dir, snapshotKind, payload, meta = {}) {
|
|
|
194
231
|
snapshotKind,
|
|
195
232
|
createdAt: envelope.createdAt,
|
|
196
233
|
relativePath: path.relative(dir, filePath),
|
|
234
|
+
tags: metaTags,
|
|
197
235
|
summary,
|
|
198
236
|
};
|
|
199
237
|
updateSnapshotIndex(snapshotDir, record);
|
|
@@ -236,6 +274,79 @@ function getHistory(dir, limit = 20) {
|
|
|
236
274
|
.slice(0, limit);
|
|
237
275
|
}
|
|
238
276
|
|
|
277
|
+
function buildCheckDiffDetail(previousResult, currentResult) {
|
|
278
|
+
const source = currentResult || previousResult || {};
|
|
279
|
+
const previousState = previousResult ? previousResult.passed : undefined;
|
|
280
|
+
const currentState = currentResult ? currentResult.passed : undefined;
|
|
281
|
+
return {
|
|
282
|
+
key: source.key,
|
|
283
|
+
name: source.name || source.key,
|
|
284
|
+
impact: source.impact || null,
|
|
285
|
+
category: source.category || null,
|
|
286
|
+
previousState,
|
|
287
|
+
currentState,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function collectCheckDiff(previousResults = [], currentResults = []) {
|
|
292
|
+
const prevMap = new Map();
|
|
293
|
+
const currMap = new Map();
|
|
294
|
+
|
|
295
|
+
for (const result of previousResults) {
|
|
296
|
+
if (result && result.key) prevMap.set(result.key, result);
|
|
297
|
+
}
|
|
298
|
+
for (const result of currentResults) {
|
|
299
|
+
if (result && result.key) currMap.set(result.key, result);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const regressions = [];
|
|
303
|
+
const improvements = [];
|
|
304
|
+
const newlyApplicable = [];
|
|
305
|
+
const noLongerApplicable = [];
|
|
306
|
+
const newChecks = [];
|
|
307
|
+
const removedChecks = [];
|
|
308
|
+
|
|
309
|
+
const allKeys = [...new Set([...prevMap.keys(), ...currMap.keys()])].sort();
|
|
310
|
+
for (const key of allKeys) {
|
|
311
|
+
const previousResult = prevMap.get(key);
|
|
312
|
+
const currentResult = currMap.get(key);
|
|
313
|
+
const previousState = previousResult ? previousResult.passed : undefined;
|
|
314
|
+
const currentState = currentResult ? currentResult.passed : undefined;
|
|
315
|
+
const detail = buildCheckDiffDetail(previousResult, currentResult);
|
|
316
|
+
|
|
317
|
+
if (!previousResult) {
|
|
318
|
+
if (currentState !== undefined) {
|
|
319
|
+
newChecks.push(detail);
|
|
320
|
+
}
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!currentResult) {
|
|
325
|
+
removedChecks.push(detail);
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (previousState === true && currentState === false) {
|
|
330
|
+
regressions.push(detail);
|
|
331
|
+
} else if (previousState === false && currentState === true) {
|
|
332
|
+
improvements.push(detail);
|
|
333
|
+
} else if ((previousState === null || previousState === undefined) && (currentState === true || currentState === false)) {
|
|
334
|
+
newlyApplicable.push(detail);
|
|
335
|
+
} else if ((currentState === null || currentState === undefined) && (previousState === true || previousState === false)) {
|
|
336
|
+
noLongerApplicable.push(detail);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
regressions,
|
|
342
|
+
improvements,
|
|
343
|
+
newlyApplicable,
|
|
344
|
+
noLongerApplicable,
|
|
345
|
+
newChecks,
|
|
346
|
+
removedChecks,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
239
350
|
/**
|
|
240
351
|
* Compare the two most recent audit snapshots and return the delta.
|
|
241
352
|
* @param {string} dir - Project root directory.
|
|
@@ -247,6 +358,8 @@ function compareLatest(dir) {
|
|
|
247
358
|
|
|
248
359
|
const current = audits[0];
|
|
249
360
|
const previous = audits[1];
|
|
361
|
+
const currentPayload = loadSnapshotPayload(dir, current);
|
|
362
|
+
const previousPayload = loadSnapshotPayload(dir, previous);
|
|
250
363
|
|
|
251
364
|
const delta = {
|
|
252
365
|
score: (current.summary?.score || 0) - (previous.summary?.score || 0),
|
|
@@ -254,26 +367,64 @@ function compareLatest(dir) {
|
|
|
254
367
|
passed: (current.summary?.passed || 0) - (previous.summary?.passed || 0),
|
|
255
368
|
};
|
|
256
369
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
370
|
+
let regressionDetails = [];
|
|
371
|
+
let improvementDetails = [];
|
|
372
|
+
let newlyApplicableDetails = [];
|
|
373
|
+
let noLongerApplicableDetails = [];
|
|
374
|
+
let newChecks = [];
|
|
375
|
+
let removedChecks = [];
|
|
376
|
+
let regressions = [];
|
|
377
|
+
let improvements = [];
|
|
378
|
+
let detailedDiffAvailable = false;
|
|
379
|
+
|
|
380
|
+
if (currentPayload && previousPayload && Array.isArray(currentPayload.results) && Array.isArray(previousPayload.results)) {
|
|
381
|
+
const diff = collectCheckDiff(previousPayload.results, currentPayload.results);
|
|
382
|
+
regressionDetails = diff.regressions;
|
|
383
|
+
improvementDetails = diff.improvements;
|
|
384
|
+
newlyApplicableDetails = diff.newlyApplicable;
|
|
385
|
+
noLongerApplicableDetails = diff.noLongerApplicable;
|
|
386
|
+
newChecks = diff.newChecks;
|
|
387
|
+
removedChecks = diff.removedChecks;
|
|
388
|
+
regressions = regressionDetails.map((item) => item.key);
|
|
389
|
+
improvements = improvementDetails.map((item) => item.key);
|
|
390
|
+
detailedDiffAvailable = true;
|
|
391
|
+
} else {
|
|
392
|
+
const prevKeys = new Set(previous.summary?.topActionKeys || []);
|
|
393
|
+
const currKeys = new Set(current.summary?.topActionKeys || []);
|
|
394
|
+
for (const key of currKeys) {
|
|
395
|
+
if (!prevKeys.has(key)) regressions.push(key);
|
|
396
|
+
}
|
|
397
|
+
for (const key of prevKeys) {
|
|
398
|
+
if (!currKeys.has(key)) improvements.push(key);
|
|
399
|
+
}
|
|
268
400
|
}
|
|
269
401
|
|
|
270
402
|
return {
|
|
271
403
|
scoreType: 'audit-snapshot-score',
|
|
272
|
-
current: {
|
|
273
|
-
|
|
404
|
+
current: {
|
|
405
|
+
date: current.createdAt,
|
|
406
|
+
score: current.summary?.score,
|
|
407
|
+
passed: current.summary?.passed,
|
|
408
|
+
tags: current.tags || [],
|
|
409
|
+
scoreType: 'audit-snapshot-score',
|
|
410
|
+
},
|
|
411
|
+
previous: {
|
|
412
|
+
date: previous.createdAt,
|
|
413
|
+
score: previous.summary?.score,
|
|
414
|
+
passed: previous.summary?.passed,
|
|
415
|
+
tags: previous.tags || [],
|
|
416
|
+
scoreType: 'audit-snapshot-score',
|
|
417
|
+
},
|
|
274
418
|
delta,
|
|
275
419
|
regressions,
|
|
276
420
|
improvements,
|
|
421
|
+
regressionDetails,
|
|
422
|
+
improvementDetails,
|
|
423
|
+
newlyApplicableDetails,
|
|
424
|
+
noLongerApplicableDetails,
|
|
425
|
+
newChecks,
|
|
426
|
+
removedChecks,
|
|
427
|
+
detailedDiffAvailable,
|
|
277
428
|
trend: delta.score > 0 ? 'improving' : delta.score < 0 ? 'regressing' : 'stable',
|
|
278
429
|
};
|
|
279
430
|
}
|
|
@@ -303,13 +454,13 @@ function formatSnapshotBootstrap(dir, goal = 'history') {
|
|
|
303
454
|
|
|
304
455
|
if (snapshotCount === 0) {
|
|
305
456
|
lines.push(' Bootstrap it with:');
|
|
306
|
-
lines.push(' 1. Run `nerviq audit --snapshot` to save the baseline.');
|
|
457
|
+
lines.push(' 1. Run `nerviq audit --snapshot --tag "baseline"` to save the baseline.');
|
|
307
458
|
lines.push(' 2. Make a meaningful repo change (`nerviq setup --auto` or `nerviq fix --all-critical --auto`).');
|
|
308
|
-
lines.push(' 3. Run `nerviq audit --snapshot`
|
|
459
|
+
lines.push(' 3. Run `nerviq audit --snapshot --tag "after-change"` to capture the next state.');
|
|
309
460
|
} else {
|
|
310
461
|
lines.push(' Next:');
|
|
311
462
|
lines.push(' 1. Make a meaningful repo change (`nerviq setup --auto` or `nerviq fix --all-critical --auto`).');
|
|
312
|
-
lines.push(' 2. Run `nerviq audit --snapshot` again.');
|
|
463
|
+
lines.push(' 2. Run `nerviq audit --snapshot --tag "after-change"` again.');
|
|
313
464
|
}
|
|
314
465
|
|
|
315
466
|
if (goal === 'compare') {
|
|
@@ -340,7 +491,7 @@ function formatHistory(dir) {
|
|
|
340
491
|
const score = entry.summary?.score ?? '?';
|
|
341
492
|
const passed = entry.summary?.passed ?? '?';
|
|
342
493
|
const total = entry.summary?.checkCount ?? '?';
|
|
343
|
-
lines.push(` ${dateDisplay} snapshot ${score}/100 (${passed}/${total} checks passing)`);
|
|
494
|
+
lines.push(` ${dateDisplay} snapshot${formatSnapshotTags(entry.tags)} ${score}/100 (${passed}/${total} checks passing)`);
|
|
344
495
|
}
|
|
345
496
|
|
|
346
497
|
const comparison = compareLatest(dir);
|
|
@@ -348,6 +499,9 @@ function formatHistory(dir) {
|
|
|
348
499
|
lines.push('');
|
|
349
500
|
const sign = comparison.delta.score >= 0 ? '+' : '';
|
|
350
501
|
lines.push(` Latest snapshot trend: ${comparison.trend} (${sign}${comparison.delta.score} since previous snapshot)`);
|
|
502
|
+
if ((comparison.previous.tags || []).length > 0 || (comparison.current.tags || []).length > 0) {
|
|
503
|
+
lines.push(` Snapshot tags: previous${formatSnapshotTags(comparison.previous.tags)} -> current${formatSnapshotTags(comparison.current.tags)}`);
|
|
504
|
+
}
|
|
351
505
|
if (comparison.improvements.length > 0) {
|
|
352
506
|
lines.push(` Fixed: ${comparison.improvements.join(', ')}`);
|
|
353
507
|
}
|
|
@@ -378,21 +532,22 @@ function exportTrendReport(dir) {
|
|
|
378
532
|
'',
|
|
379
533
|
'## Audit Snapshot History',
|
|
380
534
|
'',
|
|
381
|
-
'| Date | Score | Passed | Checks |',
|
|
382
|
-
'
|
|
535
|
+
'| Date | Tags | Score | Passed | Checks |',
|
|
536
|
+
'|------|------|-------|--------|--------|',
|
|
383
537
|
];
|
|
384
538
|
|
|
385
539
|
for (const entry of history) {
|
|
386
540
|
const date = entry.createdAt?.split('T')[0] || '?';
|
|
387
|
-
|
|
541
|
+
const tags = (entry.tags || []).length > 0 ? entry.tags.join(', ') : '-';
|
|
542
|
+
lines.push(`| ${date} | ${tags} | ${entry.summary?.score ?? '?'}/100 | ${entry.summary?.passed ?? '?'} | ${entry.summary?.checkCount ?? '?'} |`);
|
|
388
543
|
}
|
|
389
544
|
|
|
390
545
|
if (comparison) {
|
|
391
546
|
lines.push('');
|
|
392
547
|
lines.push('## Latest Comparison');
|
|
393
548
|
lines.push('');
|
|
394
|
-
lines.push(`- **Previous snapshot score:** ${comparison.previous.score}/100 (${comparison.previous.date?.split('T')[0]})`);
|
|
395
|
-
lines.push(`- **Current snapshot score:** ${comparison.current.score}/100 (${comparison.current.date?.split('T')[0]})`);
|
|
549
|
+
lines.push(`- **Previous snapshot score:** ${comparison.previous.score}/100 (${comparison.previous.date?.split('T')[0]})${formatSnapshotTags(comparison.previous.tags)}`);
|
|
550
|
+
lines.push(`- **Current snapshot score:** ${comparison.current.score}/100 (${comparison.current.date?.split('T')[0]})${formatSnapshotTags(comparison.current.tags)}`);
|
|
396
551
|
lines.push(`- **Snapshot delta:** ${comparison.delta.score >= 0 ? '+' : ''}${comparison.delta.score} points`);
|
|
397
552
|
lines.push(`- **Trend:** ${comparison.trend}`);
|
|
398
553
|
if (comparison.improvements.length > 0) lines.push(`- **Fixed:** ${comparison.improvements.join(', ')}`);
|
|
@@ -627,42 +782,11 @@ function checkHealth(dir) {
|
|
|
627
782
|
|
|
628
783
|
const currentResults = currentPayload.results || [];
|
|
629
784
|
const previousResults = previousPayload.results || [];
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
}
|
|
636
|
-
const currMap = {};
|
|
637
|
-
for (const r of currentResults) {
|
|
638
|
-
if (r.key) currMap[r.key] = r.passed;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
const regressions = []; // was passing → now failing
|
|
642
|
-
const improvements = []; // was failing → now passing
|
|
643
|
-
const newChecks = []; // not in previous
|
|
644
|
-
const removedChecks = []; // not in current
|
|
645
|
-
|
|
646
|
-
for (const r of currentResults) {
|
|
647
|
-
if (!r.key) continue;
|
|
648
|
-
const prev = prevMap[r.key];
|
|
649
|
-
const curr = r.passed;
|
|
650
|
-
if (prev === undefined) {
|
|
651
|
-
if (curr !== null) newChecks.push({ key: r.key, name: r.name, impact: r.impact, passed: curr });
|
|
652
|
-
continue;
|
|
653
|
-
}
|
|
654
|
-
if (prev === true && curr === false) {
|
|
655
|
-
regressions.push({ key: r.key, name: r.name, impact: r.impact, category: r.category });
|
|
656
|
-
} else if (prev === false && curr === true) {
|
|
657
|
-
improvements.push({ key: r.key, name: r.name, impact: r.impact, category: r.category });
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
for (const r of previousResults) {
|
|
662
|
-
if (r.key && currMap[r.key] === undefined) {
|
|
663
|
-
removedChecks.push({ key: r.key, name: r.name });
|
|
664
|
-
}
|
|
665
|
-
}
|
|
785
|
+
const diff = collectCheckDiff(previousResults, currentResults);
|
|
786
|
+
const regressions = diff.regressions;
|
|
787
|
+
const improvements = diff.improvements;
|
|
788
|
+
const newChecks = diff.newChecks;
|
|
789
|
+
const removedChecks = diff.removedChecks;
|
|
666
790
|
|
|
667
791
|
// Detect potential platform format changes:
|
|
668
792
|
// If 3+ checks in the same category regressed, flag it
|
|
@@ -855,6 +979,8 @@ module.exports = {
|
|
|
855
979
|
writeActivityArtifact,
|
|
856
980
|
writeRollbackArtifact,
|
|
857
981
|
writeSnapshotArtifact,
|
|
982
|
+
normalizeSnapshotTags,
|
|
983
|
+
formatSnapshotTags,
|
|
858
984
|
readSnapshotIndex,
|
|
859
985
|
getHistory,
|
|
860
986
|
compareLatest,
|
package/src/aider/freshness.js
CHANGED
|
@@ -93,11 +93,11 @@ const PROPAGATION_CHECKLIST = [
|
|
|
93
93
|
];
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
|
-
* Check release gate — are all P0 sources fresh?
|
|
97
|
-
*/
|
|
98
|
-
function checkReleaseGate(overrides = {}) {
|
|
99
|
-
const now = new Date();
|
|
100
|
-
const results = P0_SOURCES.map(source => {
|
|
96
|
+
* Check release gate — are all P0 sources fresh?
|
|
97
|
+
*/
|
|
98
|
+
function checkReleaseGate(overrides = {}) {
|
|
99
|
+
const now = new Date();
|
|
100
|
+
const results = P0_SOURCES.map(source => {
|
|
101
101
|
const verifiedAt = overrides[source.key] || source.verifiedAt;
|
|
102
102
|
if (!verifiedAt) {
|
|
103
103
|
return { ...source, status: 'unverified', daysStale: null };
|
|
@@ -114,26 +114,29 @@ function checkReleaseGate(overrides = {}) {
|
|
|
114
114
|
};
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
117
|
+
const stale = results.filter((result) => result.status === 'stale' || result.status === 'unverified');
|
|
118
|
+
const fresh = results.filter((result) => result.status === 'fresh');
|
|
119
|
+
const allFresh = stale.length === 0;
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
ready: allFresh,
|
|
123
|
+
stale,
|
|
124
|
+
fresh,
|
|
125
|
+
results,
|
|
126
|
+
nerviqVersion: version,
|
|
127
|
+
checkedAt: now.toISOString(),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Format release gate for display.
|
|
133
|
+
*/
|
|
134
|
+
function formatReleaseGate(gateResult) {
|
|
135
|
+
const lines = [
|
|
136
|
+
`Aider Release Freshness Gate (nerviq v${version})`,
|
|
137
|
+
`Status: ${gateResult.ready ? 'READY' : 'NOT READY'}`,
|
|
138
|
+
'',
|
|
139
|
+
];
|
|
137
140
|
|
|
138
141
|
for (const result of gateResult.results) {
|
|
139
142
|
const icon = result.status === 'fresh' ? '✓' : result.status === 'stale' ? '✗' : '?';
|
package/src/analyze.js
CHANGED
|
@@ -11,6 +11,7 @@ const { STACKS } = require('./techniques');
|
|
|
11
11
|
const { detectDomainPacks } = require('./domain-packs');
|
|
12
12
|
const { detectCodexDomainPacks } = require('./codex/domain-packs');
|
|
13
13
|
const { recommendMcpPacks } = require('./mcp-packs');
|
|
14
|
+
const { collectClaudeDenyRules } = require('./permission-rules');
|
|
14
15
|
|
|
15
16
|
const COLORS = {
|
|
16
17
|
reset: '\x1b[0m',
|
|
@@ -101,6 +102,7 @@ function collectClaudeAssets(ctx) {
|
|
|
101
102
|
const sharedSettings = ctx.jsonFile('.claude/settings.json');
|
|
102
103
|
const localSettings = ctx.jsonFile('.claude/settings.local.json');
|
|
103
104
|
const settings = sharedSettings || localSettings || null;
|
|
105
|
+
const denyRules = collectClaudeDenyRules(ctx);
|
|
104
106
|
|
|
105
107
|
const assetFiles = {
|
|
106
108
|
claudeMd: ctx.fileContent('CLAUDE.md') ? 'CLAUDE.md' : (ctx.fileContent('.claude/CLAUDE.md') ? '.claude/CLAUDE.md' : null),
|
|
@@ -129,7 +131,7 @@ function collectClaudeAssets(ctx) {
|
|
|
129
131
|
},
|
|
130
132
|
permissions: settings && settings.permissions ? {
|
|
131
133
|
defaultMode: settings.permissions.defaultMode || null,
|
|
132
|
-
hasDenyRules:
|
|
134
|
+
hasDenyRules: denyRules.length > 0,
|
|
133
135
|
} : null,
|
|
134
136
|
settingsSource: assetFiles.settings,
|
|
135
137
|
summaryLine: `Commands: ${assetFiles.commands.length} | Rules: ${assetFiles.rules.length} | Hooks: ${assetFiles.hooks.length} | Agents: ${assetFiles.agents.length} | Skills: ${assetFiles.skills.length} | MCP servers: ${settings && settings.mcpServers ? Object.keys(settings.mcpServers).length : 0}`,
|
package/src/anti-patterns.js
CHANGED
|
@@ -8,6 +8,8 @@ const {
|
|
|
8
8
|
hasDocumentedVerificationGuidance,
|
|
9
9
|
hasDocumentedTestCommand,
|
|
10
10
|
} = require('./instruction-surfaces');
|
|
11
|
+
const { collectClaudeDenyRules } = require('./permission-rules');
|
|
12
|
+
const { containsEmbeddedSecret } = require('./secret-patterns');
|
|
11
13
|
|
|
12
14
|
const ANTI_PATTERNS = [
|
|
13
15
|
{
|
|
@@ -32,7 +34,7 @@ const ANTI_PATTERNS = [
|
|
|
32
34
|
detect: (ctx) => {
|
|
33
35
|
const settings = ctx.jsonFile('.claude/settings.json') || ctx.jsonFile('.claude/settings.local.json');
|
|
34
36
|
if (!settings || !settings.permissions) return true;
|
|
35
|
-
return
|
|
37
|
+
return collectClaudeDenyRules(ctx).length === 0;
|
|
36
38
|
},
|
|
37
39
|
},
|
|
38
40
|
{
|
|
@@ -322,7 +324,7 @@ const ANTI_PATTERNS = [
|
|
|
322
324
|
];
|
|
323
325
|
for (const file of hookFiles) {
|
|
324
326
|
const content = ctx.fileContent(`.claude/hooks/${file}`) || '';
|
|
325
|
-
if (secretPatterns.some(p => p.test(content))) {
|
|
327
|
+
if (secretPatterns.some(p => p.test(content)) || containsEmbeddedSecret(content)) {
|
|
326
328
|
return true;
|
|
327
329
|
}
|
|
328
330
|
}
|