@promptci/core 0.0.1
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/LICENSE +201 -0
- package/README.md +30 -0
- package/dist/agent-practices.d.ts +22 -0
- package/dist/agent-practices.d.ts.map +1 -0
- package/dist/agent-practices.js +582 -0
- package/dist/agent-practices.js.map +1 -0
- package/dist/baseline.d.ts +40 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +172 -0
- package/dist/baseline.js.map +1 -0
- package/dist/canonical-owner.d.ts +3 -0
- package/dist/canonical-owner.d.ts.map +1 -0
- package/dist/canonical-owner.js +131 -0
- package/dist/canonical-owner.js.map +1 -0
- package/dist/cheap-model-routing.d.ts +4 -0
- package/dist/cheap-model-routing.d.ts.map +1 -0
- package/dist/cheap-model-routing.js +43 -0
- package/dist/cheap-model-routing.js.map +1 -0
- package/dist/ci-alignment.d.ts +10 -0
- package/dist/ci-alignment.d.ts.map +1 -0
- package/dist/ci-alignment.js +343 -0
- package/dist/ci-alignment.js.map +1 -0
- package/dist/cli-output-filter.d.ts +16 -0
- package/dist/cli-output-filter.d.ts.map +1 -0
- package/dist/cli-output-filter.js +88 -0
- package/dist/cli-output-filter.js.map +1 -0
- package/dist/command-validity.d.ts +12 -0
- package/dist/command-validity.d.ts.map +1 -0
- package/dist/command-validity.js +543 -0
- package/dist/command-validity.js.map +1 -0
- package/dist/conflicts.d.ts +49 -0
- package/dist/conflicts.d.ts.map +1 -0
- package/dist/conflicts.js +1065 -0
- package/dist/conflicts.js.map +1 -0
- package/dist/context-analysis.d.ts +15 -0
- package/dist/context-analysis.d.ts.map +1 -0
- package/dist/context-analysis.js +24 -0
- package/dist/context-analysis.js.map +1 -0
- package/dist/context-bloat.d.ts +17 -0
- package/dist/context-bloat.d.ts.map +1 -0
- package/dist/context-bloat.js +148 -0
- package/dist/context-bloat.js.map +1 -0
- package/dist/context-cost.d.ts +4 -0
- package/dist/context-cost.d.ts.map +1 -0
- package/dist/context-cost.js +70 -0
- package/dist/context-cost.js.map +1 -0
- package/dist/context-optimizer.d.ts +14 -0
- package/dist/context-optimizer.d.ts.map +1 -0
- package/dist/context-optimizer.js +219 -0
- package/dist/context-optimizer.js.map +1 -0
- package/dist/context-recommender.d.ts +3 -0
- package/dist/context-recommender.d.ts.map +1 -0
- package/dist/context-recommender.js +329 -0
- package/dist/context-recommender.js.map +1 -0
- package/dist/dead-references.d.ts +33 -0
- package/dist/dead-references.d.ts.map +1 -0
- package/dist/dead-references.js +607 -0
- package/dist/dead-references.js.map +1 -0
- package/dist/detectors.d.ts +11 -0
- package/dist/detectors.d.ts.map +1 -0
- package/dist/detectors.js +160 -0
- package/dist/detectors.js.map +1 -0
- package/dist/duplicates.d.ts +36 -0
- package/dist/duplicates.d.ts.map +1 -0
- package/dist/duplicates.js +436 -0
- package/dist/duplicates.js.map +1 -0
- package/dist/fix-engine.d.ts +16 -0
- package/dist/fix-engine.d.ts.map +1 -0
- package/dist/fix-engine.js +179 -0
- package/dist/fix-engine.js.map +1 -0
- package/dist/framework-packs.d.ts +8 -0
- package/dist/framework-packs.d.ts.map +1 -0
- package/dist/framework-packs.js +22 -0
- package/dist/framework-packs.js.map +1 -0
- package/dist/frameworks/dotnet.d.ts +4 -0
- package/dist/frameworks/dotnet.d.ts.map +1 -0
- package/dist/frameworks/dotnet.js +77 -0
- package/dist/frameworks/dotnet.js.map +1 -0
- package/dist/frameworks/go.d.ts +4 -0
- package/dist/frameworks/go.d.ts.map +1 -0
- package/dist/frameworks/go.js +39 -0
- package/dist/frameworks/go.js.map +1 -0
- package/dist/frameworks/nextjs.d.ts +4 -0
- package/dist/frameworks/nextjs.d.ts.map +1 -0
- package/dist/frameworks/nextjs.js +66 -0
- package/dist/frameworks/nextjs.js.map +1 -0
- package/dist/frameworks/python.d.ts +4 -0
- package/dist/frameworks/python.d.ts.map +1 -0
- package/dist/frameworks/python.js +66 -0
- package/dist/frameworks/python.js.map +1 -0
- package/dist/frameworks/rust.d.ts +4 -0
- package/dist/frameworks/rust.d.ts.map +1 -0
- package/dist/frameworks/rust.js +72 -0
- package/dist/frameworks/rust.js.map +1 -0
- package/dist/frameworks/unity.d.ts +4 -0
- package/dist/frameworks/unity.d.ts.map +1 -0
- package/dist/frameworks/unity.js +74 -0
- package/dist/frameworks/unity.js.map +1 -0
- package/dist/health-score.d.ts +37 -0
- package/dist/health-score.d.ts.map +1 -0
- package/dist/health-score.js +164 -0
- package/dist/health-score.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/llm.d.ts +6 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +67 -0
- package/dist/llm.js.map +1 -0
- package/dist/manifest-consistency.d.ts +37 -0
- package/dist/manifest-consistency.d.ts.map +1 -0
- package/dist/manifest-consistency.js +659 -0
- package/dist/manifest-consistency.js.map +1 -0
- package/dist/mcp-detector.d.ts +4 -0
- package/dist/mcp-detector.d.ts.map +1 -0
- package/dist/mcp-detector.js +131 -0
- package/dist/mcp-detector.js.map +1 -0
- package/dist/missing-context.d.ts +15 -0
- package/dist/missing-context.d.ts.map +1 -0
- package/dist/missing-context.js +517 -0
- package/dist/missing-context.js.map +1 -0
- package/dist/native-tool-preference.d.ts +20 -0
- package/dist/native-tool-preference.d.ts.map +1 -0
- package/dist/native-tool-preference.js +94 -0
- package/dist/native-tool-preference.js.map +1 -0
- package/dist/output-verbosity.d.ts +3 -0
- package/dist/output-verbosity.d.ts.map +1 -0
- package/dist/output-verbosity.js +83 -0
- package/dist/output-verbosity.js.map +1 -0
- package/dist/product-boundary.d.ts +11 -0
- package/dist/product-boundary.d.ts.map +1 -0
- package/dist/product-boundary.js +301 -0
- package/dist/product-boundary.js.map +1 -0
- package/dist/project-type.d.ts +15 -0
- package/dist/project-type.d.ts.map +1 -0
- package/dist/project-type.js +116 -0
- package/dist/project-type.js.map +1 -0
- package/dist/prompt-cache.d.ts +3 -0
- package/dist/prompt-cache.d.ts.map +1 -0
- package/dist/prompt-cache.js +151 -0
- package/dist/prompt-cache.js.map +1 -0
- package/dist/repo-context.d.ts +37 -0
- package/dist/repo-context.d.ts.map +1 -0
- package/dist/repo-context.js +221 -0
- package/dist/repo-context.js.map +1 -0
- package/dist/report.d.ts +87 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +558 -0
- package/dist/report.js.map +1 -0
- package/dist/scan.d.ts +7 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +43 -0
- package/dist/scan.js.map +1 -0
- package/dist/scanner.d.ts +3 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +216 -0
- package/dist/scanner.js.map +1 -0
- package/dist/security-pack.d.ts +4 -0
- package/dist/security-pack.d.ts.map +1 -0
- package/dist/security-pack.js +403 -0
- package/dist/security-pack.js.map +1 -0
- package/dist/stale-instructions.d.ts +20 -0
- package/dist/stale-instructions.d.ts.map +1 -0
- package/dist/stale-instructions.js +269 -0
- package/dist/stale-instructions.js.map +1 -0
- package/dist/suppression.d.ts +64 -0
- package/dist/suppression.d.ts.map +1 -0
- package/dist/suppression.js +338 -0
- package/dist/suppression.js.map +1 -0
- package/dist/types.d.ts +128 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/vague-guidance.d.ts +12 -0
- package/dist/vague-guidance.d.ts.map +1 -0
- package/dist/vague-guidance.js +235 -0
- package/dist/vague-guidance.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Missing-context detector for PromptCI.
|
|
3
|
+
*
|
|
4
|
+
* Flags files when they fail to mention project-specific keywords
|
|
5
|
+
* required for its detected type (Unity, TypeScript, etc.).
|
|
6
|
+
*
|
|
7
|
+
* The detector fires only when NONE of the required keywords are present,
|
|
8
|
+
* minimising false positives on repos that use custom tooling with different
|
|
9
|
+
* command names.
|
|
10
|
+
*
|
|
11
|
+
* All findings are heuristic — wording is cautious.
|
|
12
|
+
*/
|
|
13
|
+
import * as crypto from 'node:crypto';
|
|
14
|
+
const PROJECT_TYPE_RULES = {
|
|
15
|
+
unity: {
|
|
16
|
+
// Any one of these signals Unity-specific coverage
|
|
17
|
+
keywords: ['unity', 'monobehaviour', 'gameobject', 'c#', 'unity editor', 'prefab'],
|
|
18
|
+
label: 'Unity',
|
|
19
|
+
minimumPresent: 1,
|
|
20
|
+
},
|
|
21
|
+
typescript: {
|
|
22
|
+
// Broad signals: build command, test runner, or tsconfig reference
|
|
23
|
+
keywords: [
|
|
24
|
+
'npm run build', 'pnpm run build', 'yarn run build', 'pnpm build', 'npm run test',
|
|
25
|
+
'npm test', 'pnpm test', 'yarn test', 'vitest', 'jest', 'typescript', 'tsconfig',
|
|
26
|
+
'tsc ', 'ts-node',
|
|
27
|
+
],
|
|
28
|
+
label: 'TypeScript',
|
|
29
|
+
minimumPresent: 1,
|
|
30
|
+
},
|
|
31
|
+
dotnet: {
|
|
32
|
+
keywords: ['dotnet', '.sln', '.csproj', 'xunit', 'nunit', 'mstest', 'dotnet test', 'dotnet build'],
|
|
33
|
+
label: '.NET',
|
|
34
|
+
minimumPresent: 1,
|
|
35
|
+
},
|
|
36
|
+
nextjs: {
|
|
37
|
+
keywords: ['next.js', 'nextjs', 'next build', 'next dev', 'app router', 'pages router', 'vercel'],
|
|
38
|
+
label: 'Next.js',
|
|
39
|
+
minimumPresent: 1,
|
|
40
|
+
},
|
|
41
|
+
python: {
|
|
42
|
+
keywords: ['python', 'pip', 'poetry', 'venv', 'pytest', 'pyproject.toml'],
|
|
43
|
+
label: 'Python',
|
|
44
|
+
minimumPresent: 1,
|
|
45
|
+
},
|
|
46
|
+
go: {
|
|
47
|
+
keywords: ['golang', 'go.mod', 'go test', 'go build'],
|
|
48
|
+
label: 'Go',
|
|
49
|
+
minimumPresent: 1,
|
|
50
|
+
},
|
|
51
|
+
rust: {
|
|
52
|
+
keywords: ['rust', 'cargo', 'cargo build', 'cargo test', 'rustc'],
|
|
53
|
+
label: 'Rust',
|
|
54
|
+
minimumPresent: 1,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
function issueId(key) {
|
|
58
|
+
const hash = crypto.createHash('sha1').update(key).digest('hex').slice(0, 12);
|
|
59
|
+
return `missing-context-${hash}`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* MC1: raw `String.includes` let bare single-word keywords match as a
|
|
63
|
+
* substring of an unrelated word — 'go' inside "algorithm"/"logo"/"django"/
|
|
64
|
+
* "good", 'pip' inside "pipeline", 'rust' inside "trust", 'unity' inside
|
|
65
|
+
* "community"/"opportunity" — making the "Missing X-specific context" check
|
|
66
|
+
* effectively unable to ever fire for those project types.
|
|
67
|
+
* Multi-word/punctuated keywords ("go test", "go.mod", "c#") are specific
|
|
68
|
+
* enough that plain substring matching is safe and stays unchanged; only a
|
|
69
|
+
* BARE alphanumeric word gets a `\b` word-boundary regex instead.
|
|
70
|
+
*/
|
|
71
|
+
function keywordPresent(content, keyword) {
|
|
72
|
+
const kw = keyword.toLowerCase();
|
|
73
|
+
if (/[^a-z0-9]/.test(kw))
|
|
74
|
+
return content.includes(kw);
|
|
75
|
+
return new RegExp(`\\b${kw}\\b`).test(content);
|
|
76
|
+
}
|
|
77
|
+
// ── Global checks (independent of project type) ───────────────────────────────
|
|
78
|
+
/**
|
|
79
|
+
* MC2: extracts the CONTENT of closed fenced code blocks (``` or ~~~,
|
|
80
|
+
* matching fence char/length per CommonMark) so setup-command patterns can be
|
|
81
|
+
* tested against real block content only.
|
|
82
|
+
*
|
|
83
|
+
* The old approach used a single regex per pattern like
|
|
84
|
+
* `` ` ```[\s\S]*?command` `` (non-greedy but unbounded) — it could skip
|
|
85
|
+
* straight past the block's OWN closing fence and match a command mentioned
|
|
86
|
+
* arbitrarily later in the file, in prose or in a completely different code
|
|
87
|
+
* block. A tempered-dot rewrite (`` `(?:(?!```)[\s\S])*?` ``) looked like a
|
|
88
|
+
* fix but has the same underlying flaw for a file with an EVEN number of
|
|
89
|
+
* fences: the regex engine, unable to start a match at the true opening
|
|
90
|
+
* fence (blocked by the closing fence ahead), backtracks to start at the
|
|
91
|
+
* CLOSING fence instead and treats IT as a new opener — with no third fence
|
|
92
|
+
* left to bound it, everything after is fair game again. Regex alone can't
|
|
93
|
+
* reliably track "am I inside a block" across a variable number of fences;
|
|
94
|
+
* a real line-by-line scan (the same fence-tracking approach used 5x
|
|
95
|
+
* elsewhere in this codebase) does.
|
|
96
|
+
*/
|
|
97
|
+
function extractFencedBlockContents(content) {
|
|
98
|
+
const lines = content.split('\n');
|
|
99
|
+
const blocks = [];
|
|
100
|
+
let inBlock = false;
|
|
101
|
+
let fenceChar = null;
|
|
102
|
+
let fenceLen = 0;
|
|
103
|
+
let current = [];
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
if (!inBlock) {
|
|
106
|
+
const openMatch = /^ {0,3}([`~]{3,})/.exec(line);
|
|
107
|
+
if (openMatch) {
|
|
108
|
+
inBlock = true;
|
|
109
|
+
fenceChar = openMatch[1][0] ?? null;
|
|
110
|
+
fenceLen = openMatch[1].length;
|
|
111
|
+
current = [];
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const closeMatch = /^ {0,3}([`~]{3,})\s*$/.exec(line);
|
|
116
|
+
if (closeMatch && closeMatch[1][0] === fenceChar && closeMatch[1].length >= fenceLen) {
|
|
117
|
+
inBlock = false;
|
|
118
|
+
blocks.push(current.join('\n'));
|
|
119
|
+
fenceChar = null;
|
|
120
|
+
fenceLen = 0;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
current.push(line);
|
|
124
|
+
}
|
|
125
|
+
// An unclosed trailing fence's partial content is intentionally excluded —
|
|
126
|
+
// this check only counts CONFIRMED, closed blocks as real documentation.
|
|
127
|
+
return blocks.join('\n');
|
|
128
|
+
}
|
|
129
|
+
/** Patterns that only count when found INSIDE a fenced code block's content. */
|
|
130
|
+
const FENCED_SETUP_COMMAND_PATTERNS = [
|
|
131
|
+
/(npm|pnpm|yarn|uv)\s+(install|ci|run|sync|test|build|lint|add)/,
|
|
132
|
+
/dotnet\s+(build|test|restore)/,
|
|
133
|
+
/make\s+(build|test|install)/,
|
|
134
|
+
/cargo\s+(build|test)/,
|
|
135
|
+
/go\s+(build|test)/,
|
|
136
|
+
/uv\s+(run|sync|install|add)\b/,
|
|
137
|
+
// BUG-011: Python
|
|
138
|
+
/pip\s+(install|sync|download)\b/,
|
|
139
|
+
/python\s+-m\s+(venv|pytest|pip|build)\b/,
|
|
140
|
+
/python\s+setup\.py\b/,
|
|
141
|
+
/\bpytest\b/,
|
|
142
|
+
];
|
|
143
|
+
/**
|
|
144
|
+
* Patterns that signal a setup/validation command via inline backticks or
|
|
145
|
+
* plain prose — these are legitimate signals regardless of surrounding
|
|
146
|
+
* fence context, so they're tested against the FULL content, not just
|
|
147
|
+
* extracted block contents.
|
|
148
|
+
*/
|
|
149
|
+
const PROSE_SETUP_COMMAND_PATTERNS = [
|
|
150
|
+
// Inline backtick commands: `npm run build`, `pnpm test`, `uv sync`, etc.
|
|
151
|
+
/`(npm|pnpm|yarn)\s+(run\s+)?(build|test|lint|install)/,
|
|
152
|
+
/`(uv|dotnet)\s+(run|sync|install|test|build)/,
|
|
153
|
+
// BUG-011: Python — inline backtick
|
|
154
|
+
/`pip\s+(install|sync)/,
|
|
155
|
+
/`python\s+-m\s+(venv|pytest|pip)/,
|
|
156
|
+
/`pytest\b/,
|
|
157
|
+
// BUG-011: Go — inline backtick (fenced block pattern exists above but not inline)
|
|
158
|
+
/`go\s+(build|test|run|install)\b/,
|
|
159
|
+
// Plain prose with explicit command names
|
|
160
|
+
/(npm|pnpm|yarn)\s+run\s+(build|test|lint)/i,
|
|
161
|
+
/pnpm\s+(build|test|install|lint)/i,
|
|
162
|
+
/\buv\s+(run|sync|install|add)\b/i,
|
|
163
|
+
/\b(install|build|test)\s+(command|step|instruction)/i,
|
|
164
|
+
/how\s+to\s+(install|build|run|test)/i,
|
|
165
|
+
/getting\s+started/i,
|
|
166
|
+
// BUG-011: Python — plain prose
|
|
167
|
+
/\bpip\s+install\b/i,
|
|
168
|
+
/\bpython\s+-m\s+(venv|pytest|pip|build)\b/i,
|
|
169
|
+
/\bpytest\b/i,
|
|
170
|
+
/\bpython\s+setup\.py\b/i,
|
|
171
|
+
// BUG-011: Go — plain prose
|
|
172
|
+
/\bgo\s+(test|build|run)\s/i,
|
|
173
|
+
];
|
|
174
|
+
/**
|
|
175
|
+
* Patterns that signal security / secret-handling awareness in instructions.
|
|
176
|
+
* Use non-word-boundary suffixes so plurals match: "secrets", "credentials", etc.
|
|
177
|
+
*/
|
|
178
|
+
const SECURITY_PATTERNS = [
|
|
179
|
+
/\b(auth(entication|orization)?|rls|row.?level.?security)\b/i,
|
|
180
|
+
// Plurals: secrets, credentials, passwords, tokens, api keys
|
|
181
|
+
/\bsecrets?\b/i,
|
|
182
|
+
/\bcredentials?\b/i,
|
|
183
|
+
/\bpasswords?\b/i,
|
|
184
|
+
/\bapi.?keys?\b/i,
|
|
185
|
+
/\btokens?\b/i,
|
|
186
|
+
/\b(sanitize|sanitises?|injection)\b/i,
|
|
187
|
+
/\bvalidate\b.{0,50}\b(input|param)s?\b/i,
|
|
188
|
+
/\b(never\s+commit|do\s+not\s+commit|don.?t\s+commit).{0,50}\b(secret|key|credential)s?\b/i,
|
|
189
|
+
/\b(env(ironment)?\s+var|\.env)\b/i,
|
|
190
|
+
/\b(ssm|parameter\s+store|vault|keychain)\b/i,
|
|
191
|
+
];
|
|
192
|
+
function checkSetupCommands(files) {
|
|
193
|
+
const combined = files.map((f) => f.content).join('\n');
|
|
194
|
+
const fencedContent = extractFencedBlockContents(combined);
|
|
195
|
+
const hasSetup = FENCED_SETUP_COMMAND_PATTERNS.some((p) => p.test(fencedContent)) ||
|
|
196
|
+
PROSE_SETUP_COMMAND_PATTERNS.some((p) => p.test(combined));
|
|
197
|
+
if (hasSetup)
|
|
198
|
+
return null;
|
|
199
|
+
return {
|
|
200
|
+
id: issueId('missing-setup-commands'),
|
|
201
|
+
severity: 'warning',
|
|
202
|
+
category: 'missing_context',
|
|
203
|
+
title: 'No setup or validation commands found',
|
|
204
|
+
summary: 'No instruction file appears to document how to install dependencies, build, or run tests. ' +
|
|
205
|
+
'Agents cannot reliably set up or validate the project without this.',
|
|
206
|
+
filePaths: files.map((f) => f.path),
|
|
207
|
+
locations: [],
|
|
208
|
+
evidence: ['No install/build/test commands found in any instruction file.'],
|
|
209
|
+
recommendation: 'Add a "Setup" or "Getting Started" section with the exact commands to install, build, and test: ' +
|
|
210
|
+
'e.g. "pnpm install && pnpm build && pnpm test".',
|
|
211
|
+
confidence: 0.75,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* BUG-007: Detect credential sections that describe storage but omit the security
|
|
216
|
+
* surface (token scopes, validation, expiry/rotation).
|
|
217
|
+
*
|
|
218
|
+
* Fires when:
|
|
219
|
+
* 1. At least one instruction file mentions credential/token/OAuth handling
|
|
220
|
+
* (indicating there IS a credentials section)
|
|
221
|
+
* 2. But none of the files document scopes, token validation, or expiry handling
|
|
222
|
+
*
|
|
223
|
+
* This catches the pattern from GraftCLI: Security/Credentials sections that tell
|
|
224
|
+
* agents WHERE tokens are stored but not what scopes they need or how they expire.
|
|
225
|
+
*/
|
|
226
|
+
const CREDENTIAL_SECTION_PATTERNS = [
|
|
227
|
+
/\b(?:oauth|api\s*key|access\s*token|bearer\s*token|personal\s*access\s*token|github\s+token|gitlab\s+token)/i,
|
|
228
|
+
/\bPAT\b/,
|
|
229
|
+
/\b(?:store[sd]?\s+(?:in|to)\s+(?:keychain|config|file)|credential\s+storage|token\s+storage)/i,
|
|
230
|
+
];
|
|
231
|
+
const CREDENTIAL_SURFACE_PATTERNS = [
|
|
232
|
+
// Token scopes / permissions documented
|
|
233
|
+
/\b(?:scope[sd]?|permission[sd]?|required\s+scope|token\s+scope)/i,
|
|
234
|
+
// Validation / verification in any form (validate, validated, verifying, verified, etc.)
|
|
235
|
+
/\b(?:validat(?:e[sd]?|ion|ing)|verif(?:y|ied|ies|ication|ying))\b/i,
|
|
236
|
+
// Expiry or rotation documented
|
|
237
|
+
/\btoken\s+(?:expir[yi]|refresh|rotat)/i,
|
|
238
|
+
/\b(?:expir(?:y|ation|e[sd]?|ing)|refresh\s+token|token\s+rotation)\b/i,
|
|
239
|
+
];
|
|
240
|
+
function checkCredentialSurface(files) {
|
|
241
|
+
const combined = files.map((f) => f.content).join('\n');
|
|
242
|
+
const hasCredentialSection = CREDENTIAL_SECTION_PATTERNS.some((p) => p.test(combined));
|
|
243
|
+
if (!hasCredentialSection)
|
|
244
|
+
return null;
|
|
245
|
+
const hasSurfaceDoc = CREDENTIAL_SURFACE_PATTERNS.some((p) => p.test(combined));
|
|
246
|
+
if (hasSurfaceDoc)
|
|
247
|
+
return null;
|
|
248
|
+
return {
|
|
249
|
+
id: issueId('missing-credential-surface'),
|
|
250
|
+
severity: 'warning',
|
|
251
|
+
category: 'missing_context',
|
|
252
|
+
title: 'Credential section missing scope/validation/expiry documentation',
|
|
253
|
+
summary: 'Instruction files describe credential or token storage but do not document ' +
|
|
254
|
+
'required token scopes, how tokens are validated on first use, or how token ' +
|
|
255
|
+
'expiry and rotation are handled. Agents cannot safely handle auth edge cases ' +
|
|
256
|
+
'without this information.',
|
|
257
|
+
filePaths: files.map((f) => f.path),
|
|
258
|
+
locations: [],
|
|
259
|
+
evidence: ['Credential/token handling found, but no mention of scopes, validation, or expiry.'],
|
|
260
|
+
recommendation: 'Add documentation covering: (1) which OAuth scopes or API permissions are required, ' +
|
|
261
|
+
'(2) how tokens are validated on first use or after rotation, ' +
|
|
262
|
+
'(3) what happens when a token expires (error message, re-auth flow).',
|
|
263
|
+
confidence: 0.65,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function checkSecurityRules(files) {
|
|
267
|
+
const combined = files.map((f) => f.content).join('\n');
|
|
268
|
+
const hasSecurity = SECURITY_PATTERNS.some((p) => p.test(combined));
|
|
269
|
+
if (hasSecurity)
|
|
270
|
+
return null;
|
|
271
|
+
return {
|
|
272
|
+
id: issueId('missing-security-rules'),
|
|
273
|
+
severity: 'info',
|
|
274
|
+
category: 'missing_context',
|
|
275
|
+
title: 'No security or secret-handling rules found',
|
|
276
|
+
summary: 'No instruction file mentions authentication, secrets, API keys, RLS policies, or input validation. ' +
|
|
277
|
+
'Agents that handle user data or credentials benefit from explicit security guidelines.',
|
|
278
|
+
filePaths: files.map((f) => f.path),
|
|
279
|
+
locations: [],
|
|
280
|
+
evidence: ['No auth, secrets, RLS, or input validation keywords found.'],
|
|
281
|
+
recommendation: 'Add a security section covering: how auth is handled, where secrets live (.env, vault), ' +
|
|
282
|
+
'RLS/data-isolation requirements, and what agents must never commit or log.',
|
|
283
|
+
confidence: 0.65,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
// ── BUG-008: Secret rotation / incident response check ───────────────────────
|
|
287
|
+
/**
|
|
288
|
+
* Patterns indicating the project handles security-sensitive credentials or keys
|
|
289
|
+
* (JWT, TLS, API signing keys, etc.) that require rotation procedures.
|
|
290
|
+
*/
|
|
291
|
+
const SECURITY_SENSITIVE_PATTERNS = [
|
|
292
|
+
/\b(?:jwt|jwks|json\s+web\s+token)\b/i,
|
|
293
|
+
/\b(?:tls|ssl)\s+(?:cert(?:ificate)?|key)\b/i,
|
|
294
|
+
/\bsigning\s+key\b/i,
|
|
295
|
+
/\b(?:private|secret)\s+key\b/i,
|
|
296
|
+
/\bapi\s+(?:secret|signing)\b/i,
|
|
297
|
+
/\b(?:redis|kafka|broker)\s+(?:credential|password|secret|auth)\b/i,
|
|
298
|
+
/\bkey\s+(?:store|vault|ring)\b/i,
|
|
299
|
+
];
|
|
300
|
+
/**
|
|
301
|
+
* Patterns indicating rotation, key compromise response, or incident procedures
|
|
302
|
+
* are documented.
|
|
303
|
+
*/
|
|
304
|
+
const SECRET_ROTATION_PATTERNS = [
|
|
305
|
+
/\b(?:rotat(?:e[sd]?|ion|ing)|key\s+rotation|secret\s+rotation|credential\s+rotation)\b/i,
|
|
306
|
+
/\b(?:compromis(?:e[sd]?|ing)|key\s+compromise|breach)\b/i,
|
|
307
|
+
/\b(?:incident\s+response|security\s+incident)\b/i,
|
|
308
|
+
/\b(?:revo(?:ke[sd]?|cation)|invalidat(?:e[sd]?|ion))\b/i,
|
|
309
|
+
/\bwhat\s+to\s+do\s+(?:if|when)\s+.{0,40}(?:key|secret|credential|token)\b/i,
|
|
310
|
+
/\b(?:key|secret|token|cert(?:ificate)?)\s+expir(?:y|ation|es?|ing)\b/i,
|
|
311
|
+
/\brenew(?:ing|al|ed)?\s+(?:cert(?:ificate)?|key|token)\b/i,
|
|
312
|
+
];
|
|
313
|
+
function checkSecretRotation(files) {
|
|
314
|
+
const combined = files.map((f) => f.content).join('\n');
|
|
315
|
+
const hasSensitiveKeys = SECURITY_SENSITIVE_PATTERNS.some((p) => p.test(combined));
|
|
316
|
+
if (!hasSensitiveKeys)
|
|
317
|
+
return null;
|
|
318
|
+
const hasRotationDocs = SECRET_ROTATION_PATTERNS.some((p) => p.test(combined));
|
|
319
|
+
if (hasRotationDocs)
|
|
320
|
+
return null;
|
|
321
|
+
return {
|
|
322
|
+
id: issueId('missing-secret-rotation'),
|
|
323
|
+
severity: 'warning',
|
|
324
|
+
category: 'missing_context',
|
|
325
|
+
title: 'Security-sensitive project missing secret rotation / incident response guidance',
|
|
326
|
+
summary: 'Instruction files reference security-sensitive credentials or cryptographic keys (JWT, TLS, signing keys, etc.) ' +
|
|
327
|
+
'but do not document how to rotate them, what to do on suspected compromise, or how to handle expiry. ' +
|
|
328
|
+
'Agents cannot safely manage security events without this information.',
|
|
329
|
+
filePaths: files.map((f) => f.path),
|
|
330
|
+
locations: [],
|
|
331
|
+
evidence: [
|
|
332
|
+
'Security-sensitive key/credential references found, but no rotation, compromise, or incident-response procedure documented.',
|
|
333
|
+
],
|
|
334
|
+
recommendation: 'Add a section covering: (1) how to rotate secrets or API keys, ' +
|
|
335
|
+
'(2) what to do if a key is suspected compromised (revoke, re-issue, notify), ' +
|
|
336
|
+
'(3) how certificate/token expiry is handled.',
|
|
337
|
+
confidence: 0.65,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
// ── BUG-009: Environment variable list completeness check ─────────────────────
|
|
341
|
+
/**
|
|
342
|
+
* Pattern for environment variable names: ALL_CAPS identifiers of ≥ 4 chars.
|
|
343
|
+
* Common enough to appear in any project that uses env vars.
|
|
344
|
+
*/
|
|
345
|
+
const ENV_VAR_NAME_RE = /\b([A-Z][A-Z0-9_]{3,})\b/g;
|
|
346
|
+
/**
|
|
347
|
+
* Patterns that indicate a comprehensive env var section exists.
|
|
348
|
+
*/
|
|
349
|
+
const ENV_SECTION_PATTERNS = [
|
|
350
|
+
/\b(?:environment\s+variables?|env\s+vars?|required\s+(?:env(?:ironment)?\s+)?variables?)\b/i,
|
|
351
|
+
/\.env\.example\b/i,
|
|
352
|
+
/\benv(?:ironment)?\s+(?:setup|configuration|config)\b/i,
|
|
353
|
+
/\bconfigure\s+(?:your\s+)?environment\b/i,
|
|
354
|
+
];
|
|
355
|
+
/** Common all-caps words that are not environment variable names. */
|
|
356
|
+
const ENV_VAR_STOP_WORDS = new Set([
|
|
357
|
+
'API', 'URL', 'HTTP', 'HTML', 'JSON', 'SQL', 'JWT', 'TLS', 'SSL', 'RLS',
|
|
358
|
+
'CRUD', 'REST', 'CORS', 'UUID', 'AUTH', 'NOTE', 'TODO', 'HACK', 'FIXME',
|
|
359
|
+
'WARN', 'INFO', 'NULL', 'TRUE', 'FALSE', 'NONE', 'VOID', 'ENUM', 'TYPE',
|
|
360
|
+
'TEST', 'PROD', 'DEV', 'DOCS', 'SKIP', 'PASS', 'FAIL', 'DONE', 'STOP',
|
|
361
|
+
]);
|
|
362
|
+
function checkEnvVarList(files) {
|
|
363
|
+
const combined = files.map((f) => f.content).join('\n');
|
|
364
|
+
const hasEnvSection = ENV_SECTION_PATTERNS.some((p) => p.test(combined));
|
|
365
|
+
if (hasEnvSection)
|
|
366
|
+
return null;
|
|
367
|
+
// Count distinct env-var-like names, excluding common false-positive caps words
|
|
368
|
+
const envVarNames = new Set();
|
|
369
|
+
let m;
|
|
370
|
+
const re = new RegExp(ENV_VAR_NAME_RE.source, ENV_VAR_NAME_RE.flags);
|
|
371
|
+
while ((m = re.exec(combined)) !== null) {
|
|
372
|
+
const name = m[1];
|
|
373
|
+
if (!ENV_VAR_STOP_WORDS.has(name) && name.includes('_')) {
|
|
374
|
+
// Must contain an underscore — distinguishes real env vars from acronyms
|
|
375
|
+
envVarNames.add(name);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// Only flag when ≥ 3 distinct env vars are named but there's no dedicated section
|
|
379
|
+
if (envVarNames.size < 3)
|
|
380
|
+
return null;
|
|
381
|
+
const examples = [...envVarNames].slice(0, 4).join(', ');
|
|
382
|
+
return {
|
|
383
|
+
id: issueId('missing-env-var-list'),
|
|
384
|
+
severity: 'info',
|
|
385
|
+
category: 'missing_context',
|
|
386
|
+
title: 'Environment variables referenced but no comprehensive list documented',
|
|
387
|
+
summary: `Instruction files mention ${envVarNames.size} environment variable name(s) (e.g. ${examples}) ` +
|
|
388
|
+
`but do not have a dedicated section listing all required variables. ` +
|
|
389
|
+
`Agents and new developers cannot reliably set up the project without a complete env var reference.`,
|
|
390
|
+
filePaths: files.map((f) => f.path),
|
|
391
|
+
locations: [],
|
|
392
|
+
evidence: [`${envVarNames.size} env-var-like names found: ${examples}…`],
|
|
393
|
+
recommendation: 'Add an "Environment Variables" or "Required Environment" section listing every required variable, ' +
|
|
394
|
+
'its purpose, and a safe example value. A .env.example file reference also satisfies this check.',
|
|
395
|
+
confidence: 0.6,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
// ── BUG-010: Copilot topic-silence check ──────────────────────────────────────
|
|
399
|
+
/**
|
|
400
|
+
* Topics that agents commonly need guidance on. Each entry defines:
|
|
401
|
+
* - label: Human-readable topic name (used in the issue title/summary)
|
|
402
|
+
* - patterns: At least one must match in a file for it to "cover" the topic
|
|
403
|
+
* - recommendation: Specific advice for the copilot-instructions.md author
|
|
404
|
+
*
|
|
405
|
+
* Only fires when at least one OTHER instruction file covers the topic —
|
|
406
|
+
* the point is that copilot-instructions.md is the lone silent outlier.
|
|
407
|
+
*/
|
|
408
|
+
const CROSS_FILE_TOPICS = [
|
|
409
|
+
{
|
|
410
|
+
label: 'file naming convention',
|
|
411
|
+
patterns: [
|
|
412
|
+
/\b(?:kebab-case|camelCase|camelcase|PascalCase|pascalcase|snake_case|naming\s+convention)\b/i,
|
|
413
|
+
],
|
|
414
|
+
recommendation: 'Add a file naming rule to copilot-instructions.md (e.g. "Use kebab-case for all ' +
|
|
415
|
+
'files") so Copilot agents apply the same convention as the rest of the codebase.',
|
|
416
|
+
},
|
|
417
|
+
];
|
|
418
|
+
/**
|
|
419
|
+
* BUG-010: Detect when copilot-instructions.md is present but completely silent
|
|
420
|
+
* on a topic that is covered — and possibly in conflict — across other files.
|
|
421
|
+
*
|
|
422
|
+
* A Copilot agent reads only its own instruction file and has no guidance on the
|
|
423
|
+
* topic, even though human/Claude/Cursor agents are told what to do (or disagree
|
|
424
|
+
* about it).
|
|
425
|
+
*/
|
|
426
|
+
function checkCopilotTopicSilences(files) {
|
|
427
|
+
const normalizedPath = (file) => file.path.replace(/\\/g, '/').toLowerCase();
|
|
428
|
+
const isRootCopilotFile = (file) => {
|
|
429
|
+
const normalized = normalizedPath(file);
|
|
430
|
+
return normalized === '.github/copilot-instructions.md' ||
|
|
431
|
+
normalized.endsWith('/.github/copilot-instructions.md');
|
|
432
|
+
};
|
|
433
|
+
const isCopilotInstructionFile = (file) => file.fileType === 'copilot' || normalizedPath(file).includes('/.github/instructions/');
|
|
434
|
+
const copilotFile = files.find(isRootCopilotFile);
|
|
435
|
+
if (!copilotFile)
|
|
436
|
+
return [];
|
|
437
|
+
const copilotFiles = files.filter(isCopilotInstructionFile);
|
|
438
|
+
const otherFiles = files.filter((f) => !copilotFiles.includes(f));
|
|
439
|
+
if (otherFiles.length === 0)
|
|
440
|
+
return [];
|
|
441
|
+
const issues = [];
|
|
442
|
+
for (const { label, patterns, recommendation } of CROSS_FILE_TOPICS) {
|
|
443
|
+
const mentionedInOthers = otherFiles.some((f) => patterns.some((p) => p.test(f.content)));
|
|
444
|
+
if (!mentionedInOthers)
|
|
445
|
+
continue; // topic not documented anywhere — skip
|
|
446
|
+
const mentionedInCopilot = copilotFiles.some((f) => patterns.some((p) => p.test(f.content)));
|
|
447
|
+
if (mentionedInCopilot)
|
|
448
|
+
continue; // copilot already covers it — skip
|
|
449
|
+
issues.push({
|
|
450
|
+
id: issueId(`copilot-topic-silence:${label}`),
|
|
451
|
+
severity: 'info',
|
|
452
|
+
category: 'missing_context',
|
|
453
|
+
title: `copilot-instructions.md is silent on: ${label}`,
|
|
454
|
+
summary: `Other instruction files document the ${label}, but Copilot instruction files ` +
|
|
455
|
+
`do not mention it. Copilot agents will have no guidance on this topic.`,
|
|
456
|
+
filePaths: [copilotFile.path],
|
|
457
|
+
locations: [{ filePath: copilotFile.path }],
|
|
458
|
+
evidence: [
|
|
459
|
+
`"${label}" is documented in other instruction files but absent from Copilot instruction files.`,
|
|
460
|
+
],
|
|
461
|
+
recommendation,
|
|
462
|
+
confidence: 0.6,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
return issues;
|
|
466
|
+
}
|
|
467
|
+
export function detectMissingContext(files, projectType) {
|
|
468
|
+
const issues = [];
|
|
469
|
+
// ── Global checks (always run) ────────────────────────────────────────────
|
|
470
|
+
const setupIssue = checkSetupCommands(files);
|
|
471
|
+
if (setupIssue)
|
|
472
|
+
issues.push(setupIssue);
|
|
473
|
+
const securityIssue = checkSecurityRules(files);
|
|
474
|
+
if (securityIssue)
|
|
475
|
+
issues.push(securityIssue);
|
|
476
|
+
// BUG-007: credential section present but security surface (scopes/validation/expiry) absent
|
|
477
|
+
const credSurfaceIssue = checkCredentialSurface(files);
|
|
478
|
+
if (credSurfaceIssue)
|
|
479
|
+
issues.push(credSurfaceIssue);
|
|
480
|
+
// BUG-008: security-sensitive keys referenced but no rotation / incident-response docs
|
|
481
|
+
const secretRotationIssue = checkSecretRotation(files);
|
|
482
|
+
if (secretRotationIssue)
|
|
483
|
+
issues.push(secretRotationIssue);
|
|
484
|
+
// BUG-009: env vars referenced but no comprehensive env var list / section
|
|
485
|
+
const envVarIssue = checkEnvVarList(files);
|
|
486
|
+
if (envVarIssue)
|
|
487
|
+
issues.push(envVarIssue);
|
|
488
|
+
// BUG-010: copilot-instructions.md missing topics covered by other files
|
|
489
|
+
issues.push(...checkCopilotTopicSilences(files));
|
|
490
|
+
// ── Project-type-specific checks ──────────────────────────────────────────
|
|
491
|
+
if (projectType === 'auto' || projectType === 'unknown')
|
|
492
|
+
return issues;
|
|
493
|
+
const rules = PROJECT_TYPE_RULES[projectType];
|
|
494
|
+
if (!rules)
|
|
495
|
+
return issues;
|
|
496
|
+
const allContent = files.map((f) => f.content.toLowerCase()).join('\n');
|
|
497
|
+
const present = rules.keywords.filter((kw) => keywordPresent(allContent, kw));
|
|
498
|
+
if (present.length < rules.minimumPresent) {
|
|
499
|
+
const exampleKeywords = rules.keywords.slice(0, 3).join(', ');
|
|
500
|
+
issues.push({
|
|
501
|
+
id: issueId(projectType),
|
|
502
|
+
severity: 'warning',
|
|
503
|
+
category: 'missing_context',
|
|
504
|
+
title: `Missing ${rules.label}-specific context`,
|
|
505
|
+
summary: `This appears to be a ${rules.label} project, but the instruction files do not mention ` +
|
|
506
|
+
`any ${rules.label}-specific context (e.g. build commands, test runner, tooling).`,
|
|
507
|
+
filePaths: files.map((f) => f.path),
|
|
508
|
+
locations: [],
|
|
509
|
+
evidence: [`No keywords found matching: ${exampleKeywords}…`],
|
|
510
|
+
recommendation: `Add a section covering ${rules.label}-specific build commands, testing conventions, ` +
|
|
511
|
+
`and common project patterns so AI assistants have enough context to help effectively.`,
|
|
512
|
+
confidence: 0.75,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
return issues;
|
|
516
|
+
}
|
|
517
|
+
//# sourceMappingURL=missing-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missing-context.js","sourceRoot":"","sources":["../src/missing-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native tool preference detector for PromptCI.
|
|
3
|
+
*
|
|
4
|
+
* Checks whether Claude-targeted instruction files guide agents to prefer
|
|
5
|
+
* Claude Code's built-in tools (Read, Grep, Glob) over equivalent Bash
|
|
6
|
+
* commands (cat, grep, find).
|
|
7
|
+
*
|
|
8
|
+
* Why it matters:
|
|
9
|
+
* - Built-in tools are faster (dedicated implementation, no subprocess overhead)
|
|
10
|
+
* - They use fewer tokens — Glob returns compact path lists, Grep returns
|
|
11
|
+
* matched lines only, Read returns file content without shell noise
|
|
12
|
+
* - They bypass the PreToolUse hook entirely, so no RTK rewrite is needed
|
|
13
|
+
* - They respect Claude Code's permission model without separate Bash approval
|
|
14
|
+
*
|
|
15
|
+
* Only fires when Claude-targeted instruction files exist but contain no
|
|
16
|
+
* guidance about preferring these tools.
|
|
17
|
+
*/
|
|
18
|
+
import type { InstructionFile, PromptCiIssue } from './types.js';
|
|
19
|
+
export declare function detectNativeToolPreference(files: InstructionFile[]): PromptCiIssue[];
|
|
20
|
+
//# sourceMappingURL=native-tool-preference.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-tool-preference.d.ts","sourceRoot":"","sources":["../src/native-tool-preference.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAgCjE,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,aAAa,EAAE,CAkDpF"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native tool preference detector for PromptCI.
|
|
3
|
+
*
|
|
4
|
+
* Checks whether Claude-targeted instruction files guide agents to prefer
|
|
5
|
+
* Claude Code's built-in tools (Read, Grep, Glob) over equivalent Bash
|
|
6
|
+
* commands (cat, grep, find).
|
|
7
|
+
*
|
|
8
|
+
* Why it matters:
|
|
9
|
+
* - Built-in tools are faster (dedicated implementation, no subprocess overhead)
|
|
10
|
+
* - They use fewer tokens — Glob returns compact path lists, Grep returns
|
|
11
|
+
* matched lines only, Read returns file content without shell noise
|
|
12
|
+
* - They bypass the PreToolUse hook entirely, so no RTK rewrite is needed
|
|
13
|
+
* - They respect Claude Code's permission model without separate Bash approval
|
|
14
|
+
*
|
|
15
|
+
* Only fires when Claude-targeted instruction files exist but contain no
|
|
16
|
+
* guidance about preferring these tools.
|
|
17
|
+
*/
|
|
18
|
+
import * as path from 'node:path';
|
|
19
|
+
// Positive signals — guidance about preferring native/built-in tools already exists.
|
|
20
|
+
// Tool names (Read, Grep, Glob) are matched case-sensitively — they are always
|
|
21
|
+
// capitalized when referring to Claude Code built-ins, unlike shell commands.
|
|
22
|
+
const NATIVE_TOOL_SIGNALS = [
|
|
23
|
+
/\bRead\s+tool\b/,
|
|
24
|
+
/\bGrep\s+tool\b/,
|
|
25
|
+
/\bGlob\s+tool\b/,
|
|
26
|
+
/\b[Uu]se\s+Read\b/,
|
|
27
|
+
/\b[Uu]se\s+Grep\b/,
|
|
28
|
+
/\b[Uu]se\s+Glob\b/,
|
|
29
|
+
/\b[Pp]refer\s+Read\b/,
|
|
30
|
+
/\b[Pp]refer\s+Grep\b/,
|
|
31
|
+
/\b[Pp]refer\s+Glob\b/,
|
|
32
|
+
/\bdedicated\s+tools?\b.*\binstead\s+of\s+(cat|grep|find)\b/i,
|
|
33
|
+
/\b(avoid|instead\s+of|not)\b.*\b(cat|grep|find)\b.*\b(Read|Grep|Glob)\b/,
|
|
34
|
+
/\b(Read|Grep|Glob)\b.*\b(instead\s+of|not|avoid)\b.*\b(cat|grep|find)\b/i,
|
|
35
|
+
/built[- ]in\s+tools?\s+(over|instead\s+of|not|vs\.?)\s+(bash|shell|cat|grep|find)/i,
|
|
36
|
+
];
|
|
37
|
+
function isClaudeFile(file) {
|
|
38
|
+
const p = file.path.replace(/\\/g, '/').toLowerCase();
|
|
39
|
+
const base = path.basename(file.path).toLowerCase();
|
|
40
|
+
return (file.fileType === 'claude' ||
|
|
41
|
+
base === 'claude.md' ||
|
|
42
|
+
p.includes('/.claude/') ||
|
|
43
|
+
base === 'claude.local.md');
|
|
44
|
+
}
|
|
45
|
+
export function detectNativeToolPreference(files) {
|
|
46
|
+
const claudeFiles = files.filter(isClaudeFile);
|
|
47
|
+
if (claudeFiles.length === 0)
|
|
48
|
+
return [];
|
|
49
|
+
const combinedClaudeContent = claudeFiles.map((f) => f.content).join('\n');
|
|
50
|
+
const hasGuidance = NATIVE_TOOL_SIGNALS.some((pat) => pat.test(combinedClaudeContent));
|
|
51
|
+
if (hasGuidance)
|
|
52
|
+
return [];
|
|
53
|
+
// Also check all instruction files — guidance in AGENTS.md counts if Claude reads it.
|
|
54
|
+
const combinedAll = files.map((f) => f.content).join('\n');
|
|
55
|
+
const hasGuidanceAnywhere = NATIVE_TOOL_SIGNALS.some((pat) => pat.test(combinedAll));
|
|
56
|
+
if (hasGuidanceAnywhere)
|
|
57
|
+
return [];
|
|
58
|
+
return [
|
|
59
|
+
{
|
|
60
|
+
id: 'native-tool-preference-missing',
|
|
61
|
+
severity: 'info',
|
|
62
|
+
category: 'agent_practices',
|
|
63
|
+
title: 'No guidance to prefer built-in tools over Bash equivalents (Claude Code)',
|
|
64
|
+
summary: "Claude Code's built-in Read, Grep, and Glob tools are faster and more token-efficient " +
|
|
65
|
+
'than their Bash equivalents (cat/head/tail, grep/rg, find). Instructions that explicitly ' +
|
|
66
|
+
'prefer these tools reduce context noise and bypass the Bash permission prompt entirely. ' +
|
|
67
|
+
'No such guidance was found in Claude-targeted instruction files.',
|
|
68
|
+
filePaths: claudeFiles.map((f) => f.path),
|
|
69
|
+
locations: [],
|
|
70
|
+
evidence: [
|
|
71
|
+
`No Read/Grep/Glob preference guidance found in: ${claudeFiles.map((f) => path.basename(f.path)).join(', ')}.`,
|
|
72
|
+
],
|
|
73
|
+
recommendation: 'Add a rule to Claude-targeted files instructing agents to prefer Read over cat/head/tail, ' +
|
|
74
|
+
'Grep over grep/rg, and Glob over find when working with local files. Reserve Bash for ' +
|
|
75
|
+
'operations that have no dedicated tool (e.g. git commands, running tests).',
|
|
76
|
+
fixRecipe: [
|
|
77
|
+
'## Tool Selection (Claude Code)',
|
|
78
|
+
'',
|
|
79
|
+
'Prefer dedicated tools over Bash for file operations — they are faster, more token-efficient,',
|
|
80
|
+
'and bypass the Bash permission prompt:',
|
|
81
|
+
'',
|
|
82
|
+
'- **File search**: Use `Glob` (not `find` or `ls`)',
|
|
83
|
+
'- **Content search**: Use `Grep` (not `grep` or `rg`)',
|
|
84
|
+
'- **Read files**: Use `Read` (not `cat`/`head`/`tail`)',
|
|
85
|
+
'',
|
|
86
|
+
'Reserve Bash for operations without a dedicated tool: git commands, running builds/tests,',
|
|
87
|
+
'package manager commands, and other shell-only operations.',
|
|
88
|
+
].join('\n'),
|
|
89
|
+
confidence: 0.7,
|
|
90
|
+
impact: 'medium',
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=native-tool-preference.js.map
|