projscan 1.11.0 → 2.1.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 (141) hide show
  1. package/README.md +40 -30
  2. package/dist/cli/_shared.d.ts +2 -1
  3. package/dist/cli/_shared.js +23 -18
  4. package/dist/cli/_shared.js.map +1 -1
  5. package/dist/cli/commands/analyze.js +7 -3
  6. package/dist/cli/commands/analyze.js.map +1 -1
  7. package/dist/cli/commands/applyFix.js +2 -2
  8. package/dist/cli/commands/applyFix.js.map +1 -1
  9. package/dist/cli/commands/audit.js +2 -2
  10. package/dist/cli/commands/audit.js.map +1 -1
  11. package/dist/cli/commands/badge.js +2 -1
  12. package/dist/cli/commands/badge.js.map +1 -1
  13. package/dist/cli/commands/ci.js +3 -3
  14. package/dist/cli/commands/ci.js.map +1 -1
  15. package/dist/cli/commands/coupling.js +2 -2
  16. package/dist/cli/commands/coupling.js.map +1 -1
  17. package/dist/cli/commands/coverage.js +2 -2
  18. package/dist/cli/commands/coverage.js.map +1 -1
  19. package/dist/cli/commands/dependencies.js +2 -2
  20. package/dist/cli/commands/dependencies.js.map +1 -1
  21. package/dist/cli/commands/diagram.js +2 -2
  22. package/dist/cli/commands/diagram.js.map +1 -1
  23. package/dist/cli/commands/diff.js +2 -2
  24. package/dist/cli/commands/diff.js.map +1 -1
  25. package/dist/cli/commands/doctor.js +3 -3
  26. package/dist/cli/commands/doctor.js.map +1 -1
  27. package/dist/cli/commands/explain.js +6 -7
  28. package/dist/cli/commands/explain.js.map +1 -1
  29. package/dist/cli/commands/explainIssue.js +2 -2
  30. package/dist/cli/commands/explainIssue.js.map +1 -1
  31. package/dist/cli/commands/file.js +2 -2
  32. package/dist/cli/commands/file.js.map +1 -1
  33. package/dist/cli/commands/fix.js +2 -1
  34. package/dist/cli/commands/fix.js.map +1 -1
  35. package/dist/cli/commands/fixSuggest.js +2 -2
  36. package/dist/cli/commands/fixSuggest.js.map +1 -1
  37. package/dist/cli/commands/help.js +2 -1
  38. package/dist/cli/commands/help.js.map +1 -1
  39. package/dist/cli/commands/hotspots.js +2 -2
  40. package/dist/cli/commands/hotspots.js.map +1 -1
  41. package/dist/cli/commands/impact.js +2 -2
  42. package/dist/cli/commands/impact.js.map +1 -1
  43. package/dist/cli/commands/init.js +2 -1
  44. package/dist/cli/commands/init.js.map +1 -1
  45. package/dist/cli/commands/installHook.js +2 -1
  46. package/dist/cli/commands/installHook.js.map +1 -1
  47. package/dist/cli/commands/mcp.js +2 -1
  48. package/dist/cli/commands/mcp.js.map +1 -1
  49. package/dist/cli/commands/memory.js +5 -4
  50. package/dist/cli/commands/memory.js.map +1 -1
  51. package/dist/cli/commands/outdated.js +2 -2
  52. package/dist/cli/commands/outdated.js.map +1 -1
  53. package/dist/cli/commands/plugin.d.ts +3 -3
  54. package/dist/cli/commands/plugin.js +91 -11
  55. package/dist/cli/commands/plugin.js.map +1 -1
  56. package/dist/cli/commands/prDiff.js +2 -2
  57. package/dist/cli/commands/prDiff.js.map +1 -1
  58. package/dist/cli/commands/preflight.d.ts +1 -0
  59. package/dist/cli/commands/preflight.js +80 -0
  60. package/dist/cli/commands/preflight.js.map +1 -0
  61. package/dist/cli/commands/review.js +2 -2
  62. package/dist/cli/commands/review.js.map +1 -1
  63. package/dist/cli/commands/search.js +2 -2
  64. package/dist/cli/commands/search.js.map +1 -1
  65. package/dist/cli/commands/session.js +5 -5
  66. package/dist/cli/commands/session.js.map +1 -1
  67. package/dist/cli/commands/structure.js +2 -2
  68. package/dist/cli/commands/structure.js.map +1 -1
  69. package/dist/cli/commands/taint.js +2 -2
  70. package/dist/cli/commands/taint.js.map +1 -1
  71. package/dist/cli/commands/upgrade.js +2 -2
  72. package/dist/cli/commands/upgrade.js.map +1 -1
  73. package/dist/cli/commands/watch.js +2 -1
  74. package/dist/cli/commands/watch.js.map +1 -1
  75. package/dist/cli/commands/workspace.js +4 -2
  76. package/dist/cli/commands/workspace.js.map +1 -1
  77. package/dist/cli/commands/workspaces.js +2 -2
  78. package/dist/cli/commands/workspaces.js.map +1 -1
  79. package/dist/cli/index.js +2 -0
  80. package/dist/cli/index.js.map +1 -1
  81. package/dist/core/fileInspector.d.ts +2 -5
  82. package/dist/core/fileInspector.js +28 -103
  83. package/dist/core/fileInspector.js.map +1 -1
  84. package/dist/core/languages/LanguageAdapter.d.ts +3 -1
  85. package/dist/core/languages/LanguageAdapter.js +13 -1
  86. package/dist/core/languages/LanguageAdapter.js.map +1 -1
  87. package/dist/core/pluginDx.d.ts +14 -0
  88. package/dist/core/pluginDx.js +298 -0
  89. package/dist/core/pluginDx.js.map +1 -0
  90. package/dist/core/plugins.d.ts +5 -6
  91. package/dist/core/plugins.js +99 -13
  92. package/dist/core/plugins.js.map +1 -1
  93. package/dist/core/preflight.d.ts +11 -0
  94. package/dist/core/preflight.js +416 -0
  95. package/dist/core/preflight.js.map +1 -0
  96. package/dist/core/review.js +101 -1
  97. package/dist/core/review.js.map +1 -1
  98. package/dist/core/sessionResources.d.ts +6 -0
  99. package/dist/core/sessionResources.js +216 -0
  100. package/dist/core/sessionResources.js.map +1 -0
  101. package/dist/index.d.ts +2 -0
  102. package/dist/index.js +1 -0
  103. package/dist/index.js.map +1 -1
  104. package/dist/mcp/resources.js +25 -0
  105. package/dist/mcp/resources.js.map +1 -1
  106. package/dist/mcp/tools/_shared.d.ts +1 -1
  107. package/dist/mcp/tools/_shared.js +3 -15
  108. package/dist/mcp/tools/_shared.js.map +1 -1
  109. package/dist/mcp/tools/explain.js +1 -3
  110. package/dist/mcp/tools/explain.js.map +1 -1
  111. package/dist/mcp/tools/plugin.d.ts +4 -7
  112. package/dist/mcp/tools/plugin.js +6 -9
  113. package/dist/mcp/tools/plugin.js.map +1 -1
  114. package/dist/mcp/tools/preflight.d.ts +2 -0
  115. package/dist/mcp/tools/preflight.js +51 -0
  116. package/dist/mcp/tools/preflight.js.map +1 -0
  117. package/dist/mcp/tools.js +2 -0
  118. package/dist/mcp/tools.js.map +1 -1
  119. package/dist/reporters/htmlReporter.d.ts +2 -1
  120. package/dist/reporters/htmlReporter.js +70 -0
  121. package/dist/reporters/htmlReporter.js.map +1 -1
  122. package/dist/reporters/jsonReporter.d.ts +1 -0
  123. package/dist/reporters/jsonReporter.js +25 -19
  124. package/dist/reporters/jsonReporter.js.map +1 -1
  125. package/dist/tool-manifest.json +35 -5
  126. package/dist/types.d.ts +137 -0
  127. package/dist/utils/banner.js +14 -4
  128. package/dist/utils/banner.js.map +1 -1
  129. package/dist/utils/fileWalker.js +4 -0
  130. package/dist/utils/fileWalker.js.map +1 -1
  131. package/dist/utils/formatSupport.d.ts +58 -0
  132. package/dist/utils/formatSupport.js +63 -0
  133. package/dist/utils/formatSupport.js.map +1 -0
  134. package/docs/2.0-MIGRATION.md +80 -0
  135. package/docs/PLUGIN-AUTHORING.md +239 -0
  136. package/docs/examples/plugins/policy.mjs +16 -0
  137. package/docs/examples/plugins/policy.projscan-plugin.json +8 -0
  138. package/docs/examples/plugins/team-radar.mjs +17 -0
  139. package/docs/examples/plugins/team-radar.projscan-plugin.json +8 -0
  140. package/docs/plugin.schema.json +70 -0
  141. package/package.json +10 -4
@@ -0,0 +1,416 @@
1
+ import { scanRepository } from './repositoryScanner.js';
2
+ import { collectIssues } from './issueEngine.js';
3
+ import { analyzeHotspots } from './hotspotAnalyzer.js';
4
+ import { computeReview } from './review.js';
5
+ import { loadSession } from './session.js';
6
+ import { PLUGIN_PREVIEW_FLAG, pluginsEnabled } from './plugins.js';
7
+ import { getChangedFiles } from '../utils/changedFiles.js';
8
+ import { loadConfig, applyConfigToIssues } from '../utils/config.js';
9
+ import { calculateScore } from '../utils/scoreCalculator.js';
10
+ const DEFAULT_MAX_CHANGED_FILES = 50;
11
+ const MAX_EVIDENCE_FILES = 40;
12
+ export async function computePreflight(rootPath, options = {}) {
13
+ const mode = options.mode ?? 'before_edit';
14
+ const configResult = await loadConfig(rootPath).catch(() => ({ config: { ignore: [] } }));
15
+ const scan = await scanRepository(rootPath, { ignore: configResult.config.ignore });
16
+ const issues = applyConfigToIssues(await collectIssuesWithPluginOption(rootPath, scan.files, options.enablePlugins), configResult.config);
17
+ const health = calculateScore(issues);
18
+ const changedFiles = await safeChangedFiles(rootPath, mode, options.baseRef);
19
+ const session = await safeSession(rootPath);
20
+ const hotspots = await safeHotspots(rootPath, scan.files, issues);
21
+ const review = await safeReview(rootPath, mode, options);
22
+ const reasons = buildPreflightReasons({
23
+ mode,
24
+ issues,
25
+ changedFiles,
26
+ health,
27
+ session,
28
+ hotspots,
29
+ review,
30
+ maxChangedFiles: options.maxChangedFiles ?? DEFAULT_MAX_CHANGED_FILES,
31
+ });
32
+ const verdict = decidePreflightVerdict(reasons);
33
+ const evidence = buildEvidence({
34
+ health,
35
+ changedFiles,
36
+ session,
37
+ hotspots,
38
+ issues,
39
+ pluginsEnabledForRun: options.enablePlugins === true || pluginsEnabled(),
40
+ review,
41
+ });
42
+ const truncated = evidence.session?.truncated === true ||
43
+ changedFiles.files.length > MAX_EVIDENCE_FILES ||
44
+ (evidence.hotspots?.touched.length ?? 0) > MAX_EVIDENCE_FILES;
45
+ const report = {
46
+ schemaVersion: 1,
47
+ mode,
48
+ verdict,
49
+ summary: '',
50
+ reasons,
51
+ evidence,
52
+ requiredChecks: buildRequiredChecks(mode, health, changedFiles, review),
53
+ suggestedNextActions: buildSuggestedActions(reasons, mode, changedFiles),
54
+ toolCalls: buildToolCalls(reasons, mode, changedFiles),
55
+ ...(truncated ? { truncated: true } : {}),
56
+ };
57
+ return { ...report, summary: summarizePreflight(report) };
58
+ }
59
+ export function decidePreflightVerdict(reasons) {
60
+ if (reasons.some((reason) => reason.severity === 'error'))
61
+ return 'block';
62
+ if (reasons.some((reason) => reason.severity === 'warning'))
63
+ return 'caution';
64
+ return 'proceed';
65
+ }
66
+ export function summarizePreflight(report) {
67
+ if (report.reasons.length === 0) {
68
+ return `${report.verdict}: no blocking or cautionary signals found`;
69
+ }
70
+ return `${report.verdict}: ${report.reasons[0].message}`;
71
+ }
72
+ async function collectIssuesWithPluginOption(rootPath, files, enablePlugins) {
73
+ if (enablePlugins !== true)
74
+ return collectIssues(rootPath, files);
75
+ const previous = process.env[PLUGIN_PREVIEW_FLAG];
76
+ process.env[PLUGIN_PREVIEW_FLAG] = '1';
77
+ try {
78
+ return await collectIssues(rootPath, files);
79
+ }
80
+ finally {
81
+ if (previous === undefined)
82
+ delete process.env[PLUGIN_PREVIEW_FLAG];
83
+ else
84
+ process.env[PLUGIN_PREVIEW_FLAG] = previous;
85
+ }
86
+ }
87
+ async function safeChangedFiles(rootPath, mode, baseRef) {
88
+ if (mode === 'before_edit') {
89
+ return {
90
+ available: false,
91
+ count: 0,
92
+ files: [],
93
+ baseRef: null,
94
+ reason: 'changed-file detection is not required before edits',
95
+ };
96
+ }
97
+ try {
98
+ const result = await getChangedFiles(rootPath, baseRef);
99
+ return {
100
+ available: result.available,
101
+ count: result.files.length,
102
+ files: result.files,
103
+ baseRef: result.baseRef,
104
+ ...(result.reason ? { reason: result.reason } : {}),
105
+ };
106
+ }
107
+ catch (err) {
108
+ return {
109
+ available: false,
110
+ count: 0,
111
+ files: [],
112
+ baseRef: null,
113
+ reason: err instanceof Error ? err.message : String(err),
114
+ };
115
+ }
116
+ }
117
+ async function safeSession(rootPath) {
118
+ const { session } = await loadSession(rootPath);
119
+ const touchedFiles = Object.values(session.touchedFiles)
120
+ .sort((a, b) => {
121
+ const byTime = Date.parse(b.lastTouchedAt) - Date.parse(a.lastTouchedAt);
122
+ return byTime !== 0 ? byTime : a.file.localeCompare(b.file);
123
+ })
124
+ .map((touch) => touch.file);
125
+ return {
126
+ id: session.id,
127
+ touchedFiles,
128
+ eventCount: session.events.length,
129
+ };
130
+ }
131
+ async function safeHotspots(rootPath, files, issues) {
132
+ try {
133
+ return await analyzeHotspots(rootPath, files, issues, { limit: 20 });
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ }
139
+ async function safeReview(rootPath, mode, options) {
140
+ if (mode === 'before_edit') {
141
+ return { available: false, reason: 'review is not required before edits' };
142
+ }
143
+ try {
144
+ const report = await computeReview(rootPath, {
145
+ base: options.baseRef,
146
+ head: options.headRef,
147
+ });
148
+ return {
149
+ available: report.available,
150
+ verdict: report.available ? report.verdict : undefined,
151
+ summary: report.available ? report.summary.join('; ') : undefined,
152
+ reason: report.available ? undefined : report.reason,
153
+ newTaintFlows: report.available ? report.newTaintFlows.length : undefined,
154
+ };
155
+ }
156
+ catch (err) {
157
+ return {
158
+ available: false,
159
+ reason: err instanceof Error ? err.message : String(err),
160
+ };
161
+ }
162
+ }
163
+ function buildPreflightReasons(input) {
164
+ const reasons = [];
165
+ const changedSet = new Set(input.changedFiles.files);
166
+ const changedOnly = input.mode !== 'before_edit' && input.changedFiles.available;
167
+ for (const issue of input.issues) {
168
+ if (!issue.id.startsWith('plugin:'))
169
+ continue;
170
+ reasons.push({
171
+ severity: issue.severity,
172
+ source: 'plugin',
173
+ issueId: issue.id,
174
+ file: firstIssueFile(issue),
175
+ message: `${issue.severity === 'error' ? 'Plugin policy blocks' : 'Plugin policy flags'} ${issue.id}: ${issue.title}`,
176
+ tool: 'projscan_plugin',
177
+ });
178
+ }
179
+ if (changedOnly) {
180
+ const changedIssues = input.issues.filter((issue) => issueTouchesChangedFile(issue, changedSet));
181
+ const error = changedIssues.find((issue) => issue.severity === 'error');
182
+ const warning = changedIssues.find((issue) => issue.severity === 'warning');
183
+ if (error) {
184
+ reasons.push({
185
+ severity: 'error',
186
+ source: 'doctor',
187
+ issueId: error.id,
188
+ file: firstIssueFile(error),
189
+ message: `Health error on changed file: ${error.title}`,
190
+ tool: 'projscan_doctor',
191
+ });
192
+ }
193
+ else if (warning) {
194
+ reasons.push({
195
+ severity: 'warning',
196
+ source: 'doctor',
197
+ issueId: warning.id,
198
+ file: firstIssueFile(warning),
199
+ message: `Health warning on changed file: ${warning.title}`,
200
+ tool: 'projscan_doctor',
201
+ });
202
+ }
203
+ }
204
+ else if (input.mode !== 'before_edit' && !input.changedFiles.available) {
205
+ reasons.push({
206
+ severity: 'warning',
207
+ source: 'changed-files',
208
+ message: `Changed files unavailable: ${input.changedFiles.reason ?? 'unknown reason'}`,
209
+ tool: 'projscan_review',
210
+ });
211
+ }
212
+ if (input.mode !== 'before_edit' &&
213
+ input.changedFiles.available &&
214
+ input.changedFiles.count > input.maxChangedFiles) {
215
+ reasons.push({
216
+ severity: 'warning',
217
+ source: 'changed-files',
218
+ message: `${input.changedFiles.count} changed files exceeds the preflight threshold of ${input.maxChangedFiles}`,
219
+ tool: 'projscan_review',
220
+ });
221
+ }
222
+ if (input.review.available) {
223
+ if ((input.review.newTaintFlows ?? 0) > 0) {
224
+ reasons.push({
225
+ severity: 'error',
226
+ source: 'taint',
227
+ message: `${input.review.newTaintFlows} new taint flow(s) found in review`,
228
+ tool: 'projscan_review',
229
+ });
230
+ }
231
+ if (input.review.verdict === 'block') {
232
+ reasons.push({
233
+ severity: 'error',
234
+ source: 'review',
235
+ message: 'Review verdict is block',
236
+ tool: 'projscan_review',
237
+ });
238
+ }
239
+ else if (input.review.verdict === 'review') {
240
+ reasons.push({
241
+ severity: 'warning',
242
+ source: 'review',
243
+ message: 'Review verdict requires careful review',
244
+ tool: 'projscan_review',
245
+ });
246
+ }
247
+ }
248
+ else if (input.mode !== 'before_edit') {
249
+ reasons.push({
250
+ severity: 'warning',
251
+ source: 'review',
252
+ message: `Review unavailable: ${input.review.reason ?? 'unknown reason'}`,
253
+ tool: 'projscan_review',
254
+ });
255
+ }
256
+ const touched = new Set(input.session.touchedFiles);
257
+ const hotspotTouches = input.hotspots?.available === true
258
+ ? input.hotspots.hotspots.filter((hotspot) => touched.has(hotspot.relativePath) && hotspot.riskScore >= 40)
259
+ : [];
260
+ for (const hotspot of hotspotTouches.slice(0, 3)) {
261
+ reasons.push({
262
+ severity: 'warning',
263
+ source: 'hotspots',
264
+ file: hotspot.relativePath,
265
+ message: `Touched file overlaps high-risk hotspot ${hotspot.relativePath} (risk ${hotspot.riskScore})`,
266
+ tool: 'projscan_hotspots',
267
+ });
268
+ }
269
+ if (input.health.errors > 0 && input.mode === 'before_merge' && !changedOnly) {
270
+ reasons.push({
271
+ severity: 'warning',
272
+ source: 'doctor',
273
+ message: `${input.health.errors} project health error(s) exist; changed-file scoping was unavailable`,
274
+ tool: 'projscan_doctor',
275
+ });
276
+ }
277
+ return reasons;
278
+ }
279
+ function buildEvidence(input) {
280
+ const pluginIssues = input.issues.filter((issue) => issue.id.startsWith('plugin:'));
281
+ const touched = input.hotspots?.available === true
282
+ ? input.hotspots.hotspots
283
+ .filter((hotspot) => input.session.touchedFiles.includes(hotspot.relativePath))
284
+ .slice(0, MAX_EVIDENCE_FILES)
285
+ .map((hotspot) => ({ file: hotspot.relativePath, riskScore: hotspot.riskScore }))
286
+ : [];
287
+ const sessionTouchedFiles = input.session.touchedFiles.slice(0, MAX_EVIDENCE_FILES);
288
+ const changedEvidenceFiles = input.changedFiles.files.slice(0, MAX_EVIDENCE_FILES);
289
+ return {
290
+ health: {
291
+ score: input.health.score,
292
+ grade: input.health.grade,
293
+ errors: input.health.errors,
294
+ warnings: input.health.warnings,
295
+ infos: input.health.infos,
296
+ },
297
+ changedFiles: {
298
+ available: input.changedFiles.available,
299
+ count: input.changedFiles.count,
300
+ files: changedEvidenceFiles,
301
+ ...(input.changedFiles.reason ? { reason: input.changedFiles.reason } : {}),
302
+ },
303
+ review: {
304
+ available: input.review.available,
305
+ ...(input.review.verdict ? { verdict: input.review.verdict } : {}),
306
+ ...(input.review.summary ? { summary: input.review.summary } : {}),
307
+ ...(input.review.reason ? { reason: input.review.reason } : {}),
308
+ },
309
+ session: {
310
+ id: input.session.id,
311
+ touchedFiles: sessionTouchedFiles,
312
+ totalTouchedFiles: input.session.touchedFiles.length,
313
+ eventCount: input.session.eventCount,
314
+ ...(input.session.touchedFiles.length > MAX_EVIDENCE_FILES ? { truncated: true } : {}),
315
+ },
316
+ hotspots: { touched },
317
+ plugins: {
318
+ enabled: input.pluginsEnabledForRun,
319
+ errorIssues: pluginIssues.filter((issue) => issue.severity === 'error').length,
320
+ warningIssues: pluginIssues.filter((issue) => issue.severity === 'warning').length,
321
+ },
322
+ };
323
+ }
324
+ function buildRequiredChecks(mode, health, changedFiles, review) {
325
+ const checks = [
326
+ {
327
+ name: 'health',
328
+ status: health.errors > 0 ? 'fail' : health.warnings > 0 ? 'warn' : 'pass',
329
+ reason: `${health.errors} error(s), ${health.warnings} warning(s), ${health.infos} info`,
330
+ },
331
+ ];
332
+ checks.push({
333
+ name: 'changed-files',
334
+ status: changedFiles.available ? 'pass' : 'unavailable',
335
+ reason: changedFiles.available
336
+ ? `${changedFiles.count} changed file(s)`
337
+ : changedFiles.reason ?? 'changed-file detection unavailable',
338
+ });
339
+ checks.push({
340
+ name: 'review',
341
+ status: mode === 'before_edit'
342
+ ? 'unavailable'
343
+ : !review.available
344
+ ? 'unavailable'
345
+ : review.verdict === 'block'
346
+ ? 'fail'
347
+ : review.verdict === 'review'
348
+ ? 'warn'
349
+ : 'pass',
350
+ reason: mode === 'before_edit'
351
+ ? 'review is not required before edits'
352
+ : review.available
353
+ ? review.summary ?? review.verdict
354
+ : review.reason ?? 'review unavailable',
355
+ });
356
+ return checks;
357
+ }
358
+ function buildSuggestedActions(reasons, mode, changedFiles) {
359
+ if (reasons.length === 0)
360
+ return [];
361
+ const actions = [];
362
+ if (reasons.some((reason) => reason.source === 'review' || reason.source === 'taint')) {
363
+ actions.push({
364
+ label: 'Inspect the full review before continuing',
365
+ command: 'projscan review --format json',
366
+ tool: 'projscan_review',
367
+ });
368
+ }
369
+ if (reasons.some((reason) => reason.source === 'doctor' || reason.source === 'plugin')) {
370
+ actions.push({
371
+ label: 'Inspect health issues and plugin policy findings',
372
+ command: 'projscan doctor --format json',
373
+ tool: 'projscan_doctor',
374
+ });
375
+ }
376
+ if (reasons.some((reason) => reason.source === 'hotspots')) {
377
+ actions.push({
378
+ label: 'Inspect touched hotspots',
379
+ command: 'projscan hotspots --format json',
380
+ tool: 'projscan_hotspots',
381
+ });
382
+ }
383
+ if (mode !== 'before_edit' && !changedFiles.available) {
384
+ actions.push({
385
+ label: 'Run preflight with an explicit base ref',
386
+ command: 'projscan preflight --base-ref main --format json',
387
+ });
388
+ }
389
+ return dedupeActions(actions);
390
+ }
391
+ function buildToolCalls(reasons, mode, changedFiles) {
392
+ return buildSuggestedActions(reasons, mode, changedFiles).map((action) => ({
393
+ label: action.label,
394
+ ...(action.tool ? { tool: action.tool } : {}),
395
+ ...(action.args ? { args: action.args } : {}),
396
+ }));
397
+ }
398
+ function dedupeActions(actions) {
399
+ const seen = new Set();
400
+ const out = [];
401
+ for (const action of actions) {
402
+ const key = `${action.label}:${action.command ?? action.tool ?? ''}`;
403
+ if (seen.has(key))
404
+ continue;
405
+ seen.add(key);
406
+ out.push(action);
407
+ }
408
+ return out;
409
+ }
410
+ function issueTouchesChangedFile(issue, changedFiles) {
411
+ return (issue.locations ?? []).some((location) => location.file && changedFiles.has(location.file));
412
+ }
413
+ function firstIssueFile(issue) {
414
+ return issue.locations?.find((location) => location.file)?.file;
415
+ }
416
+ //# sourceMappingURL=preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/core/preflight.ts"],"names":[],"mappings":"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"}
@@ -126,6 +126,7 @@ export async function computeReview(rootPath, options = {}) {
126
126
  const riskyFunctions = findRiskyFunctions(baseGraph, headGraph, prDiff);
127
127
  // Dependency changes across root + workspaces.
128
128
  const dependencyChanges = diffManifests(basePackageManifests, headPackageManifests);
129
+ const contractChanges = buildContractChanges(prDiff, baseGraph, headGraph, basePackageManifests, headPackageManifests);
129
130
  // 1.6+ — taint flows newly introduced at head. A flow is "new" iff
130
131
  // (a) the (sourceFn, sinkFn) pair didn't exist at base, AND
131
132
  // (b) at least one file along the flow's path is in the PR diff.
@@ -149,6 +150,7 @@ export async function computeReview(rootPath, options = {}) {
149
150
  newCycles,
150
151
  riskyFunctions,
151
152
  dependencyChanges,
153
+ contractChanges,
152
154
  newTaintFlows,
153
155
  verdict,
154
156
  summary,
@@ -399,8 +401,22 @@ async function readOneManifest(dir, manifestFile, workspaceName) {
399
401
  manifestFile,
400
402
  dependencies: parsed.dependencies ?? {},
401
403
  devDependencies: parsed.devDependencies ?? {},
404
+ entrypoints: readEntrypoints(parsed),
402
405
  };
403
406
  }
407
+ function readEntrypoints(parsed) {
408
+ const out = {};
409
+ for (const field of ['main', 'module', 'types', 'exports', 'bin']) {
410
+ const value = parsed[field];
411
+ if (value === undefined)
412
+ continue;
413
+ out[field] = entrypointValue(value);
414
+ }
415
+ return out;
416
+ }
417
+ function entrypointValue(value) {
418
+ return typeof value === 'string' ? value : JSON.stringify(value);
419
+ }
404
420
  function diffManifests(base, head) {
405
421
  const out = [];
406
422
  const allManifests = new Set([...base.keys(), ...head.keys()]);
@@ -417,6 +433,81 @@ function diffManifests(base, head) {
417
433
  out.sort((a, b) => a.manifestFile.localeCompare(b.manifestFile));
418
434
  return out;
419
435
  }
436
+ function buildContractChanges(prDiff, baseGraph, headGraph, baseManifests, headManifests) {
437
+ const changes = [];
438
+ for (const file of prDiff.filesAdded) {
439
+ const entry = headGraph.files.get(file);
440
+ for (const exp of entry?.exports ?? []) {
441
+ changes.push(exportContractChange('export-added', file, exp.name));
442
+ }
443
+ }
444
+ for (const file of prDiff.filesRemoved) {
445
+ const entry = baseGraph.files.get(file);
446
+ for (const exp of entry?.exports ?? []) {
447
+ changes.push(exportContractChange('export-removed', file, exp.name));
448
+ }
449
+ }
450
+ for (const file of prDiff.filesModified) {
451
+ for (const symbol of file.exportsAdded) {
452
+ changes.push(exportContractChange('export-added', file.relativePath, symbol));
453
+ }
454
+ for (const symbol of file.exportsRemoved) {
455
+ changes.push(exportContractChange('export-removed', file.relativePath, symbol));
456
+ }
457
+ for (const rename of file.exportsRenamed) {
458
+ changes.push({
459
+ kind: 'export-renamed',
460
+ file: file.relativePath,
461
+ symbol: rename.to,
462
+ before: rename.from,
463
+ after: rename.to,
464
+ confidence: 'high',
465
+ why: `Export "${rename.from}" was renamed to "${rename.to}" in ${file.relativePath}; downstream imports of the old name can fail at compile time or runtime.`,
466
+ });
467
+ }
468
+ }
469
+ changes.push(...entrypointContractChanges(baseManifests, headManifests));
470
+ return changes;
471
+ }
472
+ function exportContractChange(kind, file, symbol) {
473
+ return {
474
+ kind,
475
+ file,
476
+ symbol,
477
+ confidence: 'high',
478
+ why: kind === 'export-added'
479
+ ? `Export "${symbol}" was added in ${file}; downstream code may start depending on a new public API.`
480
+ : `Export "${symbol}" was removed from ${file}; downstream imports can fail at compile time or runtime.`,
481
+ };
482
+ }
483
+ function entrypointContractChanges(base, head) {
484
+ const out = [];
485
+ const allManifests = new Set([...base.keys(), ...head.keys()]);
486
+ for (const manifestFile of allManifests) {
487
+ const baseEntrypoints = base.get(manifestFile)?.entrypoints ?? {};
488
+ const headEntrypoints = head.get(manifestFile)?.entrypoints ?? {};
489
+ const fields = new Set([...Object.keys(baseEntrypoints), ...Object.keys(headEntrypoints)]);
490
+ for (const field of fields) {
491
+ const before = baseEntrypoints[field];
492
+ const after = headEntrypoints[field];
493
+ if (before === after)
494
+ continue;
495
+ const kind = field === 'exports' ? 'public-export-changed' : 'entrypoint-changed';
496
+ out.push({
497
+ kind,
498
+ file: manifestFile,
499
+ symbol: field,
500
+ ...(before !== undefined ? { before } : {}),
501
+ ...(after !== undefined ? { after } : {}),
502
+ confidence: 'high',
503
+ why: kind === 'public-export-changed'
504
+ ? `${manifestFile} package "exports" changed; consumers may resolve different public modules.`
505
+ : `${manifestFile} package "${field}" changed from ${before ?? '<unset>'} to ${after ?? '<unset>'}; package consumers may load a different entrypoint.`,
506
+ });
507
+ }
508
+ }
509
+ return out.sort((a, b) => `${a.file}:${a.symbol ?? ''}`.localeCompare(`${b.file}:${b.symbol ?? ''}`));
510
+ }
420
511
  function diffOneManifest(base, head, manifestFile) {
421
512
  const workspace = head?.workspace ?? base?.workspace ?? '';
422
513
  const baseDeps = base?.dependencies ?? {};
@@ -645,7 +736,15 @@ export function shapeReviewForTier(report, tier) {
645
736
  const riskyFunctionsAdded = report.riskyFunctions.length;
646
737
  const depsChanged = report.dependencyChanges.length;
647
738
  const taintFlowsAdded = report.newTaintFlows?.length ?? 0;
648
- const totals = { filesChanged, cyclesAdded, riskyFunctionsAdded, depsChanged, taintFlowsAdded };
739
+ const contractChanges = report.contractChanges?.length ?? 0;
740
+ const totals = {
741
+ filesChanged,
742
+ cyclesAdded,
743
+ riskyFunctionsAdded,
744
+ depsChanged,
745
+ taintFlowsAdded,
746
+ contractChanges,
747
+ };
649
748
  if (tier === 'verdict-only') {
650
749
  return {
651
750
  available: report.available,
@@ -683,6 +782,7 @@ export function shapeReviewForTier(report, tier) {
683
782
  newCycles: report.newCycles.slice(0, 3),
684
783
  riskyFunctions: report.riskyFunctions.slice(0, 3),
685
784
  dependencyChanges: report.dependencyChanges.slice(0, 3),
785
+ contractChanges: report.contractChanges?.slice(0, TOP) ?? [],
686
786
  newTaintFlows: report.newTaintFlows?.slice(0, 5) ?? [],
687
787
  verdict: report.verdict,
688
788
  summary: report.summary,