@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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"deep-diff.js","names":[],"sources":["../../../src/analysis/diff/deep-diff.ts"],"sourcesContent":["/**\n * Deep diff engine for IO schema comparison.\n *\n * Compares input/output schemas field-by-field to detect\n * breaking and non-breaking changes.\n */\n\nimport type { FieldSnapshot, IoSnapshot } from '../snapshot/types';\nimport type { SemanticDiffItem } from '../../types/analysis-types';\n\n/**\n * Deep diff options.\n */\nexport interface DeepDiffOptions {\n /** Only report breaking changes */\n breakingOnly?: boolean;\n /** Path prefix for nested diffs */\n pathPrefix?: string;\n}\n\n/**\n * Compute deep differences between two IO schemas.\n */\nexport function computeIoDiff(\n base: IoSnapshot,\n head: IoSnapshot,\n options: DeepDiffOptions = {}\n): SemanticDiffItem[] {\n const diffs: SemanticDiffItem[] = [];\n\n // Compare input schemas\n diffs.push(...computeFieldsDiff(base.input, head.input, 'io.input', options));\n\n // Compare output schemas\n diffs.push(\n ...computeFieldsDiff(base.output, head.output, 'io.output', options)\n );\n\n return options.breakingOnly\n ? diffs.filter((d) => d.type === 'breaking')\n : diffs;\n}\n\n/**\n * Compute differences between two field maps.\n */\nexport function computeFieldsDiff(\n baseFields: Record<string, FieldSnapshot>,\n headFields: Record<string, FieldSnapshot>,\n pathPrefix: string,\n options: DeepDiffOptions = {}\n): SemanticDiffItem[] {\n const diffs: SemanticDiffItem[] = [];\n const baseNames = new Set(Object.keys(baseFields));\n const headNames = new Set(Object.keys(headFields));\n\n // Check for removed fields\n for (const name of baseNames) {\n if (!headNames.has(name)) {\n const baseField = baseFields[name];\n diffs.push({\n type: 'breaking',\n path: `${pathPrefix}.${name}`,\n oldValue: baseField,\n newValue: undefined,\n description: `Field '${name}' was removed`,\n });\n }\n }\n\n // Check for added fields\n for (const name of headNames) {\n if (!baseNames.has(name)) {\n const headField = headFields[name];\n const isBreaking = headField?.required === true;\n diffs.push({\n type: isBreaking ? 'breaking' : 'added',\n path: `${pathPrefix}.${name}`,\n oldValue: undefined,\n newValue: headField,\n description: isBreaking\n ? `Required field '${name}' was added`\n : `Optional field '${name}' was added`,\n });\n }\n }\n\n // Check for changed fields\n for (const name of baseNames) {\n if (headNames.has(name)) {\n const baseField = baseFields[name];\n const headField = headFields[name];\n if (baseField && headField) {\n diffs.push(\n ...computeFieldDiff(\n baseField,\n headField,\n `${pathPrefix}.${name}`,\n options\n )\n );\n }\n }\n }\n\n return diffs;\n}\n\n/**\n * Compute differences between two field definitions.\n */\nexport function computeFieldDiff(\n base: FieldSnapshot,\n head: FieldSnapshot,\n path: string,\n _options: DeepDiffOptions = {}\n): SemanticDiffItem[] {\n const diffs: SemanticDiffItem[] = [];\n\n // Type change is always breaking\n if (base.type !== head.type) {\n diffs.push({\n type: 'breaking',\n path: `${path}.type`,\n oldValue: base.type,\n newValue: head.type,\n description: `Field type changed from '${base.type}' to '${head.type}'`,\n });\n }\n\n // Required change\n if (base.required !== head.required) {\n const isBreaking = !base.required && head.required; // Optional -> Required is breaking\n diffs.push({\n type: isBreaking ? 'breaking' : 'changed',\n path: `${path}.required`,\n oldValue: base.required,\n newValue: head.required,\n description: isBreaking\n ? `Field '${base.name}' changed from optional to required`\n : `Field '${base.name}' changed from required to optional`,\n });\n }\n\n // Nullable change\n if (base.nullable !== head.nullable) {\n const isBreaking = base.nullable && !head.nullable; // Nullable -> Non-nullable is breaking\n diffs.push({\n type: isBreaking ? 'breaking' : 'changed',\n path: `${path}.nullable`,\n oldValue: base.nullable,\n newValue: head.nullable,\n description: isBreaking\n ? `Field '${base.name}' is no longer nullable`\n : `Field '${base.name}' is now nullable`,\n });\n }\n\n // Enum values change\n if (base.type === 'enum' && head.type === 'enum') {\n const baseValues = new Set(base.enumValues ?? []);\n const headValues = new Set(head.enumValues ?? []);\n\n // Removed enum values are breaking\n for (const value of baseValues) {\n if (!headValues.has(value)) {\n diffs.push({\n type: 'breaking',\n path: `${path}.enumValues`,\n oldValue: base.enumValues,\n newValue: head.enumValues,\n description: `Enum value '${value}' was removed`,\n });\n }\n }\n\n // Added enum values are non-breaking\n for (const value of headValues) {\n if (!baseValues.has(value)) {\n diffs.push({\n type: 'added',\n path: `${path}.enumValues`,\n oldValue: base.enumValues,\n newValue: head.enumValues,\n description: `Enum value '${value}' was added`,\n });\n }\n }\n }\n\n // Nested object fields\n if (\n base.type === 'object' &&\n head.type === 'object' &&\n base.properties &&\n head.properties\n ) {\n diffs.push(\n ...computeFieldsDiff(base.properties, head.properties, path, _options)\n );\n }\n\n // Array items\n if (\n base.type === 'array' &&\n head.type === 'array' &&\n base.items &&\n head.items\n ) {\n diffs.push(\n ...computeFieldDiff(base.items, head.items, `${path}.items`, _options)\n );\n }\n\n return diffs;\n}\n\n/**\n * Classify a diff as breaking based on context.\n */\nexport function isBreakingChange(\n diff: SemanticDiffItem,\n context: 'input' | 'output'\n): boolean {\n // In output context, removing/changing fields is always breaking\n if (context === 'output') {\n return diff.type === 'breaking' || diff.type === 'removed';\n }\n\n // In input context, adding required fields is breaking\n if (context === 'input') {\n if (diff.type === 'added' && diff.description?.includes('Required field')) {\n return true;\n }\n return diff.type === 'breaking';\n }\n\n return diff.type === 'breaking';\n}\n"],"mappings":"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"}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { scanSpecSource } from "../spec-scan.js";
|
|
2
|
-
|
|
3
|
-
//#region src/analysis/diff/semantic.ts
|
|
4
|
-
/**
|
|
5
|
-
* Compute semantic differences between two spec sources.
|
|
6
|
-
*/
|
|
7
|
-
function computeSemanticDiff(aCode, aPath, bCode, bPath, options = {}) {
|
|
8
|
-
const a = scanSpecSource(aCode, aPath);
|
|
9
|
-
const b = scanSpecSource(bCode, bPath);
|
|
10
|
-
const diffs = [];
|
|
11
|
-
compareScalar(diffs, "specType", a.specType, b.specType, {
|
|
12
|
-
breaking: true,
|
|
13
|
-
label: "Spec type"
|
|
14
|
-
});
|
|
15
|
-
compareScalar(diffs, "key", a.key, b.key, {
|
|
16
|
-
breaking: true,
|
|
17
|
-
label: "Key"
|
|
18
|
-
});
|
|
19
|
-
compareScalar(diffs, "version", a.version, b.version, {
|
|
20
|
-
breaking: true,
|
|
21
|
-
label: "Version"
|
|
22
|
-
});
|
|
23
|
-
compareScalar(diffs, "kind", a.kind, b.kind, {
|
|
24
|
-
breaking: true,
|
|
25
|
-
label: "Kind"
|
|
26
|
-
});
|
|
27
|
-
compareScalar(diffs, "stability", a.stability, b.stability, {
|
|
28
|
-
breaking: isStabilityDowngrade(a, b),
|
|
29
|
-
label: "Stability"
|
|
30
|
-
});
|
|
31
|
-
compareArray(diffs, "owners", a.owners ?? [], b.owners ?? [], { label: "Owners" });
|
|
32
|
-
compareArray(diffs, "tags", a.tags ?? [], b.tags ?? [], { label: "Tags" });
|
|
33
|
-
compareStructuralHints(diffs, a, b);
|
|
34
|
-
return options.breakingOnly ? diffs.filter((d) => d.type === "breaking") : diffs;
|
|
35
|
-
}
|
|
36
|
-
function compareScalar(diffs, path, a, b, config) {
|
|
37
|
-
if (a === b) return;
|
|
38
|
-
diffs.push({
|
|
39
|
-
type: config.breaking ? "breaking" : "changed",
|
|
40
|
-
path,
|
|
41
|
-
oldValue: a,
|
|
42
|
-
newValue: b,
|
|
43
|
-
description: `${config.label} changed`
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
function compareArray(diffs, path, a, b, config) {
|
|
47
|
-
const aSorted = [...a].sort();
|
|
48
|
-
const bSorted = [...b].sort();
|
|
49
|
-
if (JSON.stringify(aSorted) === JSON.stringify(bSorted)) return;
|
|
50
|
-
diffs.push({
|
|
51
|
-
type: "changed",
|
|
52
|
-
path,
|
|
53
|
-
oldValue: aSorted,
|
|
54
|
-
newValue: bSorted,
|
|
55
|
-
description: `${config.label} changed`
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
function isStabilityDowngrade(a, b) {
|
|
59
|
-
const order = {
|
|
60
|
-
experimental: 0,
|
|
61
|
-
beta: 1,
|
|
62
|
-
stable: 2,
|
|
63
|
-
deprecated: 3
|
|
64
|
-
};
|
|
65
|
-
const aValue = a.stability ? order[a.stability] ?? 0 : 0;
|
|
66
|
-
return (b.stability ? order[b.stability] ?? 0 : 0) > aValue;
|
|
67
|
-
}
|
|
68
|
-
function compareStructuralHints(diffs, a, b) {
|
|
69
|
-
compareScalar(diffs, "hasMeta", a.hasMeta, b.hasMeta, {
|
|
70
|
-
breaking: a.specType === "operation" || b.specType === "operation",
|
|
71
|
-
label: "meta section presence"
|
|
72
|
-
});
|
|
73
|
-
compareScalar(diffs, "hasIo", a.hasIo, b.hasIo, {
|
|
74
|
-
breaking: a.specType === "operation" || b.specType === "operation",
|
|
75
|
-
label: "io section presence"
|
|
76
|
-
});
|
|
77
|
-
compareScalar(diffs, "hasPolicy", a.hasPolicy, b.hasPolicy, {
|
|
78
|
-
breaking: a.specType === "operation" || b.specType === "operation",
|
|
79
|
-
label: "policy section presence"
|
|
80
|
-
});
|
|
81
|
-
compareScalar(diffs, "hasPayload", a.hasPayload, b.hasPayload, {
|
|
82
|
-
breaking: a.specType === "event" || b.specType === "event",
|
|
83
|
-
label: "payload section presence"
|
|
84
|
-
});
|
|
85
|
-
compareScalar(diffs, "hasContent", a.hasContent, b.hasContent, {
|
|
86
|
-
breaking: a.specType === "presentation" || b.specType === "presentation",
|
|
87
|
-
label: "content section presence"
|
|
88
|
-
});
|
|
89
|
-
compareScalar(diffs, "hasDefinition", a.hasDefinition, b.hasDefinition, {
|
|
90
|
-
breaking: a.specType === "workflow" || b.specType === "workflow",
|
|
91
|
-
label: "definition section presence"
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
//#endregion
|
|
96
|
-
export { computeSemanticDiff };
|
|
97
|
-
//# sourceMappingURL=semantic.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"semantic.js","names":[],"sources":["../../../src/analysis/diff/semantic.ts"],"sourcesContent":["/**\n * Semantic diff computation for contract specs.\n * Extracted from cli-contractspec/src/commands/diff/semantic.ts\n */\n\nimport type {\n SemanticDiffItem,\n SemanticDiffOptions,\n SpecScanResult,\n} from '../../types/analysis-types';\nimport { scanSpecSource } from '../spec-scan';\n\n/**\n * Compute semantic differences between two spec sources.\n */\nexport function computeSemanticDiff(\n aCode: string,\n aPath: string,\n bCode: string,\n bPath: string,\n options: SemanticDiffOptions = {}\n): SemanticDiffItem[] {\n const a = scanSpecSource(aCode, aPath);\n const b = scanSpecSource(bCode, bPath);\n\n const diffs: SemanticDiffItem[] = [];\n\n compareScalar(diffs, 'specType', a.specType, b.specType, {\n breaking: true,\n label: 'Spec type',\n });\n\n compareScalar(diffs, 'key', a.key, b.key, {\n breaking: true,\n label: 'Key',\n });\n compareScalar(diffs, 'version', a.version, b.version, {\n breaking: true,\n label: 'Version',\n });\n compareScalar(diffs, 'kind', a.kind, b.kind, {\n breaking: true,\n label: 'Kind',\n });\n\n compareScalar(diffs, 'stability', a.stability, b.stability, {\n breaking: isStabilityDowngrade(a, b),\n label: 'Stability',\n });\n\n compareArray(diffs, 'owners', a.owners ?? [], b.owners ?? [], {\n label: 'Owners',\n });\n compareArray(diffs, 'tags', a.tags ?? [], b.tags ?? [], { label: 'Tags' });\n\n compareStructuralHints(diffs, a, b);\n\n const filtered = options.breakingOnly\n ? diffs.filter((d) => d.type === 'breaking')\n : diffs;\n\n return filtered;\n}\n\nfunction compareScalar(\n diffs: SemanticDiffItem[],\n path: string,\n a: unknown,\n b: unknown,\n config: { breaking: boolean; label: string }\n) {\n if (a === b) return;\n diffs.push({\n type: config.breaking ? 'breaking' : 'changed',\n path,\n oldValue: a,\n newValue: b,\n description: `${config.label} changed`,\n });\n}\n\nfunction compareArray(\n diffs: SemanticDiffItem[],\n path: string,\n a: string[],\n b: string[],\n config: { label: string }\n) {\n const aSorted = [...a].sort();\n const bSorted = [...b].sort();\n if (JSON.stringify(aSorted) === JSON.stringify(bSorted)) return;\n\n diffs.push({\n type: 'changed',\n path,\n oldValue: aSorted,\n newValue: bSorted,\n description: `${config.label} changed`,\n });\n}\n\nfunction isStabilityDowngrade(a: SpecScanResult, b: SpecScanResult): boolean {\n const order: Record<string, number> = {\n experimental: 0,\n beta: 1,\n stable: 2,\n deprecated: 3,\n };\n const aValue = a.stability ? (order[a.stability] ?? 0) : 0;\n const bValue = b.stability ? (order[b.stability] ?? 0) : 0;\n // Moving toward deprecated is effectively a breaking signal for consumers.\n return bValue > aValue;\n}\n\nfunction compareStructuralHints(\n diffs: SemanticDiffItem[],\n a: SpecScanResult,\n b: SpecScanResult\n) {\n // For operations these sections are usually required; missing them is breaking.\n compareScalar(diffs, 'hasMeta', a.hasMeta, b.hasMeta, {\n breaking: a.specType === 'operation' || b.specType === 'operation',\n label: 'meta section presence',\n });\n compareScalar(diffs, 'hasIo', a.hasIo, b.hasIo, {\n breaking: a.specType === 'operation' || b.specType === 'operation',\n label: 'io section presence',\n });\n compareScalar(diffs, 'hasPolicy', a.hasPolicy, b.hasPolicy, {\n breaking: a.specType === 'operation' || b.specType === 'operation',\n label: 'policy section presence',\n });\n compareScalar(diffs, 'hasPayload', a.hasPayload, b.hasPayload, {\n breaking: a.specType === 'event' || b.specType === 'event',\n label: 'payload section presence',\n });\n compareScalar(diffs, 'hasContent', a.hasContent, b.hasContent, {\n breaking: a.specType === 'presentation' || b.specType === 'presentation',\n label: 'content section presence',\n });\n compareScalar(diffs, 'hasDefinition', a.hasDefinition, b.hasDefinition, {\n breaking: a.specType === 'workflow' || b.specType === 'workflow',\n label: 'definition section presence',\n });\n}\n"],"mappings":";;;;;;AAeA,SAAgB,oBACd,OACA,OACA,OACA,OACA,UAA+B,EAAE,EACb;CACpB,MAAM,IAAI,eAAe,OAAO,MAAM;CACtC,MAAM,IAAI,eAAe,OAAO,MAAM;CAEtC,MAAM,QAA4B,EAAE;AAEpC,eAAc,OAAO,YAAY,EAAE,UAAU,EAAE,UAAU;EACvD,UAAU;EACV,OAAO;EACR,CAAC;AAEF,eAAc,OAAO,OAAO,EAAE,KAAK,EAAE,KAAK;EACxC,UAAU;EACV,OAAO;EACR,CAAC;AACF,eAAc,OAAO,WAAW,EAAE,SAAS,EAAE,SAAS;EACpD,UAAU;EACV,OAAO;EACR,CAAC;AACF,eAAc,OAAO,QAAQ,EAAE,MAAM,EAAE,MAAM;EAC3C,UAAU;EACV,OAAO;EACR,CAAC;AAEF,eAAc,OAAO,aAAa,EAAE,WAAW,EAAE,WAAW;EAC1D,UAAU,qBAAqB,GAAG,EAAE;EACpC,OAAO;EACR,CAAC;AAEF,cAAa,OAAO,UAAU,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAC5D,OAAO,UACR,CAAC;AACF,cAAa,OAAO,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,QAAQ,CAAC;AAE1E,wBAAuB,OAAO,GAAG,EAAE;AAMnC,QAJiB,QAAQ,eACrB,MAAM,QAAQ,MAAM,EAAE,SAAS,WAAW,GAC1C;;AAKN,SAAS,cACP,OACA,MACA,GACA,GACA,QACA;AACA,KAAI,MAAM,EAAG;AACb,OAAM,KAAK;EACT,MAAM,OAAO,WAAW,aAAa;EACrC;EACA,UAAU;EACV,UAAU;EACV,aAAa,GAAG,OAAO,MAAM;EAC9B,CAAC;;AAGJ,SAAS,aACP,OACA,MACA,GACA,GACA,QACA;CACA,MAAM,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM;CAC7B,MAAM,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM;AAC7B,KAAI,KAAK,UAAU,QAAQ,KAAK,KAAK,UAAU,QAAQ,CAAE;AAEzD,OAAM,KAAK;EACT,MAAM;EACN;EACA,UAAU;EACV,UAAU;EACV,aAAa,GAAG,OAAO,MAAM;EAC9B,CAAC;;AAGJ,SAAS,qBAAqB,GAAmB,GAA4B;CAC3E,MAAM,QAAgC;EACpC,cAAc;EACd,MAAM;EACN,QAAQ;EACR,YAAY;EACb;CACD,MAAM,SAAS,EAAE,YAAa,MAAM,EAAE,cAAc,IAAK;AAGzD,SAFe,EAAE,YAAa,MAAM,EAAE,cAAc,IAAK,KAEzC;;AAGlB,SAAS,uBACP,OACA,GACA,GACA;AAEA,eAAc,OAAO,WAAW,EAAE,SAAS,EAAE,SAAS;EACpD,UAAU,EAAE,aAAa,eAAe,EAAE,aAAa;EACvD,OAAO;EACR,CAAC;AACF,eAAc,OAAO,SAAS,EAAE,OAAO,EAAE,OAAO;EAC9C,UAAU,EAAE,aAAa,eAAe,EAAE,aAAa;EACvD,OAAO;EACR,CAAC;AACF,eAAc,OAAO,aAAa,EAAE,WAAW,EAAE,WAAW;EAC1D,UAAU,EAAE,aAAa,eAAe,EAAE,aAAa;EACvD,OAAO;EACR,CAAC;AACF,eAAc,OAAO,cAAc,EAAE,YAAY,EAAE,YAAY;EAC7D,UAAU,EAAE,aAAa,WAAW,EAAE,aAAa;EACnD,OAAO;EACR,CAAC;AACF,eAAc,OAAO,cAAc,EAAE,YAAY,EAAE,YAAY;EAC7D,UAAU,EAAE,aAAa,kBAAkB,EAAE,aAAa;EAC1D,OAAO;EACR,CAAC;AACF,eAAc,OAAO,iBAAiB,EAAE,eAAe,EAAE,eAAe;EACtE,UAAU,EAAE,aAAa,cAAc,EAAE,aAAa;EACtD,OAAO;EACR,CAAC"}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { isStability, matchStringArrayField, matchStringField, matchStringFieldIn } from "./utils/matchers.js";
|
|
2
|
-
|
|
3
|
-
//#region src/analysis/example-scan.ts
|
|
4
|
-
/**
|
|
5
|
-
* Check if a file is an example file based on naming conventions.
|
|
6
|
-
*/
|
|
7
|
-
function isExampleFile(filePath) {
|
|
8
|
-
return filePath.includes("/example.") || filePath.endsWith(".example.ts");
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Scan an example source file to extract metadata.
|
|
12
|
-
*/
|
|
13
|
-
function scanExampleSource(code, filePath) {
|
|
14
|
-
const key = matchStringField(code, "key") ?? extractKeyFromFilePath(filePath);
|
|
15
|
-
const version = matchStringField(code, "version") ?? void 0;
|
|
16
|
-
const title = matchStringField(code, "title") ?? void 0;
|
|
17
|
-
const description = matchStringField(code, "description") ?? void 0;
|
|
18
|
-
const summary = matchStringField(code, "summary") ?? void 0;
|
|
19
|
-
const kind = matchStringField(code, "kind") ?? void 0;
|
|
20
|
-
const visibility = matchStringField(code, "visibility") ?? void 0;
|
|
21
|
-
const domain = matchStringField(code, "domain") ?? void 0;
|
|
22
|
-
const stabilityRaw = matchStringField(code, "stability");
|
|
23
|
-
return {
|
|
24
|
-
filePath,
|
|
25
|
-
key,
|
|
26
|
-
version,
|
|
27
|
-
title,
|
|
28
|
-
description,
|
|
29
|
-
summary,
|
|
30
|
-
kind,
|
|
31
|
-
visibility,
|
|
32
|
-
domain,
|
|
33
|
-
stability: isStability(stabilityRaw) ? stabilityRaw : void 0,
|
|
34
|
-
owners: matchStringArrayField(code, "owners"),
|
|
35
|
-
tags: matchStringArrayField(code, "tags"),
|
|
36
|
-
docs: extractDocs(code),
|
|
37
|
-
surfaces: extractSurfaces(code),
|
|
38
|
-
entrypoints: extractEntrypoints(code)
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Extract docs section from source code.
|
|
43
|
-
*/
|
|
44
|
-
function extractDocs(code) {
|
|
45
|
-
const docsMatch = code.match(/docs\s*:\s*\{([\s\S]*?)\}/);
|
|
46
|
-
if (!docsMatch?.[1]) return void 0;
|
|
47
|
-
const docsContent = docsMatch[1];
|
|
48
|
-
return {
|
|
49
|
-
rootDocId: matchStringFieldIn(docsContent, "rootDocId") ?? void 0,
|
|
50
|
-
goalDocId: matchStringFieldIn(docsContent, "goalDocId") ?? void 0,
|
|
51
|
-
usageDocId: matchStringFieldIn(docsContent, "usageDocId") ?? void 0
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Extract surfaces section from source code.
|
|
56
|
-
*/
|
|
57
|
-
function extractSurfaces(code) {
|
|
58
|
-
const surfaces = {
|
|
59
|
-
templates: false,
|
|
60
|
-
sandbox: {
|
|
61
|
-
enabled: false,
|
|
62
|
-
modes: []
|
|
63
|
-
},
|
|
64
|
-
studio: {
|
|
65
|
-
enabled: false,
|
|
66
|
-
installable: false
|
|
67
|
-
},
|
|
68
|
-
mcp: { enabled: false }
|
|
69
|
-
};
|
|
70
|
-
surfaces.templates = /surfaces\s*:\s*\{[\s\S]*?templates\s*:\s*true/.test(code);
|
|
71
|
-
const sandboxMatch = code.match(/sandbox\s*:\s*\{\s*enabled\s*:\s*(true|false)\s*,\s*modes\s*:\s*\[([^\]]*)\]/);
|
|
72
|
-
if (sandboxMatch) {
|
|
73
|
-
surfaces.sandbox.enabled = sandboxMatch[1] === "true";
|
|
74
|
-
if (sandboxMatch[2]) surfaces.sandbox.modes = Array.from(sandboxMatch[2].matchAll(/['"]([^'"]+)['"]/g)).map((m) => m[1]).filter((v) => typeof v === "string");
|
|
75
|
-
}
|
|
76
|
-
const studioMatch = code.match(/studio\s*:\s*\{\s*enabled\s*:\s*(true|false)\s*,\s*installable\s*:\s*(true|false)/);
|
|
77
|
-
if (studioMatch) {
|
|
78
|
-
surfaces.studio.enabled = studioMatch[1] === "true";
|
|
79
|
-
surfaces.studio.installable = studioMatch[2] === "true";
|
|
80
|
-
}
|
|
81
|
-
const mcpMatch = code.match(/mcp\s*:\s*\{\s*enabled\s*:\s*(true|false)/);
|
|
82
|
-
if (mcpMatch) surfaces.mcp.enabled = mcpMatch[1] === "true";
|
|
83
|
-
return surfaces;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Extract entrypoints section from source code.
|
|
87
|
-
*/
|
|
88
|
-
function extractEntrypoints(code) {
|
|
89
|
-
const entrypoints = { packageName: "" };
|
|
90
|
-
const entrypointsMatch = code.match(/entrypoints\s*:\s*\{([\s\S]*?)\}(?=\s*[,}])/);
|
|
91
|
-
if (!entrypointsMatch?.[1]) return entrypoints;
|
|
92
|
-
const content = entrypointsMatch[1];
|
|
93
|
-
entrypoints.packageName = matchStringFieldIn(content, "packageName") ?? "unknown";
|
|
94
|
-
entrypoints.feature = matchStringFieldIn(content, "feature") ?? void 0;
|
|
95
|
-
entrypoints.blueprint = matchStringFieldIn(content, "blueprint") ?? void 0;
|
|
96
|
-
entrypoints.contracts = matchStringFieldIn(content, "contracts") ?? void 0;
|
|
97
|
-
entrypoints.presentations = matchStringFieldIn(content, "presentations") ?? void 0;
|
|
98
|
-
entrypoints.handlers = matchStringFieldIn(content, "handlers") ?? void 0;
|
|
99
|
-
entrypoints.ui = matchStringFieldIn(content, "ui") ?? void 0;
|
|
100
|
-
entrypoints.docs = matchStringFieldIn(content, "docs") ?? void 0;
|
|
101
|
-
return entrypoints;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Extract key from file path as fallback.
|
|
105
|
-
*/
|
|
106
|
-
function extractKeyFromFilePath(filePath) {
|
|
107
|
-
const parts = filePath.split("/");
|
|
108
|
-
const examplesIndex = parts.findIndex((p) => p === "examples");
|
|
109
|
-
const exampleName = parts[examplesIndex + 1];
|
|
110
|
-
if (examplesIndex !== -1 && exampleName !== void 0) return exampleName;
|
|
111
|
-
return (parts.pop() ?? filePath).replace(/\.example\.[jt]s$/, "").replace(/[^a-zA-Z0-9-]/g, "-");
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
//#endregion
|
|
115
|
-
export { isExampleFile, scanExampleSource };
|
|
116
|
-
//# sourceMappingURL=example-scan.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"example-scan.js","names":[],"sources":["../../src/analysis/example-scan.ts"],"sourcesContent":["import {\n isStability,\n matchStringArrayField,\n matchStringField,\n matchStringFieldIn,\n} from './utils/matchers';\nimport type { ExampleScanResult } from '../types/analysis-types';\n\n/**\n * Check if a file is an example file based on naming conventions.\n */\nexport function isExampleFile(filePath: string): boolean {\n return filePath.includes('/example.') || filePath.endsWith('.example.ts');\n}\n\n/**\n * Scan an example source file to extract metadata.\n */\nexport function scanExampleSource(\n code: string,\n filePath: string\n): ExampleScanResult {\n const key = matchStringField(code, 'key') ?? extractKeyFromFilePath(filePath);\n const versionRaw = matchStringField(code, 'version');\n const version = versionRaw ?? undefined;\n const title = matchStringField(code, 'title') ?? undefined;\n const description = matchStringField(code, 'description') ?? undefined;\n const summary = matchStringField(code, 'summary') ?? undefined;\n const kind = matchStringField(code, 'kind') ?? undefined;\n const visibility = matchStringField(code, 'visibility') ?? undefined;\n const domain = matchStringField(code, 'domain') ?? 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 // Extract docs\n const docs = extractDocs(code);\n\n // Extract surfaces\n const surfaces = extractSurfaces(code);\n\n // Extract entrypoints\n const entrypoints = extractEntrypoints(code);\n\n return {\n filePath,\n key,\n version,\n title,\n description,\n summary,\n kind,\n visibility,\n domain,\n stability,\n owners,\n tags,\n docs,\n surfaces,\n entrypoints,\n };\n}\n\n/**\n * Extract docs section from source code.\n */\nfunction extractDocs(code: string): ExampleScanResult['docs'] {\n const docsMatch = code.match(/docs\\s*:\\s*\\{([\\s\\S]*?)\\}/);\n if (!docsMatch?.[1]) return undefined;\n\n const docsContent = docsMatch[1];\n return {\n rootDocId: matchStringFieldIn(docsContent, 'rootDocId') ?? undefined,\n goalDocId: matchStringFieldIn(docsContent, 'goalDocId') ?? undefined,\n usageDocId: matchStringFieldIn(docsContent, 'usageDocId') ?? undefined,\n };\n}\n\n/**\n * Extract surfaces section from source code.\n */\nfunction extractSurfaces(code: string): ExampleScanResult['surfaces'] {\n const surfaces: ExampleScanResult['surfaces'] = {\n templates: false,\n sandbox: { enabled: false, modes: [] },\n studio: { enabled: false, installable: false },\n mcp: { enabled: false },\n };\n\n // Check templates directly in the surfaces section\n surfaces.templates = /surfaces\\s*:\\s*\\{[\\s\\S]*?templates\\s*:\\s*true/.test(\n code\n );\n\n // Check sandbox - look for the sandbox object and extract its content\n const sandboxMatch = code.match(\n /sandbox\\s*:\\s*\\{\\s*enabled\\s*:\\s*(true|false)\\s*,\\s*modes\\s*:\\s*\\[([^\\]]*)\\]/\n );\n if (sandboxMatch) {\n surfaces.sandbox.enabled = sandboxMatch[1] === 'true';\n if (sandboxMatch[2]) {\n surfaces.sandbox.modes = Array.from(\n sandboxMatch[2].matchAll(/['\"]([^'\"]+)['\"]/g)\n )\n .map((m) => m[1])\n .filter((v): v is string => typeof v === 'string');\n }\n }\n\n // Check studio - look for studio object\n const studioMatch = code.match(\n /studio\\s*:\\s*\\{\\s*enabled\\s*:\\s*(true|false)\\s*,\\s*installable\\s*:\\s*(true|false)/\n );\n if (studioMatch) {\n surfaces.studio.enabled = studioMatch[1] === 'true';\n surfaces.studio.installable = studioMatch[2] === 'true';\n }\n\n // Check mcp\n const mcpMatch = code.match(/mcp\\s*:\\s*\\{\\s*enabled\\s*:\\s*(true|false)/);\n if (mcpMatch) {\n surfaces.mcp.enabled = mcpMatch[1] === 'true';\n }\n\n return surfaces;\n}\n\n/**\n * Extract entrypoints section from source code.\n */\nfunction extractEntrypoints(code: string): ExampleScanResult['entrypoints'] {\n const entrypoints: ExampleScanResult['entrypoints'] = {\n packageName: '',\n };\n\n const entrypointsMatch = code.match(\n /entrypoints\\s*:\\s*\\{([\\s\\S]*?)\\}(?=\\s*[,}])/\n );\n if (!entrypointsMatch?.[1]) return entrypoints;\n\n const content = entrypointsMatch[1];\n\n entrypoints.packageName =\n matchStringFieldIn(content, 'packageName') ?? 'unknown';\n entrypoints.feature = matchStringFieldIn(content, 'feature') ?? undefined;\n entrypoints.blueprint = matchStringFieldIn(content, 'blueprint') ?? undefined;\n entrypoints.contracts = matchStringFieldIn(content, 'contracts') ?? undefined;\n entrypoints.presentations =\n matchStringFieldIn(content, 'presentations') ?? undefined;\n entrypoints.handlers = matchStringFieldIn(content, 'handlers') ?? undefined;\n entrypoints.ui = matchStringFieldIn(content, 'ui') ?? undefined;\n entrypoints.docs = matchStringFieldIn(content, 'docs') ?? undefined;\n\n return entrypoints;\n}\n\n/**\n * Extract key from file path as fallback.\n */\nfunction extractKeyFromFilePath(filePath: string): string {\n // Try to get package name from path\n const parts = filePath.split('/');\n const examplesIndex = parts.findIndex((p) => p === 'examples');\n const exampleName = parts[examplesIndex + 1];\n if (examplesIndex !== -1 && exampleName !== undefined) {\n return exampleName;\n }\n // Fallback to filename\n const fileName = parts.pop() ?? filePath;\n return fileName\n .replace(/\\.example\\.[jt]s$/, '')\n .replace(/[^a-zA-Z0-9-]/g, '-');\n}\n"],"mappings":"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"}
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import { Node, Project, SyntaxKind } from "ts-morph";
|
|
2
|
-
|
|
3
|
-
//#region src/analysis/feature-extractor.ts
|
|
4
|
-
/**
|
|
5
|
-
* Extract specs referenced in a feature.
|
|
6
|
-
* Uses ts-morph for robust parsing of object structures.
|
|
7
|
-
*/
|
|
8
|
-
function extractFeatureRefs(code) {
|
|
9
|
-
const result = {
|
|
10
|
-
operations: [],
|
|
11
|
-
events: [],
|
|
12
|
-
presentations: [],
|
|
13
|
-
experiments: [],
|
|
14
|
-
capabilities: {
|
|
15
|
-
provides: [],
|
|
16
|
-
requires: []
|
|
17
|
-
},
|
|
18
|
-
opToPresentationLinks: [],
|
|
19
|
-
presentationsTargets: []
|
|
20
|
-
};
|
|
21
|
-
const sourceFile = new Project({ useInMemoryFileSystem: true }).createSourceFile("feature.ts", code);
|
|
22
|
-
const extractRefs = (obj, propName, allowKeyOnly = false) => {
|
|
23
|
-
const refs = [];
|
|
24
|
-
const prop = obj.getProperty(propName);
|
|
25
|
-
if (prop && Node.isPropertyAssignment(prop)) {
|
|
26
|
-
const init = prop.getInitializer();
|
|
27
|
-
if (init && Node.isArrayLiteralExpression(init)) {
|
|
28
|
-
for (const elem of init.getElements()) if (Node.isObjectLiteralExpression(elem)) {
|
|
29
|
-
const keyText = getTextFromProp(elem, "key");
|
|
30
|
-
const verText = getTextFromProp(elem, "version");
|
|
31
|
-
if (keyText && verText) refs.push({
|
|
32
|
-
key: keyText,
|
|
33
|
-
version: verText
|
|
34
|
-
});
|
|
35
|
-
else if (keyText && allowKeyOnly) refs.push({
|
|
36
|
-
key: keyText,
|
|
37
|
-
version: "1.0.0"
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return refs;
|
|
43
|
-
};
|
|
44
|
-
const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
45
|
-
for (const call of callExpressions) if ([
|
|
46
|
-
"defineFeature",
|
|
47
|
-
"defineAppConfig",
|
|
48
|
-
"defineAppBlueprint"
|
|
49
|
-
].includes(call.getExpression().getText())) {
|
|
50
|
-
const args = call.getArguments();
|
|
51
|
-
if (args.length > 0 && Node.isObjectLiteralExpression(args[0])) {
|
|
52
|
-
const obj = args[0];
|
|
53
|
-
result.operations.push(...extractRefs(obj, "operations"));
|
|
54
|
-
result.events.push(...extractRefs(obj, "events"));
|
|
55
|
-
result.presentations.push(...extractRefs(obj, "presentations"));
|
|
56
|
-
result.experiments.push(...extractRefs(obj, "experiments"));
|
|
57
|
-
const capsProp = obj.getProperty("capabilities");
|
|
58
|
-
if (capsProp && Node.isPropertyAssignment(capsProp)) {
|
|
59
|
-
const capsObj = capsProp.getInitializer();
|
|
60
|
-
if (capsObj && Node.isObjectLiteralExpression(capsObj)) {
|
|
61
|
-
result.capabilities.provides.push(...extractRefs(capsObj, "provides"));
|
|
62
|
-
result.capabilities.requires.push(...extractRefs(capsObj, "requires", true));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
const linksProp = obj.getProperty("opToPresentation");
|
|
66
|
-
if (linksProp && Node.isPropertyAssignment(linksProp)) {
|
|
67
|
-
const linksArr = linksProp.getInitializer();
|
|
68
|
-
if (linksArr && Node.isArrayLiteralExpression(linksArr)) linksArr.getElements().forEach((link) => {
|
|
69
|
-
if (Node.isObjectLiteralExpression(link)) {
|
|
70
|
-
const opProp = link.getProperty("op");
|
|
71
|
-
const presProp = link.getProperty("pres");
|
|
72
|
-
let opRef;
|
|
73
|
-
let presRef;
|
|
74
|
-
if (opProp && Node.isPropertyAssignment(opProp)) {
|
|
75
|
-
const val = opProp.getInitializer();
|
|
76
|
-
if (val && Node.isObjectLiteralExpression(val)) {
|
|
77
|
-
const key = getTextFromProp(val, "key");
|
|
78
|
-
const version = getTextFromProp(val, "version");
|
|
79
|
-
if (key && version) opRef = {
|
|
80
|
-
key,
|
|
81
|
-
version
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (presProp && Node.isPropertyAssignment(presProp)) {
|
|
86
|
-
const val = presProp.getInitializer();
|
|
87
|
-
if (val && Node.isObjectLiteralExpression(val)) {
|
|
88
|
-
const key = getTextFromProp(val, "key");
|
|
89
|
-
const version = getTextFromProp(val, "version");
|
|
90
|
-
if (key && version) presRef = {
|
|
91
|
-
key,
|
|
92
|
-
version
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (opRef && presRef) result.opToPresentationLinks.push({
|
|
97
|
-
op: opRef,
|
|
98
|
-
pres: presRef
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
extractLinks(obj, result.opToPresentationLinks);
|
|
104
|
-
const targetsProp = obj.getProperty("presentationsTargets");
|
|
105
|
-
if (targetsProp && Node.isPropertyAssignment(targetsProp)) {
|
|
106
|
-
const targetsArr = targetsProp.getInitializer();
|
|
107
|
-
if (targetsArr && Node.isArrayLiteralExpression(targetsArr)) targetsArr.getElements().forEach((targetBlock) => {
|
|
108
|
-
if (Node.isObjectLiteralExpression(targetBlock)) {
|
|
109
|
-
const key = getTextFromProp(targetBlock, "key");
|
|
110
|
-
const version = getTextFromProp(targetBlock, "version");
|
|
111
|
-
const targetsList = getTargetsList(targetBlock, "targets");
|
|
112
|
-
if (key && version && targetsList) result.presentationsTargets.push({
|
|
113
|
-
key,
|
|
114
|
-
version,
|
|
115
|
-
targets: targetsList
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
extractPresentationTargets(obj, result.presentationsTargets);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return result;
|
|
124
|
-
}
|
|
125
|
-
function extractLinks(obj, opToPresentationLinks) {
|
|
126
|
-
const interactionsProp = obj.getProperty("interactions");
|
|
127
|
-
if (interactionsProp && Node.isPropertyAssignment(interactionsProp)) {
|
|
128
|
-
const interactionsArr = interactionsProp.getInitializer();
|
|
129
|
-
if (interactionsArr && Node.isArrayLiteralExpression(interactionsArr)) interactionsArr.getElements().forEach((interaction) => {
|
|
130
|
-
if (Node.isObjectLiteralExpression(interaction)) {
|
|
131
|
-
const triggerProp = interaction.getProperty("trigger");
|
|
132
|
-
const triggerObj = triggerProp && Node.isPropertyAssignment(triggerProp) && triggerProp.getInitializer();
|
|
133
|
-
const actionProp = interaction.getProperty("action");
|
|
134
|
-
const actionObj = actionProp && Node.isPropertyAssignment(actionProp) && actionProp.getInitializer();
|
|
135
|
-
if (triggerObj && Node.isObjectLiteralExpression(triggerObj) && actionObj && Node.isObjectLiteralExpression(actionObj)) {
|
|
136
|
-
const triggerType = getTextFromProp(triggerObj, "type");
|
|
137
|
-
const actionType = getTextFromProp(actionObj, "type");
|
|
138
|
-
if (triggerType === "presentation" && actionType === "operation") {
|
|
139
|
-
const presRef = extractNestedRef(triggerObj, "presentation");
|
|
140
|
-
const opRef = extractNestedRef(actionObj, "operation");
|
|
141
|
-
if (presRef && opRef) opToPresentationLinks.push({
|
|
142
|
-
op: opRef,
|
|
143
|
-
pres: presRef
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
function extractNestedRef(obj, propName) {
|
|
152
|
-
const prop = obj.getProperty(propName);
|
|
153
|
-
if (prop && Node.isPropertyAssignment(prop)) {
|
|
154
|
-
const val = prop.getInitializer();
|
|
155
|
-
if (val && Node.isObjectLiteralExpression(val)) {
|
|
156
|
-
const key = getTextFromProp(val, "key");
|
|
157
|
-
const version = getTextFromProp(val, "version");
|
|
158
|
-
if (key && version) return {
|
|
159
|
-
key,
|
|
160
|
-
version
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
function extractPresentationTargets(obj, presentationsTargets) {
|
|
166
|
-
const presentationsProp = obj.getProperty("presentations");
|
|
167
|
-
if (presentationsProp && Node.isPropertyAssignment(presentationsProp)) {
|
|
168
|
-
const presentationsArr = presentationsProp.getInitializer();
|
|
169
|
-
if (presentationsArr && Node.isArrayLiteralExpression(presentationsArr)) {
|
|
170
|
-
for (const elem of presentationsArr.getElements()) if (Node.isObjectLiteralExpression(elem)) {
|
|
171
|
-
const keyText = getTextFromProp(elem, "key");
|
|
172
|
-
const verText = getTextFromProp(elem, "version");
|
|
173
|
-
const targetsList = getTargetsList(elem, "targets");
|
|
174
|
-
if (keyText && verText && targetsList) presentationsTargets.push({
|
|
175
|
-
key: keyText,
|
|
176
|
-
version: verText,
|
|
177
|
-
targets: targetsList
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
function getTextFromProp(obj, propName) {
|
|
184
|
-
const prop = obj.getProperty(propName);
|
|
185
|
-
if (prop && Node.isPropertyAssignment(prop)) {
|
|
186
|
-
const init = prop.getInitializer();
|
|
187
|
-
if (init && Node.isStringLiteral(init)) return init.getLiteralText();
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
function getTargetsList(obj, propName) {
|
|
191
|
-
const prop = obj.getProperty(propName);
|
|
192
|
-
if (prop && Node.isPropertyAssignment(prop)) {
|
|
193
|
-
const init = prop.getInitializer();
|
|
194
|
-
if (init && Node.isArrayLiteralExpression(init)) return init.getElements().map((e) => {
|
|
195
|
-
if (Node.isStringLiteral(e)) return e.getLiteralText();
|
|
196
|
-
return { target: e.getText() };
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
//#endregion
|
|
202
|
-
export { extractFeatureRefs };
|
|
203
|
-
//# sourceMappingURL=feature-extractor.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"feature-extractor.js","names":[],"sources":["../../src/analysis/feature-extractor.ts"],"sourcesContent":["import {\n Project,\n Node,\n SyntaxKind,\n type ObjectLiteralExpression,\n} from 'ts-morph';\nimport type { RefInfo } from '../types/analysis-types';\n\n/**\n * Extract specs referenced in a feature.\n * Uses ts-morph for robust parsing of object structures.\n */\nexport function extractFeatureRefs(code: string): {\n operations: RefInfo[];\n events: RefInfo[];\n presentations: RefInfo[];\n experiments: RefInfo[];\n capabilities: { provides: RefInfo[]; requires: RefInfo[] };\n opToPresentationLinks: { op: RefInfo; pres: RefInfo }[];\n presentationsTargets: {\n key: string;\n version: string;\n targets: Record<string, unknown>[];\n }[];\n} {\n const result = {\n operations: [] as RefInfo[],\n events: [] as RefInfo[],\n presentations: [] as RefInfo[],\n experiments: [] as RefInfo[],\n capabilities: {\n provides: [] as RefInfo[],\n requires: [] as RefInfo[],\n },\n opToPresentationLinks: [] as { op: RefInfo; pres: RefInfo }[],\n presentationsTargets: [] as {\n key: string;\n version: string;\n targets: Record<string, unknown>[];\n }[],\n };\n\n const project = new Project({ useInMemoryFileSystem: true });\n const sourceFile = project.createSourceFile('feature.ts', code);\n\n // Helper to extract ref array from an object property\n const extractRefs = (\n obj: ObjectLiteralExpression,\n propName: string,\n allowKeyOnly = false\n ): RefInfo[] => {\n const refs: RefInfo[] = [];\n const prop = obj.getProperty(propName);\n\n if (prop && Node.isPropertyAssignment(prop)) {\n const init = prop.getInitializer();\n if (init && Node.isArrayLiteralExpression(init)) {\n for (const elem of init.getElements()) {\n if (Node.isObjectLiteralExpression(elem)) {\n const keyText = getTextFromProp(elem, 'key');\n const verText = getTextFromProp(elem, 'version');\n\n if (keyText && verText) {\n refs.push({ key: keyText, version: verText });\n } else if (keyText && allowKeyOnly) {\n refs.push({ key: keyText, version: '1.0.0' });\n }\n }\n }\n }\n }\n return refs;\n };\n\n // Find defineFeature call\n const callExpressions = sourceFile.getDescendantsOfKind(\n SyntaxKind.CallExpression\n );\n\n for (const call of callExpressions) {\n if (\n ['defineFeature', 'defineAppConfig', 'defineAppBlueprint'].includes(\n call.getExpression().getText()\n )\n ) {\n const args = call.getArguments();\n if (args.length > 0 && Node.isObjectLiteralExpression(args[0])) {\n const obj = args[0];\n\n // Extract direct lists\n result.operations.push(...extractRefs(obj, 'operations'));\n result.events.push(...extractRefs(obj, 'events'));\n result.presentations.push(...extractRefs(obj, 'presentations'));\n result.experiments.push(...extractRefs(obj, 'experiments'));\n\n // Capabilities\n const capsProp = obj.getProperty('capabilities');\n if (capsProp && Node.isPropertyAssignment(capsProp)) {\n const capsObj = capsProp.getInitializer();\n if (capsObj && Node.isObjectLiteralExpression(capsObj)) {\n result.capabilities.provides.push(\n ...extractRefs(capsObj, 'provides')\n );\n result.capabilities.requires.push(\n ...extractRefs(capsObj, 'requires', true)\n );\n }\n }\n\n // Op to Presentation Links\n // explicit 'opToPresentation' field\n const linksProp = obj.getProperty('opToPresentation');\n if (linksProp && Node.isPropertyAssignment(linksProp)) {\n const linksArr = linksProp.getInitializer();\n if (linksArr && Node.isArrayLiteralExpression(linksArr)) {\n linksArr.getElements().forEach((link) => {\n if (Node.isObjectLiteralExpression(link)) {\n const opProp = link.getProperty('op');\n const presProp = link.getProperty('pres');\n\n let opRef: RefInfo | undefined;\n let presRef: RefInfo | undefined;\n\n if (opProp && Node.isPropertyAssignment(opProp)) {\n const val = opProp.getInitializer();\n if (val && Node.isObjectLiteralExpression(val)) {\n const key = getTextFromProp(val, 'key');\n const version = getTextFromProp(val, 'version');\n if (key && version) opRef = { key, version };\n }\n }\n if (presProp && Node.isPropertyAssignment(presProp)) {\n const val = presProp.getInitializer();\n if (val && Node.isObjectLiteralExpression(val)) {\n const key = getTextFromProp(val, 'key');\n const version = getTextFromProp(val, 'version');\n if (key && version) presRef = { key, version };\n }\n }\n\n if (opRef && presRef) {\n result.opToPresentationLinks.push({\n op: opRef,\n pres: presRef,\n });\n }\n }\n });\n }\n }\n\n // Also support 'interactions' style for forward compatibility\n extractLinks(obj, result.opToPresentationLinks);\n\n // Presentation Targets\n // explicit 'presentationsTargets' field\n const targetsProp = obj.getProperty('presentationsTargets');\n if (targetsProp && Node.isPropertyAssignment(targetsProp)) {\n const targetsArr = targetsProp.getInitializer();\n if (targetsArr && Node.isArrayLiteralExpression(targetsArr)) {\n targetsArr.getElements().forEach((targetBlock) => {\n if (Node.isObjectLiteralExpression(targetBlock)) {\n const key = getTextFromProp(targetBlock, 'key');\n const version = getTextFromProp(targetBlock, 'version');\n const targetsList = getTargetsList(targetBlock, 'targets');\n\n if (key && version && targetsList) {\n result.presentationsTargets.push({\n key,\n version,\n targets: targetsList as Record<string, unknown>[],\n });\n }\n }\n });\n }\n }\n\n // Also support inline targets in presentations list\n extractPresentationTargets(obj, result.presentationsTargets);\n }\n }\n }\n\n return result;\n}\n\nfunction extractLinks(\n obj: ObjectLiteralExpression,\n opToPresentationLinks: { op: RefInfo; pres: RefInfo }[]\n): void {\n const interactionsProp = obj.getProperty('interactions');\n if (interactionsProp && Node.isPropertyAssignment(interactionsProp)) {\n const interactionsArr = interactionsProp.getInitializer();\n if (interactionsArr && Node.isArrayLiteralExpression(interactionsArr)) {\n interactionsArr.getElements().forEach((interaction) => {\n if (Node.isObjectLiteralExpression(interaction)) {\n // Check: trigger: { type: 'presentation', presentation: { key, version } }\n // Check: action: { type: 'operation', operation: { key, version } }\n\n const triggerProp = interaction.getProperty('trigger');\n const triggerObj =\n triggerProp &&\n Node.isPropertyAssignment(triggerProp) &&\n triggerProp.getInitializer();\n\n const actionProp = interaction.getProperty('action');\n const actionObj =\n actionProp &&\n Node.isPropertyAssignment(actionProp) &&\n actionProp.getInitializer();\n\n if (\n triggerObj &&\n Node.isObjectLiteralExpression(triggerObj) &&\n actionObj &&\n Node.isObjectLiteralExpression(actionObj)\n ) {\n const triggerType = getTextFromProp(triggerObj, 'type');\n const actionType = getTextFromProp(actionObj, 'type');\n\n if (triggerType === 'presentation' && actionType === 'operation') {\n const presRef = extractNestedRef(triggerObj, 'presentation');\n const opRef = extractNestedRef(actionObj, 'operation');\n\n if (presRef && opRef) {\n opToPresentationLinks.push({ op: opRef, pres: presRef });\n }\n }\n }\n }\n });\n }\n }\n}\n\nfunction extractNestedRef(\n obj: ObjectLiteralExpression,\n propName: string\n): RefInfo | undefined {\n const prop = obj.getProperty(propName);\n if (prop && Node.isPropertyAssignment(prop)) {\n const val = prop.getInitializer();\n if (val && Node.isObjectLiteralExpression(val)) {\n const key = getTextFromProp(val, 'key');\n const version = getTextFromProp(val, 'version');\n if (key && version) return { key, version };\n }\n }\n return undefined;\n}\n\nfunction extractPresentationTargets(\n obj: ObjectLiteralExpression,\n presentationsTargets: {\n key: string;\n version: string;\n targets: Record<string, unknown>[];\n }[]\n): void {\n const presentationsProp = obj.getProperty('presentations');\n if (presentationsProp && Node.isPropertyAssignment(presentationsProp)) {\n const presentationsArr = presentationsProp.getInitializer();\n if (presentationsArr && Node.isArrayLiteralExpression(presentationsArr)) {\n for (const elem of presentationsArr.getElements()) {\n if (Node.isObjectLiteralExpression(elem)) {\n const keyText = getTextFromProp(elem, 'key');\n const verText = getTextFromProp(elem, 'version');\n\n // Use targets extraction from shared utility or local helper\n const targetsList = getTargetsList(elem, 'targets');\n\n if (keyText && verText && targetsList) {\n presentationsTargets.push({\n key: keyText,\n version: verText,\n targets: targetsList as Record<string, unknown>[],\n });\n }\n }\n }\n }\n }\n}\n\nfunction getTextFromProp(\n obj: ObjectLiteralExpression,\n propName: string\n): string | undefined {\n const prop = obj.getProperty(propName);\n if (prop && Node.isPropertyAssignment(prop)) {\n const init = prop.getInitializer();\n if (init && Node.isStringLiteral(init)) {\n return init.getLiteralText();\n }\n }\n return undefined;\n}\n\nfunction getTargetsList(\n obj: ObjectLiteralExpression,\n propName: string\n): unknown[] | undefined {\n const prop = obj.getProperty(propName);\n if (prop && Node.isPropertyAssignment(prop)) {\n const init = prop.getInitializer();\n if (init && Node.isArrayLiteralExpression(init)) {\n // Just extract strings for now, or raw text if they are identifiers/enums\n return init.getElements().map((e) => {\n if (Node.isStringLiteral(e)) return e.getLiteralText();\n // Fallback: create a dummy record to match the Expected type in the rest of the app\n return { target: e.getText() } as unknown;\n });\n }\n }\n return undefined;\n}\n"],"mappings":"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"}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { isStability, matchStringArrayField, matchStringField } from "./utils/matchers.js";
|
|
2
|
-
import { extractFeatureRefs } from "./feature-extractor.js";
|
|
3
|
-
|
|
4
|
-
//#region src/analysis/feature-scan.ts
|
|
5
|
-
/**
|
|
6
|
-
* Check if a file is a feature file based on naming conventions.
|
|
7
|
-
*/
|
|
8
|
-
function isFeatureFile(filePath) {
|
|
9
|
-
return filePath.includes(".feature.") && filePath.endsWith(".ts");
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Scan a feature source file to extract metadata.
|
|
13
|
-
*/
|
|
14
|
-
function scanFeatureSource(code, filePath) {
|
|
15
|
-
const key = matchStringField(code, "key") ?? extractKeyFromFilePath(filePath);
|
|
16
|
-
const version = matchStringField(code, "version") ?? "1.0.0";
|
|
17
|
-
const title = matchStringField(code, "title") ?? void 0;
|
|
18
|
-
const description = matchStringField(code, "description") ?? void 0;
|
|
19
|
-
const goal = matchStringField(code, "goal") ?? void 0;
|
|
20
|
-
const context = matchStringField(code, "context") ?? void 0;
|
|
21
|
-
const stabilityRaw = matchStringField(code, "stability");
|
|
22
|
-
const stability = isStability(stabilityRaw) ? stabilityRaw : void 0;
|
|
23
|
-
const owners = matchStringArrayField(code, "owners");
|
|
24
|
-
const tags = matchStringArrayField(code, "tags");
|
|
25
|
-
const refs = extractFeatureRefs(code);
|
|
26
|
-
return {
|
|
27
|
-
filePath,
|
|
28
|
-
key,
|
|
29
|
-
version,
|
|
30
|
-
title,
|
|
31
|
-
description,
|
|
32
|
-
goal,
|
|
33
|
-
context,
|
|
34
|
-
stability,
|
|
35
|
-
owners,
|
|
36
|
-
tags,
|
|
37
|
-
operations: refs.operations,
|
|
38
|
-
events: refs.events,
|
|
39
|
-
presentations: refs.presentations,
|
|
40
|
-
experiments: refs.experiments,
|
|
41
|
-
capabilities: refs.capabilities,
|
|
42
|
-
opToPresentationLinks: refs.opToPresentationLinks,
|
|
43
|
-
presentationsTargets: refs.presentationsTargets,
|
|
44
|
-
sourceBlock: code
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Extract key from file path as fallback.
|
|
49
|
-
*/
|
|
50
|
-
function extractKeyFromFilePath(filePath) {
|
|
51
|
-
return (filePath.split("/").pop() ?? filePath).replace(/\.feature\.[jt]s$/, "").replace(/[^a-zA-Z0-9-]/g, "-");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
//#endregion
|
|
55
|
-
export { isFeatureFile, scanFeatureSource };
|
|
56
|
-
//# sourceMappingURL=feature-scan.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"feature-scan.js","names":[],"sources":["../../src/analysis/feature-scan.ts"],"sourcesContent":["import {\n isStability,\n matchStringArrayField,\n matchStringField,\n} from './utils/matchers';\nimport type { FeatureScanResult } from '../types/analysis-types';\nimport { extractFeatureRefs } from './feature-extractor';\n\n/**\n * Check if a file is a feature file based on naming conventions.\n */\nexport function isFeatureFile(filePath: string): boolean {\n return filePath.includes('.feature.') && filePath.endsWith('.ts');\n}\n\n/**\n * Scan a feature source file to extract metadata.\n */\nexport function scanFeatureSource(\n code: string,\n filePath: string\n): FeatureScanResult {\n const key = matchStringField(code, 'key') ?? extractKeyFromFilePath(filePath);\n const versionRaw = matchStringField(code, 'version');\n const version = versionRaw ?? '1.0.0'; // Default version\n const title = matchStringField(code, 'title') ?? undefined;\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 // Parse structure using ts-morph to extract nested refs\n const refs = extractFeatureRefs(code);\n\n return {\n filePath,\n key,\n version,\n title,\n description,\n goal,\n context,\n stability,\n owners,\n tags,\n operations: refs.operations,\n events: refs.events,\n presentations: refs.presentations,\n experiments: refs.experiments,\n capabilities: refs.capabilities,\n opToPresentationLinks: refs.opToPresentationLinks,\n presentationsTargets: refs.presentationsTargets,\n sourceBlock: code,\n };\n}\n\n/**\n * Extract key from file path as fallback.\n */\nfunction extractKeyFromFilePath(filePath: string): string {\n const fileName = filePath.split('/').pop() ?? filePath;\n return fileName\n .replace(/\\.feature\\.[jt]s$/, '')\n .replace(/[^a-zA-Z0-9-]/g, '-');\n}\n"],"mappings":";;;;;;;AAWA,SAAgB,cAAc,UAA2B;AACvD,QAAO,SAAS,SAAS,YAAY,IAAI,SAAS,SAAS,MAAM;;;;;AAMnE,SAAgB,kBACd,MACA,UACmB;CACnB,MAAM,MAAM,iBAAiB,MAAM,MAAM,IAAI,uBAAuB,SAAS;CAE7E,MAAM,UADa,iBAAiB,MAAM,UAAU,IACtB;CAC9B,MAAM,QAAQ,iBAAiB,MAAM,QAAQ,IAAI;CACjD,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,OAAO,mBAAmB,KAAK;AAErC,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YAAY,KAAK;EACjB,QAAQ,KAAK;EACb,eAAe,KAAK;EACpB,aAAa,KAAK;EAClB,cAAc,KAAK;EACnB,uBAAuB,KAAK;EAC5B,sBAAsB,KAAK;EAC3B,aAAa;EACd;;;;;AAMH,SAAS,uBAAuB,UAA0B;AAExD,SADiB,SAAS,MAAM,IAAI,CAAC,KAAK,IAAI,UAE3C,QAAQ,qBAAqB,GAAG,CAChC,QAAQ,kBAAkB,IAAI"}
|