@contractspec/module.workspace 1.57.0 → 1.58.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,163 +0,0 @@
|
|
|
1
|
-
import { scanSpecSource } from "../spec-scan.js";
|
|
2
|
-
import { computeHash, sortFields, sortSpecs } from "./normalizer.js";
|
|
3
|
-
|
|
4
|
-
//#region src/analysis/snapshot/snapshot.ts
|
|
5
|
-
/**
|
|
6
|
-
* Contract snapshot generation.
|
|
7
|
-
*
|
|
8
|
-
* Generates canonical, deterministic snapshots from spec source files
|
|
9
|
-
* for comparison and impact detection.
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* Generate a contract snapshot from spec source files.
|
|
13
|
-
*
|
|
14
|
-
* @param specs - Array of { path, content } for each spec file
|
|
15
|
-
* @param options - Snapshot generation options
|
|
16
|
-
* @returns Canonical contract snapshot
|
|
17
|
-
*/
|
|
18
|
-
function generateSnapshot(specs, options = {}) {
|
|
19
|
-
const snapshots = [];
|
|
20
|
-
for (const { path, content } of specs) {
|
|
21
|
-
const scanned = scanSpecSource(content, path);
|
|
22
|
-
if (options.types && !options.types.includes(scanned.specType)) continue;
|
|
23
|
-
if (scanned.specType === "operation" && scanned.key && scanned.version !== void 0) {
|
|
24
|
-
const opSnapshot = createOperationSnapshot(scanned, content);
|
|
25
|
-
if (opSnapshot) snapshots.push(opSnapshot);
|
|
26
|
-
} else if (scanned.specType === "event" && scanned.key && scanned.version !== void 0) {
|
|
27
|
-
const eventSnapshot = createEventSnapshot(scanned, content);
|
|
28
|
-
if (eventSnapshot) snapshots.push(eventSnapshot);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
const sortedSpecs = sortSpecs(snapshots);
|
|
32
|
-
const hash = computeHash({ specs: sortedSpecs });
|
|
33
|
-
return {
|
|
34
|
-
version: "1.0.0",
|
|
35
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
36
|
-
specs: sortedSpecs,
|
|
37
|
-
hash
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Create an operation snapshot from scanned spec data.
|
|
42
|
-
*/
|
|
43
|
-
function createOperationSnapshot(scanned, content) {
|
|
44
|
-
if (!scanned.key || scanned.version === void 0) return null;
|
|
45
|
-
const io = extractIoFromSource(content);
|
|
46
|
-
const http = extractHttpBinding(content);
|
|
47
|
-
return {
|
|
48
|
-
type: "operation",
|
|
49
|
-
key: scanned.key,
|
|
50
|
-
version: scanned.version,
|
|
51
|
-
kind: scanned.kind === "command" || scanned.kind === "query" ? scanned.kind : "command",
|
|
52
|
-
stability: scanned.stability ?? "experimental",
|
|
53
|
-
http: http ?? void 0,
|
|
54
|
-
io,
|
|
55
|
-
authLevel: extractAuthLevel(content),
|
|
56
|
-
emittedEvents: scanned.emittedEvents
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Create an event snapshot from scanned spec data.
|
|
61
|
-
*/
|
|
62
|
-
function createEventSnapshot(scanned, content) {
|
|
63
|
-
if (!scanned.key || scanned.version === void 0) return null;
|
|
64
|
-
const payload = extractPayloadFromSource(content);
|
|
65
|
-
return {
|
|
66
|
-
type: "event",
|
|
67
|
-
key: scanned.key,
|
|
68
|
-
version: scanned.version,
|
|
69
|
-
stability: scanned.stability ?? "experimental",
|
|
70
|
-
payload
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Extract IO schema from source code.
|
|
75
|
-
* This is a heuristic extraction - not full Zod introspection.
|
|
76
|
-
*/
|
|
77
|
-
function extractIoFromSource(content) {
|
|
78
|
-
const input = extractSchemaFields(content, "input");
|
|
79
|
-
const output = extractSchemaFields(content, "output");
|
|
80
|
-
return {
|
|
81
|
-
input: sortFields(input),
|
|
82
|
-
output: sortFields(output)
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Extract payload schema from event source code.
|
|
87
|
-
*/
|
|
88
|
-
function extractPayloadFromSource(content) {
|
|
89
|
-
return sortFields(extractSchemaFields(content, "payload"));
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Extract schema fields from a specific section of the source.
|
|
93
|
-
*/
|
|
94
|
-
function extractSchemaFields(content, section) {
|
|
95
|
-
const fields = {};
|
|
96
|
-
const sectionPattern = new RegExp(`${section}\\s*:\\s*z\\.object\\(\\{([^}]+)\\}`, "s");
|
|
97
|
-
const sectionMatch = content.match(sectionPattern);
|
|
98
|
-
if (!sectionMatch?.[1]) return fields;
|
|
99
|
-
const sectionContent = sectionMatch[1];
|
|
100
|
-
const fieldPattern = /(\w+)\s*:\s*z\.(\w+)\((.*?)\)/g;
|
|
101
|
-
let match;
|
|
102
|
-
while ((match = fieldPattern.exec(sectionContent)) !== null) {
|
|
103
|
-
const [, fieldName, zodType] = match;
|
|
104
|
-
if (!fieldName || !zodType) continue;
|
|
105
|
-
const isOptional = sectionContent.includes(`${fieldName}:`) && sectionContent.slice(sectionContent.indexOf(`${fieldName}:`)).includes(".optional()");
|
|
106
|
-
const isNullable = sectionContent.includes(`${fieldName}:`) && sectionContent.slice(sectionContent.indexOf(`${fieldName}:`)).includes(".nullable()");
|
|
107
|
-
fields[fieldName] = {
|
|
108
|
-
name: fieldName,
|
|
109
|
-
type: mapZodTypeToFieldType(zodType),
|
|
110
|
-
required: !isOptional,
|
|
111
|
-
nullable: isNullable
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
return fields;
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Map Zod type to FieldType.
|
|
118
|
-
*/
|
|
119
|
-
function mapZodTypeToFieldType(zodType) {
|
|
120
|
-
return {
|
|
121
|
-
string: "string",
|
|
122
|
-
number: "number",
|
|
123
|
-
boolean: "boolean",
|
|
124
|
-
object: "object",
|
|
125
|
-
array: "array",
|
|
126
|
-
enum: "enum",
|
|
127
|
-
union: "union",
|
|
128
|
-
literal: "literal",
|
|
129
|
-
date: "date",
|
|
130
|
-
coerce: "unknown"
|
|
131
|
-
}[zodType] ?? "unknown";
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Extract HTTP binding from source code.
|
|
135
|
-
*/
|
|
136
|
-
function extractHttpBinding(content) {
|
|
137
|
-
const methodMatch = content.match(/method\s*:\s*['"](\w+)['"]/);
|
|
138
|
-
const pathMatch = content.match(/path\s*:\s*['"]([^'"]+)['"]/);
|
|
139
|
-
if (methodMatch?.[1] && pathMatch?.[1]) {
|
|
140
|
-
const method = methodMatch[1].toUpperCase();
|
|
141
|
-
if ([
|
|
142
|
-
"GET",
|
|
143
|
-
"POST",
|
|
144
|
-
"PUT",
|
|
145
|
-
"PATCH",
|
|
146
|
-
"DELETE"
|
|
147
|
-
].includes(method)) return {
|
|
148
|
-
method,
|
|
149
|
-
path: pathMatch[1]
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Extract auth level from source code.
|
|
156
|
-
*/
|
|
157
|
-
function extractAuthLevel(content) {
|
|
158
|
-
return content.match(/auth\s*:\s*['"](\w+)['"]/)?.[1];
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
//#endregion
|
|
162
|
-
export { generateSnapshot };
|
|
163
|
-
//# sourceMappingURL=snapshot.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.js","names":[],"sources":["../../../src/analysis/snapshot/snapshot.ts"],"sourcesContent":["/**\n * Contract snapshot generation.\n *\n * Generates canonical, deterministic snapshots from spec source files\n * for comparison and impact detection.\n */\n\nimport { scanSpecSource } from '../spec-scan';\nimport { computeHash, sortSpecs, sortFields } from './normalizer';\nimport type {\n ContractSnapshot,\n EventSnapshot,\n FieldSnapshot,\n FieldType,\n IoSnapshot,\n OperationSnapshot,\n SnapshotOptions,\n SpecSnapshot,\n} from './types';\n\n/**\n * Generate a contract snapshot from spec source files.\n *\n * @param specs - Array of { path, content } for each spec file\n * @param options - Snapshot generation options\n * @returns Canonical contract snapshot\n */\nexport function generateSnapshot(\n specs: { path: string; content: string }[],\n options: SnapshotOptions = {}\n): ContractSnapshot {\n const snapshots: SpecSnapshot[] = [];\n\n for (const { path, content } of specs) {\n const scanned = scanSpecSource(content, path);\n\n // Filter by types if specified\n if (\n options.types &&\n !options.types.includes(scanned.specType as 'operation' | 'event')\n ) {\n continue;\n }\n\n if (\n scanned.specType === 'operation' &&\n scanned.key &&\n scanned.version !== undefined\n ) {\n const opSnapshot = createOperationSnapshot(scanned, content);\n if (opSnapshot) {\n snapshots.push(opSnapshot);\n }\n } else if (\n scanned.specType === 'event' &&\n scanned.key &&\n scanned.version !== undefined\n ) {\n const eventSnapshot = createEventSnapshot(scanned, content);\n if (eventSnapshot) {\n snapshots.push(eventSnapshot);\n }\n }\n }\n\n const sortedSpecs = sortSpecs(snapshots);\n const hash = computeHash({ specs: sortedSpecs });\n\n return {\n version: '1.0.0',\n generatedAt: new Date().toISOString(),\n specs: sortedSpecs,\n hash,\n };\n}\n\n/**\n * Create an operation snapshot from scanned spec data.\n */\nfunction createOperationSnapshot(\n scanned: ReturnType<typeof scanSpecSource>,\n content: string\n): OperationSnapshot | null {\n if (!scanned.key || scanned.version === undefined) {\n return null;\n }\n\n const io = extractIoFromSource(content);\n const http = extractHttpBinding(content);\n\n return {\n type: 'operation',\n key: scanned.key,\n version: scanned.version,\n kind:\n scanned.kind === 'command' || scanned.kind === 'query'\n ? scanned.kind\n : 'command',\n stability: scanned.stability ?? 'experimental',\n http: http ?? undefined,\n io,\n authLevel: extractAuthLevel(content),\n emittedEvents: scanned.emittedEvents,\n };\n}\n\n/**\n * Create an event snapshot from scanned spec data.\n */\nfunction createEventSnapshot(\n scanned: ReturnType<typeof scanSpecSource>,\n content: string\n): EventSnapshot | null {\n if (!scanned.key || scanned.version === undefined) {\n return null;\n }\n\n const payload = extractPayloadFromSource(content);\n\n return {\n type: 'event',\n key: scanned.key,\n version: scanned.version,\n stability: scanned.stability ?? 'experimental',\n payload,\n };\n}\n\n/**\n * Extract IO schema from source code.\n * This is a heuristic extraction - not full Zod introspection.\n */\nfunction extractIoFromSource(content: string): IoSnapshot {\n const input = extractSchemaFields(content, 'input');\n const output = extractSchemaFields(content, 'output');\n\n return {\n input: sortFields(input) as Record<string, FieldSnapshot>,\n output: sortFields(output) as Record<string, FieldSnapshot>,\n };\n}\n\n/**\n * Extract payload schema from event source code.\n */\nfunction extractPayloadFromSource(\n content: string\n): Record<string, FieldSnapshot> {\n const fields = extractSchemaFields(content, 'payload');\n return sortFields(fields) as Record<string, FieldSnapshot>;\n}\n\n/**\n * Extract schema fields from a specific section of the source.\n */\nfunction extractSchemaFields(\n content: string,\n section: 'input' | 'output' | 'payload'\n): Record<string, FieldSnapshot> {\n const fields: Record<string, FieldSnapshot> = {};\n\n // Look for z.object({ ... }) patterns within the section\n const sectionPattern = new RegExp(\n `${section}\\\\s*:\\\\s*z\\\\.object\\\\(\\\\{([^}]+)\\\\}`,\n 's'\n );\n const sectionMatch = content.match(sectionPattern);\n\n if (!sectionMatch?.[1]) {\n return fields;\n }\n\n const sectionContent = sectionMatch[1];\n\n // Match field definitions: fieldName: z.string(), z.number(), etc.\n const fieldPattern = /(\\w+)\\s*:\\s*z\\.(\\w+)\\((.*?)\\)/g;\n let match;\n\n while ((match = fieldPattern.exec(sectionContent)) !== null) {\n const [, fieldName, zodType] = match;\n if (!fieldName || !zodType) continue;\n\n const isOptional =\n sectionContent.includes(`${fieldName}:`) &&\n sectionContent\n .slice(sectionContent.indexOf(`${fieldName}:`))\n .includes('.optional()');\n const isNullable =\n sectionContent.includes(`${fieldName}:`) &&\n sectionContent\n .slice(sectionContent.indexOf(`${fieldName}:`))\n .includes('.nullable()');\n\n fields[fieldName] = {\n name: fieldName,\n type: mapZodTypeToFieldType(zodType),\n required: !isOptional,\n nullable: isNullable,\n };\n }\n\n return fields;\n}\n\n/**\n * Map Zod type to FieldType.\n */\nfunction mapZodTypeToFieldType(zodType: string): FieldType {\n const mapping: Record<string, FieldType> = {\n string: 'string',\n number: 'number',\n boolean: 'boolean',\n object: 'object',\n array: 'array',\n enum: 'enum',\n union: 'union',\n literal: 'literal',\n date: 'date',\n coerce: 'unknown',\n };\n return mapping[zodType] ?? 'unknown';\n}\n\n/**\n * Extract HTTP binding from source code.\n */\nfunction extractHttpBinding(content: string): {\n method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n path: string;\n} | null {\n // Look for http: { method: 'X', path: 'Y' } pattern\n const methodMatch = content.match(/method\\s*:\\s*['\"](\\w+)['\"]/);\n const pathMatch = content.match(/path\\s*:\\s*['\"]([^'\"]+)['\"]/);\n\n if (methodMatch?.[1] && pathMatch?.[1]) {\n const method = methodMatch[1].toUpperCase();\n if (['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {\n return {\n method: method as 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',\n path: pathMatch[1],\n };\n }\n }\n\n return null;\n}\n\n/**\n * Extract auth level from source code.\n */\nfunction extractAuthLevel(content: string): string | undefined {\n const authMatch = content.match(/auth\\s*:\\s*['\"](\\w+)['\"]/);\n return authMatch?.[1];\n}\n"],"mappings":"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"}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { scanAllSpecsFromSource } from "./spec-scan.js";
|
|
2
|
-
import { isFeatureFile, scanFeatureSource } from "./feature-scan.js";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
4
|
-
|
|
5
|
-
//#region src/analysis/spec-parser.ts
|
|
6
|
-
/**
|
|
7
|
-
* Spec Parser
|
|
8
|
-
*
|
|
9
|
-
* Coordinates scanning of source files and mapping to ParsedSpec format
|
|
10
|
-
* for LLM consumption.
|
|
11
|
-
*/
|
|
12
|
-
/**
|
|
13
|
-
* Load and parse specs from a source file.
|
|
14
|
-
*/
|
|
15
|
-
async function loadSpecFromSource(filePath) {
|
|
16
|
-
try {
|
|
17
|
-
const code = await readFile(filePath, "utf-8");
|
|
18
|
-
if (isFeatureFile(filePath)) return [mapFeatureResultToParsedSpec(scanFeatureSource(code, filePath))];
|
|
19
|
-
return scanAllSpecsFromSource(code, filePath).map(mapSpecResultToParsedSpec);
|
|
20
|
-
} catch (error) {
|
|
21
|
-
console.warn(`Failed to parse spec from ${filePath}:`, error);
|
|
22
|
-
return [];
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Map FeatureScanResult to ParsedSpec.
|
|
27
|
-
*/
|
|
28
|
-
function mapFeatureResultToParsedSpec(result) {
|
|
29
|
-
return {
|
|
30
|
-
meta: {
|
|
31
|
-
key: result.key,
|
|
32
|
-
version: "1.0.0",
|
|
33
|
-
description: result.description,
|
|
34
|
-
stability: result.stability,
|
|
35
|
-
owners: result.owners,
|
|
36
|
-
tags: result.tags,
|
|
37
|
-
goal: result.goal,
|
|
38
|
-
context: result.context
|
|
39
|
-
},
|
|
40
|
-
specType: "feature",
|
|
41
|
-
filePath: result.filePath,
|
|
42
|
-
sourceBlock: result.sourceBlock,
|
|
43
|
-
operations: result.operations.map(mapToSpecRef),
|
|
44
|
-
events: result.events.map(mapToSpecRef),
|
|
45
|
-
presentations: result.presentations.map(mapToSpecRef)
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Map SpecScanResult to ParsedSpec.
|
|
50
|
-
*/
|
|
51
|
-
function mapSpecResultToParsedSpec(result) {
|
|
52
|
-
return {
|
|
53
|
-
meta: {
|
|
54
|
-
key: result.key ?? "unknown",
|
|
55
|
-
version: result.version ?? "1.0.0",
|
|
56
|
-
description: result.description,
|
|
57
|
-
stability: result.stability,
|
|
58
|
-
owners: result.owners,
|
|
59
|
-
tags: result.tags,
|
|
60
|
-
goal: result.goal,
|
|
61
|
-
context: result.context
|
|
62
|
-
},
|
|
63
|
-
specType: result.specType,
|
|
64
|
-
kind: result.kind,
|
|
65
|
-
hasIo: result.hasIo,
|
|
66
|
-
hasPolicy: result.hasPolicy,
|
|
67
|
-
hasPayload: result.hasPayload,
|
|
68
|
-
hasContent: result.hasContent,
|
|
69
|
-
hasDefinition: result.hasDefinition,
|
|
70
|
-
emittedEvents: result.emittedEvents?.map(mapToSpecRef),
|
|
71
|
-
policyRefs: result.policyRefs?.map(mapToSpecRef),
|
|
72
|
-
testRefs: result.testRefs?.map(mapToSpecRef),
|
|
73
|
-
filePath: result.filePath,
|
|
74
|
-
sourceBlock: result.sourceBlock
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Map RefInfo to SpecRef.
|
|
79
|
-
*/
|
|
80
|
-
function mapToSpecRef(ref) {
|
|
81
|
-
return {
|
|
82
|
-
name: ref.key,
|
|
83
|
-
version: ref.version
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
//#endregion
|
|
88
|
-
export { loadSpecFromSource };
|
|
89
|
-
//# sourceMappingURL=spec-parser.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"spec-parser.js","names":[],"sources":["../../src/analysis/spec-parser.ts"],"sourcesContent":["/**\n * Spec Parser\n *\n * Coordinates scanning of source files and mapping to ParsedSpec format\n * for LLM consumption.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { isFeatureFile, scanFeatureSource } from './feature-scan';\nimport { scanAllSpecsFromSource } from './spec-scan';\nimport type { ParsedSpec, ParsedSpecMeta, SpecRef } from '../types/llm-types';\nimport type {\n FeatureScanResult,\n RefInfo,\n SpecScanResult,\n} from '../types/analysis-types';\n\n/**\n * Load and parse specs from a source file.\n */\nexport async function loadSpecFromSource(\n filePath: string\n): Promise<ParsedSpec[]> {\n try {\n const code = await readFile(filePath, 'utf-8');\n\n if (isFeatureFile(filePath)) {\n const featureResult = scanFeatureSource(code, filePath);\n return [mapFeatureResultToParsedSpec(featureResult)];\n }\n\n const specResults = scanAllSpecsFromSource(code, filePath);\n return specResults.map(mapSpecResultToParsedSpec);\n } catch (error) {\n // Return empty array if file reading fails\n console.warn(`Failed to parse spec from ${filePath}:`, error);\n return [];\n }\n}\n\n/**\n * Map FeatureScanResult to ParsedSpec.\n */\nfunction mapFeatureResultToParsedSpec(result: FeatureScanResult): ParsedSpec {\n const meta: ParsedSpecMeta = {\n key: result.key,\n version: '1.0.0', // Default for features if not specified\n description: result.description,\n stability: result.stability,\n owners: result.owners,\n tags: result.tags,\n goal: result.goal,\n context: result.context,\n };\n\n return {\n meta,\n specType: 'feature',\n filePath: result.filePath,\n sourceBlock: result.sourceBlock,\n operations: result.operations.map(mapToSpecRef),\n events: result.events.map(mapToSpecRef),\n presentations: result.presentations.map(mapToSpecRef),\n };\n}\n\n/**\n * Map SpecScanResult to ParsedSpec.\n */\nfunction mapSpecResultToParsedSpec(result: SpecScanResult): ParsedSpec {\n const meta: ParsedSpecMeta = {\n key: result.key ?? 'unknown',\n version: result.version ?? '1.0.0',\n description: result.description,\n stability: result.stability,\n owners: result.owners,\n tags: result.tags,\n goal: result.goal,\n context: result.context,\n };\n\n return {\n meta,\n specType: result.specType,\n kind: result.kind,\n hasIo: result.hasIo,\n hasPolicy: result.hasPolicy,\n hasPayload: result.hasPayload,\n hasContent: result.hasContent,\n hasDefinition: result.hasDefinition,\n emittedEvents: result.emittedEvents?.map(mapToSpecRef),\n policyRefs: result.policyRefs?.map(mapToSpecRef),\n testRefs: result.testRefs?.map(mapToSpecRef),\n filePath: result.filePath,\n sourceBlock: result.sourceBlock,\n };\n}\n\n/**\n * Map RefInfo to SpecRef.\n */\nfunction mapToSpecRef(ref: RefInfo): SpecRef {\n return {\n name: ref.key,\n version: ref.version,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAoBA,eAAsB,mBACpB,UACuB;AACvB,KAAI;EACF,MAAM,OAAO,MAAM,SAAS,UAAU,QAAQ;AAE9C,MAAI,cAAc,SAAS,CAEzB,QAAO,CAAC,6BADc,kBAAkB,MAAM,SAAS,CACJ,CAAC;AAItD,SADoB,uBAAuB,MAAM,SAAS,CACvC,IAAI,0BAA0B;UAC1C,OAAO;AAEd,UAAQ,KAAK,6BAA6B,SAAS,IAAI,MAAM;AAC7D,SAAO,EAAE;;;;;;AAOb,SAAS,6BAA6B,QAAuC;AAY3E,QAAO;EACL,MAZ2B;GAC3B,KAAK,OAAO;GACZ,SAAS;GACT,aAAa,OAAO;GACpB,WAAW,OAAO;GAClB,QAAQ,OAAO;GACf,MAAM,OAAO;GACb,MAAM,OAAO;GACb,SAAS,OAAO;GACjB;EAIC,UAAU;EACV,UAAU,OAAO;EACjB,aAAa,OAAO;EACpB,YAAY,OAAO,WAAW,IAAI,aAAa;EAC/C,QAAQ,OAAO,OAAO,IAAI,aAAa;EACvC,eAAe,OAAO,cAAc,IAAI,aAAa;EACtD;;;;;AAMH,SAAS,0BAA0B,QAAoC;AAYrE,QAAO;EACL,MAZ2B;GAC3B,KAAK,OAAO,OAAO;GACnB,SAAS,OAAO,WAAW;GAC3B,aAAa,OAAO;GACpB,WAAW,OAAO;GAClB,QAAQ,OAAO;GACf,MAAM,OAAO;GACb,MAAM,OAAO;GACb,SAAS,OAAO;GACjB;EAIC,UAAU,OAAO;EACjB,MAAM,OAAO;EACb,OAAO,OAAO;EACd,WAAW,OAAO;EAClB,YAAY,OAAO;EACnB,YAAY,OAAO;EACnB,eAAe,OAAO;EACtB,eAAe,OAAO,eAAe,IAAI,aAAa;EACtD,YAAY,OAAO,YAAY,IAAI,aAAa;EAChD,UAAU,OAAO,UAAU,IAAI,aAAa;EAC5C,UAAU,OAAO;EACjB,aAAa,OAAO;EACrB;;;;;AAMH,SAAS,aAAa,KAAuB;AAC3C,QAAO;EACL,MAAM,IAAI;EACV,SAAS,IAAI;EACd"}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { escapeRegex, findMatchingDelimiter, matchStringField, matchVersionField } from "./utils/matchers.js";
|
|
2
|
-
|
|
3
|
-
//#region src/analysis/spec-parsing-utils.ts
|
|
4
|
-
function parsePolicy(code) {
|
|
5
|
-
const policyBlock = code.match(/policy\s*:\s*\{([\s\S]*?)\}/);
|
|
6
|
-
if (!policyBlock?.[1]) return [];
|
|
7
|
-
return extractRefList(policyBlock[1], "policies") ?? [];
|
|
8
|
-
}
|
|
9
|
-
function extractRefList(code, field) {
|
|
10
|
-
const regex = new RegExp(`${escapeRegex(field)}\\s*:\\s*\\[([\\s\\S]*?)\\]`);
|
|
11
|
-
const match = code.match(regex);
|
|
12
|
-
if (!match?.[1]) return void 0;
|
|
13
|
-
const inner = match[1];
|
|
14
|
-
const items = [];
|
|
15
|
-
const parts = inner.match(/\{[\s\S]*?\}/g);
|
|
16
|
-
if (parts) for (const part of parts) {
|
|
17
|
-
const k = matchStringField(part, "key");
|
|
18
|
-
const v = matchVersionField(part, "version");
|
|
19
|
-
if (k) items.push({
|
|
20
|
-
key: k,
|
|
21
|
-
version: v ?? "1.0.0"
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
return items.length > 0 ? items : void 0;
|
|
25
|
-
}
|
|
26
|
-
function extractTestRefs(code) {
|
|
27
|
-
const regex = new RegExp(`testRefs\\s*:\\s*\\[([\\s\\S]*?)\\]`);
|
|
28
|
-
const match = code.match(regex);
|
|
29
|
-
if (!match?.[1]) return void 0;
|
|
30
|
-
const inner = match[1];
|
|
31
|
-
const items = [];
|
|
32
|
-
const parts = inner.match(/\{[\s\S]*?\}/g);
|
|
33
|
-
if (parts) for (const part of parts) {
|
|
34
|
-
const k = matchStringField(part, "key");
|
|
35
|
-
const v = matchVersionField(part, "version");
|
|
36
|
-
const t = matchStringField(part, "type");
|
|
37
|
-
if (k) items.push({
|
|
38
|
-
key: k,
|
|
39
|
-
version: v ?? "1.0.0",
|
|
40
|
-
type: t === "error" ? "error" : "success"
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
return items.length > 0 ? items : void 0;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Extract test target from a TestSpec source.
|
|
47
|
-
* Parses the `target: { type: 'operation', key, version }` field OR
|
|
48
|
-
* the nested format `target: { type: 'operation', operation: { key, version } }`.
|
|
49
|
-
*/
|
|
50
|
-
function extractTestTarget(code) {
|
|
51
|
-
const targetStartMatch = code.match(/target\s*:\s*\{/);
|
|
52
|
-
if (!targetStartMatch || targetStartMatch.index === void 0) return void 0;
|
|
53
|
-
const openBraceIndex = targetStartMatch.index + targetStartMatch[0].length - 1;
|
|
54
|
-
const closeBraceIndex = findMatchingDelimiter(code, openBraceIndex, "{", "}");
|
|
55
|
-
if (closeBraceIndex === -1) return void 0;
|
|
56
|
-
const targetBlock = code.substring(openBraceIndex + 1, closeBraceIndex);
|
|
57
|
-
const typeMatch = targetBlock.match(/type\s*:\s*['"](\w+)['"]/);
|
|
58
|
-
if (!typeMatch?.[1]) return void 0;
|
|
59
|
-
const type = typeMatch[1];
|
|
60
|
-
if (type !== "operation" && type !== "workflow") return void 0;
|
|
61
|
-
const flatKey = matchStringField(targetBlock, "key");
|
|
62
|
-
if (flatKey) return {
|
|
63
|
-
type,
|
|
64
|
-
key: flatKey,
|
|
65
|
-
version: matchVersionField(targetBlock, "version")
|
|
66
|
-
};
|
|
67
|
-
const refBlockMatch = targetBlock.match(new RegExp(`${type}\\s*:\\s*\\{([\\s\\S]*?)\\}`));
|
|
68
|
-
if (!refBlockMatch?.[1]) return void 0;
|
|
69
|
-
const refBlock = refBlockMatch[1];
|
|
70
|
-
const key = matchStringField(refBlock, "key");
|
|
71
|
-
if (!key) return void 0;
|
|
72
|
-
return {
|
|
73
|
-
type,
|
|
74
|
-
key,
|
|
75
|
-
version: matchVersionField(refBlock, "version")
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Extract test coverage info from a TestSpec source.
|
|
80
|
-
* Checks for presence of success (expectOutput) and failure (expectError) scenarios.
|
|
81
|
-
* Supports both formats:
|
|
82
|
-
* - New: `expectOutput: {}` and `expectError: {}`
|
|
83
|
-
* - Old: `{ type: 'expectOutput', ... }` and `{ type: 'expectError', ... }`
|
|
84
|
-
*/
|
|
85
|
-
function extractTestCoverage(code) {
|
|
86
|
-
const hasSuccessNew = /expectOutput\s*:/.test(code);
|
|
87
|
-
const hasErrorNew = /expectError\s*:/.test(code);
|
|
88
|
-
const hasSuccessOld = /(['"]?)type\1\s*:\s*['"]expectOutput['"]/.test(code);
|
|
89
|
-
const hasErrorOld = /(['"]?)type\1\s*:\s*['"]expectError['"]/.test(code);
|
|
90
|
-
return {
|
|
91
|
-
hasSuccess: hasSuccessNew || hasSuccessOld,
|
|
92
|
-
hasError: hasErrorNew || hasErrorOld
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
//#endregion
|
|
97
|
-
export { extractRefList, extractTestCoverage, extractTestRefs, extractTestTarget, parsePolicy };
|
|
98
|
-
//# sourceMappingURL=spec-parsing-utils.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"spec-parsing-utils.js","names":[],"sources":["../../src/analysis/spec-parsing-utils.ts"],"sourcesContent":["import {\n escapeRegex,\n matchStringField,\n matchVersionField,\n findMatchingDelimiter,\n} from './utils/matchers';\n\nexport function parsePolicy(code: string): { key: string; version: string }[] {\n const policyBlock = code.match(/policy\\s*:\\s*\\{([\\s\\S]*?)\\}/);\n if (!policyBlock?.[1]) return [];\n\n return extractRefList(policyBlock[1], 'policies') ?? [];\n}\n\nexport function extractRefList(\n code: string,\n field: string\n): { key: string; version: string }[] | undefined {\n const regex = new RegExp(`${escapeRegex(field)}\\\\s*:\\\\s*\\\\[([\\\\s\\\\S]*?)\\\\]`);\n const match = code.match(regex);\n if (!match?.[1]) return undefined;\n\n const inner = match[1];\n const items: { key: string; version: string }[] = [];\n\n const parts = inner.match(/\\{[\\s\\S]*?\\}/g);\n if (parts) {\n for (const part of parts) {\n const k = matchStringField(part, 'key');\n const v = matchVersionField(part, 'version');\n if (k) {\n items.push({ key: k, version: v ?? '1.0.0' });\n }\n }\n }\n\n return items.length > 0 ? items : undefined;\n}\n\nexport function extractTestRefs(\n code: string\n): { key: string; version: string; type: 'success' | 'error' }[] | undefined {\n const regex = new RegExp(`testRefs\\\\s*:\\\\s*\\\\[([\\\\s\\\\S]*?)\\\\]`);\n const match = code.match(regex);\n if (!match?.[1]) return undefined;\n\n const inner = match[1];\n const items: { key: string; version: string; type: 'success' | 'error' }[] =\n [];\n\n const parts = inner.match(/\\{[\\s\\S]*?\\}/g);\n if (parts) {\n for (const part of parts) {\n const k = matchStringField(part, 'key');\n const v = matchVersionField(part, 'version');\n const t = matchStringField(part, 'type');\n if (k) {\n items.push({\n key: k,\n version: v ?? '1.0.0',\n type: t === 'error' ? 'error' : 'success',\n });\n }\n }\n }\n\n return items.length > 0 ? items : undefined;\n}\n\n/**\n * Extract test target from a TestSpec source.\n * Parses the `target: { type: 'operation', key, version }` field OR\n * the nested format `target: { type: 'operation', operation: { key, version } }`.\n */\nexport function extractTestTarget(\n code: string\n):\n | { type: 'operation' | 'workflow'; key: string; version: string | undefined }\n | undefined {\n // Find target block start\n const targetStartMatch = code.match(/target\\s*:\\s*\\{/);\n if (!targetStartMatch || targetStartMatch.index === undefined)\n return undefined;\n\n const openBraceIndex =\n targetStartMatch.index + targetStartMatch[0].length - 1;\n const closeBraceIndex = findMatchingDelimiter(code, openBraceIndex, '{', '}');\n\n if (closeBraceIndex === -1) return undefined;\n\n const targetBlock = code.substring(openBraceIndex + 1, closeBraceIndex);\n\n // Extract the type\n const typeMatch = targetBlock.match(/type\\s*:\\s*['\"](\\w+)['\"]/);\n if (!typeMatch?.[1]) return undefined;\n\n const type = typeMatch[1];\n if (type !== 'operation' && type !== 'workflow') return undefined;\n\n // Try flat format first: { type: 'operation', key: '...', version: '...' }\n const flatKey = matchStringField(targetBlock, 'key');\n if (flatKey) {\n const flatVersion = matchVersionField(targetBlock, 'version');\n return {\n type,\n key: flatKey,\n version: flatVersion,\n };\n }\n\n // Try nested format: { type: 'operation', operation: { key: '...', version: '...' } }\n const refBlockMatch = targetBlock.match(\n new RegExp(`${type}\\\\s*:\\\\s*\\\\{([\\\\s\\\\S]*?)\\\\}`)\n );\n\n if (!refBlockMatch?.[1]) return undefined;\n\n const refBlock = refBlockMatch[1];\n\n // Extract key and version from the ref block\n const key = matchStringField(refBlock, 'key');\n if (!key) return undefined;\n\n const version = matchVersionField(refBlock, 'version');\n\n return {\n type,\n key,\n version,\n };\n}\n\n/**\n * Extract test coverage info from a TestSpec source.\n * Checks for presence of success (expectOutput) and failure (expectError) scenarios.\n * Supports both formats:\n * - New: `expectOutput: {}` and `expectError: {}`\n * - Old: `{ type: 'expectOutput', ... }` and `{ type: 'expectError', ... }`\n */\nexport function extractTestCoverage(code: string): {\n hasSuccess: boolean;\n hasError: boolean;\n} {\n // Check new format: expectOutput: or expectError: as keys\n const hasSuccessNew = /expectOutput\\s*:/.test(code);\n const hasErrorNew = /expectError\\s*:/.test(code);\n\n // Check old format: { type: 'expectOutput' } or { type: 'expectError' }\n const hasSuccessOld = /(['\"]?)type\\1\\s*:\\s*['\"]expectOutput['\"]/.test(code);\n const hasErrorOld = /(['\"]?)type\\1\\s*:\\s*['\"]expectError['\"]/.test(code);\n\n return {\n hasSuccess: hasSuccessNew || hasSuccessOld,\n hasError: hasErrorNew || hasErrorOld,\n };\n}\n"],"mappings":";;;AAOA,SAAgB,YAAY,MAAkD;CAC5E,MAAM,cAAc,KAAK,MAAM,8BAA8B;AAC7D,KAAI,CAAC,cAAc,GAAI,QAAO,EAAE;AAEhC,QAAO,eAAe,YAAY,IAAI,WAAW,IAAI,EAAE;;AAGzD,SAAgB,eACd,MACA,OACgD;CAChD,MAAM,QAAQ,IAAI,OAAO,GAAG,YAAY,MAAM,CAAC,6BAA6B;CAC5E,MAAM,QAAQ,KAAK,MAAM,MAAM;AAC/B,KAAI,CAAC,QAAQ,GAAI,QAAO;CAExB,MAAM,QAAQ,MAAM;CACpB,MAAM,QAA4C,EAAE;CAEpD,MAAM,QAAQ,MAAM,MAAM,gBAAgB;AAC1C,KAAI,MACF,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,IAAI,iBAAiB,MAAM,MAAM;EACvC,MAAM,IAAI,kBAAkB,MAAM,UAAU;AAC5C,MAAI,EACF,OAAM,KAAK;GAAE,KAAK;GAAG,SAAS,KAAK;GAAS,CAAC;;AAKnD,QAAO,MAAM,SAAS,IAAI,QAAQ;;AAGpC,SAAgB,gBACd,MAC2E;CAC3E,MAAM,QAAQ,IAAI,OAAO,sCAAsC;CAC/D,MAAM,QAAQ,KAAK,MAAM,MAAM;AAC/B,KAAI,CAAC,QAAQ,GAAI,QAAO;CAExB,MAAM,QAAQ,MAAM;CACpB,MAAM,QACJ,EAAE;CAEJ,MAAM,QAAQ,MAAM,MAAM,gBAAgB;AAC1C,KAAI,MACF,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,IAAI,iBAAiB,MAAM,MAAM;EACvC,MAAM,IAAI,kBAAkB,MAAM,UAAU;EAC5C,MAAM,IAAI,iBAAiB,MAAM,OAAO;AACxC,MAAI,EACF,OAAM,KAAK;GACT,KAAK;GACL,SAAS,KAAK;GACd,MAAM,MAAM,UAAU,UAAU;GACjC,CAAC;;AAKR,QAAO,MAAM,SAAS,IAAI,QAAQ;;;;;;;AAQpC,SAAgB,kBACd,MAGY;CAEZ,MAAM,mBAAmB,KAAK,MAAM,kBAAkB;AACtD,KAAI,CAAC,oBAAoB,iBAAiB,UAAU,OAClD,QAAO;CAET,MAAM,iBACJ,iBAAiB,QAAQ,iBAAiB,GAAG,SAAS;CACxD,MAAM,kBAAkB,sBAAsB,MAAM,gBAAgB,KAAK,IAAI;AAE7E,KAAI,oBAAoB,GAAI,QAAO;CAEnC,MAAM,cAAc,KAAK,UAAU,iBAAiB,GAAG,gBAAgB;CAGvE,MAAM,YAAY,YAAY,MAAM,2BAA2B;AAC/D,KAAI,CAAC,YAAY,GAAI,QAAO;CAE5B,MAAM,OAAO,UAAU;AACvB,KAAI,SAAS,eAAe,SAAS,WAAY,QAAO;CAGxD,MAAM,UAAU,iBAAiB,aAAa,MAAM;AACpD,KAAI,QAEF,QAAO;EACL;EACA,KAAK;EACL,SAJkB,kBAAkB,aAAa,UAAU;EAK5D;CAIH,MAAM,gBAAgB,YAAY,MAChC,IAAI,OAAO,GAAG,KAAK,6BAA6B,CACjD;AAED,KAAI,CAAC,gBAAgB,GAAI,QAAO;CAEhC,MAAM,WAAW,cAAc;CAG/B,MAAM,MAAM,iBAAiB,UAAU,MAAM;AAC7C,KAAI,CAAC,IAAK,QAAO;AAIjB,QAAO;EACL;EACA;EACA,SALc,kBAAkB,UAAU,UAAU;EAMrD;;;;;;;;;AAUH,SAAgB,oBAAoB,MAGlC;CAEA,MAAM,gBAAgB,mBAAmB,KAAK,KAAK;CACnD,MAAM,cAAc,kBAAkB,KAAK,KAAK;CAGhD,MAAM,gBAAgB,2CAA2C,KAAK,KAAK;CAC3E,MAAM,cAAc,0CAA0C,KAAK,KAAK;AAExE,QAAO;EACL,YAAY,iBAAiB;EAC7B,UAAU,eAAe;EAC1B"}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { findMatchingDelimiter, isStability, matchStringArrayField, matchStringField, matchVersionField } from "./utils/matchers.js";
|
|
2
|
-
import { extractArrayConstants, resolveVariablesInBlock } from "./utils/variables.js";
|
|
3
|
-
import { extractRefList, extractTestCoverage, extractTestRefs, extractTestTarget, parsePolicy } from "./spec-parsing-utils.js";
|
|
4
|
-
|
|
5
|
-
//#region src/analysis/spec-scan.ts
|
|
6
|
-
/**
|
|
7
|
-
* Scan all specs from a single source file.
|
|
8
|
-
*/
|
|
9
|
-
function scanAllSpecsFromSource(code, filePath) {
|
|
10
|
-
const results = [];
|
|
11
|
-
const variables = extractArrayConstants(code);
|
|
12
|
-
const definitionRegex = /export\s+const\s+(\w+)\s*=\s*define(Command|Query|Event|Presentation|Capability|Policy|Type|Example|AppConfig|Integration|Workflow|TestSpec|Feature)\s*\(/g;
|
|
13
|
-
let match;
|
|
14
|
-
while ((match = definitionRegex.exec(code)) !== null) {
|
|
15
|
-
const start = match.index;
|
|
16
|
-
const end = findMatchingDelimiter(code, start + match[0].length - 1, "(", ")");
|
|
17
|
-
if (end === -1) continue;
|
|
18
|
-
let finalEnd = end;
|
|
19
|
-
if (code[finalEnd + 1] === ";") finalEnd++;
|
|
20
|
-
const resolvedBlock = resolveVariablesInBlock(code.substring(start, finalEnd + 1), variables);
|
|
21
|
-
const result = scanSpecSource(resolvedBlock, filePath);
|
|
22
|
-
if (result) results.push({
|
|
23
|
-
...result,
|
|
24
|
-
sourceBlock: resolvedBlock
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
if (results.length === 0 && filePath.includes(".spec.")) {
|
|
28
|
-
if (scanSpecSource(code, filePath).key !== "unknown") {
|
|
29
|
-
const result = scanSpecSource(resolveVariablesInBlock(code, variables), filePath);
|
|
30
|
-
results.push(result);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return results;
|
|
34
|
-
}
|
|
35
|
-
function inferSpecTypeFromCodeBlock(fileSourceCode) {
|
|
36
|
-
if (fileSourceCode.includes("defineCommand")) return {
|
|
37
|
-
specType: "operation",
|
|
38
|
-
kind: "command"
|
|
39
|
-
};
|
|
40
|
-
if (fileSourceCode.includes("defineQuery")) return {
|
|
41
|
-
specType: "operation",
|
|
42
|
-
kind: "query"
|
|
43
|
-
};
|
|
44
|
-
if (fileSourceCode.includes("defineEvent")) return {
|
|
45
|
-
specType: "event",
|
|
46
|
-
kind: "event"
|
|
47
|
-
};
|
|
48
|
-
if (fileSourceCode.includes("definePresentation")) return {
|
|
49
|
-
specType: "presentation",
|
|
50
|
-
kind: "presentation"
|
|
51
|
-
};
|
|
52
|
-
if (fileSourceCode.includes("definePolicy")) return {
|
|
53
|
-
specType: "policy",
|
|
54
|
-
kind: "policy"
|
|
55
|
-
};
|
|
56
|
-
if (fileSourceCode.includes("defineCapability")) return {
|
|
57
|
-
specType: "capability",
|
|
58
|
-
kind: "capability"
|
|
59
|
-
};
|
|
60
|
-
if (fileSourceCode.includes("defineExample")) return {
|
|
61
|
-
specType: "example",
|
|
62
|
-
kind: "example"
|
|
63
|
-
};
|
|
64
|
-
if (fileSourceCode.includes("defineAppConfig") && !fileSourceCode.includes("export const defineAppConfig")) return {
|
|
65
|
-
specType: "app-config",
|
|
66
|
-
kind: "app-config"
|
|
67
|
-
};
|
|
68
|
-
if (fileSourceCode.includes("defineIntegration")) return {
|
|
69
|
-
specType: "integration",
|
|
70
|
-
kind: "integration"
|
|
71
|
-
};
|
|
72
|
-
if (fileSourceCode.includes("defineWorkflow")) return {
|
|
73
|
-
specType: "workflow",
|
|
74
|
-
kind: "workflow"
|
|
75
|
-
};
|
|
76
|
-
if (fileSourceCode.includes("defineTestSpec")) return {
|
|
77
|
-
specType: "test-spec",
|
|
78
|
-
kind: "test-spec"
|
|
79
|
-
};
|
|
80
|
-
if (fileSourceCode.includes("defineFeature")) return {
|
|
81
|
-
specType: "feature",
|
|
82
|
-
kind: "feature"
|
|
83
|
-
};
|
|
84
|
-
return {
|
|
85
|
-
specType: "unknown",
|
|
86
|
-
kind: "unknown"
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Scan a single spec source string.
|
|
91
|
-
*/
|
|
92
|
-
function scanSpecSource(code, filePath) {
|
|
93
|
-
const key = (code.match(/key\s*:\s*['"]([^'"]+)['"]/) ?? code.match(/export\s+const\s+(\w+)\s*=/))?.[1] ?? "unknown";
|
|
94
|
-
const version = matchVersionField(code, "version");
|
|
95
|
-
const description = matchStringField(code, "description") ?? void 0;
|
|
96
|
-
const goal = matchStringField(code, "goal") ?? void 0;
|
|
97
|
-
const context = matchStringField(code, "context") ?? void 0;
|
|
98
|
-
const stabilityRaw = matchStringField(code, "stability");
|
|
99
|
-
const stability = isStability(stabilityRaw) ? stabilityRaw : void 0;
|
|
100
|
-
const owners = matchStringArrayField(code, "owners");
|
|
101
|
-
const tags = matchStringArrayField(code, "tags");
|
|
102
|
-
const inferredSpecType = inferSpecTypeFromCodeBlock(code);
|
|
103
|
-
const hasMeta = /meta\s*:\s*\{/.test(code);
|
|
104
|
-
const hasIo = /io\s*:\s*\{/.test(code);
|
|
105
|
-
const hasPolicy = /policy\s*:\s*\{/.test(code);
|
|
106
|
-
const hasPayload = /payload\s*:\s*\{/.test(code);
|
|
107
|
-
const hasContent = /content\s*:\s*\{/.test(code);
|
|
108
|
-
const hasDefinition = /definition\s*:\s*\{/.test(code);
|
|
109
|
-
const emittedEvents = extractRefList(code, "emits") ?? extractRefList(code, "emittedEvents");
|
|
110
|
-
const testRefs = extractTestRefs(code);
|
|
111
|
-
const policyRefs = hasPolicy ? parsePolicy(code) : void 0;
|
|
112
|
-
return {
|
|
113
|
-
filePath,
|
|
114
|
-
key,
|
|
115
|
-
version,
|
|
116
|
-
specType: inferredSpecType.specType,
|
|
117
|
-
kind: inferredSpecType.kind,
|
|
118
|
-
description,
|
|
119
|
-
goal,
|
|
120
|
-
context,
|
|
121
|
-
stability,
|
|
122
|
-
owners,
|
|
123
|
-
tags,
|
|
124
|
-
hasMeta,
|
|
125
|
-
hasIo,
|
|
126
|
-
hasPolicy,
|
|
127
|
-
hasPayload,
|
|
128
|
-
hasContent,
|
|
129
|
-
hasDefinition,
|
|
130
|
-
emittedEvents,
|
|
131
|
-
policyRefs,
|
|
132
|
-
testRefs,
|
|
133
|
-
testTarget: extractTestTarget(code),
|
|
134
|
-
testCoverage: extractTestCoverage(code),
|
|
135
|
-
sourceBlock: code
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Infer spec type from file path convention.
|
|
140
|
-
*/
|
|
141
|
-
function inferSpecTypeFromFilePath(filePath) {
|
|
142
|
-
if (filePath.includes(".contracts.") || /\/operations?\//.test(filePath)) return "operation";
|
|
143
|
-
if (filePath.includes(".event.") || /\/events?\//.test(filePath)) return "event";
|
|
144
|
-
if (filePath.includes(".presentation.") || /\/presentations?\//.test(filePath)) return "presentation";
|
|
145
|
-
if (filePath.includes(".policy.") || /\/policies?\//.test(filePath)) return "policy";
|
|
146
|
-
if (filePath.includes(".feature.") || /\/features?\//.test(filePath)) return "feature";
|
|
147
|
-
if (filePath.includes(".type.") || /\/types?\//.test(filePath)) return "type";
|
|
148
|
-
if (filePath.includes(".example.") || /\/examples?\//.test(filePath)) return "example";
|
|
149
|
-
if (filePath.includes(".app-config.")) return "app-config";
|
|
150
|
-
if (filePath.includes(".workflow.") || /\/workflows?\//.test(filePath)) return "workflow";
|
|
151
|
-
if (filePath.includes(".integration.") || /\/integrations?\//.test(filePath)) return "integration";
|
|
152
|
-
return "unknown";
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
//#endregion
|
|
156
|
-
export { inferSpecTypeFromCodeBlock, inferSpecTypeFromFilePath, scanAllSpecsFromSource, scanSpecSource };
|
|
157
|
-
//# sourceMappingURL=spec-scan.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"spec-scan.js","names":[],"sources":["../../src/analysis/spec-scan.ts"],"sourcesContent":["/**\n * Spec scanning utilities.\n *\n * Scans source code to extract spec definitions and metadata without execution.\n */\n\nimport type {\n AnalyzedOperationKind,\n AnalyzedSpecType,\n SpecScanResult,\n} from '../types/analysis-types';\nimport {\n extractArrayConstants,\n resolveVariablesInBlock,\n} from './utils/variables';\n\nimport {\n findMatchingDelimiter,\n isStability,\n matchStringArrayField,\n matchStringField,\n matchVersionField,\n} from './utils/matchers';\n\nimport {\n extractRefList,\n extractTestCoverage,\n extractTestRefs,\n extractTestTarget,\n parsePolicy,\n} from './spec-parsing-utils';\n\nexport { extractTestTarget, extractTestCoverage } from './spec-parsing-utils';\n\n/**\n * Scan all specs from a single source file.\n */\nexport function scanAllSpecsFromSource(\n code: string,\n filePath: string\n): SpecScanResult[] {\n const results: SpecScanResult[] = [];\n\n // Pre-scan for variables available in file scope\n const variables = extractArrayConstants(code);\n\n // Match export definitions: export const X = defineXXX calls\n const definitionRegex =\n /export\\s+const\\s+(\\w+)\\s*=\\s*define(Command|Query|Event|Presentation|Capability|Policy|Type|Example|AppConfig|Integration|Workflow|TestSpec|Feature)\\s*\\(/g;\n let match;\n\n while ((match = definitionRegex.exec(code)) !== null) {\n const start = match.index;\n const openParenIndex = start + match[0].length - 1;\n const end = findMatchingDelimiter(code, openParenIndex, '(', ')');\n if (end === -1) continue;\n\n // Optional: include trailing semicolon\n let finalEnd = end;\n if (code[finalEnd + 1] === ';') {\n finalEnd++;\n }\n\n const block = code.substring(start, finalEnd + 1);\n\n // Resolve variables in the block\n const resolvedBlock = resolveVariablesInBlock(block, variables);\n\n const result = scanSpecSource(resolvedBlock, filePath);\n if (result) {\n results.push({\n ...result,\n sourceBlock: resolvedBlock, // Ensure the result has the resolved block\n });\n }\n }\n\n // Fallback: legacy file scan if no explicit exports found\n if (results.length === 0 && filePath.includes('.spec.')) {\n const result = scanSpecSource(code, filePath);\n if (result.key !== 'unknown') {\n // Try to resolve globally even for fallback (though scope is harder)\n const resolvedBlock = resolveVariablesInBlock(code, variables);\n const result = scanSpecSource(resolvedBlock, filePath);\n results.push(result);\n }\n }\n\n return results;\n}\n\nexport function inferSpecTypeFromCodeBlock(fileSourceCode: string): {\n specType: AnalyzedSpecType;\n kind: AnalyzedOperationKind;\n} {\n if (fileSourceCode.includes('defineCommand')) {\n return {\n specType: 'operation',\n kind: 'command',\n };\n }\n if (fileSourceCode.includes('defineQuery')) {\n return {\n specType: 'operation',\n kind: 'query',\n };\n }\n if (fileSourceCode.includes('defineEvent')) {\n return {\n specType: 'event',\n kind: 'event',\n };\n }\n if (fileSourceCode.includes('definePresentation')) {\n return {\n specType: 'presentation',\n kind: 'presentation',\n };\n }\n if (fileSourceCode.includes('definePolicy')) {\n return {\n specType: 'policy',\n kind: 'policy',\n };\n }\n if (fileSourceCode.includes('defineCapability')) {\n return {\n specType: 'capability',\n kind: 'capability',\n };\n }\n if (fileSourceCode.includes('defineExample')) {\n return {\n specType: 'example',\n kind: 'example',\n };\n }\n if (\n fileSourceCode.includes('defineAppConfig') &&\n !fileSourceCode.includes('export const defineAppConfig')\n ) {\n return {\n specType: 'app-config',\n kind: 'app-config',\n };\n }\n if (fileSourceCode.includes('defineIntegration')) {\n return {\n specType: 'integration',\n kind: 'integration',\n };\n }\n if (fileSourceCode.includes('defineWorkflow')) {\n return {\n specType: 'workflow',\n kind: 'workflow',\n };\n }\n if (fileSourceCode.includes('defineTestSpec')) {\n return {\n specType: 'test-spec',\n kind: 'test-spec',\n };\n }\n\n if (fileSourceCode.includes('defineFeature')) {\n return {\n specType: 'feature',\n kind: 'feature',\n };\n }\n\n return {\n specType: 'unknown',\n kind: 'unknown',\n };\n}\n\n/**\n * Scan a single spec source string.\n */\nexport function scanSpecSource(code: string, filePath: string): SpecScanResult {\n const keyMatch =\n code.match(/key\\s*:\\s*['\"]([^'\"]+)['\"]/) ??\n code.match(/export\\s+const\\s+(\\w+)\\s*=/);\n const key = keyMatch?.[1] ?? 'unknown';\n\n const version = matchVersionField(code, 'version');\n const description = matchStringField(code, 'description') ?? undefined;\n const goal = matchStringField(code, 'goal') ?? undefined;\n const context = matchStringField(code, 'context') ?? undefined;\n const stabilityRaw = matchStringField(code, 'stability');\n const stability = isStability(stabilityRaw) ? stabilityRaw : undefined;\n const owners = matchStringArrayField(code, 'owners');\n const tags = matchStringArrayField(code, 'tags');\n\n // Determine type\n const inferredSpecType = inferSpecTypeFromCodeBlock(code);\n\n // Check feature flags/sections\n const hasMeta = /meta\\s*:\\s*\\{/.test(code);\n const hasIo = /io\\s*:\\s*\\{/.test(code);\n const hasPolicy = /policy\\s*:\\s*\\{/.test(code);\n const hasPayload = /payload\\s*:\\s*\\{/.test(code);\n const hasContent = /content\\s*:\\s*\\{/.test(code);\n const hasDefinition = /definition\\s*:\\s*\\{/.test(code);\n\n // References\n const emittedEvents =\n extractRefList(code, 'emits') ?? extractRefList(code, 'emittedEvents');\n const testRefs = extractTestRefs(code);\n\n const policyRefs = hasPolicy ? parsePolicy(code) : undefined;\n\n return {\n filePath,\n key,\n version,\n specType: inferredSpecType.specType,\n kind: inferredSpecType.kind,\n description,\n goal,\n context,\n stability,\n owners,\n tags,\n hasMeta,\n hasIo,\n hasPolicy,\n hasPayload,\n hasContent,\n hasDefinition,\n emittedEvents,\n policyRefs,\n testRefs,\n testTarget: extractTestTarget(code),\n testCoverage: extractTestCoverage(code),\n sourceBlock: code,\n };\n}\n\n/**\n * Infer spec type from file path convention.\n */\nexport function inferSpecTypeFromFilePath(\n filePath: string\n): SpecScanResult['specType'] | 'feature' | 'unknown' {\n if (filePath.includes('.contracts.') || /\\/operations?\\//.test(filePath)) {\n return 'operation';\n }\n if (filePath.includes('.event.') || /\\/events?\\//.test(filePath)) {\n return 'event';\n }\n if (\n filePath.includes('.presentation.') ||\n /\\/presentations?\\//.test(filePath)\n ) {\n return 'presentation';\n }\n if (filePath.includes('.policy.') || /\\/policies?\\//.test(filePath)) {\n return 'policy';\n }\n if (filePath.includes('.feature.') || /\\/features?\\//.test(filePath)) {\n return 'feature';\n }\n if (filePath.includes('.type.') || /\\/types?\\//.test(filePath)) {\n return 'type';\n }\n if (filePath.includes('.example.') || /\\/examples?\\//.test(filePath)) {\n return 'example';\n }\n if (filePath.includes('.app-config.')) {\n return 'app-config';\n }\n if (filePath.includes('.workflow.') || /\\/workflows?\\//.test(filePath)) {\n return 'workflow';\n }\n if (\n filePath.includes('.integration.') ||\n /\\/integrations?\\//.test(filePath)\n ) {\n return 'integration';\n }\n return 'unknown';\n}\n"],"mappings":";;;;;;;;AAqCA,SAAgB,uBACd,MACA,UACkB;CAClB,MAAM,UAA4B,EAAE;CAGpC,MAAM,YAAY,sBAAsB,KAAK;CAG7C,MAAM,kBACJ;CACF,IAAI;AAEJ,SAAQ,QAAQ,gBAAgB,KAAK,KAAK,MAAM,MAAM;EACpD,MAAM,QAAQ,MAAM;EAEpB,MAAM,MAAM,sBAAsB,MADX,QAAQ,MAAM,GAAG,SAAS,GACO,KAAK,IAAI;AACjE,MAAI,QAAQ,GAAI;EAGhB,IAAI,WAAW;AACf,MAAI,KAAK,WAAW,OAAO,IACzB;EAMF,MAAM,gBAAgB,wBAHR,KAAK,UAAU,OAAO,WAAW,EAAE,EAGI,UAAU;EAE/D,MAAM,SAAS,eAAe,eAAe,SAAS;AACtD,MAAI,OACF,SAAQ,KAAK;GACX,GAAG;GACH,aAAa;GACd,CAAC;;AAKN,KAAI,QAAQ,WAAW,KAAK,SAAS,SAAS,SAAS,EAErD;MADe,eAAe,MAAM,SAAS,CAClC,QAAQ,WAAW;GAG5B,MAAM,SAAS,eADO,wBAAwB,MAAM,UAAU,EACjB,SAAS;AACtD,WAAQ,KAAK,OAAO;;;AAIxB,QAAO;;AAGT,SAAgB,2BAA2B,gBAGzC;AACA,KAAI,eAAe,SAAS,gBAAgB,CAC1C,QAAO;EACL,UAAU;EACV,MAAM;EACP;AAEH,KAAI,eAAe,SAAS,cAAc,CACxC,QAAO;EACL,UAAU;EACV,MAAM;EACP;AAEH,KAAI,eAAe,SAAS,cAAc,CACxC,QAAO;EACL,UAAU;EACV,MAAM;EACP;AAEH,KAAI,eAAe,SAAS,qBAAqB,CAC/C,QAAO;EACL,UAAU;EACV,MAAM;EACP;AAEH,KAAI,eAAe,SAAS,eAAe,CACzC,QAAO;EACL,UAAU;EACV,MAAM;EACP;AAEH,KAAI,eAAe,SAAS,mBAAmB,CAC7C,QAAO;EACL,UAAU;EACV,MAAM;EACP;AAEH,KAAI,eAAe,SAAS,gBAAgB,CAC1C,QAAO;EACL,UAAU;EACV,MAAM;EACP;AAEH,KACE,eAAe,SAAS,kBAAkB,IAC1C,CAAC,eAAe,SAAS,+BAA+B,CAExD,QAAO;EACL,UAAU;EACV,MAAM;EACP;AAEH,KAAI,eAAe,SAAS,oBAAoB,CAC9C,QAAO;EACL,UAAU;EACV,MAAM;EACP;AAEH,KAAI,eAAe,SAAS,iBAAiB,CAC3C,QAAO;EACL,UAAU;EACV,MAAM;EACP;AAEH,KAAI,eAAe,SAAS,iBAAiB,CAC3C,QAAO;EACL,UAAU;EACV,MAAM;EACP;AAGH,KAAI,eAAe,SAAS,gBAAgB,CAC1C,QAAO;EACL,UAAU;EACV,MAAM;EACP;AAGH,QAAO;EACL,UAAU;EACV,MAAM;EACP;;;;;AAMH,SAAgB,eAAe,MAAc,UAAkC;CAI7E,MAAM,OAFJ,KAAK,MAAM,6BAA6B,IACxC,KAAK,MAAM,6BAA6B,IACnB,MAAM;CAE7B,MAAM,UAAU,kBAAkB,MAAM,UAAU;CAClD,MAAM,cAAc,iBAAiB,MAAM,cAAc,IAAI;CAC7D,MAAM,OAAO,iBAAiB,MAAM,OAAO,IAAI;CAC/C,MAAM,UAAU,iBAAiB,MAAM,UAAU,IAAI;CACrD,MAAM,eAAe,iBAAiB,MAAM,YAAY;CACxD,MAAM,YAAY,YAAY,aAAa,GAAG,eAAe;CAC7D,MAAM,SAAS,sBAAsB,MAAM,SAAS;CACpD,MAAM,OAAO,sBAAsB,MAAM,OAAO;CAGhD,MAAM,mBAAmB,2BAA2B,KAAK;CAGzD,MAAM,UAAU,gBAAgB,KAAK,KAAK;CAC1C,MAAM,QAAQ,cAAc,KAAK,KAAK;CACtC,MAAM,YAAY,kBAAkB,KAAK,KAAK;CAC9C,MAAM,aAAa,mBAAmB,KAAK,KAAK;CAChD,MAAM,aAAa,mBAAmB,KAAK,KAAK;CAChD,MAAM,gBAAgB,sBAAsB,KAAK,KAAK;CAGtD,MAAM,gBACJ,eAAe,MAAM,QAAQ,IAAI,eAAe,MAAM,gBAAgB;CACxE,MAAM,WAAW,gBAAgB,KAAK;CAEtC,MAAM,aAAa,YAAY,YAAY,KAAK,GAAG;AAEnD,QAAO;EACL;EACA;EACA;EACA,UAAU,iBAAiB;EAC3B,MAAM,iBAAiB;EACvB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YAAY,kBAAkB,KAAK;EACnC,cAAc,oBAAoB,KAAK;EACvC,aAAa;EACd;;;;;AAMH,SAAgB,0BACd,UACoD;AACpD,KAAI,SAAS,SAAS,cAAc,IAAI,kBAAkB,KAAK,SAAS,CACtE,QAAO;AAET,KAAI,SAAS,SAAS,UAAU,IAAI,cAAc,KAAK,SAAS,CAC9D,QAAO;AAET,KACE,SAAS,SAAS,iBAAiB,IACnC,qBAAqB,KAAK,SAAS,CAEnC,QAAO;AAET,KAAI,SAAS,SAAS,WAAW,IAAI,gBAAgB,KAAK,SAAS,CACjE,QAAO;AAET,KAAI,SAAS,SAAS,YAAY,IAAI,gBAAgB,KAAK,SAAS,CAClE,QAAO;AAET,KAAI,SAAS,SAAS,SAAS,IAAI,aAAa,KAAK,SAAS,CAC5D,QAAO;AAET,KAAI,SAAS,SAAS,YAAY,IAAI,gBAAgB,KAAK,SAAS,CAClE,QAAO;AAET,KAAI,SAAS,SAAS,eAAe,CACnC,QAAO;AAET,KAAI,SAAS,SAAS,aAAa,IAAI,iBAAiB,KAAK,SAAS,CACpE,QAAO;AAET,KACE,SAAS,SAAS,gBAAgB,IAClC,oBAAoB,KAAK,SAAS,CAElC,QAAO;AAET,QAAO"}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
//#region src/analysis/utils/matchers.ts
|
|
2
|
-
/**
|
|
3
|
-
* Escape regex special characters.
|
|
4
|
-
*/
|
|
5
|
-
function escapeRegex(value) {
|
|
6
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Match a string field in source code.
|
|
10
|
-
*/
|
|
11
|
-
function matchStringField(code, field) {
|
|
12
|
-
const regex = new RegExp(`${escapeRegex(field)}\\s*:\\s*['"]([^'"]+)['"]`);
|
|
13
|
-
return code.match(regex)?.[1] ?? null;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Match a string field within a limited scope.
|
|
17
|
-
*/
|
|
18
|
-
function matchStringFieldIn(code, field) {
|
|
19
|
-
return matchStringField(code, field);
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Match a version field which can be a string or number.
|
|
23
|
-
*/
|
|
24
|
-
function matchVersionField(code, field) {
|
|
25
|
-
const regex = new RegExp(`${escapeRegex(field)}\\s*:\\s*(?:['"]([^'"]+)['"]|(\\d+(?:\\.\\d+)*))`);
|
|
26
|
-
const match = code.match(regex);
|
|
27
|
-
if (match?.[1]) return match[1];
|
|
28
|
-
if (match?.[2]) return match[2];
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Match a string array field in source code.
|
|
32
|
-
*/
|
|
33
|
-
function matchStringArrayField(code, field) {
|
|
34
|
-
const regex = new RegExp(`${escapeRegex(field)}\\s*:\\s*\\[([\\s\\S]*?)\\]`);
|
|
35
|
-
const match = code.match(regex);
|
|
36
|
-
if (!match?.[1]) return void 0;
|
|
37
|
-
const inner = match[1];
|
|
38
|
-
const items = Array.from(inner.matchAll(/['"]([^'"]+)['"]/g)).map((m) => m[1]).filter((value) => typeof value === "string" && value.length > 0);
|
|
39
|
-
return items.length > 0 ? items : void 0;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Check if a value is a valid stability.
|
|
43
|
-
*/
|
|
44
|
-
function isStability(value) {
|
|
45
|
-
return value === "experimental" || value === "beta" || value === "stable" || value === "deprecated";
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Find matching closing delimiter for an opening delimiter.
|
|
49
|
-
* Returns the index of the closing delimiter or -1 if not found.
|
|
50
|
-
*/
|
|
51
|
-
function findMatchingDelimiter(code, startIndex, openChar, closeChar) {
|
|
52
|
-
let depth = 0;
|
|
53
|
-
let inString = false;
|
|
54
|
-
let stringChar = "";
|
|
55
|
-
for (let i = startIndex; i < code.length; i++) {
|
|
56
|
-
const char = code[i];
|
|
57
|
-
const prevChar = i > 0 ? code[i - 1] : "";
|
|
58
|
-
if ((char === "\"" || char === "'" || char === "`") && prevChar !== "\\") {
|
|
59
|
-
if (!inString) {
|
|
60
|
-
inString = true;
|
|
61
|
-
stringChar = char;
|
|
62
|
-
} else if (char === stringChar) inString = false;
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
if (inString) continue;
|
|
66
|
-
if (char === openChar) depth++;
|
|
67
|
-
else if (char === closeChar) {
|
|
68
|
-
depth--;
|
|
69
|
-
if (depth === 0) return i;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return -1;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
//#endregion
|
|
76
|
-
export { escapeRegex, findMatchingDelimiter, isStability, matchStringArrayField, matchStringField, matchStringFieldIn, matchVersionField };
|
|
77
|
-
//# sourceMappingURL=matchers.js.map
|