@mergesignal/shared 0.2.8 → 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 (43) hide show
  1. package/dist/deriveScanNarrative.d.ts +0 -3
  2. package/dist/deriveScanNarrative.d.ts.map +1 -1
  3. package/dist/deriveScanNarrative.js +29 -18
  4. package/dist/deriveScanNarrative.test.js +37 -8
  5. package/dist/engineWireRegression.test.d.ts +2 -0
  6. package/dist/engineWireRegression.test.d.ts.map +1 -0
  7. package/dist/engineWireRegression.test.js +57 -0
  8. package/dist/fixtures/legacyEngineRepoIntelligence.d.ts +28 -0
  9. package/dist/fixtures/legacyEngineRepoIntelligence.d.ts.map +1 -0
  10. package/dist/fixtures/legacyEngineRepoIntelligence.js +28 -0
  11. package/dist/fixtures/repoIntelligenceFixtures.d.ts +0 -2
  12. package/dist/fixtures/repoIntelligenceFixtures.d.ts.map +1 -1
  13. package/dist/fixtures/repoIntelligenceFixtures.js +14 -4
  14. package/dist/fixtures/repoIntelligenceTestHelpers.d.ts +5 -0
  15. package/dist/fixtures/repoIntelligenceTestHelpers.d.ts.map +1 -0
  16. package/dist/fixtures/repoIntelligenceTestHelpers.js +28 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -0
  20. package/dist/narrativeParity.test.js +2 -1
  21. package/dist/narrativePresentation.test.js +2 -1
  22. package/dist/repoIntelligenceMappers.d.ts +20 -0
  23. package/dist/repoIntelligenceMappers.d.ts.map +1 -0
  24. package/dist/repoIntelligenceMappers.js +60 -0
  25. package/dist/repoIntelligenceSchema.contract.test.d.ts +2 -0
  26. package/dist/repoIntelligenceSchema.contract.test.d.ts.map +1 -0
  27. package/dist/repoIntelligenceSchema.contract.test.js +19 -0
  28. package/dist/repoIntelligenceSchema.d.ts +52 -83
  29. package/dist/repoIntelligenceSchema.d.ts.map +1 -1
  30. package/dist/repoIntelligenceSchema.js +99 -141
  31. package/dist/repoIntelligenceValidation.d.ts +18 -0
  32. package/dist/repoIntelligenceValidation.d.ts.map +1 -0
  33. package/dist/repoIntelligenceValidation.js +76 -0
  34. package/dist/repoIntelligenceValidation.persist.test.d.ts +2 -0
  35. package/dist/repoIntelligenceValidation.persist.test.d.ts.map +1 -0
  36. package/dist/repoIntelligenceValidation.persist.test.js +44 -0
  37. package/dist/scanDetailViewModel.test.js +2 -1
  38. package/dist/scanNarrativeFacts.d.ts +2 -0
  39. package/dist/scanNarrativeFacts.d.ts.map +1 -1
  40. package/dist/scanNarrativeFacts.js +1 -0
  41. package/dist/types.d.ts +15 -3
  42. package/dist/types.d.ts.map +1 -1
  43. package/package.json +1 -1
@@ -1,106 +1,64 @@
1
+ /**
2
+ * Canonical `ScanResult.repoIntelligence` wire contract (Wave 1 + Wave 2).
3
+ *
4
+ * Bump {@link REPO_INTELLIGENCE_ABI} when this schema changes incompatibly.
5
+ * Engine maps internal signals to this shape at emit time; consumers must use
6
+ * {@link safeParseRepoIntelligence} and honor `analysisPreparation.repoIntelligenceValidation`.
7
+ */
1
8
  import { z } from "zod";
2
- import type { BlastRadiusLevel, ReachabilityKind, RuntimeSurfaceKind } from "./scanNarrativeFacts.js";
3
- declare const looseSurfaceSchema: z.ZodUnion<readonly [z.ZodEnum<{
4
- unknown: "unknown";
5
- runtime: "runtime";
6
- build: "build";
7
- test: "test";
8
- }>, z.ZodString, z.ZodObject<{
9
- kind: z.ZodString;
10
- }, z.core.$loose>]>;
11
- declare const looseReachabilitySchema: z.ZodUnion<readonly [z.ZodEnum<{
12
- unknown: "unknown";
13
- on_runtime_paths: "on_runtime_paths";
14
- build_only: "build_only";
15
- test_only: "test_only";
16
- unreachable: "unreachable";
17
- }>, z.ZodString, z.ZodObject<{
18
- kind: z.ZodString;
19
- }, z.core.$loose>]>;
20
- export declare const repoIntelligenceSchema: z.ZodObject<{
21
- packages: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
22
- runtimeSurface: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
9
+ import type { ScanResult } from "./types.js";
10
+ import type { BlastRadiusLevel } from "./scanNarrativeFacts.js";
11
+ /** Bump when wire schema changes incompatibly. */
12
+ export declare const REPO_INTELLIGENCE_ABI: "1";
13
+ /** Strict canonical wire schema — single source of truth. */
14
+ export declare const repoIntelligenceWireSchema: z.ZodObject<{
15
+ packages: z.ZodRecord<z.ZodString, z.ZodObject<{
16
+ runtimeSurface: z.ZodEnum<{
23
17
  unknown: "unknown";
24
18
  runtime: "runtime";
25
19
  build: "build";
26
20
  test: "test";
27
- }>, z.ZodString, z.ZodObject<{
28
- kind: z.ZodString;
29
- }, z.core.$loose>]>>;
30
- reachability: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
21
+ }>;
22
+ reachability: z.ZodEnum<{
31
23
  unknown: "unknown";
32
24
  on_runtime_paths: "on_runtime_paths";
33
25
  build_only: "build_only";
34
26
  test_only: "test_only";
35
27
  unreachable: "unreachable";
36
- }>, z.ZodString, z.ZodObject<{
37
- kind: z.ZodString;
38
- }, z.core.$loose>]>>;
39
- packageUsage: z.ZodOptional<z.ZodObject<{
40
- packageName: z.ZodOptional<z.ZodString>;
41
- package: z.ZodOptional<z.ZodString>;
42
- files: z.ZodOptional<z.ZodArray<z.ZodString>>;
28
+ }>;
29
+ usage: z.ZodObject<{
30
+ packageName: z.ZodString;
31
+ files: z.ZodArray<z.ZodString>;
43
32
  paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
44
33
  criticalPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
45
34
  areas: z.ZodOptional<z.ZodArray<z.ZodString>>;
46
- }, z.core.$loose>>;
47
- usage: z.ZodOptional<z.ZodObject<{
48
- packageName: z.ZodOptional<z.ZodString>;
49
- package: z.ZodOptional<z.ZodString>;
50
- files: z.ZodOptional<z.ZodArray<z.ZodString>>;
51
- paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
52
- criticalPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
53
- areas: z.ZodOptional<z.ZodArray<z.ZodString>>;
54
- }, z.core.$loose>>;
35
+ }, z.core.$strip>;
55
36
  areas: z.ZodOptional<z.ZodArray<z.ZodString>>;
56
- hotspots: z.ZodOptional<z.ZodArray<z.ZodObject<{
57
- packageName: z.ZodOptional<z.ZodString>;
58
- paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
59
- depth: z.ZodOptional<z.ZodNumber>;
60
- }, z.core.$loose>>>;
61
- }, z.core.$loose>>>;
37
+ }, z.core.$strip>>;
62
38
  packageUsage: z.ZodOptional<z.ZodArray<z.ZodObject<{
63
- packageName: z.ZodOptional<z.ZodString>;
64
- package: z.ZodOptional<z.ZodString>;
65
- files: z.ZodOptional<z.ZodArray<z.ZodString>>;
39
+ packageName: z.ZodString;
40
+ files: z.ZodArray<z.ZodString>;
66
41
  paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
67
42
  criticalPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
68
43
  areas: z.ZodOptional<z.ZodArray<z.ZodString>>;
69
- }, z.core.$loose>>>;
44
+ }, z.core.$strip>>>;
70
45
  blastRadius: z.ZodOptional<z.ZodObject<{
71
- level: z.ZodOptional<z.ZodEnum<{
46
+ level: z.ZodEnum<{
72
47
  moderate: "moderate";
73
48
  narrow: "narrow";
74
49
  wide: "wide";
75
- }>>;
50
+ }>;
76
51
  factors: z.ZodOptional<z.ZodArray<z.ZodString>>;
77
52
  changedPackageCount: z.ZodOptional<z.ZodNumber>;
78
- }, z.core.$loose>>;
79
- runtimeSurface: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
80
- unknown: "unknown";
81
- runtime: "runtime";
82
- build: "build";
83
- test: "test";
84
- }>, z.ZodString, z.ZodObject<{
85
- kind: z.ZodString;
86
- }, z.core.$loose>]>>;
87
- reachability: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
88
- unknown: "unknown";
89
- on_runtime_paths: "on_runtime_paths";
90
- build_only: "build_only";
91
- test_only: "test_only";
92
- unreachable: "unreachable";
93
- }>, z.ZodString, z.ZodObject<{
94
- kind: z.ZodString;
95
- }, z.core.$loose>]>>;
53
+ }, z.core.$strip>>;
96
54
  applicationAreas: z.ZodOptional<z.ZodArray<z.ZodObject<{
97
55
  id: z.ZodString;
98
56
  label: z.ZodString;
99
- }, z.core.$loose>>>;
57
+ }, z.core.$strip>>>;
100
58
  affectedAreas: z.ZodOptional<z.ZodArray<z.ZodObject<{
101
59
  id: z.ZodString;
102
60
  label: z.ZodString;
103
- }, z.core.$loose>>>;
61
+ }, z.core.$strip>>>;
104
62
  hotspots: z.ZodOptional<z.ZodArray<z.ZodObject<{
105
63
  packageName: z.ZodString;
106
64
  source: z.ZodOptional<z.ZodEnum<{
@@ -108,16 +66,27 @@ export declare const repoIntelligenceSchema: z.ZodObject<{
108
66
  graph: "graph";
109
67
  }>>;
110
68
  depth: z.ZodOptional<z.ZodNumber>;
111
- paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
112
- }, z.core.$loose>>>;
69
+ paths: z.ZodArray<z.ZodString>;
70
+ }, z.core.$strip>>>;
113
71
  frameworks: z.ZodOptional<z.ZodArray<z.ZodString>>;
114
- }, z.core.$loose>;
115
- export type RepoIntelligence = z.infer<typeof repoIntelligenceSchema>;
116
- export declare function parseRepoIntelligence(raw: unknown): RepoIntelligence | null;
117
- export declare function normalizeRuntimeSurfaceKind(raw: string | undefined): RuntimeSurfaceKind;
118
- export declare function normalizeReachabilityKind(raw: string | undefined): ReachabilityKind;
119
- export declare function readSurfaceFromLoose(value: z.infer<typeof looseSurfaceSchema> | undefined): RuntimeSurfaceKind | null;
120
- export declare function readReachabilityFromLoose(value: z.infer<typeof looseReachabilitySchema> | undefined): ReachabilityKind | null;
72
+ }, z.core.$strip>;
73
+ export type RepoIntelligence = z.infer<typeof repoIntelligenceWireSchema>;
74
+ export type ParseRepoIntelligenceSuccess = {
75
+ ok: true;
76
+ value: RepoIntelligence;
77
+ };
78
+ export type ParseRepoIntelligenceFailure = {
79
+ ok: false;
80
+ issues: string[];
81
+ issueCount: number;
82
+ packageCount: number;
83
+ };
84
+ export type ParseRepoIntelligenceResult = ParseRepoIntelligenceSuccess | ParseRepoIntelligenceFailure;
85
+ export declare function countRepoIntelligencePackages(raw: unknown): number;
86
+ export declare function safeParseRepoIntelligence(raw: unknown): ParseRepoIntelligenceResult;
87
+ /** Throws when wire block is present but invalid (CI / tests only). */
88
+ export declare function validateRepoIntelligenceWire(raw: unknown): RepoIntelligence;
89
+ /** CI helper — fails test when engine output includes invalid repoIntelligence. */
90
+ export declare function assertEngineOutputRepoIntelligenceValid(scan: ScanResult): void;
121
91
  export declare function readBlastLevel(level: BlastRadiusLevel | undefined): BlastRadiusLevel | null;
122
- export {};
123
92
  //# sourceMappingURL=repoIntelligenceSchema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"repoIntelligenceSchema.d.ts","sourceRoot":"","sources":["../src/repoIntelligenceSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,yBAAyB,CAAC;AAcjC,QAAA,MAAM,kBAAkB;;;;;;;mBAItB,CAAC;AAEH,QAAA,MAAM,uBAAuB;;;;;;;;mBAI3B,CAAC;AAyCH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8BnB,CAAC;AAEjB,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAc3E;AAuBD,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,kBAAkB,CAUpB;AAED,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,gBAAgB,CAgBlB;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,GAAG,SAAS,GACpD,kBAAkB,GAAG,IAAI,CAO3B;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,GAAG,SAAS,GACzD,gBAAgB,GAAG,IAAI,CAOzB;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,gBAAgB,GAAG,SAAS,GAClC,gBAAgB,GAAG,IAAI,CAKzB"}
1
+ {"version":3,"file":"repoIntelligenceSchema.d.ts","sourceRoot":"","sources":["../src/repoIntelligenceSchema.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,kDAAkD;AAClD,eAAO,MAAM,qBAAqB,EAAG,GAAY,CAAC;AA+ClD,6DAA6D;AAC7D,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAQrC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE1E,MAAM,MAAM,4BAA4B,GAAG;IACzC,EAAE,EAAE,IAAI,CAAC;IACT,KAAK,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GACnC,4BAA4B,GAC5B,4BAA4B,CAAC;AAQjC,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAUlE;AAED,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,OAAO,GACX,2BAA2B,CAyC7B;AAED,uEAAuE;AACvE,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAM3E;AAED,mFAAmF;AACnF,wBAAgB,uCAAuC,CACrD,IAAI,EAAE,UAAU,GACf,IAAI,CAGN;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,gBAAgB,GAAG,SAAS,GAClC,gBAAgB,GAAG,IAAI,CAKzB"}
@@ -1,4 +1,13 @@
1
+ /**
2
+ * Canonical `ScanResult.repoIntelligence` wire contract (Wave 1 + Wave 2).
3
+ *
4
+ * Bump {@link REPO_INTELLIGENCE_ABI} when this schema changes incompatibly.
5
+ * Engine maps internal signals to this shape at emit time; consumers must use
6
+ * {@link safeParseRepoIntelligence} and honor `analysisPreparation.repoIntelligenceValidation`.
7
+ */
1
8
  import { z } from "zod";
9
+ /** Bump when wire schema changes incompatibly. */
10
+ export const REPO_INTELLIGENCE_ABI = "1";
2
11
  const surfaceKindSchema = z.enum(["runtime", "build", "test", "unknown"]);
3
12
  const reachabilityKindSchema = z.enum([
4
13
  "on_runtime_paths",
@@ -8,163 +17,112 @@ const reachabilityKindSchema = z.enum([
8
17
  "unknown",
9
18
  ]);
10
19
  const blastLevelSchema = z.enum(["narrow", "moderate", "wide"]);
11
- const looseSurfaceSchema = z.union([
12
- surfaceKindSchema,
13
- z.string(),
14
- z.object({ kind: z.string() }).passthrough(),
15
- ]);
16
- const looseReachabilitySchema = z.union([
17
- reachabilityKindSchema,
18
- z.string(),
19
- z.object({ kind: z.string() }).passthrough(),
20
- ]);
21
- const packageUsageEntrySchema = z
22
- .object({
23
- packageName: z.string().optional(),
24
- package: z.string().optional(),
25
- files: z.array(z.string()).optional(),
20
+ const packageUsageWireSchema = z.object({
21
+ packageName: z.string().min(1),
22
+ files: z.array(z.string()),
26
23
  paths: z.array(z.string()).optional(),
27
24
  criticalPaths: z.array(z.string()).optional(),
28
25
  areas: z.array(z.string()).optional(),
29
- })
30
- .passthrough();
31
- const packageIntelSchema = z
32
- .object({
33
- runtimeSurface: looseSurfaceSchema.optional(),
34
- reachability: looseReachabilitySchema.optional(),
35
- packageUsage: packageUsageEntrySchema.optional(),
36
- usage: packageUsageEntrySchema.optional(),
26
+ });
27
+ const packageIntelWireSchema = z.object({
28
+ runtimeSurface: surfaceKindSchema,
29
+ reachability: reachabilityKindSchema,
30
+ usage: packageUsageWireSchema,
37
31
  areas: z.array(z.string()).optional(),
38
- hotspots: z
39
- .array(z
40
- .object({
41
- packageName: z.string().optional(),
42
- paths: z.array(z.string()).optional(),
43
- depth: z.number().optional(),
44
- })
45
- .passthrough())
46
- .optional(),
47
- })
48
- .passthrough();
49
- const areaSchema = z
50
- .object({
51
- id: z.string(),
52
- label: z.string(),
53
- })
54
- .passthrough();
55
- export const repoIntelligenceSchema = z
56
- .object({
57
- packages: z.record(z.string(), packageIntelSchema).optional(),
58
- packageUsage: z.array(packageUsageEntrySchema).optional(),
59
- blastRadius: z
60
- .object({
61
- level: blastLevelSchema.optional(),
62
- factors: z.array(z.string()).optional(),
63
- changedPackageCount: z.number().optional(),
64
- })
65
- .passthrough()
66
- .optional(),
67
- runtimeSurface: looseSurfaceSchema.optional(),
68
- reachability: looseReachabilitySchema.optional(),
32
+ });
33
+ const areaSchema = z.object({
34
+ id: z.string().min(1),
35
+ label: z.string().min(1),
36
+ });
37
+ const hotspotWireSchema = z.object({
38
+ packageName: z.string().min(1),
39
+ source: z.enum(["code", "graph"]).optional(),
40
+ depth: z.number().optional(),
41
+ paths: z.array(z.string()),
42
+ });
43
+ const blastRadiusWireSchema = z.object({
44
+ level: blastLevelSchema,
45
+ factors: z.array(z.string()).optional(),
46
+ changedPackageCount: z.number().int().nonnegative().optional(),
47
+ });
48
+ /** Strict canonical wire schema — single source of truth. */
49
+ export const repoIntelligenceWireSchema = z.object({
50
+ packages: z.record(z.string(), packageIntelWireSchema),
51
+ packageUsage: z.array(packageUsageWireSchema).optional(),
52
+ blastRadius: blastRadiusWireSchema.optional(),
69
53
  applicationAreas: z.array(areaSchema).optional(),
70
54
  affectedAreas: z.array(areaSchema).optional(),
71
- hotspots: z
72
- .array(z
73
- .object({
74
- packageName: z.string(),
75
- source: z.enum(["code", "graph"]).optional(),
76
- depth: z.number().optional(),
77
- paths: z.array(z.string()).optional(),
78
- })
79
- .passthrough())
80
- .optional(),
81
- frameworks: z.array(z.string()).optional(),
82
- })
83
- .passthrough();
84
- export function parseRepoIntelligence(raw) {
55
+ hotspots: z.array(hotspotWireSchema).optional(),
56
+ frameworks: z.array(z.string().min(1)).optional(),
57
+ });
58
+ function formatZodIssues(error) {
59
+ return error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
60
+ }
61
+ export function countRepoIntelligencePackages(raw) {
85
62
  if (raw == null || typeof raw !== "object")
86
- return null;
87
- const parsed = repoIntelligenceSchema.safeParse(raw);
88
- if (!parsed.success)
89
- return null;
90
- const value = parsed.data;
91
- if (Object.keys(value).length === 0)
92
- return null;
93
- if (Object.keys(value).length === 1 &&
94
- value.packages &&
95
- Object.keys(value.packages).length === 0) {
96
- return null;
63
+ return 0;
64
+ const obj = raw;
65
+ if (obj.packages != null && typeof obj.packages === "object") {
66
+ return Object.keys(obj.packages).length;
67
+ }
68
+ if (Array.isArray(obj.packageUsage)) {
69
+ return obj.packageUsage.length;
97
70
  }
98
- return value;
71
+ return 0;
99
72
  }
100
- const RUNTIME_SURFACE_ALIASES = [
101
- [/\bruntime\b/i, "runtime"],
102
- [/\bproduction\b/i, "runtime"],
103
- [/\bapi\b/i, "runtime"],
104
- [/\bbuild\b/i, "build"],
105
- [/\bdev(?:elopment)?\b/i, "build"],
106
- [/\btypescript\b/i, "build"],
107
- [/\btest\b/i, "test"],
108
- [/\bvitest\b/i, "test"],
109
- [/\bjest\b/i, "test"],
110
- ];
111
- const REACHABILITY_ALIASES = [
112
- [/\bon[_\s-]?runtime/i, "on_runtime_paths"],
113
- [/\bruntime[_\s-]?path/i, "on_runtime_paths"],
114
- [/\bdirect\b/i, "on_runtime_paths"],
115
- [/\bbuild[_\s-]?only/i, "build_only"],
116
- [/\btest[_\s-]?only/i, "test_only"],
117
- [/\bunreachable\b/i, "unreachable"],
118
- ];
119
- export function normalizeRuntimeSurfaceKind(raw) {
120
- if (!raw?.trim())
121
- return "unknown";
122
- const t = raw.trim().toLowerCase();
123
- if (t === "runtime" || t === "build" || t === "test" || t === "unknown") {
124
- return t;
73
+ export function safeParseRepoIntelligence(raw) {
74
+ const packageCount = countRepoIntelligencePackages(raw);
75
+ if (raw == null || typeof raw !== "object") {
76
+ return {
77
+ ok: false,
78
+ issues: ["(root): expected object"],
79
+ issueCount: 1,
80
+ packageCount: 0,
81
+ };
125
82
  }
126
- for (const [re, kind] of RUNTIME_SURFACE_ALIASES) {
127
- if (re.test(t))
128
- return kind;
83
+ const parsed = repoIntelligenceWireSchema.safeParse(raw);
84
+ if (!parsed.success) {
85
+ const issues = formatZodIssues(parsed.error);
86
+ return {
87
+ ok: false,
88
+ issues,
89
+ issueCount: issues.length,
90
+ packageCount,
91
+ };
129
92
  }
130
- return "unknown";
131
- }
132
- export function normalizeReachabilityKind(raw) {
133
- if (!raw?.trim())
134
- return "unknown";
135
- const t = raw.trim().toLowerCase();
136
- if (t === "on_runtime_paths" ||
137
- t === "build_only" ||
138
- t === "test_only" ||
139
- t === "unreachable" ||
140
- t === "unknown") {
141
- return t;
93
+ const value = parsed.data;
94
+ if (Object.keys(value).length === 0) {
95
+ return {
96
+ ok: false,
97
+ issues: ["(root): empty object"],
98
+ issueCount: 1,
99
+ packageCount: 0,
100
+ };
142
101
  }
143
- for (const [re, kind] of REACHABILITY_ALIASES) {
144
- if (re.test(t))
145
- return kind;
102
+ if (Object.keys(value.packages).length === 0 &&
103
+ (value.packageUsage?.length ?? 0) === 0) {
104
+ return {
105
+ ok: false,
106
+ issues: ["packages: at least one package required"],
107
+ issueCount: 1,
108
+ packageCount: 0,
109
+ };
146
110
  }
147
- return "unknown";
111
+ return { ok: true, value };
148
112
  }
149
- export function readSurfaceFromLoose(value) {
150
- if (value == null)
151
- return null;
152
- if (typeof value === "string")
153
- return normalizeRuntimeSurfaceKind(value);
154
- if (typeof value === "object" && "kind" in value) {
155
- return normalizeRuntimeSurfaceKind(String(value.kind));
113
+ /** Throws when wire block is present but invalid (CI / tests only). */
114
+ export function validateRepoIntelligenceWire(raw) {
115
+ const r = safeParseRepoIntelligence(raw);
116
+ if (!r.ok) {
117
+ throw new Error(`repoIntelligence contract: ${r.issues.join("; ")}`);
156
118
  }
157
- return null;
119
+ return r.value;
158
120
  }
159
- export function readReachabilityFromLoose(value) {
160
- if (value == null)
161
- return null;
162
- if (typeof value === "string")
163
- return normalizeReachabilityKind(value);
164
- if (typeof value === "object" && "kind" in value) {
165
- return normalizeReachabilityKind(String(value.kind));
166
- }
167
- return null;
121
+ /** CI helper — fails test when engine output includes invalid repoIntelligence. */
122
+ export function assertEngineOutputRepoIntelligenceValid(scan) {
123
+ if (scan.repoIntelligence == null)
124
+ return;
125
+ validateRepoIntelligenceWire(scan.repoIntelligence);
168
126
  }
169
127
  export function readBlastLevel(level) {
170
128
  if (level === "narrow" || level === "moderate" || level === "wide") {
@@ -0,0 +1,18 @@
1
+ import type { ScanResult } from "./types.js";
2
+ export type RepoIntelligenceContractLogPayload = {
3
+ issueCount: number;
4
+ representativeIssues: string[];
5
+ packageCount: number;
6
+ methodologyVersion?: string;
7
+ };
8
+ /**
9
+ * Non-blocking production gate: validate repoIntelligence, attach metadata, retain raw payload.
10
+ */
11
+ export declare function applyRepoIntelligenceValidation(result: ScanResult, options?: {
12
+ validatedAt?: string;
13
+ }): {
14
+ result: ScanResult;
15
+ contractFailed: boolean;
16
+ logPayload?: RepoIntelligenceContractLogPayload;
17
+ };
18
+ //# sourceMappingURL=repoIntelligenceValidation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repoIntelligenceValidation.d.ts","sourceRoot":"","sources":["../src/repoIntelligenceValidation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,UAAU,EACX,MAAM,YAAY,CAAC;AAOpB,MAAM,MAAM,kCAAkC,GAAG;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAIF;;GAEG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACjC;IACD,MAAM,EAAE,UAAU,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,kCAAkC,CAAC;CACjD,CAqFA"}
@@ -0,0 +1,76 @@
1
+ import { REPO_INTELLIGENCE_ABI, countRepoIntelligencePackages, safeParseRepoIntelligence, } from "./repoIntelligenceSchema.js";
2
+ const MAX_REPRESENTATIVE_ISSUES = 5;
3
+ /**
4
+ * Non-blocking production gate: validate repoIntelligence, attach metadata, retain raw payload.
5
+ */
6
+ export function applyRepoIntelligenceValidation(result, options) {
7
+ const validatedAt = options?.validatedAt ?? new Date().toISOString();
8
+ const raw = result.repoIntelligence;
9
+ if (raw == null ||
10
+ (typeof raw === "object" && Object.keys(raw).length === 0)) {
11
+ const analysisPreparation = {
12
+ ...result.analysisPreparation,
13
+ codeIntelligenceAvailable: result.analysisPreparation?.codeIntelligenceAvailable ?? false,
14
+ warnings: result.analysisPreparation?.warnings ?? [],
15
+ repoIntelligenceValidation: {
16
+ status: "absent",
17
+ abi: REPO_INTELLIGENCE_ABI,
18
+ validatedAt,
19
+ },
20
+ };
21
+ return {
22
+ result: { ...result, analysisPreparation },
23
+ contractFailed: false,
24
+ };
25
+ }
26
+ const parsed = safeParseRepoIntelligence(raw);
27
+ const packageCount = countRepoIntelligencePackages(raw);
28
+ if (parsed.ok) {
29
+ const analysisPreparation = {
30
+ ...result.analysisPreparation,
31
+ codeIntelligenceAvailable: result.analysisPreparation?.codeIntelligenceAvailable ?? true,
32
+ warnings: result.analysisPreparation?.warnings ?? [],
33
+ repoIntelligenceValidation: {
34
+ status: "valid",
35
+ abi: REPO_INTELLIGENCE_ABI,
36
+ validatedAt,
37
+ },
38
+ };
39
+ return {
40
+ result: { ...result, analysisPreparation },
41
+ contractFailed: false,
42
+ };
43
+ }
44
+ const representativeIssues = parsed.issues.slice(0, MAX_REPRESENTATIVE_ISSUES);
45
+ const warning = {
46
+ code: "repo_intelligence_contract_invalid",
47
+ message: "Repository intelligence block failed contract validation; narrative uses graph fallback.",
48
+ details: {
49
+ issueCount: parsed.issueCount,
50
+ representativeIssues,
51
+ abi: REPO_INTELLIGENCE_ABI,
52
+ },
53
+ };
54
+ const analysisPreparation = {
55
+ ...result.analysisPreparation,
56
+ codeIntelligenceAvailable: result.analysisPreparation?.codeIntelligenceAvailable ?? true,
57
+ warnings: [...(result.analysisPreparation?.warnings ?? []), warning],
58
+ repoIntelligenceValidation: {
59
+ status: "invalid",
60
+ abi: REPO_INTELLIGENCE_ABI,
61
+ issueCount: parsed.issueCount,
62
+ representativeIssues,
63
+ validatedAt,
64
+ },
65
+ };
66
+ return {
67
+ result: { ...result, repoIntelligence: raw, analysisPreparation },
68
+ contractFailed: true,
69
+ logPayload: {
70
+ issueCount: parsed.issueCount,
71
+ representativeIssues,
72
+ packageCount,
73
+ methodologyVersion: result.methodologyVersion,
74
+ },
75
+ };
76
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=repoIntelligenceValidation.persist.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repoIntelligenceValidation.persist.test.d.ts","sourceRoot":"","sources":["../src/repoIntelligenceValidation.persist.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { deriveScanNarrative } from "./deriveScanNarrative.js";
3
+ import { legacyEngineRepoIntelligenceJwt } from "./fixtures/legacyEngineRepoIntelligence.js";
4
+ import { applyRepoIntelligenceValidation } from "./repoIntelligenceValidation.js";
5
+ const baseScan = {
6
+ totalScore: 40,
7
+ layerScores: {
8
+ security: 10,
9
+ maintainability: 10,
10
+ ecosystem: 10,
11
+ upgradeImpact: 10,
12
+ },
13
+ findings: [],
14
+ generatedAt: "2026-01-01T00:00:00.000Z",
15
+ changedPackages: ["jsonwebtoken"],
16
+ analysisPreparation: {
17
+ codeIntelligenceAvailable: true,
18
+ warnings: [],
19
+ },
20
+ };
21
+ describe("applyRepoIntelligenceValidation (production persist model)", () => {
22
+ it("retains raw invalid repoIntelligence and marks status invalid", () => {
23
+ const { result, contractFailed, logPayload } = applyRepoIntelligenceValidation({
24
+ ...baseScan,
25
+ repoIntelligence: legacyEngineRepoIntelligenceJwt,
26
+ });
27
+ expect(contractFailed).toBe(true);
28
+ expect(logPayload?.issueCount).toBeGreaterThan(0);
29
+ expect(result.repoIntelligence).toEqual(legacyEngineRepoIntelligenceJwt);
30
+ expect(result.analysisPreparation?.repoIntelligenceValidation?.status).toBe("invalid");
31
+ expect(result.analysisPreparation?.warnings.some((w) => w.code === "repo_intelligence_contract_invalid")).toBe(true);
32
+ });
33
+ it("deriveScanNarrative ignores invalid wire (graph_fallback, untrusted parse status)", () => {
34
+ const { result } = applyRepoIntelligenceValidation({
35
+ ...baseScan,
36
+ repoIntelligence: legacyEngineRepoIntelligenceJwt,
37
+ });
38
+ const facts = deriveScanNarrative(result);
39
+ expect(facts.availability.repoIntelligenceParse).toBe("untrusted");
40
+ expect(facts.availability.tiersPresent.tier1).toBe(false);
41
+ expect(facts.availability.mode).not.toBe("pr_intelligence");
42
+ expect(facts.packageUsage).toHaveLength(0);
43
+ });
44
+ });
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
2
2
  import { primaryPrExample } from "./productMessaging.js";
3
3
  import { ACT2_MAX_THEMES, TIER1_MAX_VISIBLE_IMPACTS, deriveAct2Themes, deriveOperationalImpact, deriveFollowUpBridgeNote, deriveScanDetailViewModel, deriveVerdictLine, sanitizeAct2Theme, tier1PassesRecallTest, } from "./scanDetailViewModel.js";
4
4
  import { fixtureRepoIntelligenceFastify } from "./fixtures/repoIntelligenceFixtures.js";
5
+ import { analysisPreparationWithValidRepoIntel } from "./fixtures/repoIntelligenceTestHelpers.js";
5
6
  const baseResult = {
6
7
  totalScore: 10,
7
8
  layerScores: {
@@ -133,7 +134,7 @@ describe("Tier 1 success criterion", () => {
133
134
  const vm = deriveScanDetailViewModel({
134
135
  ...baseResult,
135
136
  changedPackages: ["fastify"],
136
- analysisPreparation: { codeIntelligenceAvailable: true, warnings: [] },
137
+ analysisPreparation: analysisPreparationWithValidRepoIntel(),
137
138
  repoIntelligence: fixtureRepoIntelligenceFastify,
138
139
  insights: [
139
140
  {
@@ -14,6 +14,7 @@ export type NarrativePackageUsage = {
14
14
  files: string[];
15
15
  areas: string[];
16
16
  };
17
+ export type RepoIntelligenceParseStatus = "ok" | "invalid" | "absent" | "untrusted";
17
18
  export type ScanNarrativeFacts = {
18
19
  availability: {
19
20
  mode: NarrativeAvailabilityMode;
@@ -23,6 +24,7 @@ export type ScanNarrativeFacts = {
23
24
  tier2: boolean;
24
25
  tier3: boolean;
25
26
  };
27
+ repoIntelligenceParse: RepoIntelligenceParseStatus;
26
28
  };
27
29
  changedPackages: {
28
30
  primary: string | null;