@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.
Files changed (120) hide show
  1. package/dist/ai-config.d.ts +13 -6
  2. package/dist/ai-config.d.ts.map +1 -1
  3. package/dist/ai-config.js +25 -78
  4. package/dist/ai-config.js.map +1 -1
  5. package/dist/close-match.d.ts +97 -0
  6. package/dist/close-match.d.ts.map +1 -0
  7. package/dist/close-match.js +215 -0
  8. package/dist/close-match.js.map +1 -0
  9. package/dist/command-validity.d.ts.map +1 -1
  10. package/dist/command-validity.js +309 -76
  11. package/dist/command-validity.js.map +1 -1
  12. package/dist/context-optimizer.d.ts.map +1 -1
  13. package/dist/context-optimizer.js +9 -2
  14. package/dist/context-optimizer.js.map +1 -1
  15. package/dist/dead-references.d.ts +8 -1
  16. package/dist/dead-references.d.ts.map +1 -1
  17. package/dist/dead-references.js +162 -243
  18. package/dist/dead-references.js.map +1 -1
  19. package/dist/dependency-staleness.d.ts.map +1 -1
  20. package/dist/dependency-staleness.js +24 -38
  21. package/dist/dependency-staleness.js.map +1 -1
  22. package/dist/detectors.js +1 -1
  23. package/dist/detectors.js.map +1 -1
  24. package/dist/discovery.d.ts +138 -0
  25. package/dist/discovery.d.ts.map +1 -0
  26. package/dist/discovery.js +569 -0
  27. package/dist/discovery.js.map +1 -0
  28. package/dist/fix-engine.d.ts.map +1 -1
  29. package/dist/fix-engine.js +75 -1
  30. package/dist/fix-engine.js.map +1 -1
  31. package/dist/frameworks/dotnet.d.ts.map +1 -1
  32. package/dist/frameworks/dotnet.js +10 -11
  33. package/dist/frameworks/dotnet.js.map +1 -1
  34. package/dist/frameworks/go.d.ts.map +1 -1
  35. package/dist/frameworks/go.js +8 -4
  36. package/dist/frameworks/go.js.map +1 -1
  37. package/dist/frameworks/nextjs.d.ts.map +1 -1
  38. package/dist/frameworks/nextjs.js +8 -17
  39. package/dist/frameworks/nextjs.js.map +1 -1
  40. package/dist/frameworks/python.d.ts.map +1 -1
  41. package/dist/frameworks/python.js +5 -3
  42. package/dist/frameworks/python.js.map +1 -1
  43. package/dist/frameworks/rust.d.ts.map +1 -1
  44. package/dist/frameworks/rust.js +3 -3
  45. package/dist/frameworks/rust.js.map +1 -1
  46. package/dist/frameworks/unity.d.ts.map +1 -1
  47. package/dist/frameworks/unity.js +6 -5
  48. package/dist/frameworks/unity.js.map +1 -1
  49. package/dist/gitignore.d.ts +31 -2
  50. package/dist/gitignore.d.ts.map +1 -1
  51. package/dist/gitignore.js +33 -8
  52. package/dist/gitignore.js.map +1 -1
  53. package/dist/hooks-settings-detector.d.ts.map +1 -1
  54. package/dist/hooks-settings-detector.js +5 -1
  55. package/dist/hooks-settings-detector.js.map +1 -1
  56. package/dist/index.d.ts +7 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +12 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/known-paths.d.ts +82 -0
  61. package/dist/known-paths.d.ts.map +1 -0
  62. package/dist/known-paths.js +152 -0
  63. package/dist/known-paths.js.map +1 -0
  64. package/dist/makefile.d.ts +11 -3
  65. package/dist/makefile.d.ts.map +1 -1
  66. package/dist/makefile.js +28 -22
  67. package/dist/makefile.js.map +1 -1
  68. package/dist/manifest-consistency.d.ts +1 -1
  69. package/dist/manifest-consistency.d.ts.map +1 -1
  70. package/dist/manifest-consistency.js +12 -8
  71. package/dist/manifest-consistency.js.map +1 -1
  72. package/dist/mcp-config-detector.d.ts.map +1 -1
  73. package/dist/mcp-config-detector.js +5 -1
  74. package/dist/mcp-config-detector.js.map +1 -1
  75. package/dist/project-type.d.ts +2 -1
  76. package/dist/project-type.d.ts.map +1 -1
  77. package/dist/project-type.js +36 -48
  78. package/dist/project-type.js.map +1 -1
  79. package/dist/prompt-cache.d.ts.map +1 -1
  80. package/dist/prompt-cache.js +13 -2
  81. package/dist/prompt-cache.js.map +1 -1
  82. package/dist/repo-context.d.ts +9 -0
  83. package/dist/repo-context.d.ts.map +1 -1
  84. package/dist/repo-context.js +32 -35
  85. package/dist/repo-context.js.map +1 -1
  86. package/dist/repo-tree.d.ts +281 -0
  87. package/dist/repo-tree.d.ts.map +1 -0
  88. package/dist/repo-tree.js +455 -0
  89. package/dist/repo-tree.js.map +1 -0
  90. package/dist/report.d.ts.map +1 -1
  91. package/dist/report.js +3 -0
  92. package/dist/report.js.map +1 -1
  93. package/dist/sarif.d.ts +27 -0
  94. package/dist/sarif.d.ts.map +1 -0
  95. package/dist/sarif.js +357 -0
  96. package/dist/sarif.js.map +1 -0
  97. package/dist/scan.d.ts.map +1 -1
  98. package/dist/scan.js +8 -2
  99. package/dist/scan.js.map +1 -1
  100. package/dist/scanner.d.ts +4 -3
  101. package/dist/scanner.d.ts.map +1 -1
  102. package/dist/scanner.js +36 -77
  103. package/dist/scanner.js.map +1 -1
  104. package/dist/security-pack.d.ts.map +1 -1
  105. package/dist/security-pack.js +34 -35
  106. package/dist/security-pack.js.map +1 -1
  107. package/dist/skill-supply-chain.d.ts.map +1 -1
  108. package/dist/skill-supply-chain.js +29 -19
  109. package/dist/skill-supply-chain.js.map +1 -1
  110. package/dist/skills-detector.d.ts.map +1 -1
  111. package/dist/skills-detector.js +11 -3
  112. package/dist/skills-detector.js.map +1 -1
  113. package/dist/token-estimates.d.ts +42 -0
  114. package/dist/token-estimates.d.ts.map +1 -0
  115. package/dist/token-estimates.js +200 -0
  116. package/dist/token-estimates.js.map +1 -0
  117. package/dist/types.d.ts +89 -0
  118. package/dist/types.d.ts.map +1 -1
  119. package/dist/types.js.map +1 -1
  120. 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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,OAAO,KAAK,EAAiB,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAgCvE,wBAAsB,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAwDhE"}
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: context.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":"AAAA;;;GAGG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE/F,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGjE;;;;;;;GAOG;AACH,SAAS,qBAAqB,CAC5B,MAAuB,EACvB,WAAoC,EACpC,OAAoB;IAEpB,kFAAkF;IAClF,MAAM,SAAS,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,SAAS,IAAI,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,CAAC;SAC9E,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IACtF,MAAM,aAAa,GAAG,WAAW,CAAC,MAAM,CACtC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAC9E,CAAC;IACF,MAAM,aAAa,GAAG,CAAC,KAAoB,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,sBAAsB,CAAC;IAC3F,MAAM,OAAO,GAAG,iBAAiB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IACxF,MAAM,WAAW,GAAG,iBAAiB,CAAC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC,CAAC;IACnF,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,UAAU,EAAE,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC;IAClF,qCAAqC;IACrC,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACnD,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KACvD,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,KAAgB;IACzC,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IAErC,0EAA0E;IAC1E,6EAA6E;IAC7E,6EAA6E;IAC7E,MAAM,eAAe,GAAG,CAAC,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAErE,wCAAwC;IACxC,4EAA4E;IAC5E,iEAAiE;IACjE,MAAM,WAAW,GAAG,iBAAiB,CAAC,eAAe,CAAC,CAAC;IACvD,MAAM,SAAS,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC,CAAC;IACrE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,qBAAqB,CAAC,SAAS,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IAEtF,MAAM,WAAW,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAExC,IAAI,SAAS,CAAC;IACd,IAAI,eAAe,CAAC;IAEpB,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACnB,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC3E,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;QAC/B,eAAe,GAAG,QAAQ,CAAC,eAAe,CAAC;IAC7C,CAAC;IAED,OAAO;QACL,aAAa,EAAE,KAAK;QACpB,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrC,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,WAAW;QACX,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,4EAA4E;QAC5E,8EAA8E;QAC9E,0EAA0E;QAC1E,kEAAkE;QAClE,YAAY,EAAE;YACZ,GAAG,eAAe;YAClB,GAAG,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBAC5C,GAAG,KAAK;gBACR,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,EAAE;gBACZ,SAAS,EAAE,CAAC;gBACZ,SAAS,EAAE,CAAC;gBACZ,eAAe,EAAE,CAAC;aACnB,CAAC,CAAC;SACJ;QACD,MAAM,EAAE,MAAM;QACd,QAAQ;QACR,SAAS;QACT,eAAe;QACf,gBAAgB,EAAE,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;KACjE,CAAC;AACJ,CAAC"}
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
- export declare const MAX_FILE_SIZE: number;
4
- export declare const BINARY_CHECK_BYTES = 512;
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
@@ -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;AA8D1D,eAAO,MAAM,aAAa,QAAa,CAAC;AACxC,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAiEtC;;;;;;;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;AAyBD;;;;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,GAAG,OAAO,CAAC,eAAe,CAAC,CAiGrF;AAED,wBAAsB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAE5E"}
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
- const DEFAULT_PATTERNS = [
9
- // Core AI instruction files
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
- const IGNORE_DIRS = ['.git', 'node_modules', 'Library', 'Temp', 'bin', 'obj', 'dist', 'build', 'worktrees', '.worktrees'];
64
- // Exported so ai_config discovery (ai-config.ts) applies the exact same
65
- // size/binary policy as this scanner — one definition of "scannable file".
66
- export const MAX_FILE_SIZE = 500 * 1024;
67
- export const BINARY_CHECK_BYTES = 512;
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
- * DEFAULT_PATTERNS, minus the `.clinerules/` directory globs unless
209
- * `.clinerules` really is a directory. fast-glob scandirs `.clinerules` to
210
- * expand them, and a FILE of that name (Cline's single-file form) makes it
211
- * throw ENOTDIR — which scanFiles' catch turned into an empty scan of the
212
- * WHOLE repo. `suppressErrors` now contains that too; dropping the globs
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
- async function defaultPatterns(repoRoot) {
216
- let clineIsDir = false;
217
- try {
218
- clineIsDir = (await fs.stat(path.join(repoRoot, '.clinerules'))).isDirectory();
219
- }
220
- catch {
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
- : await defaultPatterns(repoRoot);
375
+ : defaultPatterns(repoTree);
417
376
  const ignorePatterns = [
418
377
  ...IGNORE_DIRS.map((d) => `**/${d}/**`),
419
378
  ...(input.exclude ?? []),