@contractspec/module.workspace 1.57.0 → 1.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/code-generation.d.ts +25 -0
- package/dist/ai/code-generation.d.ts.map +1 -0
- package/dist/ai/index.d.ts +5 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/prompts/code-generation.d.ts +5 -8
- package/dist/ai/prompts/code-generation.d.ts.map +1 -1
- package/dist/ai/prompts/index.d.ts +3 -0
- package/dist/ai/prompts/index.d.ts.map +1 -0
- package/dist/ai/prompts/spec-creation.d.ts +7 -11
- package/dist/ai/prompts/spec-creation.d.ts.map +1 -1
- package/dist/ai/spec-creation.d.ts +27 -0
- package/dist/ai/spec-creation.d.ts.map +1 -0
- package/dist/analysis/deps/graph.d.ts +14 -13
- package/dist/analysis/deps/graph.d.ts.map +1 -1
- package/dist/analysis/deps/index.d.ts +6 -0
- package/dist/analysis/deps/index.d.ts.map +1 -0
- package/dist/analysis/deps/parse-imports.d.ts +1 -4
- package/dist/analysis/deps/parse-imports.d.ts.map +1 -1
- package/dist/analysis/diff/deep-diff.d.ts +17 -15
- package/dist/analysis/diff/deep-diff.d.ts.map +1 -1
- package/dist/analysis/diff/deep-diff.test.d.ts +5 -0
- package/dist/analysis/diff/deep-diff.test.d.ts.map +1 -0
- package/dist/analysis/diff/index.d.ts +6 -0
- package/dist/analysis/diff/index.d.ts.map +1 -0
- package/dist/analysis/diff/semantic.d.ts +6 -6
- package/dist/analysis/diff/semantic.d.ts.map +1 -1
- package/dist/analysis/example-scan.d.ts +3 -7
- package/dist/analysis/example-scan.d.ts.map +1 -1
- package/dist/analysis/example-scan.test.d.ts +7 -0
- package/dist/analysis/example-scan.test.d.ts.map +1 -0
- package/dist/analysis/feature-extractor.d.ts +25 -0
- package/dist/analysis/feature-extractor.d.ts.map +1 -0
- package/dist/analysis/feature-scan.d.ts +3 -7
- package/dist/analysis/feature-scan.d.ts.map +1 -1
- package/dist/analysis/feature-scan.test.d.ts +2 -0
- package/dist/analysis/feature-scan.test.d.ts.map +1 -0
- package/dist/analysis/grouping.d.ts +41 -35
- package/dist/analysis/grouping.d.ts.map +1 -1
- package/dist/analysis/impact/classifier.d.ts +9 -8
- package/dist/analysis/impact/classifier.d.ts.map +1 -1
- package/dist/analysis/impact/classifier.test.d.ts +5 -0
- package/dist/analysis/impact/classifier.test.d.ts.map +1 -0
- package/dist/analysis/impact/index.d.ts +9 -0
- package/dist/analysis/impact/index.d.ts.map +1 -0
- package/dist/analysis/impact/rules.d.ts +15 -14
- package/dist/analysis/impact/rules.d.ts.map +1 -1
- package/dist/analysis/impact/types.d.ts +73 -76
- package/dist/analysis/impact/types.d.ts.map +1 -1
- package/dist/analysis/index.d.ts +14 -0
- package/dist/analysis/index.d.ts.map +1 -0
- package/dist/analysis/snapshot/index.d.ts +9 -0
- package/dist/analysis/snapshot/index.d.ts.map +1 -0
- package/dist/analysis/snapshot/normalizer.d.ts +7 -10
- package/dist/analysis/snapshot/normalizer.d.ts.map +1 -1
- package/dist/analysis/snapshot/snapshot.d.ts +10 -8
- package/dist/analysis/snapshot/snapshot.d.ts.map +1 -1
- package/dist/analysis/snapshot/snapshot.test.d.ts +5 -0
- package/dist/analysis/snapshot/snapshot.test.d.ts.map +1 -0
- package/dist/analysis/snapshot/types.d.ts +58 -56
- package/dist/analysis/snapshot/types.d.ts.map +1 -1
- package/dist/analysis/spec-parser.d.ts +8 -6
- package/dist/analysis/spec-parser.d.ts.map +1 -1
- package/dist/analysis/spec-parsing-utils.d.ts +20 -10
- package/dist/analysis/spec-parsing-utils.d.ts.map +1 -1
- package/dist/analysis/spec-scan.d.ts +13 -12
- package/dist/analysis/spec-scan.d.ts.map +1 -1
- package/dist/analysis/spec-scan.test.d.ts +2 -0
- package/dist/analysis/spec-scan.test.d.ts.map +1 -0
- package/dist/analysis/utils/matchers.d.ts +39 -0
- package/dist/analysis/utils/matchers.d.ts.map +1 -0
- package/dist/analysis/utils/variables.d.ts +15 -0
- package/dist/analysis/utils/variables.d.ts.map +1 -0
- package/dist/analysis/validate/index.d.ts +5 -0
- package/dist/analysis/validate/index.d.ts.map +1 -0
- package/dist/analysis/validate/spec-structure.d.ts +15 -14
- package/dist/analysis/validate/spec-structure.d.ts.map +1 -1
- package/dist/analysis/validate/spec-structure.test.d.ts +2 -0
- package/dist/analysis/validate/spec-structure.test.d.ts.map +1 -0
- package/dist/formatter.d.ts +28 -27
- package/dist/formatter.d.ts.map +1 -1
- package/dist/formatters/index.d.ts +8 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/spec-markdown.d.ts +13 -11
- package/dist/formatters/spec-markdown.d.ts.map +1 -1
- package/dist/formatters/spec-markdown.test.d.ts +5 -0
- package/dist/formatters/spec-markdown.test.d.ts.map +1 -0
- package/dist/formatters/spec-to-docblock.d.ts +4 -8
- package/dist/formatters/spec-to-docblock.d.ts.map +1 -1
- package/dist/index.d.ts +13 -42
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4302 -38
- package/dist/node/index.js +4301 -0
- package/dist/templates/app-config.d.ts +2 -6
- package/dist/templates/app-config.d.ts.map +1 -1
- package/dist/templates/app-config.test.d.ts +2 -0
- package/dist/templates/app-config.test.d.ts.map +1 -0
- package/dist/templates/data-view.d.ts +2 -6
- package/dist/templates/data-view.d.ts.map +1 -1
- package/dist/templates/data-view.test.d.ts +2 -0
- package/dist/templates/data-view.test.d.ts.map +1 -0
- package/dist/templates/event.d.ts +6 -6
- package/dist/templates/event.d.ts.map +1 -1
- package/dist/templates/event.test.d.ts +2 -0
- package/dist/templates/event.test.d.ts.map +1 -0
- package/dist/templates/experiment.d.ts +2 -6
- package/dist/templates/experiment.d.ts.map +1 -1
- package/dist/templates/experiment.test.d.ts +2 -0
- package/dist/templates/experiment.test.d.ts.map +1 -0
- package/dist/templates/handler.d.ts +3 -6
- package/dist/templates/handler.d.ts.map +1 -1
- package/dist/templates/handler.test.d.ts +2 -0
- package/dist/templates/handler.test.d.ts.map +1 -0
- package/dist/templates/index.d.ts +18 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/integration-utils.d.ts +13 -0
- package/dist/templates/integration-utils.d.ts.map +1 -0
- package/dist/templates/integration-utils.test.d.ts +2 -0
- package/dist/templates/integration-utils.test.d.ts.map +1 -0
- package/dist/templates/integration.d.ts +2 -6
- package/dist/templates/integration.d.ts.map +1 -1
- package/dist/templates/integration.test.d.ts +2 -0
- package/dist/templates/integration.test.d.ts.map +1 -0
- package/dist/templates/knowledge.d.ts +2 -6
- package/dist/templates/knowledge.d.ts.map +1 -1
- package/dist/templates/knowledge.test.d.ts +2 -0
- package/dist/templates/knowledge.test.d.ts.map +1 -0
- package/dist/templates/migration.d.ts +2 -6
- package/dist/templates/migration.d.ts.map +1 -1
- package/dist/templates/migration.test.d.ts +2 -0
- package/dist/templates/migration.test.d.ts.map +1 -0
- package/dist/templates/operation.d.ts +6 -6
- package/dist/templates/operation.d.ts.map +1 -1
- package/dist/templates/operation.test.d.ts +2 -0
- package/dist/templates/operation.test.d.ts.map +1 -0
- package/dist/templates/presentation.d.ts +6 -6
- package/dist/templates/presentation.d.ts.map +1 -1
- package/dist/templates/presentation.test.d.ts +2 -0
- package/dist/templates/presentation.test.d.ts.map +1 -0
- package/dist/templates/telemetry.d.ts +2 -6
- package/dist/templates/telemetry.d.ts.map +1 -1
- package/dist/templates/telemetry.test.d.ts +2 -0
- package/dist/templates/telemetry.test.d.ts.map +1 -0
- package/dist/templates/utils.d.ts +5 -8
- package/dist/templates/utils.d.ts.map +1 -1
- package/dist/templates/utils.test.d.ts +2 -0
- package/dist/templates/utils.test.d.ts.map +1 -0
- package/dist/templates/workflow-runner.d.ts +6 -13
- package/dist/templates/workflow-runner.d.ts.map +1 -1
- package/dist/templates/workflow-runner.test.d.ts +2 -0
- package/dist/templates/workflow-runner.test.d.ts.map +1 -0
- package/dist/templates/workflow.d.ts +6 -6
- package/dist/templates/workflow.d.ts.map +1 -1
- package/dist/templates/workflow.test.d.ts +2 -0
- package/dist/templates/workflow.test.d.ts.map +1 -0
- package/dist/types/analysis-types.d.ts +135 -136
- package/dist/types/analysis-types.d.ts.map +1 -1
- package/dist/types/generation-types.d.ts +36 -37
- package/dist/types/generation-types.d.ts.map +1 -1
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/llm-types.d.ts +97 -96
- package/dist/types/llm-types.d.ts.map +1 -1
- package/dist/types/rulesync-types.d.ts +17 -18
- package/dist/types/rulesync-types.d.ts.map +1 -1
- package/dist/types/spec-types.d.ts +329 -329
- package/dist/types/spec-types.d.ts.map +1 -1
- package/package.json +20 -16
- package/dist/ai/prompts/code-generation.js +0 -134
- package/dist/ai/prompts/code-generation.js.map +0 -1
- package/dist/ai/prompts/spec-creation.js +0 -102
- package/dist/ai/prompts/spec-creation.js.map +0 -1
- package/dist/analysis/deps/graph.js +0 -85
- package/dist/analysis/deps/graph.js.map +0 -1
- package/dist/analysis/deps/parse-imports.js +0 -31
- package/dist/analysis/deps/parse-imports.js.map +0 -1
- package/dist/analysis/diff/deep-diff.js +0 -114
- package/dist/analysis/diff/deep-diff.js.map +0 -1
- package/dist/analysis/diff/semantic.js +0 -97
- package/dist/analysis/diff/semantic.js.map +0 -1
- package/dist/analysis/example-scan.js +0 -116
- package/dist/analysis/example-scan.js.map +0 -1
- package/dist/analysis/feature-extractor.js +0 -203
- package/dist/analysis/feature-extractor.js.map +0 -1
- package/dist/analysis/feature-scan.js +0 -56
- package/dist/analysis/feature-scan.js.map +0 -1
- package/dist/analysis/grouping.js +0 -115
- package/dist/analysis/grouping.js.map +0 -1
- package/dist/analysis/impact/classifier.js +0 -135
- package/dist/analysis/impact/classifier.js.map +0 -1
- package/dist/analysis/impact/index.js +0 -2
- package/dist/analysis/impact/rules.js +0 -154
- package/dist/analysis/impact/rules.js.map +0 -1
- package/dist/analysis/index.js +0 -18
- package/dist/analysis/snapshot/index.js +0 -2
- package/dist/analysis/snapshot/normalizer.js +0 -67
- package/dist/analysis/snapshot/normalizer.js.map +0 -1
- package/dist/analysis/snapshot/snapshot.js +0 -163
- package/dist/analysis/snapshot/snapshot.js.map +0 -1
- package/dist/analysis/spec-parser.js +0 -89
- package/dist/analysis/spec-parser.js.map +0 -1
- package/dist/analysis/spec-parsing-utils.js +0 -98
- package/dist/analysis/spec-parsing-utils.js.map +0 -1
- package/dist/analysis/spec-scan.js +0 -157
- package/dist/analysis/spec-scan.js.map +0 -1
- package/dist/analysis/utils/matchers.js +0 -77
- package/dist/analysis/utils/matchers.js.map +0 -1
- package/dist/analysis/utils/variables.js +0 -45
- package/dist/analysis/utils/variables.js.map +0 -1
- package/dist/analysis/validate/index.js +0 -1
- package/dist/analysis/validate/spec-structure.js +0 -475
- package/dist/analysis/validate/spec-structure.js.map +0 -1
- package/dist/formatter.js +0 -163
- package/dist/formatter.js.map +0 -1
- package/dist/formatters/index.js +0 -2
- package/dist/formatters/spec-markdown.js +0 -263
- package/dist/formatters/spec-markdown.js.map +0 -1
- package/dist/formatters/spec-to-docblock.js +0 -48
- package/dist/formatters/spec-to-docblock.js.map +0 -1
- package/dist/templates/app-config.js +0 -107
- package/dist/templates/app-config.js.map +0 -1
- package/dist/templates/data-view.js +0 -69
- package/dist/templates/data-view.js.map +0 -1
- package/dist/templates/event.js +0 -41
- package/dist/templates/event.js.map +0 -1
- package/dist/templates/experiment.js +0 -88
- package/dist/templates/experiment.js.map +0 -1
- package/dist/templates/handler.js +0 -96
- package/dist/templates/handler.js.map +0 -1
- package/dist/templates/integration-utils.js +0 -102
- package/dist/templates/integration-utils.js.map +0 -1
- package/dist/templates/integration.js +0 -62
- package/dist/templates/integration.js.map +0 -1
- package/dist/templates/knowledge.js +0 -68
- package/dist/templates/knowledge.js.map +0 -1
- package/dist/templates/migration.js +0 -60
- package/dist/templates/migration.js.map +0 -1
- package/dist/templates/operation.js +0 -101
- package/dist/templates/operation.js.map +0 -1
- package/dist/templates/presentation.js +0 -79
- package/dist/templates/presentation.js.map +0 -1
- package/dist/templates/telemetry.js +0 -90
- package/dist/templates/telemetry.js.map +0 -1
- package/dist/templates/utils.js +0 -39
- package/dist/templates/utils.js.map +0 -1
- package/dist/templates/workflow-runner.js +0 -49
- package/dist/templates/workflow-runner.js.map +0 -1
- package/dist/templates/workflow.js +0 -68
- package/dist/templates/workflow.js.map +0 -1
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
//#region src/analysis/grouping.ts
|
|
2
|
-
/**
|
|
3
|
-
* Pre-built grouping strategies for spec scan results.
|
|
4
|
-
*/
|
|
5
|
-
const SpecGroupingStrategies = {
|
|
6
|
-
byTag: (item) => item.tags?.[0] ?? "untagged",
|
|
7
|
-
byOwner: (item) => item.owners?.[0] ?? "unowned",
|
|
8
|
-
byDomain: (item) => {
|
|
9
|
-
const key = item.key ?? "";
|
|
10
|
-
if (key.includes(".")) return key.split(".")[0] ?? "default";
|
|
11
|
-
return "default";
|
|
12
|
-
},
|
|
13
|
-
byStability: (item) => item.stability ?? "stable",
|
|
14
|
-
bySpecType: (item) => item.specType,
|
|
15
|
-
byDirectory: (item) => {
|
|
16
|
-
return item.filePath.split("/").slice(0, -1).join("/") || ".";
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* Filter specs by criteria.
|
|
21
|
-
*/
|
|
22
|
-
function filterSpecs(specs, filter) {
|
|
23
|
-
return specs.filter((spec) => {
|
|
24
|
-
if (filter.tags?.length) {
|
|
25
|
-
if (!filter.tags.some((tag) => spec.tags?.includes(tag))) return false;
|
|
26
|
-
}
|
|
27
|
-
if (filter.owners?.length) {
|
|
28
|
-
if (!filter.owners.some((owner) => spec.owners?.includes(owner))) return false;
|
|
29
|
-
}
|
|
30
|
-
if (filter.stability?.length) {
|
|
31
|
-
if (!filter.stability.includes(spec.stability ?? "stable")) return false;
|
|
32
|
-
}
|
|
33
|
-
if (filter.specType?.length) {
|
|
34
|
-
if (!filter.specType.includes(spec.specType)) return false;
|
|
35
|
-
}
|
|
36
|
-
if (filter.namePattern) {
|
|
37
|
-
const key = spec.key ?? "";
|
|
38
|
-
const pattern = filter.namePattern.replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
39
|
-
if (!new RegExp(`^${pattern}$`, "i").test(key)) return false;
|
|
40
|
-
}
|
|
41
|
-
return true;
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Group specs by key function.
|
|
46
|
-
*/
|
|
47
|
-
function groupSpecs(items, keyFn) {
|
|
48
|
-
const groups = /* @__PURE__ */ new Map();
|
|
49
|
-
for (const item of items) {
|
|
50
|
-
const key = keyFn(item);
|
|
51
|
-
const existing = groups.get(key);
|
|
52
|
-
if (existing) existing.push(item);
|
|
53
|
-
else groups.set(key, [item]);
|
|
54
|
-
}
|
|
55
|
-
return groups;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Group specs and return as array.
|
|
59
|
-
*/
|
|
60
|
-
function groupSpecsToArray(items, keyFn) {
|
|
61
|
-
const map = groupSpecs(items, keyFn);
|
|
62
|
-
return Array.from(map.entries()).map(([key, items]) => ({
|
|
63
|
-
key,
|
|
64
|
-
items
|
|
65
|
-
})).sort((a, b) => a.key.localeCompare(b.key));
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Get unique tags from spec results.
|
|
69
|
-
*/
|
|
70
|
-
function getUniqueSpecTags(specs) {
|
|
71
|
-
const tags = /* @__PURE__ */ new Set();
|
|
72
|
-
for (const spec of specs) for (const tag of spec.tags ?? []) tags.add(tag);
|
|
73
|
-
return Array.from(tags).sort();
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Get unique owners from spec results.
|
|
77
|
-
*/
|
|
78
|
-
function getUniqueSpecOwners(specs) {
|
|
79
|
-
const owners = /* @__PURE__ */ new Set();
|
|
80
|
-
for (const spec of specs) for (const owner of spec.owners ?? []) owners.add(owner);
|
|
81
|
-
return Array.from(owners).sort();
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Get unique domains from spec results.
|
|
85
|
-
*/
|
|
86
|
-
function getUniqueSpecDomains(specs) {
|
|
87
|
-
const domains = /* @__PURE__ */ new Set();
|
|
88
|
-
for (const spec of specs) domains.add(SpecGroupingStrategies.byDomain(spec));
|
|
89
|
-
return Array.from(domains).sort();
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Filter features by criteria.
|
|
93
|
-
*/
|
|
94
|
-
function filterFeatures(features, filter) {
|
|
95
|
-
return features.filter((feature) => {
|
|
96
|
-
if (filter.tags?.length) {
|
|
97
|
-
if (!filter.tags.some((tag) => feature.tags?.includes(tag))) return false;
|
|
98
|
-
}
|
|
99
|
-
if (filter.owners?.length) {
|
|
100
|
-
if (!filter.owners.some((owner) => feature.owners?.includes(owner))) return false;
|
|
101
|
-
}
|
|
102
|
-
if (filter.stability?.length) {
|
|
103
|
-
if (!filter.stability.includes(feature.stability ?? "stable")) return false;
|
|
104
|
-
}
|
|
105
|
-
if (filter.namePattern) {
|
|
106
|
-
const pattern = filter.namePattern.replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
107
|
-
if (!new RegExp(`^${pattern}$`, "i").test(feature.key)) return false;
|
|
108
|
-
}
|
|
109
|
-
return true;
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
//#endregion
|
|
114
|
-
export { SpecGroupingStrategies, filterFeatures, filterSpecs, getUniqueSpecDomains, getUniqueSpecOwners, getUniqueSpecTags, groupSpecs, groupSpecsToArray };
|
|
115
|
-
//# sourceMappingURL=grouping.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"grouping.js","names":[],"sources":["../../src/analysis/grouping.ts"],"sourcesContent":["/**\n * Grouping and filtering utilities for ContractSpec workspace analysis.\n * Provides services to filter and group scan results.\n */\n\nimport type {\n FeatureScanResult,\n SpecScanResult,\n} from '../types/analysis-types';\nimport type { Stability } from '../types/spec-types';\n\n/**\n * Filter criteria for spec scan results.\n */\nexport interface SpecFilter {\n /** Filter by tags (item must have at least one matching tag) */\n tags?: string[];\n /** Filter by owners (item must have at least one matching owner) */\n owners?: string[];\n /** Filter by stability levels */\n stability?: Stability[];\n /** Filter by spec type */\n specType?: SpecScanResult['specType'][];\n /** Filter by name pattern (glob) */\n namePattern?: string;\n}\n\n/**\n * Grouping key function type.\n */\nexport type GroupKeyFn<T> = (item: T) => string;\n\n/**\n * Grouped items result.\n */\nexport interface GroupedItems<T> {\n key: string;\n items: T[];\n}\n\n/**\n * Pre-built grouping strategies for spec scan results.\n */\nexport const SpecGroupingStrategies = {\n /** Group by first tag. */\n byTag: (item: SpecScanResult): string => item.tags?.[0] ?? 'untagged',\n\n /** Group by first owner. */\n byOwner: (item: SpecScanResult): string => item.owners?.[0] ?? 'unowned',\n\n /** Group by domain (first segment of name). */\n byDomain: (item: SpecScanResult): string => {\n const key = item.key ?? '';\n if (key.includes('.')) {\n return key.split('.')[0] ?? 'default';\n }\n return 'default';\n },\n\n /** Group by stability. */\n byStability: (item: SpecScanResult): string => item.stability ?? 'stable',\n\n /** Group by spec type. */\n bySpecType: (item: SpecScanResult): string => item.specType,\n\n /** Group by file directory. */\n byDirectory: (item: SpecScanResult): string => {\n const parts = item.filePath.split('/');\n // Return parent directory\n return parts.slice(0, -1).join('/') || '.';\n },\n};\n\n/**\n * Filter specs by criteria.\n */\nexport function filterSpecs(\n specs: SpecScanResult[],\n filter: SpecFilter\n): SpecScanResult[] {\n return specs.filter((spec) => {\n // Filter by tags\n if (filter.tags?.length) {\n const hasMatchingTag = filter.tags.some((tag) =>\n spec.tags?.includes(tag)\n );\n if (!hasMatchingTag) return false;\n }\n\n // Filter by owners\n if (filter.owners?.length) {\n const hasMatchingOwner = filter.owners.some((owner) =>\n spec.owners?.includes(owner)\n );\n if (!hasMatchingOwner) return false;\n }\n\n // Filter by stability\n if (filter.stability?.length) {\n if (!filter.stability.includes(spec.stability ?? 'stable')) {\n return false;\n }\n }\n\n // Filter by spec type\n if (filter.specType?.length) {\n if (!filter.specType.includes(spec.specType)) {\n return false;\n }\n }\n\n // Filter by name pattern\n if (filter.namePattern) {\n const key = spec.key ?? '';\n const pattern = filter.namePattern\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.');\n const regex = new RegExp(`^${pattern}$`, 'i');\n if (!regex.test(key)) return false;\n }\n\n return true;\n });\n}\n\n/**\n * Group specs by key function.\n */\nexport function groupSpecs<T>(\n items: T[],\n keyFn: GroupKeyFn<T>\n): Map<string, T[]> {\n const groups = new Map<string, T[]>();\n\n for (const item of items) {\n const key = keyFn(item);\n const existing = groups.get(key);\n if (existing) {\n existing.push(item);\n } else {\n groups.set(key, [item]);\n }\n }\n\n return groups;\n}\n\n/**\n * Group specs and return as array.\n */\nexport function groupSpecsToArray<T>(\n items: T[],\n keyFn: GroupKeyFn<T>\n): GroupedItems<T>[] {\n const map = groupSpecs(items, keyFn);\n return Array.from(map.entries())\n .map(([key, items]) => ({ key, items }))\n .sort((a, b) => a.key.localeCompare(b.key));\n}\n\n/**\n * Get unique tags from spec results.\n */\nexport function getUniqueSpecTags(specs: SpecScanResult[]): string[] {\n const tags = new Set<string>();\n for (const spec of specs) {\n for (const tag of spec.tags ?? []) {\n tags.add(tag);\n }\n }\n return Array.from(tags).sort();\n}\n\n/**\n * Get unique owners from spec results.\n */\nexport function getUniqueSpecOwners(specs: SpecScanResult[]): string[] {\n const owners = new Set<string>();\n for (const spec of specs) {\n for (const owner of spec.owners ?? []) {\n owners.add(owner);\n }\n }\n return Array.from(owners).sort();\n}\n\n/**\n * Get unique domains from spec results.\n */\nexport function getUniqueSpecDomains(specs: SpecScanResult[]): string[] {\n const domains = new Set<string>();\n for (const spec of specs) {\n domains.add(SpecGroupingStrategies.byDomain(spec));\n }\n return Array.from(domains).sort();\n}\n\n/**\n * Filter features by criteria.\n */\nexport function filterFeatures(\n features: FeatureScanResult[],\n filter: SpecFilter\n): FeatureScanResult[] {\n return features.filter((feature) => {\n // Filter by tags\n if (filter.tags?.length) {\n const hasMatchingTag = filter.tags.some((tag) =>\n feature.tags?.includes(tag)\n );\n if (!hasMatchingTag) return false;\n }\n\n // Filter by owners\n if (filter.owners?.length) {\n const hasMatchingOwner = filter.owners.some((owner) =>\n feature.owners?.includes(owner)\n );\n if (!hasMatchingOwner) return false;\n }\n\n // Filter by stability\n if (filter.stability?.length) {\n if (!filter.stability.includes(feature.stability ?? 'stable')) {\n return false;\n }\n }\n\n // Filter by name pattern\n if (filter.namePattern) {\n const pattern = filter.namePattern\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.');\n const regex = new RegExp(`^${pattern}$`, 'i');\n if (!regex.test(feature.key)) return false;\n }\n\n return true;\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_RULES, findMatchingRule } from "./rules.js";
|
|
2
|
-
|
|
3
|
-
//#region src/analysis/impact/classifier.ts
|
|
4
|
-
/**
|
|
5
|
-
* Classify the impact of changes between base and head snapshots.
|
|
6
|
-
*
|
|
7
|
-
* @param baseSpecs - Specs from the base (baseline) version
|
|
8
|
-
* @param headSpecs - Specs from the head (current) version
|
|
9
|
-
* @param diffs - Semantic diff items from comparison
|
|
10
|
-
* @param options - Classification options
|
|
11
|
-
* @returns Classified impact result
|
|
12
|
-
*/
|
|
13
|
-
function classifyImpact(baseSpecs, headSpecs, diffs, options = {}) {
|
|
14
|
-
const rules = options.customRules ?? DEFAULT_RULES;
|
|
15
|
-
const deltas = [];
|
|
16
|
-
const baseMap = new Map(baseSpecs.map((s) => [`${s.key}@${s.version}`, s]));
|
|
17
|
-
const headMap = new Map(headSpecs.map((s) => [`${s.key}@${s.version}`, s]));
|
|
18
|
-
const addedSpecs = [];
|
|
19
|
-
for (const spec of headSpecs) {
|
|
20
|
-
const lookupKey = `${spec.key}@${spec.version}`;
|
|
21
|
-
if (!baseMap.has(lookupKey)) addedSpecs.push({
|
|
22
|
-
key: spec.key,
|
|
23
|
-
version: spec.version,
|
|
24
|
-
type: spec.type
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
const removedSpecs = [];
|
|
28
|
-
for (const spec of baseSpecs) {
|
|
29
|
-
const lookupKey = `${spec.key}@${spec.version}`;
|
|
30
|
-
if (!headMap.has(lookupKey)) {
|
|
31
|
-
removedSpecs.push({
|
|
32
|
-
key: spec.key,
|
|
33
|
-
version: spec.version,
|
|
34
|
-
type: spec.type
|
|
35
|
-
});
|
|
36
|
-
deltas.push({
|
|
37
|
-
specKey: spec.key,
|
|
38
|
-
specVersion: spec.version,
|
|
39
|
-
specType: spec.type,
|
|
40
|
-
path: `spec.${spec.key}`,
|
|
41
|
-
severity: "breaking",
|
|
42
|
-
rule: "endpoint-removed",
|
|
43
|
-
description: `${spec.type === "operation" ? "Operation" : "Event"} '${spec.key}' was removed`
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
for (const diff of diffs) {
|
|
48
|
-
const matchingRule = findMatchingRule({
|
|
49
|
-
path: diff.path,
|
|
50
|
-
description: diff.description,
|
|
51
|
-
type: diff.type
|
|
52
|
-
}, rules);
|
|
53
|
-
const specInfo = findSpecInfo(extractSpecKey(diff.path, baseSpecs, headSpecs), baseSpecs, headSpecs);
|
|
54
|
-
deltas.push({
|
|
55
|
-
specKey: specInfo?.key ?? "unknown",
|
|
56
|
-
specVersion: specInfo?.version ?? "1.0.0",
|
|
57
|
-
specType: specInfo?.type ?? "operation",
|
|
58
|
-
path: diff.path,
|
|
59
|
-
severity: matchingRule?.severity ?? mapDiffTypeToSeverity(diff.type),
|
|
60
|
-
rule: matchingRule?.id ?? "unknown",
|
|
61
|
-
description: diff.description,
|
|
62
|
-
oldValue: diff.oldValue,
|
|
63
|
-
newValue: diff.newValue
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
for (const spec of addedSpecs) deltas.push({
|
|
67
|
-
specKey: spec.key,
|
|
68
|
-
specVersion: spec.version,
|
|
69
|
-
specType: spec.type,
|
|
70
|
-
path: `spec.${spec.key}`,
|
|
71
|
-
severity: "non_breaking",
|
|
72
|
-
rule: "endpoint-added",
|
|
73
|
-
description: `${spec.type === "operation" ? "Operation" : "Event"} '${spec.key}' was added`
|
|
74
|
-
});
|
|
75
|
-
const summary = calculateSummary(deltas, addedSpecs, removedSpecs);
|
|
76
|
-
const hasBreaking = summary.breaking > 0 || summary.removed > 0;
|
|
77
|
-
const hasNonBreaking = summary.nonBreaking > 0 || summary.added > 0;
|
|
78
|
-
return {
|
|
79
|
-
status: determineStatus(hasBreaking, hasNonBreaking),
|
|
80
|
-
hasBreaking,
|
|
81
|
-
hasNonBreaking,
|
|
82
|
-
summary,
|
|
83
|
-
deltas,
|
|
84
|
-
addedSpecs,
|
|
85
|
-
removedSpecs,
|
|
86
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Calculate summary counts from deltas.
|
|
91
|
-
*/
|
|
92
|
-
function calculateSummary(deltas, addedSpecs, removedSpecs) {
|
|
93
|
-
return {
|
|
94
|
-
breaking: deltas.filter((d) => d.severity === "breaking").length,
|
|
95
|
-
nonBreaking: deltas.filter((d) => d.severity === "non_breaking").length,
|
|
96
|
-
info: deltas.filter((d) => d.severity === "info").length,
|
|
97
|
-
added: addedSpecs.length,
|
|
98
|
-
removed: removedSpecs.length
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Determine overall status from flags.
|
|
103
|
-
*/
|
|
104
|
-
function determineStatus(hasBreaking, hasNonBreaking) {
|
|
105
|
-
if (hasBreaking) return "breaking";
|
|
106
|
-
if (hasNonBreaking) return "non-breaking";
|
|
107
|
-
return "no-impact";
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Map semantic diff type to impact severity.
|
|
111
|
-
*/
|
|
112
|
-
function mapDiffTypeToSeverity(type) {
|
|
113
|
-
switch (type) {
|
|
114
|
-
case "breaking": return "breaking";
|
|
115
|
-
case "removed": return "breaking";
|
|
116
|
-
case "added": return "non_breaking";
|
|
117
|
-
case "changed": return "info";
|
|
118
|
-
default: return "info";
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Extract spec key from a diff path (heuristic).
|
|
123
|
-
*/
|
|
124
|
-
function extractSpecKey(_path, _baseSpecs, _headSpecs) {}
|
|
125
|
-
/**
|
|
126
|
-
* Find spec info from key.
|
|
127
|
-
*/
|
|
128
|
-
function findSpecInfo(key, baseSpecs, headSpecs) {
|
|
129
|
-
if (!key) return headSpecs[0] ?? baseSpecs[0];
|
|
130
|
-
return headSpecs.find((s) => s.key === key) ?? baseSpecs.find((s) => s.key === key);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
//#endregion
|
|
134
|
-
export { classifyImpact };
|
|
135
|
-
//# sourceMappingURL=classifier.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"classifier.js","names":[],"sources":["../../../src/analysis/impact/classifier.ts"],"sourcesContent":["/**\n * Impact classifier.\n *\n * Classifies contract changes as breaking, non-breaking, or info.\n */\n\nimport type { SemanticDiffItem } from '../../types/analysis-types';\nimport type { SpecSnapshot } from '../snapshot/types';\nimport { DEFAULT_RULES, findMatchingRule } from './rules';\nimport type {\n ClassifyOptions,\n ImpactDelta,\n ImpactResult,\n ImpactStatus,\n ImpactSummary,\n} from './types';\n\n/**\n * Classify the impact of changes between base and head snapshots.\n *\n * @param baseSpecs - Specs from the base (baseline) version\n * @param headSpecs - Specs from the head (current) version\n * @param diffs - Semantic diff items from comparison\n * @param options - Classification options\n * @returns Classified impact result\n */\nexport function classifyImpact(\n baseSpecs: SpecSnapshot[],\n headSpecs: SpecSnapshot[],\n diffs: SemanticDiffItem[],\n options: ClassifyOptions = {}\n): ImpactResult {\n const rules = options.customRules ?? DEFAULT_RULES;\n const deltas: ImpactDelta[] = [];\n\n // Create lookup maps\n const baseMap = new Map(baseSpecs.map((s) => [`${s.key}@${s.version}`, s]));\n const headMap = new Map(headSpecs.map((s) => [`${s.key}@${s.version}`, s]));\n\n // Detect added specs\n const addedSpecs: ImpactResult['addedSpecs'] = [];\n for (const spec of headSpecs) {\n const lookupKey = `${spec.key}@${spec.version}`;\n if (!baseMap.has(lookupKey)) {\n addedSpecs.push({\n key: spec.key,\n version: spec.version,\n type: spec.type,\n });\n }\n }\n\n // Detect removed specs\n const removedSpecs: ImpactResult['removedSpecs'] = [];\n for (const spec of baseSpecs) {\n const lookupKey = `${spec.key}@${spec.version}`;\n if (!headMap.has(lookupKey)) {\n removedSpecs.push({\n key: spec.key,\n version: spec.version,\n type: spec.type,\n });\n\n // Removed spec is always breaking\n deltas.push({\n specKey: spec.key,\n specVersion: spec.version,\n specType: spec.type,\n path: `spec.${spec.key}`,\n severity: 'breaking',\n rule: 'endpoint-removed',\n description: `${spec.type === 'operation' ? 'Operation' : 'Event'} '${spec.key}' was removed`,\n });\n }\n }\n\n // Classify diffs\n for (const diff of diffs) {\n const matchingRule = findMatchingRule(\n { path: diff.path, description: diff.description, type: diff.type },\n rules\n );\n\n // Extract spec key from path (heuristic)\n const specKey = extractSpecKey(diff.path, baseSpecs, headSpecs);\n const specInfo = findSpecInfo(specKey, baseSpecs, headSpecs);\n\n deltas.push({\n specKey: specInfo?.key ?? 'unknown',\n specVersion: specInfo?.version ?? '1.0.0',\n specType: specInfo?.type ?? 'operation',\n path: diff.path,\n severity: matchingRule?.severity ?? mapDiffTypeToSeverity(diff.type),\n rule: matchingRule?.id ?? 'unknown',\n description: diff.description,\n oldValue: diff.oldValue,\n newValue: diff.newValue,\n });\n }\n\n // Add added specs as non-breaking changes\n for (const spec of addedSpecs) {\n deltas.push({\n specKey: spec.key,\n specVersion: spec.version,\n specType: spec.type,\n path: `spec.${spec.key}`,\n severity: 'non_breaking',\n rule: 'endpoint-added',\n description: `${spec.type === 'operation' ? 'Operation' : 'Event'} '${spec.key}' was added`,\n });\n }\n\n // Calculate summary\n const summary = calculateSummary(deltas, addedSpecs, removedSpecs);\n\n // Determine status\n const hasBreaking = summary.breaking > 0 || summary.removed > 0;\n const hasNonBreaking = summary.nonBreaking > 0 || summary.added > 0;\n const status = determineStatus(hasBreaking, hasNonBreaking);\n\n return {\n status,\n hasBreaking,\n hasNonBreaking,\n summary,\n deltas,\n addedSpecs,\n removedSpecs,\n timestamp: new Date().toISOString(),\n };\n}\n\n/**\n * Calculate summary counts from deltas.\n */\nfunction calculateSummary(\n deltas: ImpactDelta[],\n addedSpecs: ImpactResult['addedSpecs'],\n removedSpecs: ImpactResult['removedSpecs']\n): ImpactSummary {\n return {\n breaking: deltas.filter((d) => d.severity === 'breaking').length,\n nonBreaking: deltas.filter((d) => d.severity === 'non_breaking').length,\n info: deltas.filter((d) => d.severity === 'info').length,\n added: addedSpecs.length,\n removed: removedSpecs.length,\n };\n}\n\n/**\n * Determine overall status from flags.\n */\nfunction determineStatus(\n hasBreaking: boolean,\n hasNonBreaking: boolean\n): ImpactStatus {\n if (hasBreaking) return 'breaking';\n if (hasNonBreaking) return 'non-breaking';\n return 'no-impact';\n}\n\n/**\n * Map semantic diff type to impact severity.\n */\nfunction mapDiffTypeToSeverity(type: string): ImpactDelta['severity'] {\n switch (type) {\n case 'breaking':\n return 'breaking';\n case 'removed':\n return 'breaking';\n case 'added':\n return 'non_breaking';\n case 'changed':\n return 'info';\n default:\n return 'info';\n }\n}\n\n/**\n * Extract spec key from a diff path (heuristic).\n */\nfunction extractSpecKey(\n _path: string,\n _baseSpecs: SpecSnapshot[],\n _headSpecs: SpecSnapshot[]\n): string | undefined {\n // This is a simplified heuristic; in practice would need more context\n return undefined;\n}\n\n/**\n * Find spec info from key.\n */\nfunction findSpecInfo(\n key: string | undefined,\n baseSpecs: SpecSnapshot[],\n headSpecs: SpecSnapshot[]\n): SpecSnapshot | undefined {\n if (!key) return headSpecs[0] ?? baseSpecs[0];\n return (\n headSpecs.find((s) => s.key === key) ?? baseSpecs.find((s) => s.key === key)\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
//#region src/analysis/impact/rules.ts
|
|
2
|
-
/**
|
|
3
|
-
* Default breaking change rules.
|
|
4
|
-
*/
|
|
5
|
-
const BREAKING_RULES = [
|
|
6
|
-
{
|
|
7
|
-
id: "endpoint-removed",
|
|
8
|
-
description: "Endpoint/operation was removed",
|
|
9
|
-
severity: "breaking",
|
|
10
|
-
matches: (delta) => delta.path.includes("spec.") && delta.type === "removed"
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
id: "field-removed",
|
|
14
|
-
description: "Field was removed from response",
|
|
15
|
-
severity: "breaking",
|
|
16
|
-
matches: (delta) => delta.path.includes(".output.") && delta.description.includes("removed")
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
id: "field-type-changed",
|
|
20
|
-
description: "Field type was changed",
|
|
21
|
-
severity: "breaking",
|
|
22
|
-
matches: (delta) => delta.path.includes(".type") && delta.description.includes("type changed")
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
id: "field-made-required",
|
|
26
|
-
description: "Optional field became required",
|
|
27
|
-
severity: "breaking",
|
|
28
|
-
matches: (delta) => delta.path.includes(".required") && delta.description.includes("optional to required")
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
id: "enum-value-removed",
|
|
32
|
-
description: "Enum value was removed",
|
|
33
|
-
severity: "breaking",
|
|
34
|
-
matches: (delta) => delta.path.includes(".enumValues") && delta.description.includes("removed")
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
id: "nullable-removed",
|
|
38
|
-
description: "Field is no longer nullable",
|
|
39
|
-
severity: "breaking",
|
|
40
|
-
matches: (delta) => delta.path.includes(".nullable") && delta.description.includes("no longer nullable")
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
id: "method-changed",
|
|
44
|
-
description: "HTTP method was changed",
|
|
45
|
-
severity: "breaking",
|
|
46
|
-
matches: (delta) => delta.path.includes(".http.method") || delta.path.includes(".method")
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
id: "path-changed",
|
|
50
|
-
description: "HTTP path was changed",
|
|
51
|
-
severity: "breaking",
|
|
52
|
-
matches: (delta) => delta.path.includes(".http.path") || delta.path.includes(".path")
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
id: "required-field-added-to-input",
|
|
56
|
-
description: "Required field was added to input",
|
|
57
|
-
severity: "breaking",
|
|
58
|
-
matches: (delta) => delta.path.includes(".input.") && delta.description.includes("Required field")
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
id: "event-payload-field-removed",
|
|
62
|
-
description: "Event payload field was removed",
|
|
63
|
-
severity: "breaking",
|
|
64
|
-
matches: (delta) => delta.path.includes(".payload.") && delta.description.includes("removed")
|
|
65
|
-
}
|
|
66
|
-
];
|
|
67
|
-
/**
|
|
68
|
-
* Non-breaking change rules.
|
|
69
|
-
*/
|
|
70
|
-
const NON_BREAKING_RULES = [
|
|
71
|
-
{
|
|
72
|
-
id: "optional-field-added",
|
|
73
|
-
description: "Optional field was added",
|
|
74
|
-
severity: "non_breaking",
|
|
75
|
-
matches: (delta) => delta.description.includes("Optional field") && delta.description.includes("added")
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
id: "endpoint-added",
|
|
79
|
-
description: "New endpoint/operation was added",
|
|
80
|
-
severity: "non_breaking",
|
|
81
|
-
matches: (delta) => delta.path.includes("spec.") && delta.type === "added"
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
id: "enum-value-added",
|
|
85
|
-
description: "Enum value was added",
|
|
86
|
-
severity: "non_breaking",
|
|
87
|
-
matches: (delta) => delta.path.includes(".enumValues") && delta.description.includes("added")
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
id: "field-made-optional",
|
|
91
|
-
description: "Required field became optional",
|
|
92
|
-
severity: "non_breaking",
|
|
93
|
-
matches: (delta) => delta.path.includes(".required") && delta.description.includes("required to optional")
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
id: "nullable-added",
|
|
97
|
-
description: "Field is now nullable",
|
|
98
|
-
severity: "non_breaking",
|
|
99
|
-
matches: (delta) => delta.path.includes(".nullable") && delta.description.includes("now nullable")
|
|
100
|
-
}
|
|
101
|
-
];
|
|
102
|
-
/**
|
|
103
|
-
* Info-level change rules.
|
|
104
|
-
*/
|
|
105
|
-
const INFO_RULES = [
|
|
106
|
-
{
|
|
107
|
-
id: "stability-changed",
|
|
108
|
-
description: "Stability level was changed",
|
|
109
|
-
severity: "info",
|
|
110
|
-
matches: (delta) => delta.path.includes(".stability")
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
id: "description-changed",
|
|
114
|
-
description: "Description was changed",
|
|
115
|
-
severity: "info",
|
|
116
|
-
matches: (delta) => delta.path.includes(".description")
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
id: "owners-changed",
|
|
120
|
-
description: "Owners were changed",
|
|
121
|
-
severity: "info",
|
|
122
|
-
matches: (delta) => delta.path.includes(".owners")
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
id: "tags-changed",
|
|
126
|
-
description: "Tags were changed",
|
|
127
|
-
severity: "info",
|
|
128
|
-
matches: (delta) => delta.path.includes(".tags")
|
|
129
|
-
}
|
|
130
|
-
];
|
|
131
|
-
/**
|
|
132
|
-
* All default rules in priority order (breaking > non_breaking > info).
|
|
133
|
-
*/
|
|
134
|
-
const DEFAULT_RULES = [
|
|
135
|
-
...BREAKING_RULES,
|
|
136
|
-
...NON_BREAKING_RULES,
|
|
137
|
-
...INFO_RULES
|
|
138
|
-
];
|
|
139
|
-
/**
|
|
140
|
-
* Get rules by severity.
|
|
141
|
-
*/
|
|
142
|
-
function getRulesBySeverity(severity) {
|
|
143
|
-
return DEFAULT_RULES.filter((rule) => rule.severity === severity);
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Find matching rule for a delta.
|
|
147
|
-
*/
|
|
148
|
-
function findMatchingRule(delta, rules = DEFAULT_RULES) {
|
|
149
|
-
return rules.find((rule) => rule.matches(delta));
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
//#endregion
|
|
153
|
-
export { BREAKING_RULES, DEFAULT_RULES, INFO_RULES, NON_BREAKING_RULES, findMatchingRule, getRulesBySeverity };
|
|
154
|
-
//# sourceMappingURL=rules.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rules.js","names":[],"sources":["../../../src/analysis/impact/rules.ts"],"sourcesContent":["/**\n * Impact classification rules.\n *\n * Defines rules for classifying changes as breaking or non-breaking.\n */\n\nimport type { ImpactRule, ImpactSeverity } from './types';\n\n/**\n * Default breaking change rules.\n */\nexport const BREAKING_RULES: ImpactRule[] = [\n {\n id: 'endpoint-removed',\n description: 'Endpoint/operation was removed',\n severity: 'breaking',\n matches: (delta) =>\n delta.path.includes('spec.') && delta.type === 'removed',\n },\n {\n id: 'field-removed',\n description: 'Field was removed from response',\n severity: 'breaking',\n matches: (delta) =>\n delta.path.includes('.output.') && delta.description.includes('removed'),\n },\n {\n id: 'field-type-changed',\n description: 'Field type was changed',\n severity: 'breaking',\n matches: (delta) =>\n delta.path.includes('.type') &&\n delta.description.includes('type changed'),\n },\n {\n id: 'field-made-required',\n description: 'Optional field became required',\n severity: 'breaking',\n matches: (delta) =>\n delta.path.includes('.required') &&\n delta.description.includes('optional to required'),\n },\n {\n id: 'enum-value-removed',\n description: 'Enum value was removed',\n severity: 'breaking',\n matches: (delta) =>\n delta.path.includes('.enumValues') &&\n delta.description.includes('removed'),\n },\n {\n id: 'nullable-removed',\n description: 'Field is no longer nullable',\n severity: 'breaking',\n matches: (delta) =>\n delta.path.includes('.nullable') &&\n delta.description.includes('no longer nullable'),\n },\n {\n id: 'method-changed',\n description: 'HTTP method was changed',\n severity: 'breaking',\n matches: (delta) =>\n delta.path.includes('.http.method') || delta.path.includes('.method'),\n },\n {\n id: 'path-changed',\n description: 'HTTP path was changed',\n severity: 'breaking',\n matches: (delta) =>\n delta.path.includes('.http.path') || delta.path.includes('.path'),\n },\n {\n id: 'required-field-added-to-input',\n description: 'Required field was added to input',\n severity: 'breaking',\n matches: (delta) =>\n delta.path.includes('.input.') &&\n delta.description.includes('Required field'),\n },\n {\n id: 'event-payload-field-removed',\n description: 'Event payload field was removed',\n severity: 'breaking',\n matches: (delta) =>\n delta.path.includes('.payload.') && delta.description.includes('removed'),\n },\n];\n\n/**\n * Non-breaking change rules.\n */\nexport const NON_BREAKING_RULES: ImpactRule[] = [\n {\n id: 'optional-field-added',\n description: 'Optional field was added',\n severity: 'non_breaking',\n matches: (delta) =>\n delta.description.includes('Optional field') &&\n delta.description.includes('added'),\n },\n {\n id: 'endpoint-added',\n description: 'New endpoint/operation was added',\n severity: 'non_breaking',\n matches: (delta) => delta.path.includes('spec.') && delta.type === 'added',\n },\n {\n id: 'enum-value-added',\n description: 'Enum value was added',\n severity: 'non_breaking',\n matches: (delta) =>\n delta.path.includes('.enumValues') && delta.description.includes('added'),\n },\n {\n id: 'field-made-optional',\n description: 'Required field became optional',\n severity: 'non_breaking',\n matches: (delta) =>\n delta.path.includes('.required') &&\n delta.description.includes('required to optional'),\n },\n {\n id: 'nullable-added',\n description: 'Field is now nullable',\n severity: 'non_breaking',\n matches: (delta) =>\n delta.path.includes('.nullable') &&\n delta.description.includes('now nullable'),\n },\n];\n\n/**\n * Info-level change rules.\n */\nexport const INFO_RULES: ImpactRule[] = [\n {\n id: 'stability-changed',\n description: 'Stability level was changed',\n severity: 'info',\n matches: (delta) => delta.path.includes('.stability'),\n },\n {\n id: 'description-changed',\n description: 'Description was changed',\n severity: 'info',\n matches: (delta) => delta.path.includes('.description'),\n },\n {\n id: 'owners-changed',\n description: 'Owners were changed',\n severity: 'info',\n matches: (delta) => delta.path.includes('.owners'),\n },\n {\n id: 'tags-changed',\n description: 'Tags were changed',\n severity: 'info',\n matches: (delta) => delta.path.includes('.tags'),\n },\n];\n\n/**\n * All default rules in priority order (breaking > non_breaking > info).\n */\nexport const DEFAULT_RULES: ImpactRule[] = [\n ...BREAKING_RULES,\n ...NON_BREAKING_RULES,\n ...INFO_RULES,\n];\n\n/**\n * Get rules by severity.\n */\nexport function getRulesBySeverity(severity: ImpactSeverity): ImpactRule[] {\n return DEFAULT_RULES.filter((rule) => rule.severity === severity);\n}\n\n/**\n * Find matching rule for a delta.\n */\nexport function findMatchingRule(\n delta: { path: string; description: string; type: string },\n rules: ImpactRule[] = DEFAULT_RULES\n): ImpactRule | undefined {\n return rules.find((rule) => rule.matches(delta));\n}\n"],"mappings":"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"}
|
package/dist/analysis/index.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { extractTestCoverage, extractTestTarget } from "./spec-parsing-utils.js";
|
|
2
|
-
import { inferSpecTypeFromCodeBlock, inferSpecTypeFromFilePath, scanAllSpecsFromSource, scanSpecSource } from "./spec-scan.js";
|
|
3
|
-
import { isFeatureFile, scanFeatureSource } from "./feature-scan.js";
|
|
4
|
-
import { isExampleFile, scanExampleSource } from "./example-scan.js";
|
|
5
|
-
import { SpecGroupingStrategies, filterFeatures, filterSpecs, getUniqueSpecDomains, getUniqueSpecOwners, getUniqueSpecTags, groupSpecs, groupSpecsToArray } from "./grouping.js";
|
|
6
|
-
import { computeSemanticDiff } from "./diff/semantic.js";
|
|
7
|
-
import { computeFieldDiff, computeFieldsDiff, computeIoDiff, isBreakingChange } from "./diff/deep-diff.js";
|
|
8
|
-
import { addContractNode, buildReverseEdges, createContractGraph, detectCycles, findMissingDependencies, toDot } from "./deps/graph.js";
|
|
9
|
-
import { parseImportedSpecNames } from "./deps/parse-imports.js";
|
|
10
|
-
import { validateSpecStructure } from "./validate/spec-structure.js";
|
|
11
|
-
import "./validate/index.js";
|
|
12
|
-
import { computeHash, normalizeValue, sortFields, sortSpecs, toCanonicalJson } from "./snapshot/normalizer.js";
|
|
13
|
-
import { generateSnapshot } from "./snapshot/snapshot.js";
|
|
14
|
-
import "./snapshot/index.js";
|
|
15
|
-
import { BREAKING_RULES, DEFAULT_RULES, INFO_RULES, NON_BREAKING_RULES, findMatchingRule, getRulesBySeverity } from "./impact/rules.js";
|
|
16
|
-
import { classifyImpact } from "./impact/classifier.js";
|
|
17
|
-
import "./impact/index.js";
|
|
18
|
-
import { loadSpecFromSource } from "./spec-parser.js";
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { createHash } from "crypto";
|
|
2
|
-
import { compareVersions } from "compare-versions";
|
|
3
|
-
|
|
4
|
-
//#region src/analysis/snapshot/normalizer.ts
|
|
5
|
-
/**
|
|
6
|
-
* JSON normalization utilities for deterministic snapshots.
|
|
7
|
-
*
|
|
8
|
-
* Ensures that snapshots are stable across ordering, whitespace,
|
|
9
|
-
* and other non-semantic differences.
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* Normalize a value for deterministic JSON serialization.
|
|
13
|
-
* - Sorts object keys alphabetically
|
|
14
|
-
* - Removes undefined values
|
|
15
|
-
* - Preserves null values
|
|
16
|
-
*/
|
|
17
|
-
function normalizeValue(value) {
|
|
18
|
-
if (value === null || value === void 0) return value === null ? null : void 0;
|
|
19
|
-
if (Array.isArray(value)) return value.map(normalizeValue);
|
|
20
|
-
if (typeof value === "object") {
|
|
21
|
-
const obj = value;
|
|
22
|
-
const sortedKeys = Object.keys(obj).sort();
|
|
23
|
-
const normalized = {};
|
|
24
|
-
for (const key of sortedKeys) {
|
|
25
|
-
const normalizedValue = normalizeValue(obj[key]);
|
|
26
|
-
if (normalizedValue !== void 0) normalized[key] = normalizedValue;
|
|
27
|
-
}
|
|
28
|
-
return normalized;
|
|
29
|
-
}
|
|
30
|
-
return value;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Serialize a value to deterministic JSON string.
|
|
34
|
-
*/
|
|
35
|
-
function toCanonicalJson(value) {
|
|
36
|
-
return JSON.stringify(normalizeValue(value), null, 0);
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Compute a SHA-256 hash of canonical JSON representation.
|
|
40
|
-
*/
|
|
41
|
-
function computeHash(value) {
|
|
42
|
-
const canonical = toCanonicalJson(value);
|
|
43
|
-
return createHash("sha256").update(canonical).digest("hex").slice(0, 16);
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Sort specs by key and version for deterministic ordering.
|
|
47
|
-
*/
|
|
48
|
-
function sortSpecs(specs) {
|
|
49
|
-
return [...specs].sort((a, b) => {
|
|
50
|
-
const keyCompare = a.key.localeCompare(b.key);
|
|
51
|
-
if (keyCompare !== 0) return keyCompare;
|
|
52
|
-
return compareVersions(a.version, b.version);
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Sort field snapshots by name for deterministic ordering.
|
|
57
|
-
*/
|
|
58
|
-
function sortFields(fields) {
|
|
59
|
-
const sorted = {};
|
|
60
|
-
const keys = Object.keys(fields).sort();
|
|
61
|
-
for (const key of keys) sorted[key] = fields[key];
|
|
62
|
-
return sorted;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
//#endregion
|
|
66
|
-
export { computeHash, normalizeValue, sortFields, sortSpecs, toCanonicalJson };
|
|
67
|
-
//# sourceMappingURL=normalizer.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"normalizer.js","names":[],"sources":["../../../src/analysis/snapshot/normalizer.ts"],"sourcesContent":["/**\n * JSON normalization utilities for deterministic snapshots.\n *\n * Ensures that snapshots are stable across ordering, whitespace,\n * and other non-semantic differences.\n */\n\nimport { createHash } from 'crypto';\nimport { compareVersions } from 'compare-versions';\n\n/**\n * Normalize a value for deterministic JSON serialization.\n * - Sorts object keys alphabetically\n * - Removes undefined values\n * - Preserves null values\n */\nexport function normalizeValue(value: unknown): unknown {\n if (value === null || value === undefined) {\n return value === null ? null : undefined;\n }\n\n if (Array.isArray(value)) {\n return value.map(normalizeValue);\n }\n\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n const sortedKeys = Object.keys(obj).sort();\n const normalized: Record<string, unknown> = {};\n\n for (const key of sortedKeys) {\n const normalizedValue = normalizeValue(obj[key]);\n // Only include defined values\n if (normalizedValue !== undefined) {\n normalized[key] = normalizedValue;\n }\n }\n\n return normalized;\n }\n\n return value;\n}\n\n/**\n * Serialize a value to deterministic JSON string.\n */\nexport function toCanonicalJson(value: unknown): string {\n return JSON.stringify(normalizeValue(value), null, 0);\n}\n\n/**\n * Compute a SHA-256 hash of canonical JSON representation.\n */\nexport function computeHash(value: unknown): string {\n const canonical = toCanonicalJson(value);\n return createHash('sha256').update(canonical).digest('hex').slice(0, 16);\n}\n\n/**\n * Sort specs by key and version for deterministic ordering.\n */\nexport function sortSpecs<T extends { key: string; version: string }>(\n specs: T[]\n): T[] {\n return [...specs].sort((a, b) => {\n const keyCompare = a.key.localeCompare(b.key);\n if (keyCompare !== 0) return keyCompare;\n return compareVersions(a.version, b.version);\n });\n}\n\n/**\n * Sort field snapshots by name for deterministic ordering.\n */\nexport function sortFields(\n fields: Record<string, unknown>\n): Record<string, unknown> {\n const sorted: Record<string, unknown> = {};\n const keys = Object.keys(fields).sort();\n for (const key of keys) {\n sorted[key] = fields[key];\n }\n return sorted;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,SAAgB,eAAe,OAAyB;AACtD,KAAI,UAAU,QAAQ,UAAU,OAC9B,QAAO,UAAU,OAAO,OAAO;AAGjC,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,IAAI,eAAe;AAGlC,KAAI,OAAO,UAAU,UAAU;EAC7B,MAAM,MAAM;EACZ,MAAM,aAAa,OAAO,KAAK,IAAI,CAAC,MAAM;EAC1C,MAAM,aAAsC,EAAE;AAE9C,OAAK,MAAM,OAAO,YAAY;GAC5B,MAAM,kBAAkB,eAAe,IAAI,KAAK;AAEhD,OAAI,oBAAoB,OACtB,YAAW,OAAO;;AAItB,SAAO;;AAGT,QAAO;;;;;AAMT,SAAgB,gBAAgB,OAAwB;AACtD,QAAO,KAAK,UAAU,eAAe,MAAM,EAAE,MAAM,EAAE;;;;;AAMvD,SAAgB,YAAY,OAAwB;CAClD,MAAM,YAAY,gBAAgB,MAAM;AACxC,QAAO,WAAW,SAAS,CAAC,OAAO,UAAU,CAAC,OAAO,MAAM,CAAC,MAAM,GAAG,GAAG;;;;;AAM1E,SAAgB,UACd,OACK;AACL,QAAO,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM;EAC/B,MAAM,aAAa,EAAE,IAAI,cAAc,EAAE,IAAI;AAC7C,MAAI,eAAe,EAAG,QAAO;AAC7B,SAAO,gBAAgB,EAAE,SAAS,EAAE,QAAQ;GAC5C;;;;;AAMJ,SAAgB,WACd,QACyB;CACzB,MAAM,SAAkC,EAAE;CAC1C,MAAM,OAAO,OAAO,KAAK,OAAO,CAAC,MAAM;AACvC,MAAK,MAAM,OAAO,KAChB,QAAO,OAAO,OAAO;AAEvB,QAAO"}
|