@mergesignal/shared 0.2.7 → 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 (85) hide show
  1. package/dist/actionsStepSummary.d.ts.map +1 -1
  2. package/dist/actionsStepSummary.js +2 -29
  3. package/dist/collectNarrativeWhyBullets.d.ts +12 -0
  4. package/dist/collectNarrativeWhyBullets.d.ts.map +1 -0
  5. package/dist/collectNarrativeWhyBullets.js +104 -0
  6. package/dist/deriveScanNarrative.d.ts +0 -3
  7. package/dist/deriveScanNarrative.d.ts.map +1 -1
  8. package/dist/deriveScanNarrative.js +177 -63
  9. package/dist/deriveScanNarrative.test.js +90 -6
  10. package/dist/engineWireRegression.test.d.ts +2 -0
  11. package/dist/engineWireRegression.test.d.ts.map +1 -0
  12. package/dist/engineWireRegression.test.js +57 -0
  13. package/dist/fixtures/legacyEngineRepoIntelligence.d.ts +28 -0
  14. package/dist/fixtures/legacyEngineRepoIntelligence.d.ts.map +1 -0
  15. package/dist/fixtures/legacyEngineRepoIntelligence.js +28 -0
  16. package/dist/fixtures/repoIntelligenceFixtures.d.ts +2 -2
  17. package/dist/fixtures/repoIntelligenceFixtures.d.ts.map +1 -1
  18. package/dist/fixtures/repoIntelligenceFixtures.js +59 -3
  19. package/dist/fixtures/repoIntelligenceTestHelpers.d.ts +5 -0
  20. package/dist/fixtures/repoIntelligenceTestHelpers.d.ts.map +1 -0
  21. package/dist/fixtures/repoIntelligenceTestHelpers.js +28 -0
  22. package/dist/formatInsight.d.ts +2 -2
  23. package/dist/formatInsight.d.ts.map +1 -1
  24. package/dist/formatInsight.js +2 -7
  25. package/dist/formatInsight.test.js +1 -1
  26. package/dist/index.d.ts +6 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +6 -0
  29. package/dist/narrativeParity.test.d.ts +2 -0
  30. package/dist/narrativeParity.test.d.ts.map +1 -0
  31. package/dist/narrativeParity.test.js +65 -0
  32. package/dist/narrativePresentation.d.ts +50 -0
  33. package/dist/narrativePresentation.d.ts.map +1 -0
  34. package/dist/narrativePresentation.js +180 -0
  35. package/dist/narrativePresentation.test.d.ts +2 -0
  36. package/dist/narrativePresentation.test.d.ts.map +1 -0
  37. package/dist/narrativePresentation.test.js +54 -0
  38. package/dist/normalizeGeneratedText.d.ts +8 -0
  39. package/dist/normalizeGeneratedText.d.ts.map +1 -0
  40. package/dist/normalizeGeneratedText.js +31 -0
  41. package/dist/normalizeGeneratedText.test.d.ts +2 -0
  42. package/dist/normalizeGeneratedText.test.d.ts.map +1 -0
  43. package/dist/normalizeGeneratedText.test.js +18 -0
  44. package/dist/prCheckRunPresentation.d.ts +4 -3
  45. package/dist/prCheckRunPresentation.d.ts.map +1 -1
  46. package/dist/prCheckRunPresentation.js +56 -51
  47. package/dist/prCheckRunPresentation.test.js +2 -2
  48. package/dist/presentGitHubPrComment.d.ts +9 -0
  49. package/dist/presentGitHubPrComment.d.ts.map +1 -0
  50. package/dist/presentGitHubPrComment.js +80 -0
  51. package/dist/presentScanCardSummary.d.ts.map +1 -1
  52. package/dist/presentScanCardSummary.js +69 -64
  53. package/dist/repoIntelligenceMappers.d.ts +20 -0
  54. package/dist/repoIntelligenceMappers.d.ts.map +1 -0
  55. package/dist/repoIntelligenceMappers.js +60 -0
  56. package/dist/repoIntelligenceSchema.contract.test.d.ts +2 -0
  57. package/dist/repoIntelligenceSchema.contract.test.d.ts.map +1 -0
  58. package/dist/repoIntelligenceSchema.contract.test.js +19 -0
  59. package/dist/repoIntelligenceSchema.d.ts +52 -83
  60. package/dist/repoIntelligenceSchema.d.ts.map +1 -1
  61. package/dist/repoIntelligenceSchema.js +99 -141
  62. package/dist/repoIntelligenceValidation.d.ts +18 -0
  63. package/dist/repoIntelligenceValidation.d.ts.map +1 -0
  64. package/dist/repoIntelligenceValidation.js +76 -0
  65. package/dist/repoIntelligenceValidation.persist.test.d.ts +2 -0
  66. package/dist/repoIntelligenceValidation.persist.test.d.ts.map +1 -0
  67. package/dist/repoIntelligenceValidation.persist.test.js +44 -0
  68. package/dist/riskVocabulary.js +1 -1
  69. package/dist/scanCardSummary.d.ts +9 -1
  70. package/dist/scanCardSummary.d.ts.map +1 -1
  71. package/dist/scanCardSummary.js +1 -1
  72. package/dist/scanCardSummary.test.js +12 -8
  73. package/dist/scanDetailViewModel.d.ts +9 -1
  74. package/dist/scanDetailViewModel.d.ts.map +1 -1
  75. package/dist/scanDetailViewModel.js +164 -37
  76. package/dist/scanDetailViewModel.test.js +28 -1
  77. package/dist/scanNarrativeFacts.d.ts +23 -8
  78. package/dist/scanNarrativeFacts.d.ts.map +1 -1
  79. package/dist/scanNarrativeFacts.js +7 -0
  80. package/dist/scanSurfaceCopy.d.ts +26 -26
  81. package/dist/scanSurfaceCopy.d.ts.map +1 -1
  82. package/dist/scanSurfaceCopy.js +26 -26
  83. package/dist/types.d.ts +15 -3
  84. package/dist/types.d.ts.map +1 -1
  85. package/package.json +1 -1
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { deriveScanCardSummary } from "./scanCardSummary.js";
3
+ import { deriveScanNarrative } from "./deriveScanNarrative.js";
4
+ import { fixtureRepoIntelligenceFastify, fixtureRepoIntelligenceMultiPackage, } from "./fixtures/repoIntelligenceFixtures.js";
5
+ import { validateRepoIntelligenceWire } from "./repoIntelligenceSchema.js";
6
+ const baseScan = {
7
+ totalScore: 55,
8
+ layerScores: {
9
+ security: 10,
10
+ maintainability: 20,
11
+ ecosystem: 30,
12
+ upgradeImpact: 15,
13
+ },
14
+ findings: [],
15
+ generatedAt: "2026-01-01T00:00:00.000Z",
16
+ analysisPreparation: {
17
+ codeIntelligenceAvailable: true,
18
+ warnings: [],
19
+ repoIntelligenceValidation: {
20
+ status: "valid",
21
+ abi: "1",
22
+ validatedAt: "2026-01-01T00:00:00.000Z",
23
+ },
24
+ },
25
+ };
26
+ describe("engine wire regression (canonical fixtures)", () => {
27
+ it("fastify fixture: parse → tier1 pr_intelligence → card usage lines", () => {
28
+ validateRepoIntelligenceWire(fixtureRepoIntelligenceFastify);
29
+ const result = {
30
+ ...baseScan,
31
+ changedPackages: ["fastify"],
32
+ repoIntelligence: fixtureRepoIntelligenceFastify,
33
+ };
34
+ const facts = deriveScanNarrative(result);
35
+ expect(facts.availability.tiersPresent.tier1).toBe(true);
36
+ expect(facts.availability.mode).toBe("pr_intelligence");
37
+ expect(facts.availability.repoIntelligenceParse).toBe("ok");
38
+ expect(facts.packageUsage[0]?.packageName).toBe("fastify");
39
+ const card = deriveScanCardSummary(result, "done");
40
+ expect(card.usageSummary).toBeTruthy();
41
+ expect(card.verificationLine).toBeTruthy();
42
+ });
43
+ it("multi-package fixture: both packages in packageUsage", () => {
44
+ validateRepoIntelligenceWire(fixtureRepoIntelligenceMultiPackage);
45
+ const result = {
46
+ ...baseScan,
47
+ changedPackages: ["lodash", "axios"],
48
+ repoIntelligence: fixtureRepoIntelligenceMultiPackage,
49
+ };
50
+ const facts = deriveScanNarrative(result);
51
+ expect(facts.availability.mode).toBe("pr_intelligence");
52
+ expect(facts.packageUsage.map((u) => u.packageName)).toEqual([
53
+ "lodash",
54
+ "axios",
55
+ ]);
56
+ });
57
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Pre-contract engine wire shape (Wave 1/2 drift). Used to assert strict schema rejection
3
+ * and narrative fallback while preserving raw payload for forensics.
4
+ */
5
+ export declare const legacyEngineRepoIntelligenceJwt: {
6
+ readonly packageUsage: readonly [{
7
+ readonly name: "jsonwebtoken";
8
+ readonly files: readonly ["src/auth/auth.service.ts", "src/auth/login.controller.ts"];
9
+ readonly reachability: "moderate";
10
+ readonly runtimeSurface: "runtime";
11
+ readonly areas: readonly ["authentication"];
12
+ readonly blastRadius: "moderate";
13
+ }];
14
+ readonly hotspots: readonly [{
15
+ readonly file: "src/auth/auth.service.ts";
16
+ readonly fanIn: 2;
17
+ readonly areas: readonly ["authentication"];
18
+ }];
19
+ readonly frameworks: readonly [{
20
+ readonly framework: "express";
21
+ readonly roles: readonly ["middleware"];
22
+ }];
23
+ readonly blastRadius: {
24
+ readonly level: "large";
25
+ readonly factors: readonly ["reaches runtime application code"];
26
+ };
27
+ };
28
+ //# sourceMappingURL=legacyEngineRepoIntelligence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacyEngineRepoIntelligence.d.ts","sourceRoot":"","sources":["../../src/fixtures/legacyEngineRepoIntelligence.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;;CAuBlC,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Pre-contract engine wire shape (Wave 1/2 drift). Used to assert strict schema rejection
3
+ * and narrative fallback while preserving raw payload for forensics.
4
+ */
5
+ export const legacyEngineRepoIntelligenceJwt = {
6
+ packageUsage: [
7
+ {
8
+ name: "jsonwebtoken",
9
+ files: ["src/auth/auth.service.ts", "src/auth/login.controller.ts"],
10
+ reachability: "moderate",
11
+ runtimeSurface: "runtime",
12
+ areas: ["authentication"],
13
+ blastRadius: "moderate",
14
+ },
15
+ ],
16
+ hotspots: [
17
+ {
18
+ file: "src/auth/auth.service.ts",
19
+ fanIn: 2,
20
+ areas: ["authentication"],
21
+ },
22
+ ],
23
+ frameworks: [{ framework: "express", roles: ["middleware"] }],
24
+ blastRadius: {
25
+ level: "large",
26
+ factors: ["reaches runtime application code"],
27
+ },
28
+ };
@@ -3,6 +3,6 @@ import type { RepoIntelligence } from "../repoIntelligenceSchema.js";
3
3
  export declare const fixtureRepoIntelligenceFastify: RepoIntelligence;
4
4
  /** Build-only toolchain change — no runtime paths. */
5
5
  export declare const fixtureRepoIntelligenceTypescript: RepoIntelligence;
6
- /** Empty corpus stub (engine-test-fixture default). */
7
- export declare const fixtureRepoIntelligenceEmpty: RepoIntelligence;
6
+ /** Multi-package PR with distinct usage per package. */
7
+ export declare const fixtureRepoIntelligenceMultiPackage: RepoIntelligence;
8
8
  //# sourceMappingURL=repoIntelligenceFixtures.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"repoIntelligenceFixtures.d.ts","sourceRoot":"","sources":["../../src/fixtures/repoIntelligenceFixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE,iEAAiE;AACjE,eAAO,MAAM,8BAA8B,EAAE,gBA8B5C,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,iCAAiC,EAAE,gBAS/C,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,4BAA4B,EAAE,gBAE1C,CAAC"}
1
+ {"version":3,"file":"repoIntelligenceFixtures.d.ts","sourceRoot":"","sources":["../../src/fixtures/repoIntelligenceFixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE,iEAAiE;AACjE,eAAO,MAAM,8BAA8B,EAAE,gBAmC5C,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,iCAAiC,EAAE,gBAc/C,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,mCAAmC,EAAE,gBAgDjD,CAAC"}
@@ -6,6 +6,11 @@ export const fixtureRepoIntelligenceFastify = {
6
6
  reachability: "on_runtime_paths",
7
7
  usage: {
8
8
  packageName: "fastify",
9
+ files: [
10
+ "apps/api/src/server.ts",
11
+ "apps/api/src/routes/account.ts",
12
+ "apps/api/src/middleware/auth.ts",
13
+ ],
9
14
  paths: ["apps/api/src/server.ts", "apps/api/src/routes/account.ts"],
10
15
  criticalPaths: ["apps/api/src/middleware/auth.ts"],
11
16
  areas: ["API", "Auth middleware"],
@@ -36,12 +41,63 @@ export const fixtureRepoIntelligenceTypescript = {
36
41
  typescript: {
37
42
  runtimeSurface: "build",
38
43
  reachability: "build_only",
44
+ usage: {
45
+ packageName: "typescript",
46
+ files: ["tsconfig.json"],
47
+ paths: [],
48
+ },
39
49
  },
40
50
  },
41
51
  blastRadius: { level: "narrow", changedPackageCount: 1 },
42
52
  applicationAreas: [],
43
53
  };
44
- /** Empty corpus stub (engine-test-fixture default). */
45
- export const fixtureRepoIntelligenceEmpty = {
46
- packages: {},
54
+ /** Multi-package PR with distinct usage per package. */
55
+ export const fixtureRepoIntelligenceMultiPackage = {
56
+ packages: {
57
+ lodash: {
58
+ runtimeSurface: "runtime",
59
+ reachability: "on_runtime_paths",
60
+ usage: {
61
+ packageName: "lodash",
62
+ files: ["apps/billing/export.ts"],
63
+ paths: ["apps/billing/export.ts"],
64
+ areas: ["Billing"],
65
+ },
66
+ },
67
+ axios: {
68
+ runtimeSurface: "runtime",
69
+ reachability: "on_runtime_paths",
70
+ usage: {
71
+ packageName: "axios",
72
+ files: ["apps/api/client.ts"],
73
+ paths: ["apps/api/client.ts"],
74
+ areas: ["API"],
75
+ },
76
+ },
77
+ },
78
+ packageUsage: [
79
+ {
80
+ packageName: "lodash",
81
+ files: ["apps/billing/export.ts"],
82
+ paths: ["apps/billing/export.ts"],
83
+ },
84
+ {
85
+ packageName: "axios",
86
+ files: ["apps/api/client.ts"],
87
+ paths: ["apps/api/client.ts"],
88
+ },
89
+ ],
90
+ blastRadius: {
91
+ level: "moderate",
92
+ factors: ["multiple_runtime_consumers", "shared_middleware"],
93
+ changedPackageCount: 2,
94
+ },
95
+ frameworks: ["express", "react"],
96
+ hotspots: [
97
+ {
98
+ packageName: "lodash",
99
+ source: "code",
100
+ paths: ["apps/billing/export.ts"],
101
+ },
102
+ ],
47
103
  };
@@ -0,0 +1,5 @@
1
+ import type { AnalysisPreparation } from "../types.js";
2
+ /** Worker-shaped preparation for tests that consume trusted repoIntelligence. */
3
+ export declare function analysisPreparationWithValidRepoIntel(over?: Partial<AnalysisPreparation>): AnalysisPreparation;
4
+ export declare function analysisPreparationWithInvalidRepoIntel(over?: Partial<AnalysisPreparation>): AnalysisPreparation;
5
+ //# sourceMappingURL=repoIntelligenceTestHelpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repoIntelligenceTestHelpers.d.ts","sourceRoot":"","sources":["../../src/fixtures/repoIntelligenceTestHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGvD,iFAAiF;AACjF,wBAAgB,qCAAqC,CACnD,IAAI,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAClC,mBAAmB,CAWrB;AAED,wBAAgB,uCAAuC,CACrD,IAAI,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAClC,mBAAmB,CAarB"}
@@ -0,0 +1,28 @@
1
+ import { REPO_INTELLIGENCE_ABI } from "../repoIntelligenceSchema.js";
2
+ /** Worker-shaped preparation for tests that consume trusted repoIntelligence. */
3
+ export function analysisPreparationWithValidRepoIntel(over) {
4
+ return {
5
+ codeIntelligenceAvailable: true,
6
+ warnings: [],
7
+ repoIntelligenceValidation: {
8
+ status: "valid",
9
+ abi: REPO_INTELLIGENCE_ABI,
10
+ validatedAt: "2026-01-01T00:00:00.000Z",
11
+ },
12
+ ...over,
13
+ };
14
+ }
15
+ export function analysisPreparationWithInvalidRepoIntel(over) {
16
+ return {
17
+ codeIntelligenceAvailable: true,
18
+ warnings: [],
19
+ repoIntelligenceValidation: {
20
+ status: "invalid",
21
+ abi: REPO_INTELLIGENCE_ABI,
22
+ issueCount: 1,
23
+ representativeIssues: ["packages: at least one package required"],
24
+ validatedAt: "2026-01-01T00:00:00.000Z",
25
+ },
26
+ ...over,
27
+ };
28
+ }
@@ -1,4 +1,4 @@
1
- import type { PRInsight, PRDecision, PRDecisionRecommendation } from "./types.js";
1
+ import type { PRInsight, PRDecisionRecommendation } from "./types.js";
2
2
  export type FormattedInsight = {
3
3
  message: string;
4
4
  where: string;
@@ -6,5 +6,5 @@ export type FormattedInsight = {
6
6
  };
7
7
  export declare function decisionTitle(recommendation: PRDecisionRecommendation): string;
8
8
  export declare function formatInsight(insight: PRInsight): FormattedInsight;
9
- export declare function renderInsightsAsMarkdown(insights: PRInsight[], decision: PRDecision): string;
9
+ export { renderInsightsAsMarkdown } from "./presentGitHubPrComment.js";
10
10
  //# sourceMappingURL=formatInsight.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"formatInsight.d.ts","sourceRoot":"","sources":["../src/formatInsight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,UAAU,EACV,wBAAwB,EACzB,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AASF,wBAAgB,aAAa,CAC3B,cAAc,EAAE,wBAAwB,GACvC,MAAM,CAER;AAgBD,wBAAgB,aAAa,CAAC,OAAO,EAAE,SAAS,GAAG,gBAAgB,CAMlE;AAkBD,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,SAAS,EAAE,EACrB,QAAQ,EAAE,UAAU,GACnB,MAAM,CAMR"}
1
+ {"version":3,"file":"formatInsight.d.ts","sourceRoot":"","sources":["../src/formatInsight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EAET,wBAAwB,EACzB,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AASF,wBAAgB,aAAa,CAC3B,cAAc,EAAE,wBAAwB,GACvC,MAAM,CAER;AAgBD,wBAAgB,aAAa,CAAC,OAAO,EAAE,SAAS,GAAG,gBAAgB,CAMlE;AAiBD,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC"}
@@ -1,7 +1,7 @@
1
1
  const DECISION_TITLES = {
2
2
  safe: "No significant dependency risks detected",
3
3
  needs_review: "Elevated dependency merge risk",
4
- risky: "Merge blocked critical dependency risk",
4
+ risky: "Merge blocked - critical dependency risk",
5
5
  };
6
6
  // Title rendered once per PR comment, not per insight
7
7
  export function decisionTitle(recommendation) {
@@ -41,9 +41,4 @@ function renderInsightAsMarkdown(f) {
41
41
  f.action,
42
42
  ].join("\n");
43
43
  }
44
- // Renders a full PR comment: one bold title + all insights separated by dividers
45
- export function renderInsightsAsMarkdown(insights, decision) {
46
- const title = `**${decisionTitle(decision.recommendation)}**`;
47
- const blocks = insights.map((insight) => renderInsightAsMarkdown(formatInsight(insight)));
48
- return [title, "", ...blocks].join("\n\n---\n\n").trimEnd();
49
- }
44
+ export { renderInsightsAsMarkdown } from "./presentGitHubPrComment.js";
@@ -88,7 +88,7 @@ describe("renderInsightsAsMarkdown", () => {
88
88
  remediation: "fix text",
89
89
  });
90
90
  const output = renderInsightsAsMarkdown([insight], decision);
91
- expect(output).toContain("Elevated dependency merge risk");
91
+ expect(output).toContain("Needs review");
92
92
  expect(output).toContain("msg text");
93
93
  expect(output).toContain("ctx text");
94
94
  expect(output).toContain("fix text");
package/dist/index.d.ts CHANGED
@@ -13,9 +13,15 @@ export * from "./deriveCardOperationalObservations.js";
13
13
  export * from "./deriveCardDisplaySummary.js";
14
14
  export * from "./scanNarrativeFacts.js";
15
15
  export * from "./repoIntelligenceSchema.js";
16
+ export * from "./repoIntelligenceMappers.js";
17
+ export * from "./repoIntelligenceValidation.js";
16
18
  export * from "./deriveScanNarrative.js";
17
19
  export * from "./extractRepositoryContextFacts.js";
18
20
  export * from "./presentScanCardSummary.js";
21
+ export * from "./narrativePresentation.js";
22
+ export * from "./normalizeGeneratedText.js";
23
+ export * from "./collectNarrativeWhyBullets.js";
24
+ export * from "./presentGitHubPrComment.js";
19
25
  export * from "./scanCardSummary.js";
20
26
  export * from "./deriveScanDetailRecommendations.js";
21
27
  export * from "./deriveRiskScoreSummary.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,oCAAoC,CAAC;AACnD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,sCAAsC,CAAC;AACrD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,0BAA0B,CAAC;AACzC,cAAc,oCAAoC,CAAC;AACnD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,sCAAsC,CAAC;AACrD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -13,9 +13,15 @@ export * from "./deriveCardOperationalObservations.js";
13
13
  export * from "./deriveCardDisplaySummary.js";
14
14
  export * from "./scanNarrativeFacts.js";
15
15
  export * from "./repoIntelligenceSchema.js";
16
+ export * from "./repoIntelligenceMappers.js";
17
+ export * from "./repoIntelligenceValidation.js";
16
18
  export * from "./deriveScanNarrative.js";
17
19
  export * from "./extractRepositoryContextFacts.js";
18
20
  export * from "./presentScanCardSummary.js";
21
+ export * from "./narrativePresentation.js";
22
+ export * from "./normalizeGeneratedText.js";
23
+ export * from "./collectNarrativeWhyBullets.js";
24
+ export * from "./presentGitHubPrComment.js";
19
25
  export * from "./scanCardSummary.js";
20
26
  export * from "./deriveScanDetailRecommendations.js";
21
27
  export * from "./deriveRiskScoreSummary.js";
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=narrativeParity.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"narrativeParity.test.d.ts","sourceRoot":"","sources":["../src/narrativeParity.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,65 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { deriveScanNarrative } from "./deriveScanNarrative.js";
3
+ import { buildPrCheckRunSummaryMarkdown } from "./prCheckRunPresentation.js";
4
+ import { deriveScanCardSummary } from "./scanCardSummary.js";
5
+ import { deriveScanDetailViewModel } from "./scanDetailViewModel.js";
6
+ import { presentGitHubPrComment } from "./presentGitHubPrComment.js";
7
+ import { fixtureRepoIntelligenceFastify } from "./fixtures/repoIntelligenceFixtures.js";
8
+ import { analysisPreparationWithValidRepoIntel } from "./fixtures/repoIntelligenceTestHelpers.js";
9
+ const ORIGIN = "https://app.example.com";
10
+ const SCAN_ID = "22222222-2222-4222-8222-222222222222";
11
+ describe("narrative parity across consumers", () => {
12
+ it("card, detail, GitHub share primary package and reachability story", () => {
13
+ const result = {
14
+ totalScore: 55,
15
+ layerScores: {
16
+ security: 10,
17
+ maintainability: 20,
18
+ ecosystem: 30,
19
+ upgradeImpact: 15,
20
+ },
21
+ findings: [],
22
+ generatedAt: "2026-01-01T00:00:00.000Z",
23
+ changedPackages: ["fastify"],
24
+ analysisPreparation: analysisPreparationWithValidRepoIntel(),
25
+ repoIntelligence: fixtureRepoIntelligenceFastify,
26
+ decision: {
27
+ recommendation: "needs_review",
28
+ confidence: "medium",
29
+ reasoning: [],
30
+ },
31
+ insights: [
32
+ {
33
+ type: "usage_risk",
34
+ priority: "high",
35
+ confidence: "confirmed",
36
+ scope: "changed",
37
+ message: "Review fastify middleware ordering",
38
+ context: "apps/api/src/middleware/auth.ts",
39
+ remediation: "Run auth smoke tests",
40
+ },
41
+ ],
42
+ };
43
+ const card = deriveScanCardSummary(result, "done");
44
+ const detail = deriveScanDetailViewModel(result, {
45
+ scanId: SCAN_ID,
46
+ status: "done",
47
+ });
48
+ const checkRun = buildPrCheckRunSummaryMarkdown({
49
+ result,
50
+ scanId: SCAN_ID,
51
+ webAppOrigin: ORIGIN,
52
+ baseline: false,
53
+ });
54
+ const prComment = presentGitHubPrComment(deriveScanNarrative(result), result);
55
+ expect(card.changedPackagesDisplay).toContain("fastify");
56
+ expect(detail?.narrativeContext.changedPackagesDisplay).toContain("fastify");
57
+ expect(card.reachabilityLabel).toBe("On runtime paths");
58
+ expect(detail?.narrativeContext.reachabilityLabel).toBe("On runtime paths");
59
+ const asciiOnly = /^[\x00-\x7F]*$/;
60
+ expect(asciiOnly.test(checkRun)).toBe(true);
61
+ expect(asciiOnly.test(prComment)).toBe(true);
62
+ expect(checkRun.toLowerCase()).toContain("fastify");
63
+ expect(prComment.toLowerCase()).toContain("fastify");
64
+ });
65
+ });
@@ -0,0 +1,50 @@
1
+ import type { ScanNarrativeFacts } from "./scanNarrativeFacts.js";
2
+ export type SummarizePackageUsageOptions = {
3
+ maxPaths?: number;
4
+ maxPackages?: number;
5
+ };
6
+ export type PackageUsageSummary = {
7
+ pathCount: number;
8
+ pathSamples: string[];
9
+ areaLabels: string[];
10
+ packageNames: string[];
11
+ };
12
+ export type ReachabilitySummary = {
13
+ kindLabel: string | null;
14
+ pathSamples: string[];
15
+ frameworks: string[];
16
+ };
17
+ export type BlastRadiusSummary = {
18
+ levelLabel: string | null;
19
+ factors: string[];
20
+ changedPackageCount: number | null;
21
+ };
22
+ export declare function selectPrimaryChangedPackage(facts: ScanNarrativeFacts): string | null;
23
+ export declare function formatChangedPackagesShort(facts: ScanNarrativeFacts, maxNames?: number): string | null;
24
+ export declare function formatChangedPackagesDetail(facts: ScanNarrativeFacts): string[];
25
+ export declare function summarizePackageUsage(facts: ScanNarrativeFacts, options?: SummarizePackageUsageOptions): PackageUsageSummary | null;
26
+ declare function labelRuntimeSurface(facts: ScanNarrativeFacts): string | null;
27
+ declare function labelReachabilityKind(facts: ScanNarrativeFacts): string | null;
28
+ declare function labelBlastRadiusLevel(facts: ScanNarrativeFacts): string | null;
29
+ export declare function summarizeReachability(facts: ScanNarrativeFacts, maxPaths?: number): ReachabilitySummary;
30
+ export declare function summarizeBlastRadius(facts: ScanNarrativeFacts, maxFactors?: number): BlastRadiusSummary;
31
+ export declare function summarizeHotspots(facts: ScanNarrativeFacts, max?: number): Array<{
32
+ packageName: string;
33
+ source: "code" | "graph";
34
+ pathSample: string | null;
35
+ }>;
36
+ export type SelectReviewerGuidanceOptions = {
37
+ scope?: "changed" | "all" | "any";
38
+ max?: number;
39
+ };
40
+ export declare function selectReviewerGuidance(facts: ScanNarrativeFacts, options?: SelectReviewerGuidanceOptions): ScanNarrativeFacts["reviewerGuidance"];
41
+ export declare function composeVerificationPrompt(facts: ScanNarrativeFacts): string | null;
42
+ export declare function formatUsageSummaryLine(facts: ScanNarrativeFacts, maxPaths?: number): string | null;
43
+ export declare function formatBlastRadiusDetailLine(facts: ScanNarrativeFacts, maxFactors?: number): string | null;
44
+ export declare function formatFrameworksSummary(facts: ScanNarrativeFacts, max?: number): string | null;
45
+ export declare function composeContextLineFromFacts(facts: ScanNarrativeFacts, options?: {
46
+ includePathSample?: boolean;
47
+ maxAreas?: number;
48
+ }): string | null;
49
+ export { labelRuntimeSurface, labelReachabilityKind, labelBlastRadiusLevel };
50
+ //# sourceMappingURL=narrativePresentation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"narrativePresentation.d.ts","sourceRoot":"","sources":["../src/narrativePresentation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,180 @@
1
+ import { scanSurfaceCopy } from "./scanSurfaceCopy.js";
2
+ export function selectPrimaryChangedPackage(facts) {
3
+ return facts.changedPackages.primary;
4
+ }
5
+ export function formatChangedPackagesShort(facts, maxNames = 2) {
6
+ const { primary, others } = facts.changedPackages;
7
+ if (!primary)
8
+ return null;
9
+ if (others.length === 0)
10
+ return primary;
11
+ if (maxNames >= 2 && others.length === 1) {
12
+ return `${primary}, ${others[0]}`;
13
+ }
14
+ return `${primary} +${others.length}`;
15
+ }
16
+ export function formatChangedPackagesDetail(facts) {
17
+ return facts.changedPackages.all;
18
+ }
19
+ export function summarizePackageUsage(facts, options = {}) {
20
+ const maxPaths = options.maxPaths ?? 3;
21
+ const maxPackages = options.maxPackages ?? 5;
22
+ const rows = facts.packageUsage.slice(0, maxPackages);
23
+ if (rows.length === 0)
24
+ return null;
25
+ const pathSamples = [];
26
+ let pathCount = 0;
27
+ const areaLabels = [];
28
+ const packageNames = [];
29
+ for (const row of rows) {
30
+ packageNames.push(row.packageName);
31
+ const rowPaths = [...row.paths, ...row.criticalPaths, ...row.files].filter(Boolean);
32
+ pathCount += rowPaths.length;
33
+ for (const p of rowPaths) {
34
+ if (pathSamples.length >= maxPaths)
35
+ break;
36
+ if (!pathSamples.includes(p))
37
+ pathSamples.push(p);
38
+ }
39
+ for (const a of row.areas) {
40
+ if (!areaLabels.includes(a))
41
+ areaLabels.push(a);
42
+ }
43
+ }
44
+ if (pathCount === 0 && areaLabels.length === 0)
45
+ return null;
46
+ return { pathCount, pathSamples, areaLabels, packageNames };
47
+ }
48
+ function labelRuntimeSurface(facts) {
49
+ const rs = facts.runtimeSurface;
50
+ if (!rs)
51
+ return null;
52
+ return scanSurfaceCopy.narrativeCard.runtimeSurface[rs.kind];
53
+ }
54
+ function labelReachabilityKind(facts) {
55
+ const r = facts.reachability;
56
+ if (!r)
57
+ return null;
58
+ return scanSurfaceCopy.narrativeCard.reachability[r.kind];
59
+ }
60
+ function labelBlastRadiusLevel(facts) {
61
+ const br = facts.blastRadius;
62
+ if (!br)
63
+ return null;
64
+ return scanSurfaceCopy.narrativeCard.blastRadius[br.level];
65
+ }
66
+ export function summarizeReachability(facts, maxPaths = 1) {
67
+ const kindLabel = labelReachabilityKind(facts);
68
+ const paths = facts.reachability?.evidence.paths ?? [];
69
+ const frameworks = facts.reachability?.evidence.frameworks ?? facts.frameworks;
70
+ return {
71
+ kindLabel,
72
+ pathSamples: paths.slice(0, maxPaths),
73
+ frameworks: frameworks.slice(0, 3),
74
+ };
75
+ }
76
+ export function summarizeBlastRadius(facts, maxFactors = 3) {
77
+ const br = facts.blastRadius;
78
+ return {
79
+ levelLabel: labelBlastRadiusLevel(facts),
80
+ factors: (br?.factors ?? []).slice(0, maxFactors),
81
+ changedPackageCount: br?.changedPackageCount ?? null,
82
+ };
83
+ }
84
+ export function summarizeHotspots(facts, max = 5) {
85
+ return facts.hotspots.slice(0, max).map((h) => ({
86
+ packageName: h.packageName,
87
+ source: h.source,
88
+ pathSample: h.paths[0] ?? null,
89
+ }));
90
+ }
91
+ export function selectReviewerGuidance(facts, options = {}) {
92
+ const max = options.max ?? 6;
93
+ let list = facts.reviewerGuidance;
94
+ if (options.scope === "changed") {
95
+ list = list.filter((g) => g.scope === "changed");
96
+ }
97
+ else if (options.scope === "all") {
98
+ list = list.filter((g) => g.scope === "all");
99
+ }
100
+ return list.slice(0, max);
101
+ }
102
+ export function composeVerificationPrompt(facts) {
103
+ const changed = selectReviewerGuidance(facts, { scope: "changed", max: 1 });
104
+ const remediation = changed[0]?.remediation?.trim();
105
+ if (remediation)
106
+ return remediation;
107
+ const usage = summarizePackageUsage(facts, { maxPaths: 1 });
108
+ if (usage?.pathSamples[0]) {
109
+ return `Confirm behavior where this dependency is used (${usage.pathSamples[0]}).`;
110
+ }
111
+ const any = selectReviewerGuidance(facts, { max: 1 });
112
+ return any[0]?.remediation?.trim() ?? null;
113
+ }
114
+ export function formatUsageSummaryLine(facts, maxPaths = 1) {
115
+ const usage = summarizePackageUsage(facts, { maxPaths });
116
+ if (!usage)
117
+ return null;
118
+ if (usage.pathSamples.length > 0) {
119
+ const sample = usage.pathSamples[0];
120
+ if (usage.pathCount <= 1) {
121
+ return `Used in ${sample}`;
122
+ }
123
+ return `Used in ${usage.pathCount} paths (${sample})`;
124
+ }
125
+ if (usage.areaLabels.length > 0) {
126
+ return `Used in ${usage.areaLabels.slice(0, 2).join(", ")}`;
127
+ }
128
+ return null;
129
+ }
130
+ export function formatBlastRadiusDetailLine(facts, maxFactors = 1) {
131
+ const summary = summarizeBlastRadius(facts, maxFactors);
132
+ if (summary.factors.length === 0)
133
+ return null;
134
+ const factor = summary.factors[0].replace(/_/g, " ");
135
+ if (summary.levelLabel) {
136
+ return `${summary.levelLabel}: ${factor}`;
137
+ }
138
+ return factor;
139
+ }
140
+ export function formatFrameworksSummary(facts, max = 2) {
141
+ const frameworks = facts.frameworks.slice(0, max);
142
+ if (frameworks.length === 0)
143
+ return null;
144
+ if (frameworks.length === 1)
145
+ return frameworks[0];
146
+ return frameworks.join(", ");
147
+ }
148
+ export function composeContextLineFromFacts(facts, options = {}) {
149
+ const parts = [];
150
+ const runtime = labelRuntimeSurface(facts);
151
+ const reach = summarizeReachability(facts, options.includePathSample ? 1 : 0);
152
+ const blast = labelBlastRadiusLevel(facts);
153
+ if (runtime)
154
+ parts.push(runtime);
155
+ if (reach.kindLabel) {
156
+ if (options.includePathSample && reach.pathSamples[0]) {
157
+ parts.push(`${reach.kindLabel} (${reach.pathSamples[0]})`);
158
+ }
159
+ else {
160
+ parts.push(reach.kindLabel);
161
+ }
162
+ }
163
+ if (blast)
164
+ parts.push(blast);
165
+ const maxAreas = options.maxAreas ?? 2;
166
+ const areaLabels = [];
167
+ for (const area of facts.affectedAreas) {
168
+ if (areaLabels.length >= maxAreas)
169
+ break;
170
+ if (!areaLabels.includes(area.label))
171
+ areaLabels.push(area.label);
172
+ }
173
+ if (areaLabels.length > 0) {
174
+ parts.push(areaLabels.join(scanSurfaceCopy.narrativeCard.areasSeparator));
175
+ }
176
+ if (parts.length === 0)
177
+ return null;
178
+ return parts.join(scanSurfaceCopy.narrativeCard.contextSeparator);
179
+ }
180
+ export { labelRuntimeSurface, labelReachabilityKind, labelBlastRadiusLevel };