@contractspec/module.workspace 1.44.1 → 1.45.1

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 (63) hide show
  1. package/dist/ai/{code-generation.d.ts → prompts/code-generation.d.ts} +6 -10
  2. package/dist/ai/prompts/code-generation.d.ts.map +1 -0
  3. package/dist/ai/{code-generation.js → prompts/code-generation.js} +6 -10
  4. package/dist/ai/prompts/code-generation.js.map +1 -0
  5. package/dist/ai/{spec-creation.d.ts → prompts/spec-creation.d.ts} +8 -7
  6. package/dist/ai/prompts/spec-creation.d.ts.map +1 -0
  7. package/dist/ai/{spec-creation.js → prompts/spec-creation.js} +6 -6
  8. package/dist/ai/prompts/spec-creation.js.map +1 -0
  9. package/dist/analysis/example-scan.d.ts +15 -0
  10. package/dist/analysis/example-scan.d.ts.map +1 -0
  11. package/dist/analysis/example-scan.js +151 -0
  12. package/dist/analysis/example-scan.js.map +1 -0
  13. package/dist/analysis/feature-scan.js +17 -12
  14. package/dist/analysis/feature-scan.js.map +1 -1
  15. package/dist/analysis/impact/classifier.js +1 -1
  16. package/dist/analysis/impact/classifier.js.map +1 -1
  17. package/dist/analysis/impact/types.d.ts +3 -3
  18. package/dist/analysis/index.js +2 -0
  19. package/dist/analysis/snapshot/normalizer.d.ts +1 -1
  20. package/dist/analysis/snapshot/normalizer.d.ts.map +1 -1
  21. package/dist/analysis/snapshot/normalizer.js +2 -1
  22. package/dist/analysis/snapshot/normalizer.js.map +1 -1
  23. package/dist/analysis/snapshot/snapshot.js +1 -1
  24. package/dist/analysis/snapshot/snapshot.js.map +1 -1
  25. package/dist/analysis/snapshot/types.d.ts +7 -13
  26. package/dist/analysis/snapshot/types.d.ts.map +1 -1
  27. package/dist/analysis/spec-parser.d.ts +11 -0
  28. package/dist/analysis/spec-parser.d.ts.map +1 -0
  29. package/dist/analysis/spec-parser.js +89 -0
  30. package/dist/analysis/spec-parser.js.map +1 -0
  31. package/dist/analysis/spec-scan.d.ts.map +1 -1
  32. package/dist/analysis/spec-scan.js +50 -26
  33. package/dist/analysis/spec-scan.js.map +1 -1
  34. package/dist/formatter.js +5 -5
  35. package/dist/formatter.js.map +1 -1
  36. package/dist/formatters/spec-markdown.d.ts +28 -0
  37. package/dist/formatters/spec-markdown.d.ts.map +1 -0
  38. package/dist/formatters/spec-markdown.js +255 -0
  39. package/dist/formatters/spec-markdown.js.map +1 -0
  40. package/dist/formatters/spec-to-docblock.d.ts +12 -0
  41. package/dist/formatters/spec-to-docblock.d.ts.map +1 -0
  42. package/dist/formatters/spec-to-docblock.js +48 -0
  43. package/dist/formatters/spec-to-docblock.js.map +1 -0
  44. package/dist/index.d.ts +10 -5
  45. package/dist/index.js +7 -3
  46. package/dist/templates/app-config.js +1 -1
  47. package/dist/templates/app-config.js.map +1 -1
  48. package/dist/templates/data-view.js +3 -3
  49. package/dist/templates/data-view.js.map +1 -1
  50. package/dist/types/analysis-types.d.ts +56 -4
  51. package/dist/types/analysis-types.d.ts.map +1 -1
  52. package/dist/types/generation-types.d.ts +2 -1
  53. package/dist/types/generation-types.d.ts.map +1 -1
  54. package/dist/types/generation-types.js.map +1 -1
  55. package/dist/types/llm-types.d.ts +138 -0
  56. package/dist/types/llm-types.d.ts.map +1 -0
  57. package/dist/types/spec-types.d.ts +24 -26
  58. package/dist/types/spec-types.d.ts.map +1 -1
  59. package/package.json +6 -5
  60. package/dist/ai/code-generation.d.ts.map +0 -1
  61. package/dist/ai/code-generation.js.map +0 -1
  62. package/dist/ai/spec-creation.d.ts.map +0 -1
  63. package/dist/ai/spec-creation.js.map +0 -1
@@ -1,26 +1,22 @@
1
- //#region src/ai/code-generation.d.ts
1
+ //#region src/ai/prompts/code-generation.d.ts
2
2
  /**
3
- * AI prompts for code generation.
4
- * Extracted from cli-contractspec/src/ai/prompts/code-generation.ts
5
- */
6
- /**
7
- * Build prompt for generating handler implementation.
3
+ * Build prompt for generating handler implementation
8
4
  */
9
5
  declare function buildHandlerPrompt(specCode: string): string;
10
6
  /**
11
- * Build prompt for generating React component from presentation spec.
7
+ * Build prompt for generating React component from presentation spec
12
8
  */
13
9
  declare function buildComponentPrompt(specCode: string): string;
14
10
  /**
15
- * Build prompt for generating form component.
11
+ * Build prompt for generating form component
16
12
  */
17
13
  declare function buildFormPrompt(specCode: string): string;
18
14
  /**
19
- * Build prompt for generating tests.
15
+ * Build prompt for generating tests
20
16
  */
21
17
  declare function buildTestPrompt(specCode: string, implementationCode: string, testType: 'handler' | 'component'): string;
22
18
  /**
23
- * System prompt for code generation.
19
+ * System prompt for code generation
24
20
  */
25
21
  declare function getCodeGenSystemPrompt(): string;
26
22
  //#endregion
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-generation.d.ts","names":[],"sources":["../../../src/ai/prompts/code-generation.ts"],"sourcesContent":[],"mappings":";;AAGA;AA0BA;AA0BgB,iBApDA,kBAAA,CAoDe,QAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AA2B/B;AA2CA;;iBAhGgB,oBAAA;;;;iBA0BA,eAAA;;;;iBA2BA,eAAA;;;;iBA2CA,sBAAA,CAAA"}
@@ -1,10 +1,6 @@
1
- //#region src/ai/code-generation.ts
1
+ //#region src/ai/prompts/code-generation.ts
2
2
  /**
3
- * AI prompts for code generation.
4
- * Extracted from cli-contractspec/src/ai/prompts/code-generation.ts
5
- */
6
- /**
7
- * Build prompt for generating handler implementation.
3
+ * Build prompt for generating handler implementation
8
4
  */
9
5
  function buildHandlerPrompt(specCode) {
10
6
  return `You are a senior TypeScript developer implementing a handler for a contract specification.
@@ -29,7 +25,7 @@ The handler should be production-ready with proper error handling, logging point
29
25
  Return only the TypeScript code for the handler function.`;
30
26
  }
31
27
  /**
32
- * Build prompt for generating React component from presentation spec.
28
+ * Build prompt for generating React component from presentation spec
33
29
  */
34
30
  function buildComponentPrompt(specCode) {
35
31
  return `You are a senior React developer creating a component for a presentation specification.
@@ -54,7 +50,7 @@ The component should follow Atomic Design principles and be reusable.
54
50
  Return only the TypeScript/TSX code for the component.`;
55
51
  }
56
52
  /**
57
- * Build prompt for generating form component.
53
+ * Build prompt for generating form component
58
54
  */
59
55
  function buildFormPrompt(specCode) {
60
56
  return `You are a senior React developer creating a form component from a form specification.
@@ -80,7 +76,7 @@ The form should provide excellent UX with real-time validation and helpful feedb
80
76
  Return only the TypeScript/TSX code for the form component.`;
81
77
  }
82
78
  /**
83
- * Build prompt for generating tests.
79
+ * Build prompt for generating tests
84
80
  */
85
81
  function buildTestPrompt(specCode, implementationCode, testType) {
86
82
  return `You are a senior developer writing comprehensive tests.
@@ -113,7 +109,7 @@ Use clear test descriptions and follow AAA pattern (Arrange, Act, Assert).
113
109
  Return only the TypeScript test code.`;
114
110
  }
115
111
  /**
116
- * System prompt for code generation.
112
+ * System prompt for code generation
117
113
  */
118
114
  function getCodeGenSystemPrompt() {
119
115
  return `You are an expert TypeScript developer with deep knowledge of:
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-generation.js","names":[],"sources":["../../../src/ai/prompts/code-generation.ts"],"sourcesContent":["/**\n * Build prompt for generating handler implementation\n */\nexport function buildHandlerPrompt(specCode: string): string {\n return `You are a senior TypeScript developer implementing a handler for a contract specification.\n\nHere is the contract spec:\n\n\\`\\`\\`typescript\n${specCode}\n\\`\\`\\`\n\nGenerate a complete handler implementation that:\n\n1. **Matches the spec signature**: Input/output types from the spec\n2. **Handles errors**: Implement error cases defined in spec.io.errors\n3. **Emits events**: Use the events declared in spec.sideEffects.emits\n4. **Validates input**: Use zod validation from the schema\n5. **Follows best practices**: Clean, type-safe TypeScript\n6. **Includes comments**: Explain business logic\n\nThe handler should be production-ready with proper error handling, logging points, and clear structure.\n\nReturn only the TypeScript code for the handler function.`;\n}\n\n/**\n * Build prompt for generating React component from presentation spec\n */\nexport function buildComponentPrompt(specCode: string): string {\n return `You are a senior React developer creating a component for a presentation specification.\n\nHere is the presentation spec:\n\n\\`\\`\\`typescript\n${specCode}\n\\`\\`\\`\n\nGenerate a complete React component that:\n\n1. **Props interface**: Typed props from the spec\n2. **Accessibility**: Proper ARIA labels, roles, keyboard navigation\n3. **Mobile-first**: Optimized for small screens and touch\n4. **Clean UI**: Simple, intuitive interface\n5. **Type-safe**: Full TypeScript with no 'any' types\n6. **Best practices**: React hooks, proper state management\n\nThe component should follow Atomic Design principles and be reusable.\n\nReturn only the TypeScript/TSX code for the component.`;\n}\n\n/**\n * Build prompt for generating form component\n */\nexport function buildFormPrompt(specCode: string): string {\n return `You are a senior React developer creating a form component from a form specification.\n\nHere is the form spec:\n\n\\`\\`\\`typescript\n${specCode}\n\\`\\`\\`\n\nGenerate a complete form component using react-hook-form that:\n\n1. **Form validation**: Use zod schema for validation\n2. **Field types**: Proper inputs for each field type\n3. **Conditional logic**: Support visibleWhen, enabledWhen, requiredWhen predicates\n4. **Error handling**: Clear, user-friendly error messages\n5. **Accessibility**: Labels, hints, ARIA attributes\n6. **Mobile-optimized**: Touch-friendly, appropriate input types\n7. **Type-safe**: Full TypeScript\n\nThe form should provide excellent UX with real-time validation and helpful feedback.\n\nReturn only the TypeScript/TSX code for the form component.`;\n}\n\n/**\n * Build prompt for generating tests\n */\nexport function buildTestPrompt(\n specCode: string,\n implementationCode: string,\n testType: 'handler' | 'component'\n): string {\n const testFocus =\n testType === 'handler'\n ? `\n - Test all acceptance scenarios from the spec\n - Test error cases defined in spec.io.errors\n - Verify events are emitted correctly\n - Test input validation\n - Test happy path and edge cases`\n : `\n - Test rendering with various props\n - Test user interactions\n - Test accessibility (a11y)\n - Test responsive behavior\n - Test error states`;\n\n return `You are a senior developer writing comprehensive tests.\n\nSpec:\n\\`\\`\\`typescript\n${specCode}\n\\`\\`\\`\n\nImplementation:\n\\`\\`\\`typescript\n${implementationCode}\n\\`\\`\\`\n\nGenerate complete test suite using Vitest that:\n${testFocus}\n\nUse clear test descriptions and follow AAA pattern (Arrange, Act, Assert).\n\nReturn only the TypeScript test code.`;\n}\n\n/**\n * System prompt for code generation\n */\nexport function getCodeGenSystemPrompt(): string {\n return `You are an expert TypeScript developer with deep knowledge of:\n- Type-safe API design\n- React and modern hooks\n- Test-driven development\n- Accessibility best practices\n- Clean code principles\n\nGenerate production-ready code that is:\n- Fully typed (no 'any' or type assertions unless absolutely necessary)\n- Well-documented with TSDoc comments\n- Following project conventions\n- Defensive and error-safe\n- Easy to maintain and extend\n\nAlways prioritize code quality, safety, and user experience.`;\n}\n"],"mappings":";;;;AAGA,SAAgB,mBAAmB,UAA0B;AAC3D,QAAO;;;;;EAKP,SAAS;;;;;;;;;;;;;;;;;;;AAoBX,SAAgB,qBAAqB,UAA0B;AAC7D,QAAO;;;;;EAKP,SAAS;;;;;;;;;;;;;;;;;;;AAoBX,SAAgB,gBAAgB,UAA0B;AACxD,QAAO;;;;;EAKP,SAAS;;;;;;;;;;;;;;;;;;;;AAqBX,SAAgB,gBACd,UACA,oBACA,UACQ;AAgBR,QAAO;;;;EAIP,SAAS;;;;;EAKT,mBAAmB;;;;EAvBjB,aAAa,YACT;;;;;sCAMA;;;;;uBAoBI;;;;;;;;;AAUZ,SAAgB,yBAAiC;AAC/C,QAAO"}
@@ -1,25 +1,26 @@
1
- import { OpKind, PresentationKind } from "../types/spec-types.js";
1
+ import { PresentationKind } from "../../types/spec-types.js";
2
+ import { OpKind } from "@contractspec/lib.contracts";
2
3
 
3
- //#region src/ai/spec-creation.d.ts
4
+ //#region src/ai/prompts/spec-creation.d.ts
4
5
 
5
6
  /**
6
- * Build prompt for creating operation spec from description.
7
+ * Build prompt for creating operation spec from description
7
8
  */
8
9
  declare function buildOperationSpecPrompt(description: string, kind: OpKind): string;
9
10
  /**
10
- * Build prompt for creating event spec from description.
11
+ * Build prompt for creating event spec from description
11
12
  */
12
13
  declare function buildEventSpecPrompt(description: string): string;
13
14
  /**
14
- * Build prompt for creating presentation spec from description.
15
+ * Build prompt for creating presentation spec from description
15
16
  */
16
17
  declare function buildPresentationSpecPrompt(description: string, kind: PresentationKind): string;
17
18
  /**
18
- * Build system prompt for all spec generation.
19
+ * Build system prompt for all spec generation
19
20
  */
20
21
  declare function getSystemPrompt(): string;
21
22
  /**
22
- * Create example-based prompt for better results.
23
+ * Create example-based prompt for better results
23
24
  */
24
25
  declare function addExampleContext(basePrompt: string, examples: string[]): string;
25
26
  //#endregion
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-creation.d.ts","names":[],"sources":["../../../src/ai/prompts/spec-creation.ts"],"sourcesContent":[],"mappings":";;;;;;;AAMA;AA4BgB,iBA5BA,wBAAA,CA4BoB,WAAA,EAAA,MAAA,EAAA,IAAA,EA1B5B,MA0B4B,CAAA,EAAA,MAAA;AAqBpC;AAoCA;AAiBA;iBA1EgB,oBAAA;;;;iBAqBA,2BAAA,4BAER;;;;iBAkCQ,eAAA,CAAA;;;;iBAiBA,iBAAA"}
@@ -1,6 +1,6 @@
1
- //#region src/ai/spec-creation.ts
1
+ //#region src/ai/prompts/spec-creation.ts
2
2
  /**
3
- * Build prompt for creating operation spec from description.
3
+ * Build prompt for creating operation spec from description
4
4
  */
5
5
  function buildOperationSpecPrompt(description, kind) {
6
6
  return `You are a senior software architect creating a contract specification for an operation.
@@ -24,7 +24,7 @@ Create a complete contract specification following these guidelines:
24
24
  Respond with a structured spec.`;
25
25
  }
26
26
  /**
27
- * Build prompt for creating event spec from description.
27
+ * Build prompt for creating event spec from description
28
28
  */
29
29
  function buildEventSpecPrompt(description) {
30
30
  return `You are a senior software architect creating an event specification.
@@ -44,7 +44,7 @@ Events represent things that have already happened and should use past tense.
44
44
  Respond with a structured spec.`;
45
45
  }
46
46
  /**
47
- * Build prompt for creating presentation spec from description.
47
+ * Build prompt for creating presentation spec from description
48
48
  */
49
49
  function buildPresentationSpecPrompt(description, kind) {
50
50
  return `You are a senior software architect creating a presentation specification.
@@ -68,7 +68,7 @@ Create a complete presentation specification following these guidelines:
68
68
  Respond with a structured spec.`;
69
69
  }
70
70
  /**
71
- * Build system prompt for all spec generation.
71
+ * Build system prompt for all spec generation
72
72
  */
73
73
  function getSystemPrompt() {
74
74
  return `You are an expert software architect specializing in API design and contract-driven development.
@@ -84,7 +84,7 @@ Your specs are:
84
84
  Always use proper dot notation for names and ensure all metadata is meaningful and accurate.`;
85
85
  }
86
86
  /**
87
- * Create example-based prompt for better results.
87
+ * Create example-based prompt for better results
88
88
  */
89
89
  function addExampleContext(basePrompt, examples) {
90
90
  if (examples.length === 0) return basePrompt;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-creation.js","names":[],"sources":["../../../src/ai/prompts/spec-creation.ts"],"sourcesContent":["import type { OpKind } from '@contractspec/lib.contracts';\nimport type { PresentationKind } from '../../types/spec-types';\n\n/**\n * Build prompt for creating operation spec from description\n */\nexport function buildOperationSpecPrompt(\n description: string,\n kind: OpKind\n): string {\n return `You are a senior software architect creating a contract specification for an operation.\n\nThe operation is a ${kind} (${kind === 'command' ? 'changes state, has side effects' : 'read-only, idempotent'}).\n\nUser description: ${description}\n\nCreate a complete contract specification following these guidelines:\n\n1. **Name**: Use dot notation like \"domain.operationName\" (e.g., \"user.signup\", \"payment.charge\")\n2. **Version**: Start at 1\n3. **Description**: Clear, concise summary (1-2 sentences)\n4. **Goal**: Business purpose - why this operation exists\n5. **Context**: Background, constraints, scope (what it does and doesn't do)\n6. **Input/Output**: Describe the shape (we'll create schemas separately)\n7. **Auth**: Who can call this - anonymous, user, or admin\n8. **Feature Flags**: Any flags that gate this operation\n9. **Side Effects**: What events might be emitted, analytics to track\n\nRespond with a structured spec.`;\n}\n\n/**\n * Build prompt for creating event spec from description\n */\nexport function buildEventSpecPrompt(description: string): string {\n return `You are a senior software architect creating an event specification.\n\nUser description: ${description}\n\nCreate a complete event specification following these guidelines:\n\n1. **Name**: Use dot notation like \"domain.event_name\" (e.g., \"user.signup_completed\", \"payment.charged\")\n2. **Version**: Start at 1\n3. **Description**: Clear description of when this event is emitted\n4. **Payload**: Describe what data the event carries\n5. **PII Fields**: List any personally identifiable information fields (e.g., [\"email\", \"name\"])\n\nEvents represent things that have already happened and should use past tense.\n\nRespond with a structured spec.`;\n}\n\n/**\n * Build prompt for creating presentation spec from description\n */\nexport function buildPresentationSpecPrompt(\n description: string,\n kind: PresentationKind\n): string {\n const kindDescriptions = {\n web_component: 'a React component with props schema',\n markdown: 'markdown/MDX documentation or guide',\n data: 'structured data export (JSON/XML)',\n };\n\n return `You are a senior software architect creating a presentation specification.\n\nThis is a ${kind} presentation - ${kindDescriptions[kind]}.\n\nUser description: ${description}\n\nCreate a complete presentation specification following these guidelines:\n\n1. **Name**: Use dot notation like \"domain.presentation_name\" (e.g., \"user.profile_card\", \"docs.api_guide\")\n2. **Version**: Start at 1\n3. **Description**: What this presentation shows/provides\n4. **Kind-specific details**:\n ${\n kind === 'web_component'\n ? '- Component key (symbolic, resolved by host app)\\n - Props structure\\n - Analytics events to track'\n : kind === 'markdown'\n ? '- Content or resource URI\\n - Target audience'\n : '- MIME type (e.g., application/json)\\n - Data structure description'\n }\n\nRespond with a structured spec.`;\n}\n\n/**\n * Build system prompt for all spec generation\n */\nexport function getSystemPrompt(): string {\n return `You are an expert software architect specializing in API design and contract-driven development.\n\nYou create clear, well-documented specifications that serve as the single source of truth for operations, events, and presentations.\n\nYour specs are:\n- Precise and unambiguous\n- Following TypeScript conventions\n- Business-oriented (capturing the \"why\" not just \"what\")\n- Designed for both humans and AI agents to understand\n\nAlways use proper dot notation for names and ensure all metadata is meaningful and accurate.`;\n}\n\n/**\n * Create example-based prompt for better results\n */\nexport function addExampleContext(\n basePrompt: string,\n examples: string[]\n): string {\n if (examples.length === 0) return basePrompt;\n\n return `${basePrompt}\n\nHere are some good examples for reference:\n\n${examples.join('\\n\\n')}\n\nFollow this structure and quality level.`;\n}\n"],"mappings":";;;;AAMA,SAAgB,yBACd,aACA,MACQ;AACR,QAAO;;qBAEY,KAAK,IAAI,SAAS,YAAY,oCAAoC,wBAAwB;;oBAE3F,YAAY;;;;;;;;;;;;;;;;;;;AAoBhC,SAAgB,qBAAqB,aAA6B;AAChE,QAAO;;oBAEW,YAAY;;;;;;;;;;;;;;;;;AAkBhC,SAAgB,4BACd,aACA,MACQ;AAOR,QAAO;;YAEG,KAAK,kBARU;EACvB,eAAe;EACf,UAAU;EACV,MAAM;EACP,CAIiD,MAAM;;oBAEtC,YAAY;;;;;;;;KAS3B,SAAS,kBACL,2GACA,SAAS,aACP,oDACA,wEACP;;;;;;;AAQJ,SAAgB,kBAA0B;AACxC,QAAO;;;;;;;;;;;;;;;AAgBT,SAAgB,kBACd,YACA,UACQ;AACR,KAAI,SAAS,WAAW,EAAG,QAAO;AAElC,QAAO,GAAG,WAAW;;;;EAIrB,SAAS,KAAK,OAAO,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { ExampleScanResult } from "../types/analysis-types.js";
2
+
3
+ //#region src/analysis/example-scan.d.ts
4
+
5
+ /**
6
+ * Check if a file is an example file based on naming conventions.
7
+ */
8
+ declare function isExampleFile(filePath: string): boolean;
9
+ /**
10
+ * Scan an example source file to extract metadata.
11
+ */
12
+ declare function scanExampleSource(code: string, filePath: string): ExampleScanResult;
13
+ //#endregion
14
+ export { isExampleFile, scanExampleSource };
15
+ //# sourceMappingURL=example-scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example-scan.d.ts","names":[],"sources":["../../src/analysis/example-scan.ts"],"sourcesContent":[],"mappings":";;;;;;;iBAYgB,aAAA;;;;iBAOA,iBAAA,kCAGb"}
@@ -0,0 +1,151 @@
1
+ //#region src/analysis/example-scan.ts
2
+ /**
3
+ * Check if a file is an example file based on naming conventions.
4
+ */
5
+ function isExampleFile(filePath) {
6
+ return filePath.includes("/example.") || filePath.endsWith(".example.ts");
7
+ }
8
+ /**
9
+ * Scan an example source file to extract metadata.
10
+ */
11
+ function scanExampleSource(code, filePath) {
12
+ const key = matchStringField(code, "key") ?? extractKeyFromFilePath(filePath);
13
+ const version = matchStringField(code, "version") ?? void 0;
14
+ const title = matchStringField(code, "title") ?? void 0;
15
+ const description = matchStringField(code, "description") ?? void 0;
16
+ const summary = matchStringField(code, "summary") ?? void 0;
17
+ const kind = matchStringField(code, "kind") ?? void 0;
18
+ const visibility = matchStringField(code, "visibility") ?? void 0;
19
+ const domain = matchStringField(code, "domain") ?? void 0;
20
+ const stabilityRaw = matchStringField(code, "stability");
21
+ return {
22
+ filePath,
23
+ key,
24
+ version,
25
+ title,
26
+ description,
27
+ summary,
28
+ kind,
29
+ visibility,
30
+ domain,
31
+ stability: isStability(stabilityRaw) ? stabilityRaw : void 0,
32
+ owners: matchStringArrayField(code, "owners"),
33
+ tags: matchStringArrayField(code, "tags"),
34
+ docs: extractDocs(code),
35
+ surfaces: extractSurfaces(code),
36
+ entrypoints: extractEntrypoints(code)
37
+ };
38
+ }
39
+ /**
40
+ * Extract docs section from source code.
41
+ */
42
+ function extractDocs(code) {
43
+ const docsMatch = code.match(/docs\s*:\s*\{([\s\S]*?)\}/);
44
+ if (!docsMatch?.[1]) return void 0;
45
+ const docsContent = docsMatch[1];
46
+ return {
47
+ rootDocId: matchStringFieldIn(docsContent, "rootDocId") ?? void 0,
48
+ goalDocId: matchStringFieldIn(docsContent, "goalDocId") ?? void 0,
49
+ usageDocId: matchStringFieldIn(docsContent, "usageDocId") ?? void 0
50
+ };
51
+ }
52
+ /**
53
+ * Extract surfaces section from source code.
54
+ */
55
+ function extractSurfaces(code) {
56
+ const surfaces = {
57
+ templates: false,
58
+ sandbox: {
59
+ enabled: false,
60
+ modes: []
61
+ },
62
+ studio: {
63
+ enabled: false,
64
+ installable: false
65
+ },
66
+ mcp: { enabled: false }
67
+ };
68
+ surfaces.templates = /surfaces\s*:\s*\{[\s\S]*?templates\s*:\s*true/.test(code);
69
+ const sandboxMatch = code.match(/sandbox\s*:\s*\{\s*enabled\s*:\s*(true|false)\s*,\s*modes\s*:\s*\[([^\]]*)\]/);
70
+ if (sandboxMatch) {
71
+ surfaces.sandbox.enabled = sandboxMatch[1] === "true";
72
+ if (sandboxMatch[2]) surfaces.sandbox.modes = Array.from(sandboxMatch[2].matchAll(/['"]([^'"]+)['"]/g)).map((m) => m[1]).filter((v) => typeof v === "string");
73
+ }
74
+ const studioMatch = code.match(/studio\s*:\s*\{\s*enabled\s*:\s*(true|false)\s*,\s*installable\s*:\s*(true|false)/);
75
+ if (studioMatch) {
76
+ surfaces.studio.enabled = studioMatch[1] === "true";
77
+ surfaces.studio.installable = studioMatch[2] === "true";
78
+ }
79
+ const mcpMatch = code.match(/mcp\s*:\s*\{\s*enabled\s*:\s*(true|false)/);
80
+ if (mcpMatch) surfaces.mcp.enabled = mcpMatch[1] === "true";
81
+ return surfaces;
82
+ }
83
+ /**
84
+ * Extract entrypoints section from source code.
85
+ */
86
+ function extractEntrypoints(code) {
87
+ const entrypoints = { packageName: "" };
88
+ const entrypointsMatch = code.match(/entrypoints\s*:\s*\{([\s\S]*?)\}(?=\s*[,}])/);
89
+ if (!entrypointsMatch?.[1]) return entrypoints;
90
+ const content = entrypointsMatch[1];
91
+ entrypoints.packageName = matchStringFieldIn(content, "packageName") ?? "unknown";
92
+ entrypoints.feature = matchStringFieldIn(content, "feature") ?? void 0;
93
+ entrypoints.blueprint = matchStringFieldIn(content, "blueprint") ?? void 0;
94
+ entrypoints.contracts = matchStringFieldIn(content, "contracts") ?? void 0;
95
+ entrypoints.presentations = matchStringFieldIn(content, "presentations") ?? void 0;
96
+ entrypoints.handlers = matchStringFieldIn(content, "handlers") ?? void 0;
97
+ entrypoints.ui = matchStringFieldIn(content, "ui") ?? void 0;
98
+ entrypoints.docs = matchStringFieldIn(content, "docs") ?? void 0;
99
+ return entrypoints;
100
+ }
101
+ /**
102
+ * Extract key from file path as fallback.
103
+ */
104
+ function extractKeyFromFilePath(filePath) {
105
+ const parts = filePath.split("/");
106
+ const examplesIndex = parts.findIndex((p) => p === "examples");
107
+ const exampleName = parts[examplesIndex + 1];
108
+ if (examplesIndex !== -1 && exampleName !== void 0) return exampleName;
109
+ return (parts.pop() ?? filePath).replace(/\.example\.[jt]s$/, "").replace(/[^a-zA-Z0-9-]/g, "-");
110
+ }
111
+ /**
112
+ * Match a string field in source code.
113
+ */
114
+ function matchStringField(code, field) {
115
+ const regex = /* @__PURE__ */ new RegExp(`${escapeRegex(field)}\\s*:\\s*['"]([^'"]+)['"]`);
116
+ return code.match(regex)?.[1] ?? null;
117
+ }
118
+ /**
119
+ * Match a string field within a limited scope.
120
+ */
121
+ function matchStringFieldIn(code, field) {
122
+ const regex = /* @__PURE__ */ new RegExp(`${escapeRegex(field)}\\s*:\\s*['"]([^'"]+)['"]`);
123
+ return code.match(regex)?.[1] ?? null;
124
+ }
125
+ /**
126
+ * Match a string array field in source code.
127
+ */
128
+ function matchStringArrayField(code, field) {
129
+ const regex = /* @__PURE__ */ new RegExp(`${escapeRegex(field)}\\s*:\\s*\\[([\\s\\S]*?)\\]`);
130
+ const match = code.match(regex);
131
+ if (!match?.[1]) return void 0;
132
+ const inner = match[1];
133
+ const items = Array.from(inner.matchAll(/['"]([^'"]+)['"]/g)).map((m) => m[1]).filter((value) => typeof value === "string" && value.length > 0);
134
+ return items.length > 0 ? items : void 0;
135
+ }
136
+ /**
137
+ * Check if a value is a valid stability.
138
+ */
139
+ function isStability(value) {
140
+ return value === "experimental" || value === "beta" || value === "stable" || value === "deprecated";
141
+ }
142
+ /**
143
+ * Escape regex special characters.
144
+ */
145
+ function escapeRegex(value) {
146
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
147
+ }
148
+
149
+ //#endregion
150
+ export { isExampleFile, scanExampleSource };
151
+ //# sourceMappingURL=example-scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example-scan.js","names":["surfaces: ExampleScanResult['surfaces']","entrypoints: ExampleScanResult['entrypoints']"],"sources":["../../src/analysis/example-scan.ts"],"sourcesContent":["/**\n * Example file scanning utilities.\n *\n * Extracts ExampleSpec metadata from source code without execution.\n */\n\nimport type { ExampleScanResult } from '../types/analysis-types';\nimport type { Stability } from '../types/spec-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\n/**\n * Match a string field in source code.\n */\nfunction 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 */\nfunction matchStringFieldIn(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 array field in source code.\n */\nfunction 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 */\nfunction 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 * Escape regex special characters.\n */\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n"],"mappings":"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"}
@@ -12,6 +12,8 @@ function scanFeatureSource(code, filePath) {
12
12
  const key = matchStringField(code, "key") ?? extractKeyFromFilePath(filePath);
13
13
  const title = matchStringField(code, "title") ?? void 0;
14
14
  const description = matchStringField(code, "description") ?? void 0;
15
+ const goal = matchStringField(code, "goal") ?? void 0;
16
+ const context = matchStringField(code, "context") ?? void 0;
15
17
  const domain = matchStringField(code, "domain") ?? void 0;
16
18
  const stabilityRaw = matchStringField(code, "stability");
17
19
  return {
@@ -19,6 +21,8 @@ function scanFeatureSource(code, filePath) {
19
21
  key,
20
22
  title,
21
23
  description,
24
+ goal,
25
+ context,
22
26
  domain,
23
27
  stability: isStability(stabilityRaw) ? stabilityRaw : void 0,
24
28
  owners: matchStringArrayField(code, "owners"),
@@ -28,7 +32,8 @@ function scanFeatureSource(code, filePath) {
28
32
  presentations: extractRefsFromArray(code, "presentations"),
29
33
  experiments: extractRefsFromArray(code, "experiments"),
30
34
  capabilities: extractCapabilities(code),
31
- opToPresentationLinks: extractOpToPresentationLinks(code)
35
+ opToPresentationLinks: extractOpToPresentationLinks(code),
36
+ sourceBlock: code
32
37
  };
33
38
  }
34
39
  /**
@@ -39,11 +44,11 @@ function extractRefsFromArray(code, fieldName) {
39
44
  const arrayPattern = new RegExp(`${escapeRegex(fieldName)}\\s*:\\s*\\[([\\s\\S]*?)\\]`, "m");
40
45
  const arrayMatch = code.match(arrayPattern);
41
46
  if (!arrayMatch?.[1]) return refs;
42
- const refPattern = /\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(\d+)/g;
47
+ const refPattern = /\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*['"]([^'"]+)['"]/g;
43
48
  let match;
44
49
  while ((match = refPattern.exec(arrayMatch[1])) !== null) if (match[1] && match[2]) refs.push({
45
50
  key: match[1],
46
- version: Number(match[2])
51
+ version: match[2]
47
52
  });
48
53
  return refs;
49
54
  }
@@ -53,7 +58,7 @@ function extractRefsFromArray(code, fieldName) {
53
58
  function extractCapabilities(code) {
54
59
  const provides = [];
55
60
  const requires = [];
56
- const capabilitiesMatch = code.match(/capabilities\s*:\s*\{([\s\S]*?)\}/);
61
+ const capabilitiesMatch = code.match(/capabilities\s*:\s*\{([\s\S]*?)\}\s*,?\s*(?:opToPresentation|$|\})/);
57
62
  if (!capabilitiesMatch?.[1]) return {
58
63
  provides,
59
64
  requires
@@ -61,26 +66,26 @@ function extractCapabilities(code) {
61
66
  const capabilitiesContent = capabilitiesMatch[1];
62
67
  const providesMatch = capabilitiesContent.match(/provides\s*:\s*\[([\s\S]*?)\]/);
63
68
  if (providesMatch?.[1]) {
64
- const refPattern = /\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(\d+)/g;
69
+ const refPattern = /\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*['"]([^'"]+)['"]/g;
65
70
  let match;
66
71
  while ((match = refPattern.exec(providesMatch[1])) !== null) if (match[1] && match[2]) provides.push({
67
72
  key: match[1],
68
- version: Number(match[2])
73
+ version: match[2]
69
74
  });
70
75
  }
71
76
  const requiresMatch = capabilitiesContent.match(/requires\s*:\s*\[([\s\S]*?)\]/);
72
77
  if (requiresMatch?.[1]) {
73
- const refPatternWithVersion = /\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(\d+)/g;
78
+ const refPatternWithVersion = /\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*['"]([^'"]+)['"]/g;
74
79
  const refPatternKeyOnly = /\{\s*key:\s*['"]([^'"]+)['"]\s*\}/g;
75
80
  let match = null;
76
81
  while ((match = refPatternWithVersion.exec(requiresMatch[1])) !== null) if (match[1] && match[2]) requires.push({
77
82
  key: match[1],
78
- version: Number(match[2])
83
+ version: match[2]
79
84
  });
80
85
  while ((match = refPatternKeyOnly.exec(requiresMatch[1])) !== null) if (match && match[1]) {
81
86
  if (!requires.some((r) => r.key === match[1])) requires.push({
82
87
  key: match[1],
83
- version: 1
88
+ version: "1.0.0"
84
89
  });
85
90
  }
86
91
  }
@@ -96,16 +101,16 @@ function extractOpToPresentationLinks(code) {
96
101
  const links = [];
97
102
  const arrayMatch = code.match(/opToPresentation\s*:\s*\[([\s\S]*?)\]/);
98
103
  if (!arrayMatch?.[1]) return links;
99
- const linkPattern = /\{\s*op:\s*\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(\d+)\s*\}\s*,\s*pres:\s*\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*(\d+)\s*\}/g;
104
+ const linkPattern = /\{\s*op:\s*\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*['"]([^'"]+)['"]\s*\}\s*,\s*pres:\s*\{\s*key:\s*['"]([^'"]+)['"]\s*,\s*version:\s*['"]([^'"]+)['"]\s*\}/g;
100
105
  let match;
101
106
  while ((match = linkPattern.exec(arrayMatch[1])) !== null) if (match[1] && match[2] && match[3] && match[4]) links.push({
102
107
  op: {
103
108
  key: match[1],
104
- version: Number(match[2])
109
+ version: match[2]
105
110
  },
106
111
  pres: {
107
112
  key: match[3],
108
- version: Number(match[4])
113
+ version: match[4]
109
114
  }
110
115
  });
111
116
  return links;
@@ -1 +1 @@
1
- {"version":3,"file":"feature-scan.js","names":["refs: RefInfo[]","provides: RefInfo[]","requires: RefInfo[]","match: RegExpExecArray | null","links: { op: RefInfo; pres: RefInfo }[]"],"sources":["../../src/analysis/feature-scan.ts"],"sourcesContent":["/**\n * Feature file scanning utilities.\n *\n * Extracts FeatureModuleSpec metadata from source code without execution.\n */\n\nimport type { FeatureScanResult, RefInfo } from '../types/analysis-types';\nimport type { Stability } from '../types/spec-types';\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.');\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 title = matchStringField(code, 'title') ?? undefined;\n const description = matchStringField(code, 'description') ?? 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 operations\n const operations = extractRefsFromArray(code, 'operations');\n\n // Extract events\n const events = extractRefsFromArray(code, 'events');\n\n // Extract presentations\n const presentations = extractRefsFromArray(code, 'presentations');\n\n // Extract experiments\n const experiments = extractRefsFromArray(code, 'experiments');\n\n // Extract capabilities\n const capabilities = extractCapabilities(code);\n\n // Extract op to presentation links\n const opToPresentationLinks = extractOpToPresentationLinks(code);\n\n return {\n filePath,\n key,\n title,\n description,\n domain,\n stability,\n owners,\n tags,\n operations,\n events,\n presentations,\n experiments,\n capabilities,\n opToPresentationLinks,\n };\n}\n\n/**\n * Extract refs from a named array (e.g., operations, events, presentations).\n */\nfunction extractRefsFromArray(code: string, fieldName: string): RefInfo[] {\n const refs: RefInfo[] = [];\n\n // Match the array section\n const arrayPattern = new RegExp(\n `${escapeRegex(fieldName)}\\\\s*:\\\\s*\\\\[([\\\\s\\\\S]*?)\\\\]`,\n 'm'\n );\n const arrayMatch = code.match(arrayPattern);\n\n if (!arrayMatch?.[1]) return refs;\n\n // Extract each { key: 'x', version: N } entry\n const refPattern = /\\{\\s*key:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*(\\d+)/g;\n let match;\n while ((match = refPattern.exec(arrayMatch[1])) !== null) {\n if (match[1] && match[2]) {\n refs.push({\n key: match[1],\n version: Number(match[2]),\n });\n }\n }\n\n return refs;\n}\n\n/**\n * Extract capability bindings (provides and requires).\n */\nfunction extractCapabilities(code: string): {\n provides: RefInfo[];\n requires: RefInfo[];\n} {\n const provides: RefInfo[] = [];\n const requires: RefInfo[] = [];\n\n // Match the capabilities section\n const capabilitiesMatch = code.match(/capabilities\\s*:\\s*\\{([\\s\\S]*?)\\}/);\n if (!capabilitiesMatch?.[1]) {\n return { provides, requires };\n }\n\n const capabilitiesContent = capabilitiesMatch[1];\n\n // Extract provides\n const providesMatch = capabilitiesContent.match(\n /provides\\s*:\\s*\\[([\\s\\S]*?)\\]/\n );\n if (providesMatch?.[1]) {\n const refPattern = /\\{\\s*key:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*(\\d+)/g;\n let match;\n while ((match = refPattern.exec(providesMatch[1])) !== null) {\n if (match[1] && match[2]) {\n provides.push({\n key: match[1],\n version: Number(match[2]),\n });\n }\n }\n }\n\n // Extract requires\n const requiresMatch = capabilitiesContent.match(\n /requires\\s*:\\s*\\[([\\s\\S]*?)\\]/\n );\n if (requiresMatch?.[1]) {\n // Requires can have key+version or just key\n const refPatternWithVersion =\n /\\{\\s*key:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*(\\d+)/g;\n const refPatternKeyOnly = /\\{\\s*key:\\s*['\"]([^'\"]+)['\"]\\s*\\}/g;\n\n let match: RegExpExecArray | null = null;\n while ((match = refPatternWithVersion.exec(requiresMatch[1])) !== null) {\n if (match[1] && match[2]) {\n requires.push({\n key: match[1],\n version: Number(match[2]),\n });\n }\n }\n\n // Also match key-only requires (version defaults to 1)\n while ((match = refPatternKeyOnly.exec(requiresMatch[1])) !== null) {\n if (match && match[1]) {\n // Check if we already added this with a version\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const alreadyExists = requires.some((r) => r.key === match![1]);\n if (!alreadyExists) {\n requires.push({\n key: match[1],\n version: 1, // Default version\n });\n }\n }\n }\n }\n\n return { provides, requires };\n}\n\n/**\n * Extract opToPresentation links.\n */\nfunction extractOpToPresentationLinks(\n code: string\n): { op: RefInfo; pres: RefInfo }[] {\n const links: { op: RefInfo; pres: RefInfo }[] = [];\n\n // Match the opToPresentation array\n const arrayMatch = code.match(/opToPresentation\\s*:\\s*\\[([\\s\\S]*?)\\]/);\n if (!arrayMatch?.[1]) return links;\n\n // Match each link entry\n // Pattern: { op: { key: 'x', version: N }, pres: { key: 'y', version: M } }\n const linkPattern =\n /\\{\\s*op:\\s*\\{\\s*key:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*(\\d+)\\s*\\}\\s*,\\s*pres:\\s*\\{\\s*key:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*(\\d+)\\s*\\}/g;\n\n let match;\n while ((match = linkPattern.exec(arrayMatch[1])) !== null) {\n if (match[1] && match[2] && match[3] && match[4]) {\n links.push({\n op: { key: match[1], version: Number(match[2]) },\n pres: { key: match[3], version: Number(match[4]) },\n });\n }\n }\n\n return links;\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\n/**\n * Match a string field in source code.\n */\nfunction 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 array field in source code.\n */\nfunction 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 */\nfunction 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 * Escape regex special characters.\n */\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"feature-scan.js","names":["refs: RefInfo[]","provides: RefInfo[]","requires: RefInfo[]","match: RegExpExecArray | null","links: { op: RefInfo; pres: RefInfo }[]"],"sources":["../../src/analysis/feature-scan.ts"],"sourcesContent":["/**\n * Feature file scanning utilities.\n *\n * Extracts FeatureModuleSpec metadata from source code without execution.\n */\n\nimport type { FeatureScanResult, RefInfo } from '../types/analysis-types';\nimport type { Stability } from '../types/spec-types';\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.');\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 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 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 operations\n const operations = extractRefsFromArray(code, 'operations');\n\n // Extract events\n const events = extractRefsFromArray(code, 'events');\n\n // Extract presentations\n const presentations = extractRefsFromArray(code, 'presentations');\n\n // Extract experiments\n const experiments = extractRefsFromArray(code, 'experiments');\n\n // Extract capabilities\n const capabilities = extractCapabilities(code);\n\n // Extract op to presentation links\n const opToPresentationLinks = extractOpToPresentationLinks(code);\n\n return {\n filePath,\n key,\n title,\n description,\n goal,\n context,\n domain,\n stability,\n owners,\n tags,\n operations,\n events,\n presentations,\n experiments,\n capabilities,\n opToPresentationLinks,\n sourceBlock: code,\n };\n}\n\n/**\n * Extract refs from a named array (e.g., operations, events, presentations).\n */\nfunction extractRefsFromArray(code: string, fieldName: string): RefInfo[] {\n const refs: RefInfo[] = [];\n\n // Match the array section\n const arrayPattern = new RegExp(\n `${escapeRegex(fieldName)}\\\\s*:\\\\s*\\\\[([\\\\s\\\\S]*?)\\\\]`,\n 'm'\n );\n const arrayMatch = code.match(arrayPattern);\n\n if (!arrayMatch?.[1]) return refs;\n\n // Extract each { key: 'x', version: 'x.y.z' } entry\n const refPattern =\n /\\{\\s*key:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*['\"]([^'\"]+)['\"]/g;\n let match;\n while ((match = refPattern.exec(arrayMatch[1])) !== null) {\n if (match[1] && match[2]) {\n refs.push({\n key: match[1],\n version: match[2],\n });\n }\n }\n\n return refs;\n}\n\n/**\n * Extract capability bindings (provides and requires).\n */\nfunction extractCapabilities(code: string): {\n provides: RefInfo[];\n requires: RefInfo[];\n} {\n const provides: RefInfo[] = [];\n const requires: RefInfo[] = [];\n\n // Match the capabilities section - need to match nested braces properly\n const capabilitiesMatch = code.match(\n /capabilities\\s*:\\s*\\{([\\s\\S]*?)\\}\\s*,?\\s*(?:opToPresentation|$|\\})/\n );\n if (!capabilitiesMatch?.[1]) {\n return { provides, requires };\n }\n\n const capabilitiesContent = capabilitiesMatch[1];\n\n // Extract provides\n const providesMatch = capabilitiesContent.match(\n /provides\\s*:\\s*\\[([\\s\\S]*?)\\]/\n );\n if (providesMatch?.[1]) {\n const refPattern =\n /\\{\\s*key:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*['\"]([^'\"]+)['\"]/g;\n let match;\n while ((match = refPattern.exec(providesMatch[1])) !== null) {\n if (match[1] && match[2]) {\n provides.push({\n key: match[1],\n version: match[2],\n });\n }\n }\n }\n\n // Extract requires\n const requiresMatch = capabilitiesContent.match(\n /requires\\s*:\\s*\\[([\\s\\S]*?)\\]/\n );\n if (requiresMatch?.[1]) {\n // Requires can have key+version or just key\n const refPatternWithVersion =\n /\\{\\s*key:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*['\"]([^'\"]+)['\"]/g;\n const refPatternKeyOnly = /\\{\\s*key:\\s*['\"]([^'\"]+)['\"]\\s*\\}/g;\n\n let match: RegExpExecArray | null = null;\n while ((match = refPatternWithVersion.exec(requiresMatch[1])) !== null) {\n if (match[1] && match[2]) {\n requires.push({\n key: match[1],\n version: match[2],\n });\n }\n }\n\n // Also match key-only requires (version defaults to 1)\n while ((match = refPatternKeyOnly.exec(requiresMatch[1])) !== null) {\n if (match && match[1]) {\n // Check if we already added this with a version\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const alreadyExists = requires.some((r) => r.key === match![1]);\n if (!alreadyExists) {\n requires.push({\n key: match[1],\n version: '1.0.0', // Default version\n });\n }\n }\n }\n }\n\n return { provides, requires };\n}\n\n/**\n * Extract opToPresentation links.\n */\nfunction extractOpToPresentationLinks(\n code: string\n): { op: RefInfo; pres: RefInfo }[] {\n const links: { op: RefInfo; pres: RefInfo }[] = [];\n\n // Match the opToPresentation array\n const arrayMatch = code.match(/opToPresentation\\s*:\\s*\\[([\\s\\S]*?)\\]/);\n if (!arrayMatch?.[1]) return links;\n\n // Match each link entry\n // Pattern: { op: { key: 'x', version: 'N' }, pres: { key: 'y', version: 'M' } }\n const linkPattern =\n /\\{\\s*op:\\s*\\{\\s*key:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*['\"]([^'\"]+)['\"]\\s*\\}\\s*,\\s*pres:\\s*\\{\\s*key:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*['\"]([^'\"]+)['\"]\\s*\\}/g;\n\n let match;\n while ((match = linkPattern.exec(arrayMatch[1])) !== null) {\n if (match[1] && match[2] && match[3] && match[4]) {\n links.push({\n op: { key: match[1], version: match[2] },\n pres: { key: match[3], version: match[4] },\n });\n }\n }\n\n return links;\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\n/**\n * Match a string field in source code.\n */\nfunction 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 array field in source code.\n */\nfunction 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 */\nfunction 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 * Escape regex special characters.\n */\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n"],"mappings":"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"}
@@ -53,7 +53,7 @@ function classifyImpact(baseSpecs, headSpecs, diffs, options = {}) {
53
53
  const specInfo = findSpecInfo(extractSpecKey(diff.path, baseSpecs, headSpecs), baseSpecs, headSpecs);
54
54
  deltas.push({
55
55
  specKey: specInfo?.key ?? "unknown",
56
- specVersion: specInfo?.version ?? 0,
56
+ specVersion: specInfo?.version ?? "1.0.0",
57
57
  specType: specInfo?.type ?? "operation",
58
58
  path: diff.path,
59
59
  severity: matchingRule?.severity ?? mapDiffTypeToSeverity(diff.type),
@@ -1 +1 @@
1
- {"version":3,"file":"classifier.js","names":["deltas: ImpactDelta[]","addedSpecs: ImpactResult['addedSpecs']","removedSpecs: ImpactResult['removedSpecs']"],"sources":["../../../src/analysis/impact/classifier.ts"],"sourcesContent":["/**\n * Impact classifier.\n *\n * Classifies contract changes as breaking, non-breaking, or info.\n */\n\nimport type { SemanticDiffItem } from '../../types/analysis-types';\nimport type { SpecSnapshot } from '../snapshot/types';\nimport { DEFAULT_RULES, findMatchingRule } from './rules';\nimport type {\n ClassifyOptions,\n ImpactDelta,\n ImpactResult,\n ImpactStatus,\n ImpactSummary,\n} from './types';\n\n/**\n * Classify the impact of changes between base and head snapshots.\n *\n * @param baseSpecs - Specs from the base (baseline) version\n * @param headSpecs - Specs from the head (current) version\n * @param diffs - Semantic diff items from comparison\n * @param options - Classification options\n * @returns Classified impact result\n */\nexport function classifyImpact(\n baseSpecs: SpecSnapshot[],\n headSpecs: SpecSnapshot[],\n diffs: SemanticDiffItem[],\n options: ClassifyOptions = {}\n): ImpactResult {\n const rules = options.customRules ?? DEFAULT_RULES;\n const deltas: ImpactDelta[] = [];\n\n // Create lookup maps\n const baseMap = new Map(baseSpecs.map((s) => [`${s.key}@${s.version}`, s]));\n const headMap = new Map(headSpecs.map((s) => [`${s.key}@${s.version}`, s]));\n\n // Detect added specs\n const addedSpecs: ImpactResult['addedSpecs'] = [];\n for (const spec of headSpecs) {\n const lookupKey = `${spec.key}@${spec.version}`;\n if (!baseMap.has(lookupKey)) {\n addedSpecs.push({\n key: spec.key,\n version: spec.version,\n type: spec.type,\n });\n }\n }\n\n // Detect removed specs\n const removedSpecs: ImpactResult['removedSpecs'] = [];\n for (const spec of baseSpecs) {\n const lookupKey = `${spec.key}@${spec.version}`;\n if (!headMap.has(lookupKey)) {\n removedSpecs.push({\n key: spec.key,\n version: spec.version,\n type: spec.type,\n });\n\n // Removed spec is always breaking\n deltas.push({\n specKey: spec.key,\n specVersion: spec.version,\n specType: spec.type,\n path: `spec.${spec.key}`,\n severity: 'breaking',\n rule: 'endpoint-removed',\n description: `${spec.type === 'operation' ? 'Operation' : 'Event'} '${spec.key}' was removed`,\n });\n }\n }\n\n // Classify diffs\n for (const diff of diffs) {\n const matchingRule = findMatchingRule(\n { path: diff.path, description: diff.description, type: diff.type },\n rules\n );\n\n // Extract spec key from path (heuristic)\n const specKey = extractSpecKey(diff.path, baseSpecs, headSpecs);\n const specInfo = findSpecInfo(specKey, baseSpecs, headSpecs);\n\n deltas.push({\n specKey: specInfo?.key ?? 'unknown',\n specVersion: specInfo?.version ?? 0,\n specType: specInfo?.type ?? 'operation',\n path: diff.path,\n severity: matchingRule?.severity ?? mapDiffTypeToSeverity(diff.type),\n rule: matchingRule?.id ?? 'unknown',\n description: diff.description,\n oldValue: diff.oldValue,\n newValue: diff.newValue,\n });\n }\n\n // Add added specs as non-breaking changes\n for (const spec of addedSpecs) {\n deltas.push({\n specKey: spec.key,\n specVersion: spec.version,\n specType: spec.type,\n path: `spec.${spec.key}`,\n severity: 'non_breaking',\n rule: 'endpoint-added',\n description: `${spec.type === 'operation' ? 'Operation' : 'Event'} '${spec.key}' was added`,\n });\n }\n\n // Calculate summary\n const summary = calculateSummary(deltas, addedSpecs, removedSpecs);\n\n // Determine status\n const hasBreaking = summary.breaking > 0 || summary.removed > 0;\n const hasNonBreaking = summary.nonBreaking > 0 || summary.added > 0;\n const status = determineStatus(hasBreaking, hasNonBreaking);\n\n return {\n status,\n hasBreaking,\n hasNonBreaking,\n summary,\n deltas,\n addedSpecs,\n removedSpecs,\n timestamp: new Date().toISOString(),\n };\n}\n\n/**\n * Calculate summary counts from deltas.\n */\nfunction calculateSummary(\n deltas: ImpactDelta[],\n addedSpecs: ImpactResult['addedSpecs'],\n removedSpecs: ImpactResult['removedSpecs']\n): ImpactSummary {\n return {\n breaking: deltas.filter((d) => d.severity === 'breaking').length,\n nonBreaking: deltas.filter((d) => d.severity === 'non_breaking').length,\n info: deltas.filter((d) => d.severity === 'info').length,\n added: addedSpecs.length,\n removed: removedSpecs.length,\n };\n}\n\n/**\n * Determine overall status from flags.\n */\nfunction determineStatus(\n hasBreaking: boolean,\n hasNonBreaking: boolean\n): ImpactStatus {\n if (hasBreaking) return 'breaking';\n if (hasNonBreaking) return 'non-breaking';\n return 'no-impact';\n}\n\n/**\n * Map semantic diff type to impact severity.\n */\nfunction mapDiffTypeToSeverity(type: string): ImpactDelta['severity'] {\n switch (type) {\n case 'breaking':\n return 'breaking';\n case 'removed':\n return 'breaking';\n case 'added':\n return 'non_breaking';\n case 'changed':\n return 'info';\n default:\n return 'info';\n }\n}\n\n/**\n * Extract spec key from a diff path (heuristic).\n */\nfunction extractSpecKey(\n _path: string,\n _baseSpecs: SpecSnapshot[],\n _headSpecs: SpecSnapshot[]\n): string | undefined {\n // This is a simplified heuristic; in practice would need more context\n return undefined;\n}\n\n/**\n * Find spec info from key.\n */\nfunction findSpecInfo(\n key: string | undefined,\n baseSpecs: SpecSnapshot[],\n headSpecs: SpecSnapshot[]\n): SpecSnapshot | undefined {\n if (!key) return headSpecs[0] ?? baseSpecs[0];\n return (\n headSpecs.find((s) => s.key === key) ?? baseSpecs.find((s) => s.key === key)\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"classifier.js","names":["deltas: ImpactDelta[]","addedSpecs: ImpactResult['addedSpecs']","removedSpecs: ImpactResult['removedSpecs']"],"sources":["../../../src/analysis/impact/classifier.ts"],"sourcesContent":["/**\n * Impact classifier.\n *\n * Classifies contract changes as breaking, non-breaking, or info.\n */\n\nimport type { SemanticDiffItem } from '../../types/analysis-types';\nimport type { SpecSnapshot } from '../snapshot/types';\nimport { DEFAULT_RULES, findMatchingRule } from './rules';\nimport type {\n ClassifyOptions,\n ImpactDelta,\n ImpactResult,\n ImpactStatus,\n ImpactSummary,\n} from './types';\n\n/**\n * Classify the impact of changes between base and head snapshots.\n *\n * @param baseSpecs - Specs from the base (baseline) version\n * @param headSpecs - Specs from the head (current) version\n * @param diffs - Semantic diff items from comparison\n * @param options - Classification options\n * @returns Classified impact result\n */\nexport function classifyImpact(\n baseSpecs: SpecSnapshot[],\n headSpecs: SpecSnapshot[],\n diffs: SemanticDiffItem[],\n options: ClassifyOptions = {}\n): ImpactResult {\n const rules = options.customRules ?? DEFAULT_RULES;\n const deltas: ImpactDelta[] = [];\n\n // Create lookup maps\n const baseMap = new Map(baseSpecs.map((s) => [`${s.key}@${s.version}`, s]));\n const headMap = new Map(headSpecs.map((s) => [`${s.key}@${s.version}`, s]));\n\n // Detect added specs\n const addedSpecs: ImpactResult['addedSpecs'] = [];\n for (const spec of headSpecs) {\n const lookupKey = `${spec.key}@${spec.version}`;\n if (!baseMap.has(lookupKey)) {\n addedSpecs.push({\n key: spec.key,\n version: spec.version,\n type: spec.type,\n });\n }\n }\n\n // Detect removed specs\n const removedSpecs: ImpactResult['removedSpecs'] = [];\n for (const spec of baseSpecs) {\n const lookupKey = `${spec.key}@${spec.version}`;\n if (!headMap.has(lookupKey)) {\n removedSpecs.push({\n key: spec.key,\n version: spec.version,\n type: spec.type,\n });\n\n // Removed spec is always breaking\n deltas.push({\n specKey: spec.key,\n specVersion: spec.version,\n specType: spec.type,\n path: `spec.${spec.key}`,\n severity: 'breaking',\n rule: 'endpoint-removed',\n description: `${spec.type === 'operation' ? 'Operation' : 'Event'} '${spec.key}' was removed`,\n });\n }\n }\n\n // Classify diffs\n for (const diff of diffs) {\n const matchingRule = findMatchingRule(\n { path: diff.path, description: diff.description, type: diff.type },\n rules\n );\n\n // Extract spec key from path (heuristic)\n const specKey = extractSpecKey(diff.path, baseSpecs, headSpecs);\n const specInfo = findSpecInfo(specKey, baseSpecs, headSpecs);\n\n deltas.push({\n specKey: specInfo?.key ?? 'unknown',\n specVersion: specInfo?.version ?? '1.0.0',\n specType: specInfo?.type ?? 'operation',\n path: diff.path,\n severity: matchingRule?.severity ?? mapDiffTypeToSeverity(diff.type),\n rule: matchingRule?.id ?? 'unknown',\n description: diff.description,\n oldValue: diff.oldValue,\n newValue: diff.newValue,\n });\n }\n\n // Add added specs as non-breaking changes\n for (const spec of addedSpecs) {\n deltas.push({\n specKey: spec.key,\n specVersion: spec.version,\n specType: spec.type,\n path: `spec.${spec.key}`,\n severity: 'non_breaking',\n rule: 'endpoint-added',\n description: `${spec.type === 'operation' ? 'Operation' : 'Event'} '${spec.key}' was added`,\n });\n }\n\n // Calculate summary\n const summary = calculateSummary(deltas, addedSpecs, removedSpecs);\n\n // Determine status\n const hasBreaking = summary.breaking > 0 || summary.removed > 0;\n const hasNonBreaking = summary.nonBreaking > 0 || summary.added > 0;\n const status = determineStatus(hasBreaking, hasNonBreaking);\n\n return {\n status,\n hasBreaking,\n hasNonBreaking,\n summary,\n deltas,\n addedSpecs,\n removedSpecs,\n timestamp: new Date().toISOString(),\n };\n}\n\n/**\n * Calculate summary counts from deltas.\n */\nfunction calculateSummary(\n deltas: ImpactDelta[],\n addedSpecs: ImpactResult['addedSpecs'],\n removedSpecs: ImpactResult['removedSpecs']\n): ImpactSummary {\n return {\n breaking: deltas.filter((d) => d.severity === 'breaking').length,\n nonBreaking: deltas.filter((d) => d.severity === 'non_breaking').length,\n info: deltas.filter((d) => d.severity === 'info').length,\n added: addedSpecs.length,\n removed: removedSpecs.length,\n };\n}\n\n/**\n * Determine overall status from flags.\n */\nfunction determineStatus(\n hasBreaking: boolean,\n hasNonBreaking: boolean\n): ImpactStatus {\n if (hasBreaking) return 'breaking';\n if (hasNonBreaking) return 'non-breaking';\n return 'no-impact';\n}\n\n/**\n * Map semantic diff type to impact severity.\n */\nfunction mapDiffTypeToSeverity(type: string): ImpactDelta['severity'] {\n switch (type) {\n case 'breaking':\n return 'breaking';\n case 'removed':\n return 'breaking';\n case 'added':\n return 'non_breaking';\n case 'changed':\n return 'info';\n default:\n return 'info';\n }\n}\n\n/**\n * Extract spec key from a diff path (heuristic).\n */\nfunction extractSpecKey(\n _path: string,\n _baseSpecs: SpecSnapshot[],\n _headSpecs: SpecSnapshot[]\n): string | undefined {\n // This is a simplified heuristic; in practice would need more context\n return undefined;\n}\n\n/**\n * Find spec info from key.\n */\nfunction findSpecInfo(\n key: string | undefined,\n baseSpecs: SpecSnapshot[],\n headSpecs: SpecSnapshot[]\n): SpecSnapshot | undefined {\n if (!key) return headSpecs[0] ?? baseSpecs[0];\n return (\n headSpecs.find((s) => s.key === key) ?? baseSpecs.find((s) => s.key === key)\n );\n}\n"],"mappings":"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"}
@@ -13,7 +13,7 @@ interface ImpactDelta {
13
13
  /** Key of the affected spec */
14
14
  specKey: string;
15
15
  /** Version of the affected spec */
16
- specVersion: number;
16
+ specVersion: string;
17
17
  /** Type of the spec (operation, event) */
18
18
  specType: 'operation' | 'event';
19
19
  /** Path to the changed element */
@@ -52,13 +52,13 @@ interface ImpactResult {
52
52
  /** Specs that were added */
53
53
  addedSpecs: {
54
54
  key: string;
55
- version: number;
55
+ version: string;
56
56
  type: 'operation' | 'event';
57
57
  }[];
58
58
  /** Specs that were removed */
59
59
  removedSpecs: {
60
60
  key: string;
61
- version: number;
61
+ version: string;
62
62
  type: 'operation' | 'event';
63
63
  }[];
64
64
  /** Base commit/ref */