@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
package/src/analyze.js
CHANGED
|
@@ -12,6 +12,9 @@ const { detectDomainPacks } = require('./domain-packs');
|
|
|
12
12
|
const { detectCodexDomainPacks } = require('./codex/domain-packs');
|
|
13
13
|
const { recommendMcpPacks } = require('./mcp-packs');
|
|
14
14
|
const { collectClaudeDenyRules } = require('./permission-rules');
|
|
15
|
+
const { buildRepoArchetypeProfile } = require('./repo-archetype');
|
|
16
|
+
const { buildOperatingProfile } = require('./operating-profile');
|
|
17
|
+
const { buildAdoptionAdvisor } = require('./adoption-advisor');
|
|
15
18
|
|
|
16
19
|
const COLORS = {
|
|
17
20
|
reset: '\x1b[0m',
|
|
@@ -498,6 +501,30 @@ async function analyzeProject(options) {
|
|
|
498
501
|
? detectCodexDomainPacks(ctx, stacks, assets)
|
|
499
502
|
: detectDomainPacks(ctx, stacks, assets);
|
|
500
503
|
const recommendedMcpPacks = platform === 'claude' ? recommendMcpPacks(stacks, recommendedDomainPacks, { ctx, assets }) : [];
|
|
504
|
+
const repoArchetype = buildRepoArchetypeProfile({
|
|
505
|
+
ctx,
|
|
506
|
+
platform,
|
|
507
|
+
stacks,
|
|
508
|
+
assets,
|
|
509
|
+
recommendedDomainPacks,
|
|
510
|
+
recommendedMcpPacks,
|
|
511
|
+
maturity,
|
|
512
|
+
});
|
|
513
|
+
const recommendedOperatingProfile = buildOperatingProfile({
|
|
514
|
+
dir: options.dir,
|
|
515
|
+
platform,
|
|
516
|
+
repoArchetype,
|
|
517
|
+
recommendedDomainPacks,
|
|
518
|
+
recommendedMcpPacks,
|
|
519
|
+
});
|
|
520
|
+
const adoptionGuidance = buildAdoptionAdvisor({
|
|
521
|
+
platform,
|
|
522
|
+
repoArchetype,
|
|
523
|
+
recommendedOperatingProfile,
|
|
524
|
+
recommendedDomainPacks,
|
|
525
|
+
recommendedMcpPacks,
|
|
526
|
+
env: options.env || {},
|
|
527
|
+
});
|
|
501
528
|
|
|
502
529
|
const report = {
|
|
503
530
|
platform,
|
|
@@ -511,16 +538,28 @@ async function analyzeProject(options) {
|
|
|
511
538
|
stacks: stacks.map(s => s.label),
|
|
512
539
|
domains: recommendedDomainPacks.map(pack => pack.label),
|
|
513
540
|
maturity,
|
|
541
|
+
archetype: repoArchetype.label,
|
|
542
|
+
workflow: repoArchetype.primaryWorkflow.label,
|
|
543
|
+
riskLevel: repoArchetype.riskProfile.label,
|
|
544
|
+
operatingProfile: recommendedOperatingProfile.label,
|
|
545
|
+
adoptionPlan: adoptionGuidance.summary.label,
|
|
514
546
|
score: auditResult.score,
|
|
515
547
|
organicScore: auditResult.organicScore,
|
|
516
548
|
checkCount: auditResult.checkCount,
|
|
517
549
|
},
|
|
518
550
|
platformScopeNote: auditResult.platformScopeNote || null,
|
|
519
551
|
platformCaveats: auditResult.platformCaveats || [],
|
|
552
|
+
repoArchetype,
|
|
553
|
+
recommendedOperatingProfile,
|
|
554
|
+
adoptionGuidance,
|
|
520
555
|
detectedArchitecture: {
|
|
521
556
|
repoType: stacks.length > 0 ? 'stack-detected repo' : 'generic repo',
|
|
522
557
|
mainDirectories: mainDirs,
|
|
523
558
|
stackSignals: stacks.map(s => s.key),
|
|
559
|
+
stackFamily: repoArchetype.stackFamily.label,
|
|
560
|
+
topology: repoArchetype.topology.label,
|
|
561
|
+
workflow: repoArchetype.primaryWorkflow.label,
|
|
562
|
+
riskLevel: repoArchetype.riskProfile.label,
|
|
524
563
|
},
|
|
525
564
|
existingPlatformAssets: assets,
|
|
526
565
|
strengthsPreserved: toStrengths(auditResult.results),
|
|
@@ -585,11 +624,14 @@ function printAnalysis(report, options = {}) {
|
|
|
585
624
|
} else {
|
|
586
625
|
console.log(c(` Platform: ${report.platformLabel}`, 'dim'));
|
|
587
626
|
}
|
|
627
|
+
console.log(c(` Archetype: ${report.repoArchetype.label} | Workflow: ${report.repoArchetype.primaryWorkflow.label} | Risk: ${report.repoArchetype.riskProfile.label}`, 'dim'));
|
|
588
628
|
console.log(c(` Maturity: ${report.projectSummary.maturity} | Score: ${report.projectSummary.score}/100 | Organic: ${report.projectSummary.organicScore}/100`, 'dim'));
|
|
589
629
|
console.log('');
|
|
590
630
|
|
|
591
631
|
console.log(c(' Detected Architecture', 'blue'));
|
|
632
|
+
console.log(c(` Stack family: ${report.repoArchetype.stackFamily.label} | Topology: ${report.repoArchetype.topology.label} | Confidence: ${report.repoArchetype.confidence}`, 'dim'));
|
|
592
633
|
console.log(c(` Main directories: ${report.detectedArchitecture.mainDirectories.join(', ') || 'No strong structure detected yet'}`, 'dim'));
|
|
634
|
+
console.log(c(` Signals: ${report.repoArchetype.signals.join(' | ') || 'No strong archetype signals yet'}`, 'dim'));
|
|
593
635
|
console.log('');
|
|
594
636
|
|
|
595
637
|
console.log(c(` Existing ${report.existingPlatformAssets.label} Assets`, 'blue'));
|
|
@@ -683,6 +725,36 @@ function printAnalysis(report, options = {}) {
|
|
|
683
725
|
console.log('');
|
|
684
726
|
}
|
|
685
727
|
|
|
728
|
+
if (report.recommendedOperatingProfile) {
|
|
729
|
+
console.log(c(' Recommended Operating Profile', 'blue'));
|
|
730
|
+
console.log(` ${report.recommendedOperatingProfile.label}`);
|
|
731
|
+
console.log(c(` Permission: ${report.recommendedOperatingProfile.permissionProfile.label} | Governance pack: ${report.recommendedOperatingProfile.governancePack.label}`, 'dim'));
|
|
732
|
+
console.log(c(` CI shape: ${report.recommendedOperatingProfile.ciShape.label} | Platforms: ${(report.recommendedOperatingProfile.platformSupport.recommended || []).join(', ') || report.platformLabel}`, 'dim'));
|
|
733
|
+
console.log(c(` Hooks: ${report.recommendedOperatingProfile.hooks.map((hook) => hook.key).join(', ')}`, 'dim'));
|
|
734
|
+
console.log(c(` Verification: ${report.recommendedOperatingProfile.verification.required.join(', ')}`, 'dim'));
|
|
735
|
+
console.log('');
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (report.adoptionGuidance && Array.isArray(report.adoptionGuidance.items) && report.adoptionGuidance.items.length > 0) {
|
|
739
|
+
console.log(c(' Adopt / Defer / Ignore', 'blue'));
|
|
740
|
+
console.log(c(` ${report.adoptionGuidance.summary.label}`, 'dim'));
|
|
741
|
+
const groups = [
|
|
742
|
+
['adopt', 'Adopt now'],
|
|
743
|
+
['defer', 'Defer until prerequisites are ready'],
|
|
744
|
+
['ignore', 'Ignore for this repo shape'],
|
|
745
|
+
];
|
|
746
|
+
for (const [decision, label] of groups) {
|
|
747
|
+
const items = report.adoptionGuidance.items.filter((item) => item.decision === decision).slice(0, 3);
|
|
748
|
+
if (items.length === 0) continue;
|
|
749
|
+
console.log(` ${label}`);
|
|
750
|
+
for (const item of items) {
|
|
751
|
+
console.log(` - ${item.label}`);
|
|
752
|
+
console.log(c(` ${item.why}`, 'dim'));
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
console.log('');
|
|
756
|
+
}
|
|
757
|
+
|
|
686
758
|
if (report.suggestedRolloutOrder.length > 0) {
|
|
687
759
|
console.log(c(' Suggested Rollout Order', 'blue'));
|
|
688
760
|
report.suggestedRolloutOrder.forEach((item, index) => {
|
|
@@ -710,6 +782,11 @@ function exportMarkdown(report) {
|
|
|
710
782
|
if (report.platform === 'claude') {
|
|
711
783
|
lines.push(`**Domain Packs:** ${report.projectSummary.domains.join(', ') || 'Baseline General'}`);
|
|
712
784
|
}
|
|
785
|
+
lines.push(`**Archetype:** ${report.repoArchetype.label}`);
|
|
786
|
+
lines.push(`**Workflow:** ${report.repoArchetype.primaryWorkflow.label}`);
|
|
787
|
+
lines.push(`**Risk posture:** ${report.repoArchetype.riskProfile.label}`);
|
|
788
|
+
lines.push(`**Operating profile:** ${report.recommendedOperatingProfile.label}`);
|
|
789
|
+
lines.push(`**Adoption plan:** ${report.adoptionGuidance.summary.label}`);
|
|
713
790
|
lines.push(`**Maturity:** ${report.projectSummary.maturity}`);
|
|
714
791
|
lines.push('');
|
|
715
792
|
|
|
@@ -730,6 +807,57 @@ function exportMarkdown(report) {
|
|
|
730
807
|
}
|
|
731
808
|
lines.push('');
|
|
732
809
|
|
|
810
|
+
lines.push('## Repo Archetype');
|
|
811
|
+
lines.push('');
|
|
812
|
+
lines.push(`- **Label:** ${report.repoArchetype.label}`);
|
|
813
|
+
lines.push(`- **Summary:** ${report.repoArchetype.summary}`);
|
|
814
|
+
lines.push(`- **Stack family:** ${report.repoArchetype.stackFamily.label}`);
|
|
815
|
+
lines.push(`- **Topology:** ${report.repoArchetype.topology.label}`);
|
|
816
|
+
lines.push(`- **Primary workflow:** ${report.repoArchetype.primaryWorkflow.label}`);
|
|
817
|
+
lines.push(`- **Risk posture:** ${report.repoArchetype.riskProfile.label}`);
|
|
818
|
+
lines.push(`- **Confidence:** ${report.repoArchetype.confidence}`);
|
|
819
|
+
if (report.repoArchetype.signals.length > 0) {
|
|
820
|
+
lines.push(`- **Signals:** ${report.repoArchetype.signals.join(', ')}`);
|
|
821
|
+
}
|
|
822
|
+
lines.push('');
|
|
823
|
+
|
|
824
|
+
lines.push('## Recommended Operating Profile');
|
|
825
|
+
lines.push('');
|
|
826
|
+
lines.push(`- **Label:** ${report.recommendedOperatingProfile.label}`);
|
|
827
|
+
lines.push(`- **Summary:** ${report.recommendedOperatingProfile.summary}`);
|
|
828
|
+
lines.push(`- **Permission profile:** ${report.recommendedOperatingProfile.permissionProfile.label}`);
|
|
829
|
+
lines.push(`- **Governance pack:** ${report.recommendedOperatingProfile.governancePack.label}`);
|
|
830
|
+
lines.push(`- **Platform support:** ${(report.recommendedOperatingProfile.platformSupport.recommended || []).join(', ') || report.platformLabel}`);
|
|
831
|
+
if (report.recommendedOperatingProfile.platformSupport.optionalExpansion) {
|
|
832
|
+
lines.push(`- **Optional expansion:** ${report.recommendedOperatingProfile.platformSupport.optionalExpansion}`);
|
|
833
|
+
}
|
|
834
|
+
lines.push(`- **CI shape:** ${report.recommendedOperatingProfile.ciShape.label}`);
|
|
835
|
+
lines.push(`- **Verification:** ${report.recommendedOperatingProfile.verification.required.join(', ')}`);
|
|
836
|
+
lines.push(`- **Hooks:** ${report.recommendedOperatingProfile.hooks.map((hook) => hook.key).join(', ')}`);
|
|
837
|
+
lines.push('');
|
|
838
|
+
|
|
839
|
+
lines.push('## Adopt / Defer / Ignore');
|
|
840
|
+
lines.push('');
|
|
841
|
+
const decisionGroups = [
|
|
842
|
+
['adopt', 'Adopt now'],
|
|
843
|
+
['defer', 'Defer'],
|
|
844
|
+
['ignore', 'Ignore'],
|
|
845
|
+
];
|
|
846
|
+
for (const [decision, label] of decisionGroups) {
|
|
847
|
+
const items = report.adoptionGuidance.items.filter((item) => item.decision === decision);
|
|
848
|
+
if (items.length === 0) continue;
|
|
849
|
+
lines.push(`### ${label}`);
|
|
850
|
+
lines.push('');
|
|
851
|
+
for (const item of items) {
|
|
852
|
+
lines.push(`- **${item.label}** — ${item.why}`);
|
|
853
|
+
lines.push(` Evidence: ${item.evidence.join(' | ')}`);
|
|
854
|
+
lines.push(` Prerequisites: ${item.prerequisites.length > 0 ? item.prerequisites.join(' | ') : 'None'}`);
|
|
855
|
+
lines.push(` Expected benefit: ${item.expectedBenefit}`);
|
|
856
|
+
lines.push(` Rollback safety: ${item.rollbackSafety}`);
|
|
857
|
+
}
|
|
858
|
+
lines.push('');
|
|
859
|
+
}
|
|
860
|
+
|
|
733
861
|
if (report.strengthsPreserved.length > 0) {
|
|
734
862
|
lines.push('## Strengths Preserved (don\'t change these)');
|
|
735
863
|
lines.push('');
|
package/src/anti-patterns.js
CHANGED
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
} = require('./instruction-surfaces');
|
|
11
11
|
const { collectClaudeDenyRules } = require('./permission-rules');
|
|
12
12
|
const { containsEmbeddedSecret } = require('./secret-patterns');
|
|
13
|
+
const { containsPromptInjectionPattern } = require('./prompt-injection');
|
|
13
14
|
|
|
14
15
|
const ANTI_PATTERNS = [
|
|
15
16
|
{
|
|
@@ -218,6 +219,18 @@ const ANTI_PATTERNS = [
|
|
|
218
219
|
return !hasTestInMd && !hasTestScript;
|
|
219
220
|
},
|
|
220
221
|
},
|
|
222
|
+
{
|
|
223
|
+
id: 'AP023',
|
|
224
|
+
name: 'Suspicious prompt-injection phrases in repo instructions',
|
|
225
|
+
severity: 'high',
|
|
226
|
+
description: 'Instruction surfaces that say things like "ignore previous instructions", "bypass guardrails", or "score 100/100" create confusion and downstream trust problems, even when the static audit itself is not LLM-driven.',
|
|
227
|
+
platforms: ['claude', 'codex', 'cursor', 'windsurf', 'copilot', 'gemini', 'aider', 'opencode'],
|
|
228
|
+
fix: 'Remove adversarial phrases from repo instructions and replace them with an explicit trust-boundary note about treating repo/web/MCP content as untrusted data.',
|
|
229
|
+
detect: (ctx) => {
|
|
230
|
+
const content = getRepoInstructionBundle(ctx);
|
|
231
|
+
return containsPromptInjectionPattern(content);
|
|
232
|
+
},
|
|
233
|
+
},
|
|
221
234
|
{
|
|
222
235
|
id: 'AP015',
|
|
223
236
|
name: 'All permissions allowed',
|
|
@@ -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
|
+
};
|