@promptci/core 0.2.1 → 0.3.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/dist/ai-config.d.ts +13 -6
- package/dist/ai-config.d.ts.map +1 -1
- package/dist/ai-config.js +25 -78
- package/dist/ai-config.js.map +1 -1
- package/dist/close-match.d.ts +97 -0
- package/dist/close-match.d.ts.map +1 -0
- package/dist/close-match.js +215 -0
- package/dist/close-match.js.map +1 -0
- package/dist/command-validity.d.ts.map +1 -1
- package/dist/command-validity.js +309 -76
- package/dist/command-validity.js.map +1 -1
- package/dist/context-optimizer.d.ts.map +1 -1
- package/dist/context-optimizer.js +9 -2
- package/dist/context-optimizer.js.map +1 -1
- package/dist/dead-references.d.ts +8 -1
- package/dist/dead-references.d.ts.map +1 -1
- package/dist/dead-references.js +162 -243
- package/dist/dead-references.js.map +1 -1
- package/dist/dependency-staleness.d.ts.map +1 -1
- package/dist/dependency-staleness.js +24 -38
- package/dist/dependency-staleness.js.map +1 -1
- package/dist/detectors.js +1 -1
- package/dist/detectors.js.map +1 -1
- package/dist/discovery.d.ts +138 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +569 -0
- package/dist/discovery.js.map +1 -0
- package/dist/fix-engine.d.ts.map +1 -1
- package/dist/fix-engine.js +75 -1
- package/dist/fix-engine.js.map +1 -1
- package/dist/frameworks/dotnet.d.ts.map +1 -1
- package/dist/frameworks/dotnet.js +10 -11
- package/dist/frameworks/dotnet.js.map +1 -1
- package/dist/frameworks/go.d.ts.map +1 -1
- package/dist/frameworks/go.js +8 -4
- package/dist/frameworks/go.js.map +1 -1
- package/dist/frameworks/nextjs.d.ts.map +1 -1
- package/dist/frameworks/nextjs.js +8 -17
- package/dist/frameworks/nextjs.js.map +1 -1
- package/dist/frameworks/python.d.ts.map +1 -1
- package/dist/frameworks/python.js +5 -3
- package/dist/frameworks/python.js.map +1 -1
- package/dist/frameworks/rust.d.ts.map +1 -1
- package/dist/frameworks/rust.js +3 -3
- package/dist/frameworks/rust.js.map +1 -1
- package/dist/frameworks/unity.d.ts.map +1 -1
- package/dist/frameworks/unity.js +6 -5
- package/dist/frameworks/unity.js.map +1 -1
- package/dist/gitignore.d.ts +31 -2
- package/dist/gitignore.d.ts.map +1 -1
- package/dist/gitignore.js +33 -8
- package/dist/gitignore.js.map +1 -1
- package/dist/hooks-settings-detector.d.ts.map +1 -1
- package/dist/hooks-settings-detector.js +5 -1
- package/dist/hooks-settings-detector.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/known-paths.d.ts +82 -0
- package/dist/known-paths.d.ts.map +1 -0
- package/dist/known-paths.js +152 -0
- package/dist/known-paths.js.map +1 -0
- package/dist/makefile.d.ts +11 -3
- package/dist/makefile.d.ts.map +1 -1
- package/dist/makefile.js +28 -22
- package/dist/makefile.js.map +1 -1
- package/dist/manifest-consistency.d.ts +1 -1
- package/dist/manifest-consistency.d.ts.map +1 -1
- package/dist/manifest-consistency.js +12 -8
- package/dist/manifest-consistency.js.map +1 -1
- package/dist/mcp-config-detector.d.ts.map +1 -1
- package/dist/mcp-config-detector.js +5 -1
- package/dist/mcp-config-detector.js.map +1 -1
- package/dist/project-type.d.ts +2 -1
- package/dist/project-type.d.ts.map +1 -1
- package/dist/project-type.js +36 -48
- package/dist/project-type.js.map +1 -1
- package/dist/prompt-cache.d.ts.map +1 -1
- package/dist/prompt-cache.js +13 -2
- package/dist/prompt-cache.js.map +1 -1
- package/dist/repo-context.d.ts +9 -0
- package/dist/repo-context.d.ts.map +1 -1
- package/dist/repo-context.js +32 -35
- package/dist/repo-context.js.map +1 -1
- package/dist/repo-tree.d.ts +281 -0
- package/dist/repo-tree.d.ts.map +1 -0
- package/dist/repo-tree.js +455 -0
- package/dist/repo-tree.js.map +1 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +3 -0
- package/dist/report.js.map +1 -1
- package/dist/sarif.d.ts +27 -0
- package/dist/sarif.d.ts.map +1 -0
- package/dist/sarif.js +357 -0
- package/dist/sarif.js.map +1 -0
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +8 -2
- package/dist/scan.js.map +1 -1
- package/dist/scanner.d.ts +4 -3
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +36 -77
- package/dist/scanner.js.map +1 -1
- package/dist/security-pack.d.ts.map +1 -1
- package/dist/security-pack.js +34 -35
- package/dist/security-pack.js.map +1 -1
- package/dist/skill-supply-chain.d.ts.map +1 -1
- package/dist/skill-supply-chain.js +29 -19
- package/dist/skill-supply-chain.js.map +1 -1
- package/dist/skills-detector.d.ts.map +1 -1
- package/dist/skills-detector.js +11 -3
- package/dist/skills-detector.js.map +1 -1
- package/dist/token-estimates.d.ts +42 -0
- package/dist/token-estimates.d.ts.map +1 -0
- package/dist/token-estimates.js +200 -0
- package/dist/token-estimates.js.map +1 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +5 -2
package/dist/sarif.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SARIF 2.1.0 report generation for PromptCI scan results (pcic-aq2f).
|
|
3
|
+
*
|
|
4
|
+
* Competing tools already emit SARIF, which lands their findings in GitHub's
|
|
5
|
+
* Security tab for teams that gate merges on code scanning; PromptCI findings
|
|
6
|
+
* otherwise only show up in its own markdown/JSON report. This module maps a
|
|
7
|
+
* `ScanReport` onto a SARIF log without changing anything about how findings
|
|
8
|
+
* are detected or scored.
|
|
9
|
+
*
|
|
10
|
+
* Deterministic like the rest of the report pipeline (see report.ts /
|
|
11
|
+
* AGENTS.md): no timestamps, no run-invocation metadata, stable ordering.
|
|
12
|
+
*/
|
|
13
|
+
import * as nodePath from 'node:path';
|
|
14
|
+
import { INSTRUCTION_FILE_TYPES } from './types.js';
|
|
15
|
+
import { computeFingerprint } from './baseline.js';
|
|
16
|
+
const PROMPTCI_REPO_URL = 'https://github.com/SeriousGeese/PromptCI';
|
|
17
|
+
// No per-category anchor exists in the README today; point every rule at the
|
|
18
|
+
// section that documents what gets detected until one does.
|
|
19
|
+
const DETECTOR_REFERENCE_URL = `${PROMPTCI_REPO_URL}#what-gets-detected`;
|
|
20
|
+
const SARIF_SCHEMA_URI = 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/sarif-2.1/schema/sarif-schema-2.1.0.json';
|
|
21
|
+
/**
|
|
22
|
+
* Severity → SARIF level mapping (pcic-aq2f judgment call).
|
|
23
|
+
*
|
|
24
|
+
* GitHub's code-scanning UI only recognizes three levels, and `error` is the
|
|
25
|
+
* only one that can block a PR by default. PromptCI's two "this is a real
|
|
26
|
+
* problem" tiers (`critical`, `high`) both map to `error` so either one is
|
|
27
|
+
* visible to a security-tab gate; `warning` keeps its name; `info` (the
|
|
28
|
+
* lowest, advisory tier) maps to SARIF's `note`, its "worth seeing, not a
|
|
29
|
+
* problem" level.
|
|
30
|
+
*/
|
|
31
|
+
const SEVERITY_TO_SARIF_LEVEL = {
|
|
32
|
+
critical: 'error',
|
|
33
|
+
high: 'error',
|
|
34
|
+
warning: 'warning',
|
|
35
|
+
info: 'note',
|
|
36
|
+
};
|
|
37
|
+
const CATEGORY_INFO = {
|
|
38
|
+
duplicate: {
|
|
39
|
+
name: 'Duplicate instruction content',
|
|
40
|
+
shortDescription: 'Repeated or near-identical guidance across sections or files.',
|
|
41
|
+
defaultLevel: 'warning',
|
|
42
|
+
},
|
|
43
|
+
conflict: {
|
|
44
|
+
name: 'Conflicting directives',
|
|
45
|
+
shortDescription: 'Contradictory instructions, versions, or competing framework/tech choices.',
|
|
46
|
+
defaultLevel: 'error',
|
|
47
|
+
},
|
|
48
|
+
context_bloat: {
|
|
49
|
+
name: 'Context bloat',
|
|
50
|
+
shortDescription: 'Instruction files exceeding size or token-budget thresholds.',
|
|
51
|
+
defaultLevel: 'warning',
|
|
52
|
+
},
|
|
53
|
+
missing_context: {
|
|
54
|
+
name: 'Missing setup context',
|
|
55
|
+
shortDescription: 'Missing setup, validation, or project-specific guidance.',
|
|
56
|
+
defaultLevel: 'warning',
|
|
57
|
+
},
|
|
58
|
+
stale_instruction: {
|
|
59
|
+
name: 'Stale instructions',
|
|
60
|
+
shortDescription: 'Old years, dated TODOs, deprecated wording, or outdated versions.',
|
|
61
|
+
defaultLevel: 'warning',
|
|
62
|
+
},
|
|
63
|
+
vague_guidance: {
|
|
64
|
+
name: 'Vague guidance',
|
|
65
|
+
shortDescription: 'Guidance too vague for an agent or contributor to act on.',
|
|
66
|
+
defaultLevel: 'note',
|
|
67
|
+
},
|
|
68
|
+
structure: {
|
|
69
|
+
name: 'Structural issue',
|
|
70
|
+
shortDescription: 'Malformed suppression annotations or other structural problems.',
|
|
71
|
+
defaultLevel: 'warning',
|
|
72
|
+
},
|
|
73
|
+
command_validity: {
|
|
74
|
+
name: 'Invalid command reference',
|
|
75
|
+
shortDescription: 'Documented commands, scripts, or make targets that no longer exist.',
|
|
76
|
+
defaultLevel: 'warning',
|
|
77
|
+
},
|
|
78
|
+
agent_practices: {
|
|
79
|
+
name: 'Agent practice gap',
|
|
80
|
+
shortDescription: 'Gaps in agent-facing operational guidance.',
|
|
81
|
+
defaultLevel: 'note',
|
|
82
|
+
},
|
|
83
|
+
ai_config: {
|
|
84
|
+
name: 'AI setup config rot',
|
|
85
|
+
shortDescription: 'Agent Skills, subagents, hooks/settings, MCP servers, or editor rule files drifted from filesystem reality.',
|
|
86
|
+
defaultLevel: 'warning',
|
|
87
|
+
},
|
|
88
|
+
security: {
|
|
89
|
+
name: 'Security or privacy gap',
|
|
90
|
+
shortDescription: 'Security or privacy gaps in instruction guidance.',
|
|
91
|
+
defaultLevel: 'error',
|
|
92
|
+
},
|
|
93
|
+
custom: {
|
|
94
|
+
name: 'Custom rule finding',
|
|
95
|
+
shortDescription: "A finding from the repository's own .promptci/custom-rules.json rules.",
|
|
96
|
+
defaultLevel: 'warning',
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Suppressed findings are included rather than omitted (pcic-aq2f judgment
|
|
101
|
+
* call): dropping them would make a repo's suppressions invisible in the
|
|
102
|
+
* Security tab, where a reviewer might reasonably want to see what was
|
|
103
|
+
* silenced and why. SARIF's `suppressions` field marks a result as
|
|
104
|
+
* suppressed — GitHub's SARIF documentation does not promise any particular
|
|
105
|
+
* handling of a `suppressions`-bearing result, so this is "marked suppressed
|
|
106
|
+
* in the SARIF," not a guarantee about how a given consumer displays it. The
|
|
107
|
+
* justification is generic because the inline `promptci-ignore` reason text
|
|
108
|
+
* is not threaded onto `PromptCiIssue` today.
|
|
109
|
+
*/
|
|
110
|
+
const SUPPRESSED_JUSTIFICATION = "Suppressed via an inline `promptci-ignore` annotation in the scanned repository.";
|
|
111
|
+
/**
|
|
112
|
+
* A `ScanReport.repoPath`/finding path is produced by whatever machine ran
|
|
113
|
+
* the scan — a Windows checkout (`C:\git\repo`) or a POSIX one
|
|
114
|
+
* (`/home/runner/work/repo`) — and this module must handle either SHAPE
|
|
115
|
+
* regardless of which platform IT is running on (a repo scanned on Windows
|
|
116
|
+
* can still have its SARIF generated, or tested, on Linux CI and vice versa).
|
|
117
|
+
* `node:path`'s plain `path.relative`/`path.isAbsolute` bind to the CURRENT
|
|
118
|
+
* process's platform, so on Linux they treat a Windows-shaped path like
|
|
119
|
+
* `C:\git\repo\sub\CLAUDE.md` as just an opaque string with no recognized
|
|
120
|
+
* separators — `path.relative` between two such strings does not compute a
|
|
121
|
+
* real relative path, and the "is this outside the repo" check in
|
|
122
|
+
* `toRelativeUri` below then misfires. Detect the SHAPE of the input paths
|
|
123
|
+
* instead and pick `path.win32` or `path.posix` explicitly, independent of
|
|
124
|
+
* `process.platform`.
|
|
125
|
+
*/
|
|
126
|
+
function isWindowsShapedPath(p) {
|
|
127
|
+
return /^[a-zA-Z]:[\\/]/.test(p) || p.includes('\\');
|
|
128
|
+
}
|
|
129
|
+
/** Platform-independent basename (see isWindowsShapedPath) for a single path. */
|
|
130
|
+
function basenameOf(p) {
|
|
131
|
+
return (isWindowsShapedPath(p) ? nodePath.win32 : nodePath.posix).basename(p);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Forward-slash, percent-encoded, repo-relative URI for a SARIF
|
|
135
|
+
* artifactLocation.uri — or null when `absPath` does not resolve inside
|
|
136
|
+
* `repoPath` at all (such a location is dropped rather than pointing outside
|
|
137
|
+
* the scanned tree). `relative()` can return a path that climbs out with a
|
|
138
|
+
* leading `..`, or (given an unrelated absolute path) something that is not
|
|
139
|
+
* relative at all; only those two cases mean "outside" — a real directory or
|
|
140
|
+
* file NAMED `..something` (e.g. `..hidden/x`) does not start with `..`
|
|
141
|
+
* followed by a separator (or equal `..` outright), so it is left alone.
|
|
142
|
+
*/
|
|
143
|
+
function toRelativeUri(absPath, repoPath) {
|
|
144
|
+
// Platform-independent (see isWindowsShapedPath): either input looking
|
|
145
|
+
// Windows-shaped is enough to pick path.win32 for BOTH, since relative()
|
|
146
|
+
// needs a consistent separator convention across the two arguments.
|
|
147
|
+
const pathImpl = isWindowsShapedPath(repoPath) || isWindowsShapedPath(absPath) ? nodePath.win32 : nodePath.posix;
|
|
148
|
+
const rel = pathImpl.relative(repoPath, absPath);
|
|
149
|
+
if (rel === '..' || rel.startsWith(`..${pathImpl.sep}`) || pathImpl.isAbsolute(rel)) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const posix = rel.split(pathImpl.sep).join('/');
|
|
153
|
+
// RFC 3986: each path SEGMENT is percent-encoded independently, so a
|
|
154
|
+
// literal '/' produced by encoding one segment can never be mistaken for a
|
|
155
|
+
// path separator, and characters like '#', '%', ' ', or non-ASCII bytes in
|
|
156
|
+
// a real filename round-trip into a valid uri-reference.
|
|
157
|
+
return posix
|
|
158
|
+
.split('/')
|
|
159
|
+
.map((segment) => encodeURIComponent(segment))
|
|
160
|
+
.join('/');
|
|
161
|
+
}
|
|
162
|
+
/** One SARIF location for `absPath`, or null if it falls outside the repo (see toRelativeUri). */
|
|
163
|
+
function buildOneLocation(absPath, rel, startLine, endLine) {
|
|
164
|
+
const uri = rel(absPath);
|
|
165
|
+
if (uri === null)
|
|
166
|
+
return null;
|
|
167
|
+
// A detector-supplied line number is always >= 1 in practice, but never
|
|
168
|
+
// invent a line (or trust a stray 0/negative one) for a result that has
|
|
169
|
+
// none — SARIF regions are 1-based, and a fabricated "line 1" would point a
|
|
170
|
+
// reviewer at the wrong text for what is really a file-level finding.
|
|
171
|
+
const region = startLine !== undefined && startLine >= 1
|
|
172
|
+
? { startLine, ...(endLine !== undefined && endLine >= startLine ? { endLine } : {}) }
|
|
173
|
+
: undefined;
|
|
174
|
+
return {
|
|
175
|
+
physicalLocation: {
|
|
176
|
+
artifactLocation: { uri, uriBaseId: '%SRCROOT%' },
|
|
177
|
+
...(region ? { region } : {}),
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* SARIF locations for one finding, plus whether a fallback location had to
|
|
183
|
+
* be substituted (pcic-aq2f #3/#12): a finding with no usable location — no
|
|
184
|
+
* `locations` entries (or none that resolve inside the repo), and no
|
|
185
|
+
* `filePaths` either (or none that resolve inside the repo) — would
|
|
186
|
+
* otherwise serialize to `locations: []`, which GitHub's code-scanning
|
|
187
|
+
* ingestion silently drops. Falling back to the repo's primary instruction
|
|
188
|
+
* file (whichever of AGENTS.md/CLAUDE.md the scan found first, else the
|
|
189
|
+
* first scanned instruction file) keeps the finding visible, with no region
|
|
190
|
+
* attached since the fallback file is not where the finding specifically is.
|
|
191
|
+
* When nothing was scanned at all, there is nothing to fall back to and the
|
|
192
|
+
* result legitimately carries no locations.
|
|
193
|
+
*/
|
|
194
|
+
function buildLocations(issue, rel, fallbackAbsPath) {
|
|
195
|
+
const fromLocations = issue.locations
|
|
196
|
+
.map((loc) => buildOneLocation(loc.filePath, rel, loc.startLine, loc.endLine))
|
|
197
|
+
.filter((l) => l !== null);
|
|
198
|
+
if (fromLocations.length > 0)
|
|
199
|
+
return { locations: fromLocations, usedFallback: false };
|
|
200
|
+
const fromFilePaths = issue.filePaths
|
|
201
|
+
.map((fp) => buildOneLocation(fp, rel, undefined, undefined))
|
|
202
|
+
.filter((l) => l !== null);
|
|
203
|
+
if (fromFilePaths.length > 0)
|
|
204
|
+
return { locations: fromFilePaths, usedFallback: false };
|
|
205
|
+
if (fallbackAbsPath) {
|
|
206
|
+
const fallback = buildOneLocation(fallbackAbsPath, rel, undefined, undefined);
|
|
207
|
+
if (fallback)
|
|
208
|
+
return { locations: [fallback], usedFallback: true };
|
|
209
|
+
}
|
|
210
|
+
return { locations: [], usedFallback: false };
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Title + recommendation, worded as cautiously as the issue itself already
|
|
214
|
+
* is. Falls back to the title when `summary` is somehow empty, so a result
|
|
215
|
+
* never serializes with blank message text. `fallbackNote`, when set,
|
|
216
|
+
* documents that `locations` points at the repo's primary instruction file
|
|
217
|
+
* rather than the finding's actual location (see buildLocations).
|
|
218
|
+
*/
|
|
219
|
+
function buildMessageText(issue, fallbackNote) {
|
|
220
|
+
const summary = issue.summary.trim() || issue.title.trim();
|
|
221
|
+
const recommendation = issue.recommendation.trim();
|
|
222
|
+
const base = recommendation ? `${summary} Recommendation: ${recommendation}` : summary;
|
|
223
|
+
return fallbackNote ? `${base} ${fallbackNote}` : base;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Sort key used to order results deterministically: primary file, then
|
|
227
|
+
* primary line, then category, then the issue's own id — independent of
|
|
228
|
+
* detector run order. Out-of-repo locations/filePaths are excluded the same
|
|
229
|
+
* way buildLocations excludes them, so the key never diverges from where a
|
|
230
|
+
* result's own locations actually point.
|
|
231
|
+
*/
|
|
232
|
+
function sortKey(issue, rel) {
|
|
233
|
+
const loc = issue.locations.find((l) => l.startLine !== undefined && rel(l.filePath) !== null) ??
|
|
234
|
+
issue.locations.find((l) => rel(l.filePath) !== null);
|
|
235
|
+
const file = loc ? rel(loc.filePath) : issue.filePaths.map(rel).find((p) => p !== null) ?? '';
|
|
236
|
+
const line = loc?.startLine ?? 0;
|
|
237
|
+
return [file, String(line).padStart(10, '0'), issue.category, issue.id].join('\u0000');
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Ordinal (UTF-16 code-unit) string comparison, not `localeCompare`:
|
|
241
|
+
* `localeCompare`'s collation depends on the running engine's ICU data and
|
|
242
|
+
* the active locale, which is not guaranteed identical between the machine
|
|
243
|
+
* that runs this and CI's — the same input could sort differently and break
|
|
244
|
+
* the "identical reports produce byte-identical SARIF" guarantee. Plain
|
|
245
|
+
* `<`/`>` on strings compares UTF-16 code units and is the same everywhere
|
|
246
|
+
* Node runs.
|
|
247
|
+
*/
|
|
248
|
+
function ordinalCompare(a, b) {
|
|
249
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* `promptci/v1` is PromptCI's OWN stable-identity key (the same
|
|
253
|
+
* category+title+filePaths+evidence fingerprint `--baseline` already uses),
|
|
254
|
+
* not GitHub's `primaryLocationLineHash` — GitHub computes and uses that one
|
|
255
|
+
* itself. The fingerprint deliberately excludes line numbers, so two distinct
|
|
256
|
+
* findings of the same category/title/evidence (e.g. the same duplicate
|
|
257
|
+
* check firing on two different line ranges) can share a raw value; the
|
|
258
|
+
* caller's trailing `:N` (rawFingerprintWithSuffix) keeps their
|
|
259
|
+
* partialFingerprints distinct WITHIN this run without touching what makes
|
|
260
|
+
* two findings "the same" ACROSS runs.
|
|
261
|
+
*/
|
|
262
|
+
function buildResult(issue, rel, rawFingerprintWithSuffix, ruleIndex, suppressed, fallbackAbsPath) {
|
|
263
|
+
const { locations, usedFallback } = buildLocations(issue, rel, fallbackAbsPath);
|
|
264
|
+
const fallbackNote = usedFallback && fallbackAbsPath
|
|
265
|
+
? `(No specific location was recorded for this finding; pointing at ${basenameOf(fallbackAbsPath)} as the repository's primary instruction file.)`
|
|
266
|
+
: undefined;
|
|
267
|
+
const result = {
|
|
268
|
+
ruleId: issue.category,
|
|
269
|
+
ruleIndex,
|
|
270
|
+
level: SEVERITY_TO_SARIF_LEVEL[issue.severity],
|
|
271
|
+
message: { text: buildMessageText(issue, fallbackNote) },
|
|
272
|
+
locations,
|
|
273
|
+
partialFingerprints: { 'promptci/v1': rawFingerprintWithSuffix },
|
|
274
|
+
};
|
|
275
|
+
if (suppressed) {
|
|
276
|
+
result.suppressions = [{ kind: 'external', justification: SUPPRESSED_JUSTIFICATION }];
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
function buildRule(category) {
|
|
281
|
+
const info = CATEGORY_INFO[category];
|
|
282
|
+
return {
|
|
283
|
+
id: category,
|
|
284
|
+
name: info.name,
|
|
285
|
+
shortDescription: { text: info.shortDescription },
|
|
286
|
+
helpUri: DETECTOR_REFERENCE_URL,
|
|
287
|
+
defaultConfiguration: { level: info.defaultLevel },
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Absolute path of the repo's "primary" instruction file, for buildLocations'
|
|
292
|
+
* fallback: whichever of AGENTS.md/CLAUDE.md was scanned first (scan order,
|
|
293
|
+
* not alphabetical), else the first scanned instruction-type file, else
|
|
294
|
+
* undefined when nothing was scanned at all.
|
|
295
|
+
*/
|
|
296
|
+
function primaryInstructionFile(report) {
|
|
297
|
+
const scanned = report.filesScanned.filter((f) => f.aliasOf === undefined && INSTRUCTION_FILE_TYPES.has(f.fileType));
|
|
298
|
+
const primary = scanned.find((f) => {
|
|
299
|
+
const base = basenameOf(f.path).toLowerCase();
|
|
300
|
+
return base === 'agents.md' || base === 'claude.md';
|
|
301
|
+
});
|
|
302
|
+
return (primary ?? scanned[0])?.path;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Serialize a ScanReport to a SARIF 2.1.0 log.
|
|
306
|
+
*
|
|
307
|
+
* `toolVersion` is threaded in by the caller (the CLI's own `--version`
|
|
308
|
+
* value) rather than read from a package.json here, so this module stays
|
|
309
|
+
* decoupled from any particular package's on-disk layout.
|
|
310
|
+
*
|
|
311
|
+
* Active issues and suppressed issues are both included (suppressed ones
|
|
312
|
+
* carry a `suppressions` entry — see SUPPRESSED_JUSTIFICATION above); ruleId
|
|
313
|
+
* is the issue's `category`, the only detector/category identifier that is
|
|
314
|
+
* both present on every issue and stable across detector refactors.
|
|
315
|
+
*/
|
|
316
|
+
export function generateSarifReport(report, toolVersion, pretty = true) {
|
|
317
|
+
const rel = (absPath) => toRelativeUri(absPath, report.repoPath);
|
|
318
|
+
const fallbackAbsPath = primaryInstructionFile(report);
|
|
319
|
+
const entries = [
|
|
320
|
+
...report.issues.map((issue) => ({ issue, suppressed: false })),
|
|
321
|
+
...(report.suppressedIssues ?? []).map((issue) => ({ issue, suppressed: true })),
|
|
322
|
+
];
|
|
323
|
+
entries.sort((a, b) => ordinalCompare(sortKey(a.issue, rel), sortKey(b.issue, rel)));
|
|
324
|
+
const categoriesUsed = [...new Set(entries.map(({ issue }) => issue.category))].sort(ordinalCompare);
|
|
325
|
+
const rules = categoriesUsed.map((category) => buildRule(category));
|
|
326
|
+
const ruleIndexOf = new Map(categoriesUsed.map((category, i) => [category, i]));
|
|
327
|
+
// Raw (pre-suffix) fingerprints can collide within a single run — the
|
|
328
|
+
// fingerprint excludes line numbers — so track how many of each have been
|
|
329
|
+
// seen, in the already-established sort order, and give each occurrence a
|
|
330
|
+
// 0-based `:N` suffix (see buildResult).
|
|
331
|
+
const fingerprintOccurrence = new Map();
|
|
332
|
+
const results = entries.map(({ issue, suppressed }) => {
|
|
333
|
+
const rawFingerprint = computeFingerprint(issue, report.repoPath);
|
|
334
|
+
const suffix = fingerprintOccurrence.get(rawFingerprint) ?? 0;
|
|
335
|
+
fingerprintOccurrence.set(rawFingerprint, suffix + 1);
|
|
336
|
+
return buildResult(issue, rel, `${rawFingerprint}:${suffix}`, ruleIndexOf.get(issue.category), suppressed, fallbackAbsPath);
|
|
337
|
+
});
|
|
338
|
+
const log = {
|
|
339
|
+
$schema: SARIF_SCHEMA_URI,
|
|
340
|
+
version: '2.1.0',
|
|
341
|
+
runs: [
|
|
342
|
+
{
|
|
343
|
+
tool: {
|
|
344
|
+
driver: {
|
|
345
|
+
name: 'PromptCI',
|
|
346
|
+
version: toolVersion,
|
|
347
|
+
informationUri: PROMPTCI_REPO_URL,
|
|
348
|
+
rules,
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
results,
|
|
352
|
+
},
|
|
353
|
+
],
|
|
354
|
+
};
|
|
355
|
+
return pretty ? JSON.stringify(log, null, 2) : JSON.stringify(log);
|
|
356
|
+
}
|
|
357
|
+
//# sourceMappingURL=sarif.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sarif.js","sourceRoot":"","sources":["../src/sarif.ts"],"names":[],"mappings":"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"}
|
package/dist/scan.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAYH,OAAO,KAAK,EAAiB,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAgCvE,wBAAsB,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CA6DhE"}
|
package/dist/scan.js
CHANGED
|
@@ -8,6 +8,7 @@ import { runDetectors } from './detectors.js';
|
|
|
8
8
|
import { parseSuppressions, buildValidationIssues, applySuppressions } from './suppression.js';
|
|
9
9
|
import { computeHealthScore, selectTopFixes } from './health-score.js';
|
|
10
10
|
import { filterNewIssues } from './baseline.js';
|
|
11
|
+
import { annotateIssueTokens, computeAddressableTokens } from './token-estimates.js';
|
|
11
12
|
import { SKILL_SUPPLY_CHAIN_TAG } from './skill-supply-chain.js';
|
|
12
13
|
/**
|
|
13
14
|
* Inline suppression, with one carve-out: a skill-supply-chain finding is never
|
|
@@ -44,7 +45,9 @@ export async function scan(input) {
|
|
|
44
45
|
// Suppressed issues are excluded from health score and topFixes.
|
|
45
46
|
const annotations = parseSuppressions(allScannedFiles);
|
|
46
47
|
const allIssues = [...issues, ...buildValidationIssues(annotations)];
|
|
47
|
-
const { active, suppressed } = applyScanSuppressions(allIssues, annotations, context);
|
|
48
|
+
const { active: activeRaw, suppressed: suppressedRaw } = applyScanSuppressions(allIssues, annotations, context);
|
|
49
|
+
const active = annotateIssueTokens(activeRaw, allScannedFiles, context.aliasFiles);
|
|
50
|
+
const suppressed = annotateIssueTokens(suppressedRaw, allScannedFiles, context.aliasFiles);
|
|
48
51
|
const healthScore = computeHealthScore(active);
|
|
49
52
|
const topFixes = selectTopFixes(active);
|
|
50
53
|
let newIssues;
|
|
@@ -60,7 +63,10 @@ export async function scan(input) {
|
|
|
60
63
|
repoPath: context.repoRoot,
|
|
61
64
|
projectType: context.projectType,
|
|
62
65
|
healthScore,
|
|
63
|
-
metrics:
|
|
66
|
+
metrics: {
|
|
67
|
+
...context.metrics,
|
|
68
|
+
estimatedAddressableTokens: computeAddressableTokens(active, context.files, context.aliasFiles),
|
|
69
|
+
},
|
|
64
70
|
// Symlink aliases are inventory only (see RepoContext.aliasFiles): they are
|
|
65
71
|
// not scanned, so they carry no annotations to parse above. Their content and
|
|
66
72
|
// counts are zeroed so a consumer summing filesScanned does not count the
|
package/dist/scan.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"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"}
|
package/dist/scanner.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { InstructionFile, InstructionSection, ScanInput } from './types.js';
|
|
2
|
+
import { RepoTree } from './repo-tree.js';
|
|
2
3
|
import type { GitSymlinkOracle } from './git-symlinks.js';
|
|
3
|
-
|
|
4
|
-
export
|
|
4
|
+
import { MAX_FILE_SIZE, BINARY_CHECK_BYTES } from './discovery.js';
|
|
5
|
+
export { MAX_FILE_SIZE, BINARY_CHECK_BYTES };
|
|
5
6
|
/**
|
|
6
7
|
* Splits markdown into heading-delimited sections.
|
|
7
8
|
*
|
|
@@ -58,6 +59,6 @@ export declare function foldTextSymlinks(files: InstructionFile[], aliases: Inst
|
|
|
58
59
|
* folded into an alias of that file, so both platforms scan the same repo alike
|
|
59
60
|
* (see {@link foldTextSymlinks}).
|
|
60
61
|
*/
|
|
61
|
-
export declare function scanFilesWithAliases(input: ScanInput): Promise<ScanFilesResult>;
|
|
62
|
+
export declare function scanFilesWithAliases(input: ScanInput, tree?: RepoTree): Promise<ScanFilesResult>;
|
|
62
63
|
export declare function scanFiles(input: ScanInput): Promise<InstructionFile[]>;
|
|
63
64
|
//# sourceMappingURL=scanner.d.ts.map
|
package/dist/scanner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAY,eAAe,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAY,eAAe,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI3F,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAgD,aAAa,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAoBjH,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,CAAC;AAiE7C;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,kBAAkB,EAAE,CAuDtB;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAMhD;AAkBD;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG;IAAE,KAAK,EAAE,eAAe,EAAE,CAAC;IAAC,OAAO,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC;AA4HvF;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,EAAE,MAAM,EAAE,gBAAgB,GAAG,eAAe,CAgChI;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,eAAe,CAAC,CAuGtG;AAED,wBAAsB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAE5E"}
|
package/dist/scanner.js
CHANGED
|
@@ -5,66 +5,26 @@ import * as path from 'node:path';
|
|
|
5
5
|
import { scanFencedLines } from './markdown-fences.js';
|
|
6
6
|
import { isWithinRoot, realPath, realPathWithinRoot } from './path-containment.js';
|
|
7
7
|
import { createGitSymlinkOracle } from './git-symlinks.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
'CLAUDE.md',
|
|
11
|
-
'AGENTS.md',
|
|
12
|
-
'.cursorrules',
|
|
13
|
-
'.windsurfrules',
|
|
14
|
-
// Gemini CLI. Root only, like CLAUDE.md/AGENTS.md above.
|
|
15
|
-
'GEMINI.md',
|
|
16
|
-
// Cline: a single `.clinerules` file OR a `.clinerules/` directory of
|
|
17
|
-
// markdown rule files (fast-glob's onlyFiles default means the bare pattern
|
|
18
|
-
// matches only the file form; defaultPatterns() drops the directory globs
|
|
19
|
-
// when `.clinerules` is a file). `.clinerules/workflows/` holds on-demand
|
|
20
|
-
// slash-command workflows, not always-loaded rules, so it is left out.
|
|
21
|
-
'.clinerules',
|
|
22
|
-
'.clinerules/**/*.md',
|
|
23
|
-
'!.clinerules/workflows/**',
|
|
24
|
-
// Agent persona files (SOUL.md, USER.md, TOOLS.md). Repo ROOT only: the
|
|
25
|
-
// names are generic enough that a nested `docs/USER.md` is far more likely a
|
|
26
|
-
// user guide than an agent persona. deriveFileType enforces the same rule
|
|
27
|
-
// when an explicit `include` pattern pulls a nested copy in.
|
|
28
|
-
'SOUL.md',
|
|
29
|
-
'USER.md',
|
|
30
|
-
'TOOLS.md',
|
|
31
|
-
// Cursor IDE rules
|
|
32
|
-
'.cursor/rules/**',
|
|
33
|
-
// GitHub Copilot instructions (legacy + new per-file format)
|
|
34
|
-
'.github/copilot-instructions.md',
|
|
35
|
-
'.github/instructions/**/*.md',
|
|
36
|
-
// Claude memory / settings directory
|
|
37
|
-
'.claude/**/*.md',
|
|
38
|
-
// README is often the only context file in small repos
|
|
39
|
-
'README.md',
|
|
40
|
-
// BUG-I1: Additional root-level instruction files that agents commonly read.
|
|
41
|
-
// Explicit names only — keeps the set narrow enough to avoid scanning docs/
|
|
42
|
-
// but broad enough to catch QA.md, CONTRIBUTING.md, and similar convention files.
|
|
43
|
-
'QA.md',
|
|
44
|
-
'CONTRIBUTING.md',
|
|
45
|
-
'ARCHITECTURE.md',
|
|
46
|
-
'DEVELOPMENT.md',
|
|
47
|
-
'CODING_STANDARDS.md',
|
|
48
|
-
'CONVENTIONS.md',
|
|
49
|
-
'GUIDELINES.md',
|
|
50
|
-
// Explicit AI/prompt directories only — not generic docs/
|
|
51
|
-
'ai/**/*.md',
|
|
52
|
-
'ai-instructions/**/*.md',
|
|
53
|
-
'prompts/**/*.md',
|
|
54
|
-
'system-prompts/**/*.md',
|
|
55
|
-
];
|
|
56
|
-
// NOTE: docs/**/*.md is intentionally excluded — docs directories typically
|
|
57
|
-
// contain project documentation (QA reports, plans, guides) that are not AI
|
|
58
|
-
// instruction files and cause false positive context-bloat and conflict findings.
|
|
8
|
+
import { RepoTree } from './repo-tree.js';
|
|
9
|
+
import { SCAN_IGNORE_DIRS, defaultInstructionPatterns, MAX_FILE_SIZE, BINARY_CHECK_BYTES } from './discovery.js';
|
|
59
10
|
// 'worktrees'/'.worktrees' covers git worktree checkouts nested anywhere in the
|
|
60
11
|
// tree (e.g. Claude Code's .claude/worktrees/<name>/, or a top-level worktrees/
|
|
61
12
|
// dir used by other agent tooling) — these are full copies of the repo and
|
|
62
13
|
// would otherwise be scanned as duplicate instruction-file trees.
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
14
|
+
//
|
|
15
|
+
// NOTE: docs/**/*.md is intentionally excluded from the default patterns —
|
|
16
|
+
// docs directories typically contain project documentation (QA reports,
|
|
17
|
+
// plans, guides) that are not AI instruction files and cause false positive
|
|
18
|
+
// context-bloat and conflict findings. The patterns themselves live in
|
|
19
|
+
// discovery.ts (`DEFAULT_INSTRUCTION_PATTERNS`), the one place scanner.ts and
|
|
20
|
+
// a filesystem-free consumer (`selectScanPaths`) both read them from.
|
|
21
|
+
const IGNORE_DIRS = SCAN_IGNORE_DIRS;
|
|
22
|
+
// Re-exported so ai_config discovery (ai-config.ts) and the many other
|
|
23
|
+
// importers of `MAX_FILE_SIZE`/`BINARY_CHECK_BYTES` from this module keep
|
|
24
|
+
// their import path; the values themselves live in discovery.ts, the one
|
|
25
|
+
// place scanner.ts and the filesystem-free `selectScanPaths` both read them
|
|
26
|
+
// from — one definition of "scannable file".
|
|
27
|
+
export { MAX_FILE_SIZE, BINARY_CHECK_BYTES };
|
|
68
28
|
const PERSONA_ROOT_PATHS = new Set(['/SOUL.md', '/USER.md', '/TOOLS.md']);
|
|
69
29
|
// Takes the repo-root-relative path (not the absolute path): classification must
|
|
70
30
|
// depend only on where a file sits INSIDE the scanned repo. Using the absolute
|
|
@@ -200,27 +160,20 @@ export function isBinary(buffer) {
|
|
|
200
160
|
}
|
|
201
161
|
return false;
|
|
202
162
|
}
|
|
203
|
-
const CLINE_DIR_PATTERNS = new Set([
|
|
204
|
-
'.clinerules/**/*.md',
|
|
205
|
-
'!.clinerules/workflows/**',
|
|
206
|
-
]);
|
|
207
163
|
/**
|
|
208
|
-
*
|
|
209
|
-
* `.clinerules` really is a directory. fast-glob
|
|
210
|
-
* expand them, and a FILE of that name (Cline's
|
|
211
|
-
* throw ENOTDIR — which scanFiles' catch turned
|
|
212
|
-
* WHOLE repo. `suppressErrors` now contains that
|
|
213
|
-
* keeps the default scan from relying on it.
|
|
164
|
+
* discovery.ts's `DEFAULT_INSTRUCTION_PATTERNS`, minus the `.clinerules/`
|
|
165
|
+
* directory globs unless `.clinerules` really is a directory. fast-glob
|
|
166
|
+
* scandirs `.clinerules` to expand them, and a FILE of that name (Cline's
|
|
167
|
+
* single-file form) makes it throw ENOTDIR — which scanFiles' catch turned
|
|
168
|
+
* into an empty scan of the WHOLE repo. `suppressErrors` now contains that
|
|
169
|
+
* too; dropping the globs keeps the default scan from relying on it.
|
|
214
170
|
*/
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
// absent — the directory globs would match nothing anyway
|
|
222
|
-
}
|
|
223
|
-
return clineIsDir ? DEFAULT_PATTERNS : DEFAULT_PATTERNS.filter((p) => !CLINE_DIR_PATTERNS.has(p));
|
|
171
|
+
function defaultPatterns(tree) {
|
|
172
|
+
// pcic-e6xa: `.clinerules` is a fixed name, but whether it is a FILE or a
|
|
173
|
+
// DIRECTORY changes what the scan reads — so it is asked through the tree,
|
|
174
|
+
// which never follows a link out of the root and lets a caller's inventory
|
|
175
|
+
// answer when the repository is not on disk (pcic-csf).
|
|
176
|
+
return [...defaultInstructionPatterns(tree.isDir('.clinerules'))];
|
|
224
177
|
}
|
|
225
178
|
/** The canonical real path, or null when it does not exist. Kept apart from `realPath` (used only to group names). */
|
|
226
179
|
function resolveReal(p) {
|
|
@@ -408,12 +361,18 @@ export function foldTextSymlinks(files, aliases, oracle) {
|
|
|
408
361
|
* folded into an alias of that file, so both platforms scan the same repo alike
|
|
409
362
|
* (see {@link foldTextSymlinks}).
|
|
410
363
|
*/
|
|
411
|
-
export async function scanFilesWithAliases(input) {
|
|
364
|
+
export async function scanFilesWithAliases(input, tree) {
|
|
412
365
|
const repoRoot = path.resolve(input.repoPath);
|
|
413
366
|
const realRoot = resolveReal(repoRoot) ?? repoRoot;
|
|
367
|
+
// The scan's tree when the caller has one (buildRepoContext); otherwise a
|
|
368
|
+
// plain one over this root, carrying any inventory the input named.
|
|
369
|
+
const repoTree = tree ?? new RepoTree(repoRoot, {
|
|
370
|
+
...(input.knownPaths !== undefined ? { knownPaths: input.knownPaths } : {}),
|
|
371
|
+
...(input.knownPathsComplete !== undefined ? { knownPathsComplete: input.knownPathsComplete } : {}),
|
|
372
|
+
});
|
|
414
373
|
const patterns = (input.include && input.include.length > 0)
|
|
415
374
|
? input.include
|
|
416
|
-
:
|
|
375
|
+
: defaultPatterns(repoTree);
|
|
417
376
|
const ignorePatterns = [
|
|
418
377
|
...IGNORE_DIRS.map((d) => `**/${d}/**`),
|
|
419
378
|
...(input.exclude ?? []),
|