@contractspec/module.workspace 0.0.0-canary-20260113162409

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/dist/ai/prompts/code-generation.d.ts +24 -0
  4. package/dist/ai/prompts/code-generation.d.ts.map +1 -0
  5. package/dist/ai/prompts/code-generation.js +134 -0
  6. package/dist/ai/prompts/code-generation.js.map +1 -0
  7. package/dist/ai/prompts/spec-creation.d.ts +28 -0
  8. package/dist/ai/prompts/spec-creation.d.ts.map +1 -0
  9. package/dist/ai/prompts/spec-creation.js +102 -0
  10. package/dist/ai/prompts/spec-creation.js.map +1 -0
  11. package/dist/analysis/deps/graph.d.ts +34 -0
  12. package/dist/analysis/deps/graph.d.ts.map +1 -0
  13. package/dist/analysis/deps/graph.js +85 -0
  14. package/dist/analysis/deps/graph.js.map +1 -0
  15. package/dist/analysis/deps/parse-imports.d.ts +17 -0
  16. package/dist/analysis/deps/parse-imports.d.ts.map +1 -0
  17. package/dist/analysis/deps/parse-imports.js +31 -0
  18. package/dist/analysis/deps/parse-imports.js.map +1 -0
  19. package/dist/analysis/diff/deep-diff.d.ts +33 -0
  20. package/dist/analysis/diff/deep-diff.d.ts.map +1 -0
  21. package/dist/analysis/diff/deep-diff.js +114 -0
  22. package/dist/analysis/diff/deep-diff.js.map +1 -0
  23. package/dist/analysis/diff/semantic.d.ts +11 -0
  24. package/dist/analysis/diff/semantic.d.ts.map +1 -0
  25. package/dist/analysis/diff/semantic.js +97 -0
  26. package/dist/analysis/diff/semantic.js.map +1 -0
  27. package/dist/analysis/example-scan.d.ts +15 -0
  28. package/dist/analysis/example-scan.d.ts.map +1 -0
  29. package/dist/analysis/example-scan.js +116 -0
  30. package/dist/analysis/example-scan.js.map +1 -0
  31. package/dist/analysis/feature-extractor.js +203 -0
  32. package/dist/analysis/feature-extractor.js.map +1 -0
  33. package/dist/analysis/feature-scan.d.ts +15 -0
  34. package/dist/analysis/feature-scan.d.ts.map +1 -0
  35. package/dist/analysis/feature-scan.js +56 -0
  36. package/dist/analysis/feature-scan.js.map +1 -0
  37. package/dist/analysis/grouping.d.ts +79 -0
  38. package/dist/analysis/grouping.d.ts.map +1 -0
  39. package/dist/analysis/grouping.js +115 -0
  40. package/dist/analysis/grouping.js.map +1 -0
  41. package/dist/analysis/impact/classifier.d.ts +19 -0
  42. package/dist/analysis/impact/classifier.d.ts.map +1 -0
  43. package/dist/analysis/impact/classifier.js +135 -0
  44. package/dist/analysis/impact/classifier.js.map +1 -0
  45. package/dist/analysis/impact/index.js +2 -0
  46. package/dist/analysis/impact/rules.d.ts +35 -0
  47. package/dist/analysis/impact/rules.d.ts.map +1 -0
  48. package/dist/analysis/impact/rules.js +154 -0
  49. package/dist/analysis/impact/rules.js.map +1 -0
  50. package/dist/analysis/impact/types.d.ts +95 -0
  51. package/dist/analysis/impact/types.d.ts.map +1 -0
  52. package/dist/analysis/index.js +18 -0
  53. package/dist/analysis/snapshot/index.js +2 -0
  54. package/dist/analysis/snapshot/normalizer.d.ts +36 -0
  55. package/dist/analysis/snapshot/normalizer.d.ts.map +1 -0
  56. package/dist/analysis/snapshot/normalizer.js +67 -0
  57. package/dist/analysis/snapshot/normalizer.js.map +1 -0
  58. package/dist/analysis/snapshot/snapshot.d.ts +18 -0
  59. package/dist/analysis/snapshot/snapshot.d.ts.map +1 -0
  60. package/dist/analysis/snapshot/snapshot.js +163 -0
  61. package/dist/analysis/snapshot/snapshot.js.map +1 -0
  62. package/dist/analysis/snapshot/types.d.ts +74 -0
  63. package/dist/analysis/snapshot/types.d.ts.map +1 -0
  64. package/dist/analysis/spec-parser.d.ts +11 -0
  65. package/dist/analysis/spec-parser.d.ts.map +1 -0
  66. package/dist/analysis/spec-parser.js +89 -0
  67. package/dist/analysis/spec-parser.js.map +1 -0
  68. package/dist/analysis/spec-parsing-utils.d.ts +26 -0
  69. package/dist/analysis/spec-parsing-utils.d.ts.map +1 -0
  70. package/dist/analysis/spec-parsing-utils.js +98 -0
  71. package/dist/analysis/spec-parsing-utils.js.map +1 -0
  72. package/dist/analysis/spec-scan.d.ts +20 -0
  73. package/dist/analysis/spec-scan.d.ts.map +1 -0
  74. package/dist/analysis/spec-scan.js +141 -0
  75. package/dist/analysis/spec-scan.js.map +1 -0
  76. package/dist/analysis/utils/matchers.js +77 -0
  77. package/dist/analysis/utils/matchers.js.map +1 -0
  78. package/dist/analysis/utils/variables.js +45 -0
  79. package/dist/analysis/utils/variables.js.map +1 -0
  80. package/dist/analysis/validate/index.js +1 -0
  81. package/dist/analysis/validate/spec-structure.d.ts +29 -0
  82. package/dist/analysis/validate/spec-structure.d.ts.map +1 -0
  83. package/dist/analysis/validate/spec-structure.js +455 -0
  84. package/dist/analysis/validate/spec-structure.js.map +1 -0
  85. package/dist/formatter.d.ts +42 -0
  86. package/dist/formatter.d.ts.map +1 -0
  87. package/dist/formatter.js +163 -0
  88. package/dist/formatter.js.map +1 -0
  89. package/dist/formatters/index.js +2 -0
  90. package/dist/formatters/spec-markdown.d.ts +31 -0
  91. package/dist/formatters/spec-markdown.d.ts.map +1 -0
  92. package/dist/formatters/spec-markdown.js +263 -0
  93. package/dist/formatters/spec-markdown.js.map +1 -0
  94. package/dist/formatters/spec-to-docblock.d.ts +14 -0
  95. package/dist/formatters/spec-to-docblock.d.ts.map +1 -0
  96. package/dist/formatters/spec-to-docblock.js +48 -0
  97. package/dist/formatters/spec-to-docblock.js.map +1 -0
  98. package/dist/index.d.ts +42 -0
  99. package/dist/index.js +39 -0
  100. package/dist/templates/app-config.d.ts +7 -0
  101. package/dist/templates/app-config.d.ts.map +1 -0
  102. package/dist/templates/app-config.js +107 -0
  103. package/dist/templates/app-config.js.map +1 -0
  104. package/dist/templates/data-view.d.ts +7 -0
  105. package/dist/templates/data-view.d.ts.map +1 -0
  106. package/dist/templates/data-view.js +69 -0
  107. package/dist/templates/data-view.js.map +1 -0
  108. package/dist/templates/event.d.ts +11 -0
  109. package/dist/templates/event.d.ts.map +1 -0
  110. package/dist/templates/event.js +41 -0
  111. package/dist/templates/event.js.map +1 -0
  112. package/dist/templates/experiment.d.ts +7 -0
  113. package/dist/templates/experiment.d.ts.map +1 -0
  114. package/dist/templates/experiment.js +88 -0
  115. package/dist/templates/experiment.js.map +1 -0
  116. package/dist/templates/handler.d.ts +20 -0
  117. package/dist/templates/handler.d.ts.map +1 -0
  118. package/dist/templates/handler.js +96 -0
  119. package/dist/templates/handler.js.map +1 -0
  120. package/dist/templates/integration-utils.js +105 -0
  121. package/dist/templates/integration-utils.js.map +1 -0
  122. package/dist/templates/integration.d.ts +7 -0
  123. package/dist/templates/integration.d.ts.map +1 -0
  124. package/dist/templates/integration.js +62 -0
  125. package/dist/templates/integration.js.map +1 -0
  126. package/dist/templates/knowledge.d.ts +7 -0
  127. package/dist/templates/knowledge.d.ts.map +1 -0
  128. package/dist/templates/knowledge.js +69 -0
  129. package/dist/templates/knowledge.js.map +1 -0
  130. package/dist/templates/migration.d.ts +7 -0
  131. package/dist/templates/migration.d.ts.map +1 -0
  132. package/dist/templates/migration.js +61 -0
  133. package/dist/templates/migration.js.map +1 -0
  134. package/dist/templates/operation.d.ts +11 -0
  135. package/dist/templates/operation.d.ts.map +1 -0
  136. package/dist/templates/operation.js +101 -0
  137. package/dist/templates/operation.js.map +1 -0
  138. package/dist/templates/presentation.d.ts +11 -0
  139. package/dist/templates/presentation.d.ts.map +1 -0
  140. package/dist/templates/presentation.js +79 -0
  141. package/dist/templates/presentation.js.map +1 -0
  142. package/dist/templates/telemetry.d.ts +7 -0
  143. package/dist/templates/telemetry.d.ts.map +1 -0
  144. package/dist/templates/telemetry.js +90 -0
  145. package/dist/templates/telemetry.js.map +1 -0
  146. package/dist/templates/utils.d.ts +27 -0
  147. package/dist/templates/utils.d.ts.map +1 -0
  148. package/dist/templates/utils.js +39 -0
  149. package/dist/templates/utils.js.map +1 -0
  150. package/dist/templates/workflow-runner.d.ts +16 -0
  151. package/dist/templates/workflow-runner.d.ts.map +1 -0
  152. package/dist/templates/workflow-runner.js +49 -0
  153. package/dist/templates/workflow-runner.js.map +1 -0
  154. package/dist/templates/workflow.d.ts +11 -0
  155. package/dist/templates/workflow.d.ts.map +1 -0
  156. package/dist/templates/workflow.js +68 -0
  157. package/dist/templates/workflow.js.map +1 -0
  158. package/dist/types/analysis-types.d.ts +199 -0
  159. package/dist/types/analysis-types.d.ts.map +1 -0
  160. package/dist/types/generation-types.d.ts +87 -0
  161. package/dist/types/generation-types.d.ts.map +1 -0
  162. package/dist/types/generation-types.js +21 -0
  163. package/dist/types/generation-types.js.map +1 -0
  164. package/dist/types/llm-types.d.ts +138 -0
  165. package/dist/types/llm-types.d.ts.map +1 -0
  166. package/dist/types/rulesync-types.d.ts +24 -0
  167. package/dist/types/rulesync-types.d.ts.map +1 -0
  168. package/dist/types/spec-types.d.ts +343 -0
  169. package/dist/types/spec-types.d.ts.map +1 -0
  170. package/package.json +63 -0
@@ -0,0 +1,74 @@
1
+ import { EventRef } from "@contractspec/lib.contracts";
2
+
3
+ //#region src/analysis/snapshot/types.d.ts
4
+
5
+ /** Field type in a schema */
6
+ type FieldType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'enum' | 'union' | 'literal' | 'date' | 'unknown';
7
+ /** Schema field definition */
8
+ interface FieldSnapshot {
9
+ name: string;
10
+ type: FieldType;
11
+ required: boolean;
12
+ nullable: boolean;
13
+ description?: string;
14
+ enumValues?: string[];
15
+ literalValue?: unknown;
16
+ items?: FieldSnapshot;
17
+ properties?: Record<string, FieldSnapshot>;
18
+ unionTypes?: FieldSnapshot[];
19
+ }
20
+ /** IO schema snapshot */
21
+ interface IoSnapshot {
22
+ input: Record<string, FieldSnapshot>;
23
+ output: Record<string, FieldSnapshot>;
24
+ }
25
+ /** HTTP binding snapshot */
26
+ interface HttpBindingSnapshot {
27
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
28
+ path: string;
29
+ }
30
+ /** Operation snapshot */
31
+ interface OperationSnapshot {
32
+ type: 'operation';
33
+ key: string;
34
+ version: string;
35
+ kind: 'command' | 'query';
36
+ stability: string;
37
+ http?: HttpBindingSnapshot;
38
+ io: IoSnapshot;
39
+ authLevel?: string;
40
+ emittedEvents?: EventRef[];
41
+ }
42
+ /** Event payload snapshot */
43
+ interface EventSnapshot {
44
+ type: 'event';
45
+ key: string;
46
+ version: string;
47
+ stability: string;
48
+ payload: Record<string, FieldSnapshot>;
49
+ }
50
+ /** Spec snapshot union type */
51
+ type SpecSnapshot = OperationSnapshot | EventSnapshot;
52
+ /** Full contract snapshot for a workspace */
53
+ interface ContractSnapshot {
54
+ /** Schema version for forward compatibility */
55
+ version: '1.0.0';
56
+ /** Generation timestamp (ISO 8601) */
57
+ generatedAt: string;
58
+ /** Git commit SHA if available */
59
+ commitSha?: string;
60
+ /** All specs in the workspace */
61
+ specs: SpecSnapshot[];
62
+ /** Content hash for quick comparison */
63
+ hash: string;
64
+ }
65
+ /** Options for snapshot generation */
66
+ interface SnapshotOptions {
67
+ /** Glob pattern for spec discovery */
68
+ pattern?: string;
69
+ /** Include only specific spec types */
70
+ types?: ('operation' | 'event')[];
71
+ }
72
+ //#endregion
73
+ export { ContractSnapshot, EventSnapshot, FieldSnapshot, FieldType, HttpBindingSnapshot, IoSnapshot, OperationSnapshot, SnapshotOptions, SpecSnapshot };
74
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/analysis/snapshot/types.ts"],"sourcesContent":[],"mappings":";;;;;AAgCe,KAtBH,SAAA,GAsBG,QAAA,GAAA,QAAA,GAAA,SAAA,GAAA,QAAA,GAAA,OAAA,GAAA,MAAA,GAAA,OAAA,GAAA,SAAA,GAAA,MAAA,GAAA,SAAA;;AACa,UAVX,aAAA,CAUW;EAIX,IAAA,EAAA,MAAA;EACO,IAAA,EAbhB,SAagB;EAAf,QAAA,EAAA,OAAA;EACgB,QAAA,EAAA,OAAA;EAAf,WAAA,CAAA,EAAA,MAAA;EAAM,UAAA,CAAA,EAAA,MAAA,EAAA;EAIC,YAAA,CAAA,EAAA,OAAmB;EAMnB,KAAA,CAAA,EAlBP,aAkBwB;EAMzB,UAAA,CAAA,EAvBM,MAuBN,CAAA,MAAA,EAvBqB,aAuBrB,CAAA;EACH,UAAA,CAAA,EAvBS,aAuBT,EAAA;;;AAMW,UAzBA,UAAA,CAyBa;EASlB,KAAA,EAjCH,MAiCG,CAAA,MAAY,EAjCA,aAiCG,CAAA;EAGV,MAAA,EAnCP,MAmCO,CAAA,MAAgB,EAnCR,aA2ChB,CAAA;AAMT;;UA7CiB,mBAAA;;;;;UAMA,iBAAA;;;;;;SAMR;MACH;;kBAEY;;;UAID,aAAA;;;;;WAKN,eAAe;;;KAId,YAAA,GAAe,oBAAoB;;UAG9B,gBAAA;;;;;;;;SAQR;;;;;UAMQ,eAAA"}
@@ -0,0 +1,11 @@
1
+ import { ParsedSpec } from "../types/llm-types.js";
2
+
3
+ //#region src/analysis/spec-parser.d.ts
4
+
5
+ /**
6
+ * Load and parse specs from a source file.
7
+ */
8
+ declare function loadSpecFromSource(filePath: string): Promise<ParsedSpec[]>;
9
+ //#endregion
10
+ export { loadSpecFromSource };
11
+ //# sourceMappingURL=spec-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-parser.d.ts","names":[],"sources":["../../src/analysis/spec-parser.ts"],"sourcesContent":[],"mappings":";;;;;;;iBAoBsB,kBAAA,oBAEnB,QAAQ"}
@@ -0,0 +1,89 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,26 @@
1
+ //#region src/analysis/spec-parsing-utils.d.ts
2
+
3
+ /**
4
+ * Extract test target from a TestSpec source.
5
+ * Parses the `target: { type: 'operation', key, version }` field OR
6
+ * the nested format `target: { type: 'operation', operation: { key, version } }`.
7
+ */
8
+ declare function extractTestTarget(code: string): {
9
+ type: 'operation' | 'workflow';
10
+ key: string;
11
+ version: string | undefined;
12
+ } | undefined;
13
+ /**
14
+ * Extract test coverage info from a TestSpec source.
15
+ * Checks for presence of success (expectOutput) and failure (expectError) scenarios.
16
+ * Supports both formats:
17
+ * - New: `expectOutput: {}` and `expectError: {}`
18
+ * - Old: `{ type: 'expectOutput', ... }` and `{ type: 'expectError', ... }`
19
+ */
20
+ declare function extractTestCoverage(code: string): {
21
+ hasSuccess: boolean;
22
+ hasError: boolean;
23
+ };
24
+ //#endregion
25
+ export { extractTestCoverage, extractTestTarget };
26
+ //# sourceMappingURL=spec-parsing-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-parsing-utils.d.ts","names":[],"sources":["../../src/analysis/spec-parsing-utils.ts"],"sourcesContent":[],"mappings":";;;;;;;iBA0EgB,iBAAA;;;;;;;;;;;;iBAiEA,mBAAA"}
@@ -0,0 +1,98 @@
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 = /* @__PURE__ */ 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 = /* @__PURE__ */ 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(/* @__PURE__ */ 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
@@ -0,0 +1 @@
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,wBAAQ,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,wBAAQ,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,sBAChC,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"}
@@ -0,0 +1,20 @@
1
+ import { SpecScanResult } from "../types/analysis-types.js";
2
+ import { extractTestCoverage, extractTestTarget } from "./spec-parsing-utils.js";
3
+
4
+ //#region src/analysis/spec-scan.d.ts
5
+
6
+ /**
7
+ * Scan all specs from a single source file.
8
+ */
9
+ declare function scanAllSpecsFromSource(code: string, filePath: string): SpecScanResult[];
10
+ /**
11
+ * Scan a single spec source string.
12
+ */
13
+ declare function scanSpecSource(code: string, filePath: string): SpecScanResult;
14
+ /**
15
+ * Infer spec type from file path convention.
16
+ */
17
+ declare function inferSpecTypeFromFilePath(filePath: string): SpecScanResult['specType'] | 'feature' | 'unknown';
18
+ //#endregion
19
+ export { inferSpecTypeFromFilePath, scanAllSpecsFromSource, scanSpecSource };
20
+ //# sourceMappingURL=spec-scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-scan.d.ts","names":[],"sources":["../../src/analysis/spec-scan.ts"],"sourcesContent":[],"mappings":";;;;;;;;iBAiCgB,sBAAA,kCAGb;;;;iBAsDa,cAAA,kCAAgD;;;;iBAsGhD,yBAAA,oBAEb"}
@@ -0,0 +1,141 @@
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
+ /**
36
+ * Scan a single spec source string.
37
+ */
38
+ function scanSpecSource(code, filePath) {
39
+ const key = (code.match(/key\s*:\s*['"]([^'"]+)['"]/) ?? code.match(/export\s+const\s+(\w+)\s*=/))?.[1] ?? "unknown";
40
+ const version = matchVersionField(code, "version");
41
+ const description = matchStringField(code, "description") ?? void 0;
42
+ const goal = matchStringField(code, "goal") ?? void 0;
43
+ const context = matchStringField(code, "context") ?? void 0;
44
+ const stabilityRaw = matchStringField(code, "stability");
45
+ const stability = isStability(stabilityRaw) ? stabilityRaw : void 0;
46
+ const owners = matchStringArrayField(code, "owners");
47
+ const tags = matchStringArrayField(code, "tags");
48
+ let specType = "unknown";
49
+ let kind;
50
+ if (code.includes("defineCommand")) {
51
+ specType = "operation";
52
+ kind = "command";
53
+ } else if (code.includes("defineQuery")) {
54
+ specType = "operation";
55
+ kind = "query";
56
+ } else if (code.includes("defineEvent")) {
57
+ specType = "event";
58
+ kind = "event";
59
+ } else if (code.includes("definePresentation")) {
60
+ specType = "presentation";
61
+ kind = "presentation";
62
+ } else if (code.includes("definePolicy")) {
63
+ specType = "policy";
64
+ kind = "policy";
65
+ } else if (code.includes("defineCapability")) {
66
+ specType = "capability";
67
+ kind = "capability";
68
+ } else if (code.includes("defineExample")) {
69
+ specType = "example";
70
+ kind = "example";
71
+ } else if (code.includes("defineAppConfig")) {
72
+ specType = "app-config";
73
+ kind = "app-config";
74
+ } else if (code.includes("defineIntegration")) {
75
+ specType = "integration";
76
+ kind = "integration";
77
+ } else if (code.includes("defineWorkflow")) {
78
+ specType = "workflow";
79
+ kind = "workflow";
80
+ } else if (code.includes("defineTestSpec")) {
81
+ specType = "test-spec";
82
+ kind = "test-spec";
83
+ } else if (code.includes("defineFeature")) {
84
+ specType = "feature";
85
+ kind = "feature";
86
+ }
87
+ const hasMeta = /meta\s*:\s*\{/.test(code);
88
+ const hasIo = /io\s*:\s*\{/.test(code);
89
+ const hasPolicy = /policy\s*:\s*\{/.test(code);
90
+ const hasPayload = /payload\s*:\s*\{/.test(code);
91
+ const hasContent = /content\s*:\s*\{/.test(code);
92
+ const hasDefinition = /definition\s*:\s*\{/.test(code);
93
+ const emittedEvents = extractRefList(code, "emits") ?? extractRefList(code, "emittedEvents");
94
+ const testRefs = extractTestRefs(code);
95
+ const policyRefs = hasPolicy ? parsePolicy(code) : void 0;
96
+ return {
97
+ filePath,
98
+ key,
99
+ version,
100
+ specType,
101
+ kind,
102
+ description,
103
+ goal,
104
+ context,
105
+ stability,
106
+ owners,
107
+ tags,
108
+ hasMeta,
109
+ hasIo,
110
+ hasPolicy,
111
+ hasPayload,
112
+ hasContent,
113
+ hasDefinition,
114
+ emittedEvents,
115
+ policyRefs,
116
+ testRefs,
117
+ testTarget: extractTestTarget(code),
118
+ testCoverage: extractTestCoverage(code),
119
+ sourceBlock: code
120
+ };
121
+ }
122
+ /**
123
+ * Infer spec type from file path convention.
124
+ */
125
+ function inferSpecTypeFromFilePath(filePath) {
126
+ if (filePath.includes(".contracts.") || /\/operations?\//.test(filePath)) return "operation";
127
+ if (filePath.includes(".event.") || /\/events?\//.test(filePath)) return "event";
128
+ if (filePath.includes(".presentation.") || /\/presentations?\//.test(filePath)) return "presentation";
129
+ if (filePath.includes(".policy.") || /\/policies?\//.test(filePath)) return "policy";
130
+ if (filePath.includes(".feature.") || /\/features?\//.test(filePath)) return "feature";
131
+ if (filePath.includes(".type.") || /\/types?\//.test(filePath)) return "type";
132
+ if (filePath.includes(".example.") || /\/examples?\//.test(filePath)) return "example";
133
+ if (filePath.includes(".app-config.")) return "app-config";
134
+ if (filePath.includes(".workflow.") || /\/workflows?\//.test(filePath)) return "workflow";
135
+ if (filePath.includes(".integration.") || /\/integrations?\//.test(filePath)) return "integration";
136
+ return "unknown";
137
+ }
138
+
139
+ //#endregion
140
+ export { inferSpecTypeFromFilePath, scanAllSpecsFromSource, scanSpecSource };
141
+ //# sourceMappingURL=spec-scan.js.map
@@ -0,0 +1 @@
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 { SpecScanResult } 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 parsePolicy,\n extractRefList,\n extractTestRefs,\n extractTestCoverage,\n extractTestTarget,\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\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 let specType: SpecScanResult['specType'] = 'unknown';\n let kind: string | undefined;\n\n if (code.includes('defineCommand')) {\n specType = 'operation';\n kind = 'command';\n } else if (code.includes('defineQuery')) {\n specType = 'operation';\n kind = 'query';\n } else if (code.includes('defineEvent')) {\n specType = 'event';\n kind = 'event';\n } else if (code.includes('definePresentation')) {\n specType = 'presentation';\n kind = 'presentation';\n } else if (code.includes('definePolicy')) {\n specType = 'policy';\n kind = 'policy';\n } else if (code.includes('defineCapability')) {\n specType = 'capability';\n kind = 'capability';\n } else if (code.includes('defineExample')) {\n specType = 'example';\n kind = 'example';\n } else if (code.includes('defineAppConfig')) {\n specType = 'app-config';\n kind = 'app-config';\n } else if (code.includes('defineIntegration')) {\n specType = 'integration';\n kind = 'integration';\n } else if (code.includes('defineWorkflow')) {\n specType = 'workflow';\n kind = 'workflow';\n } else if (code.includes('defineTestSpec')) {\n specType = 'test-spec';\n kind = 'test-spec';\n } else if (code.includes('defineFeature')) {\n specType = 'feature';\n kind = 'feature';\n }\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,\n kind: kind as SpecScanResult['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":"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"}
@@ -0,0 +1,77 @@
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 = /* @__PURE__ */ 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 = /* @__PURE__ */ 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 = /* @__PURE__ */ 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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matchers.js","names":[],"sources":["../../../src/analysis/utils/matchers.ts"],"sourcesContent":["/**\n * Shared regex matchers and parsing utilities for spec scanning.\n */\n\nimport type { Stability } from '../../types/spec-types';\n\n/**\n * Escape regex special characters.\n */\nexport function escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Match a string field in source code.\n */\nexport function matchStringField(code: string, field: string): string | null {\n const regex = new RegExp(`${escapeRegex(field)}\\\\s*:\\\\s*['\"]([^'\"]+)['\"]`);\n const match = code.match(regex);\n return match?.[1] ?? null;\n}\n\n/**\n * Match a string field within a limited scope.\n */\nexport function matchStringFieldIn(code: string, field: string): string | null {\n return matchStringField(code, field);\n}\n\n/**\n * Match a version field which can be a string or number.\n */\nexport function matchVersionField(\n code: string,\n field: string\n): string | undefined {\n const regex = new RegExp(\n `${escapeRegex(field)}\\\\s*:\\\\s*(?:['\"]([^'\"]+)['\"]|(\\\\d+(?:\\\\.\\\\d+)*))`\n );\n const match = code.match(regex);\n if (match?.[1]) return match[1];\n if (match?.[2]) return match[2];\n return undefined;\n}\n\n/**\n * Match a string array field in source code.\n */\nexport function matchStringArrayField(\n code: string,\n field: string\n): 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 = Array.from(inner.matchAll(/['\"]([^'\"]+)['\"]/g))\n .map((m) => m[1])\n .filter(\n (value): value is string => typeof value === 'string' && value.length > 0\n );\n\n return items.length > 0 ? items : undefined;\n}\n\n/**\n * Check if a value is a valid stability.\n */\nexport function isStability(value: string | null): value is Stability {\n return (\n value === 'experimental' ||\n value === 'beta' ||\n value === 'stable' ||\n value === 'deprecated'\n );\n}\n\n/**\n * Find matching closing delimiter for an opening delimiter.\n * Returns the index of the closing delimiter or -1 if not found.\n */\nexport function findMatchingDelimiter(\n code: string,\n startIndex: number,\n openChar: string,\n closeChar: string\n): number {\n let depth = 0;\n let inString = false;\n let stringChar = '';\n\n for (let i = startIndex; i < code.length; i++) {\n const char = code[i];\n const prevChar = i > 0 ? code[i - 1] : '';\n\n // Handle string literals\n if ((char === '\"' || char === \"'\" || char === '`') && prevChar !== '\\\\') {\n if (!inString) {\n inString = true;\n stringChar = char;\n } else if (char === stringChar) {\n inString = false;\n }\n continue;\n }\n\n if (inString) continue;\n\n if (char === openChar) {\n depth++;\n } else if (char === closeChar) {\n depth--;\n if (depth === 0) {\n return i;\n }\n }\n }\n\n return -1;\n}\n\n/**\n * Find matching closing brace for an opening brace.\n * Returns the index of the closing brace or -1 if not found.\n */\nexport function findMatchingBrace(code: string, startIndex: number): number {\n return findMatchingDelimiter(code, startIndex, '{', '}');\n}\n"],"mappings":";;;;AASA,SAAgB,YAAY,OAAuB;AACjD,QAAO,MAAM,QAAQ,uBAAuB,OAAO;;;;;AAMrD,SAAgB,iBAAiB,MAAc,OAA8B;CAC3E,MAAM,wBAAQ,IAAI,OAAO,GAAG,YAAY,MAAM,CAAC,2BAA2B;AAE1E,QADc,KAAK,MAAM,MAAM,GAChB,MAAM;;;;;AAMvB,SAAgB,mBAAmB,MAAc,OAA8B;AAC7E,QAAO,iBAAiB,MAAM,MAAM;;;;;AAMtC,SAAgB,kBACd,MACA,OACoB;CACpB,MAAM,wBAAQ,IAAI,OAChB,GAAG,YAAY,MAAM,CAAC,kDACvB;CACD,MAAM,QAAQ,KAAK,MAAM,MAAM;AAC/B,KAAI,QAAQ,GAAI,QAAO,MAAM;AAC7B,KAAI,QAAQ,GAAI,QAAO,MAAM;;;;;AAO/B,SAAgB,sBACd,MACA,OACsB;CACtB,MAAM,wBAAQ,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,QAAQ,MAAM,KAAK,MAAM,SAAS,oBAAoB,CAAC,CAC1D,KAAK,MAAM,EAAE,GAAG,CAChB,QACE,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,EACzE;AAEH,QAAO,MAAM,SAAS,IAAI,QAAQ;;;;;AAMpC,SAAgB,YAAY,OAA0C;AACpE,QACE,UAAU,kBACV,UAAU,UACV,UAAU,YACV,UAAU;;;;;;AAQd,SAAgB,sBACd,MACA,YACA,UACA,WACQ;CACR,IAAI,QAAQ;CACZ,IAAI,WAAW;CACf,IAAI,aAAa;AAEjB,MAAK,IAAI,IAAI,YAAY,IAAI,KAAK,QAAQ,KAAK;EAC7C,MAAM,OAAO,KAAK;EAClB,MAAM,WAAW,IAAI,IAAI,KAAK,IAAI,KAAK;AAGvC,OAAK,SAAS,QAAO,SAAS,OAAO,SAAS,QAAQ,aAAa,MAAM;AACvE,OAAI,CAAC,UAAU;AACb,eAAW;AACX,iBAAa;cACJ,SAAS,WAClB,YAAW;AAEb;;AAGF,MAAI,SAAU;AAEd,MAAI,SAAS,SACX;WACS,SAAS,WAAW;AAC7B;AACA,OAAI,UAAU,EACZ,QAAO;;;AAKb,QAAO"}