@lssm/module.contractspec-workspace 0.0.0-canary-20251220030446 → 0.0.0-canary-20251221114240
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/deps/parse-imports.js +2 -2
- package/dist/analysis/deps/parse-imports.js.map +1 -1
- package/dist/analysis/spec-scan.d.ts.map +1 -1
- package/dist/analysis/spec-scan.js +1 -1
- package/dist/analysis/spec-scan.js.map +1 -1
- package/dist/analysis/validate/spec-structure.js +1 -1
- package/dist/analysis/validate/spec-structure.js.map +1 -1
- package/package.json +5 -5
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
*/
|
|
14
14
|
function parseImportedSpecNames(sourceCode, _fromFilePath) {
|
|
15
15
|
const imports = [];
|
|
16
|
-
const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+\.(?:contracts|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)(?:\.[jt]s)?)['"]/g;
|
|
16
|
+
const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+\.(?:contracts|contract|operation|operations|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)(?:\.[jt]s)?)['"]/g;
|
|
17
17
|
let match;
|
|
18
18
|
while ((match = importRegex.exec(sourceCode)) !== null) {
|
|
19
19
|
const importPath = match[1];
|
|
20
20
|
if (!importPath) continue;
|
|
21
21
|
if (!importPath.startsWith(".") && !importPath.startsWith("/")) continue;
|
|
22
22
|
const pathParts = importPath.split("/");
|
|
23
|
-
const name = (pathParts[pathParts.length - 1] ?? "").replace(/\.(ts|js)$/, "").replace(/\.(contracts|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)$/, "");
|
|
23
|
+
const name = (pathParts[pathParts.length - 1] ?? "").replace(/\.(ts|js)$/, "").replace(/\.(contracts|contract|operation|operations|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)$/, "");
|
|
24
24
|
if (name.length > 0) imports.push(name);
|
|
25
25
|
}
|
|
26
26
|
return Array.from(new Set(imports)).sort((a, b) => a.localeCompare(b));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-imports.js","names":["imports: string[]","match: RegExpExecArray | null"],"sources":["../../../src/analysis/deps/parse-imports.ts"],"sourcesContent":["/**\n * Import parsing utilities for dependency analysis.\n * Extracted from cli-contracts/src/commands/deps/parse-imports.ts\n */\n\n/**\n * Parse spec imports from source code.\n * Returns the names of imported specs based on file naming conventions.\n *\n * @param sourceCode - The source code to parse\n * @param fromFilePath - The path of the file being parsed (for relative resolution)\n * @returns Array of imported spec names\n */\nexport function parseImportedSpecNames(\n sourceCode: string,\n _fromFilePath: string\n): string[] {\n const imports: string[] = [];\n\n // Capture relative imports that reference spec-ish files.\n // Examples:\n // import x from './foo.contracts'\n // import { y } from '../bar.event.ts'\n const importRegex =\n /import\\s+.*?\\s+from\\s+['\"]([^'\"]+\\.(?:contracts|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)(?:\\.[jt]s)?)['\"]/g;\n\n let match: RegExpExecArray | null;\n while ((match = importRegex.exec(sourceCode)) !== null) {\n const importPath = match[1];\n if (!importPath) continue;\n if (!importPath.startsWith('.') && !importPath.startsWith('/')) continue;\n\n // Extract base name from the import path\n const pathParts = importPath.split('/');\n const base = pathParts[pathParts.length - 1] ?? '';\n\n const name = base\n .replace(/\\.(ts|js)$/, '')\n .replace(\n /\\.(contracts|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)$/,\n ''\n );\n\n if (name.length > 0) {\n imports.push(name);\n }\n }\n\n return Array.from(new Set(imports)).sort((a, b) => a.localeCompare(b));\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAgB,uBACd,YACA,eACU;CACV,MAAMA,UAAoB,EAAE;CAM5B,MAAM,cACJ;CAEF,IAAIC;AACJ,SAAQ,QAAQ,YAAY,KAAK,WAAW,MAAM,MAAM;EACtD,MAAM,aAAa,MAAM;AACzB,MAAI,CAAC,WAAY;AACjB,MAAI,CAAC,WAAW,WAAW,IAAI,IAAI,CAAC,WAAW,WAAW,IAAI,CAAE;EAGhE,MAAM,YAAY,WAAW,MAAM,IAAI;EAGvC,MAAM,QAFO,UAAU,UAAU,SAAS,MAAM,IAG7C,QAAQ,cAAc,GAAG,CACzB,QACC,
|
|
1
|
+
{"version":3,"file":"parse-imports.js","names":["imports: string[]","match: RegExpExecArray | null"],"sources":["../../../src/analysis/deps/parse-imports.ts"],"sourcesContent":["/**\n * Import parsing utilities for dependency analysis.\n * Extracted from cli-contracts/src/commands/deps/parse-imports.ts\n */\n\n/**\n * Parse spec imports from source code.\n * Returns the names of imported specs based on file naming conventions.\n *\n * @param sourceCode - The source code to parse\n * @param fromFilePath - The path of the file being parsed (for relative resolution)\n * @returns Array of imported spec names\n */\nexport function parseImportedSpecNames(\n sourceCode: string,\n _fromFilePath: string\n): string[] {\n const imports: string[] = [];\n\n // Capture relative imports that reference spec-ish files.\n // Examples:\n // import x from './foo.contracts'\n // import { y } from '../bar.event.ts'\n const importRegex =\n /import\\s+.*?\\s+from\\s+['\"]([^'\"]+\\.(?:contracts|contract|operation|operations|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)(?:\\.[jt]s)?)['\"]/g;\n\n let match: RegExpExecArray | null;\n while ((match = importRegex.exec(sourceCode)) !== null) {\n const importPath = match[1];\n if (!importPath) continue;\n if (!importPath.startsWith('.') && !importPath.startsWith('/')) continue;\n\n // Extract base name from the import path\n const pathParts = importPath.split('/');\n const base = pathParts[pathParts.length - 1] ?? '';\n\n const name = base\n .replace(/\\.(ts|js)$/, '')\n .replace(\n /\\.(contracts|contract|operation|operations|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)$/,\n ''\n );\n\n if (name.length > 0) {\n imports.push(name);\n }\n }\n\n return Array.from(new Set(imports)).sort((a, b) => a.localeCompare(b));\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAgB,uBACd,YACA,eACU;CACV,MAAMA,UAAoB,EAAE;CAM5B,MAAM,cACJ;CAEF,IAAIC;AACJ,SAAQ,QAAQ,YAAY,KAAK,WAAW,MAAM,MAAM;EACtD,MAAM,aAAa,MAAM;AACzB,MAAI,CAAC,WAAY;AACjB,MAAI,CAAC,WAAW,WAAW,IAAI,IAAI,CAAC,WAAW,WAAW,IAAI,CAAE;EAGhE,MAAM,YAAY,WAAW,MAAM,IAAI;EAGvC,MAAM,QAFO,UAAU,UAAU,SAAS,MAAM,IAG7C,QAAQ,cAAc,GAAG,CACzB,QACC,sJACA,GACD;AAEH,MAAI,KAAK,SAAS,EAChB,SAAQ,KAAK,KAAK;;AAItB,QAAO,MAAM,KAAK,IAAI,IAAI,QAAQ,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spec-scan.d.ts","names":[],"sources":["../../src/analysis/spec-scan.ts"],"sourcesContent":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"spec-scan.d.ts","names":[],"sources":["../../src/analysis/spec-scan.ts"],"sourcesContent":[],"mappings":";;;;AAoHA;AAgCA;AA0BA;AAgMA;iBA7VgB,yBAAA,oBAA6C;;;;iBA8C7C,cAAA,kCAAgD;;;;;iBAqDhD,oBAAA,gBAAoC;;;;iBAgCpC,iBAAA,gBAAiC;;;;iBA0BjC,eAAA,gBAA+B;;;;;iBAgM/B,sBAAA,kCAGb"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Supports all contract types from @lssm/lib.contracts.
|
|
5
5
|
*/
|
|
6
6
|
function inferSpecTypeFromFilePath(filePath) {
|
|
7
|
-
if (filePath.includes(".
|
|
7
|
+
if (filePath.includes(".operations.") || filePath.includes("/operations/") || filePath.includes(".operation.") || filePath.includes("/operation/")) return "operation";
|
|
8
8
|
if (filePath.includes(".event.") || filePath.includes("/events/") || filePath.endsWith("/events.ts")) return "event";
|
|
9
9
|
if (filePath.includes(".presentation.") || filePath.includes("/presentations/") || filePath.endsWith("/presentations.ts")) return "presentation";
|
|
10
10
|
if (filePath.includes(".feature.")) return "feature";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spec-scan.js","names":["events: RefInfo[]","policies: RefInfo[]","tests: RefInfo[]","results: SpecScanResult[]"],"sources":["../../src/analysis/spec-scan.ts"],"sourcesContent":["/**\n * Spec source scanning utilities.\n * Extracted from cli-contracts/src/utils/spec-scan.ts\n */\n\nimport type {\n AnalyzedSpecType,\n AnalyzedOperationKind,\n SpecScanResult,\n RefInfo,\n} from '../types/analysis-types';\nimport type { Stability } from '../types/spec-types';\n\n/**\n * Infer spec type from file path based on naming conventions.\n * Supports all contract types from @lssm/lib.contracts.\n */\nexport function inferSpecTypeFromFilePath(filePath: string): AnalyzedSpecType {\n // Check more specific patterns first\n // Operation patterns: .contracts. OR /contracts/ directory\n if (filePath.includes('.contracts.') || filePath.includes('/contracts/'))\n return 'operation';\n\n // Event patterns: .event. OR /events/ OR /events.ts\n if (\n filePath.includes('.event.') ||\n filePath.includes('/events/') ||\n filePath.endsWith('/events.ts')\n )\n return 'event';\n\n // Presentation patterns: .presentation. OR /presentations/ OR /presentations.ts\n if (\n filePath.includes('.presentation.') ||\n filePath.includes('/presentations/') ||\n filePath.endsWith('/presentations.ts')\n )\n return 'presentation';\n\n if (filePath.includes('.feature.')) return 'feature';\n if (filePath.includes('.capability.')) return 'capability';\n if (filePath.includes('.data-view.')) return 'data-view';\n if (filePath.includes('.form.')) return 'form';\n if (filePath.includes('.migration.')) return 'migration';\n if (filePath.includes('.workflow.')) return 'workflow';\n if (filePath.includes('.experiment.')) return 'experiment';\n if (filePath.includes('.integration.')) return 'integration';\n if (filePath.includes('.knowledge.')) return 'knowledge';\n if (filePath.includes('.telemetry.')) return 'telemetry';\n if (filePath.includes('.app-config.')) return 'app-config';\n if (filePath.includes('.policy.')) return 'policy';\n if (filePath.includes('.test-spec.')) return 'test-spec';\n return 'unknown';\n}\n\n/**\n * Scan spec source code to extract metadata without executing it.\n */\nexport function scanSpecSource(code: string, filePath: string): SpecScanResult {\n const specType = inferSpecTypeFromFilePath(filePath);\n\n const name = matchStringField(code, 'name');\n const description = matchStringField(code, 'description');\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 const version = matchNumberField(code, 'version');\n const kind = inferOperationKind(code);\n\n const hasMeta = /meta\\s*:\\s*{/.test(code);\n const hasIo = /\\bio\\s*:\\s*{/.test(code);\n const hasPolicy = /\\bpolicy\\s*:\\s*{/.test(code);\n const hasPayload = /\\bpayload\\s*:\\s*{/.test(code);\n const hasContent = /\\bcontent\\s*:\\s*{/.test(code);\n const hasDefinition = /\\bdefinition\\s*:\\s*{/.test(code);\n\n // Extract references from operations\n const emittedEvents =\n specType === 'operation' ? extractEmittedEvents(code) : undefined;\n const policyRefs =\n specType === 'operation' ? extractPolicyRefs(code) : undefined;\n const testRefs = extractTestRefs(code);\n\n return {\n filePath,\n specType,\n name: name ?? undefined,\n description: description ?? undefined,\n stability,\n owners,\n tags,\n version: version ?? undefined,\n kind,\n hasMeta,\n hasIo,\n hasPolicy,\n hasPayload,\n hasContent,\n hasDefinition,\n emittedEvents,\n policyRefs,\n testRefs,\n };\n}\n\n/**\n * Extract emitted event refs from operation spec source.\n * Looks for sideEffects.emits array entries.\n */\nexport function extractEmittedEvents(code: string): RefInfo[] | undefined {\n const events: RefInfo[] = [];\n\n // Match inline emit declarations: { name: 'x', version: N, ... }\n const inlinePattern = /\\{\\s*name:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*(\\d+)/g;\n let match;\n while ((match = inlinePattern.exec(code)) !== null) {\n if (match[1] && match[2]) {\n events.push({\n name: match[1],\n version: Number(match[2]),\n });\n }\n }\n\n // Match ref pattern: { ref: SomeEventSpec, ... }\n // We can't fully resolve these without execution, but we can note they exist\n const refPattern = /\\{\\s*ref:\\s*(\\w+)/g;\n while ((match = refPattern.exec(code)) !== null) {\n // Store a placeholder - actual resolution needs runtime\n if (match[1] && !match[1].startsWith('when')) {\n // We can't extract name/version from a variable ref statically\n // This is noted for completeness but won't be fully resolvable\n }\n }\n\n return events.length > 0 ? events : undefined;\n}\n\n/**\n * Extract policy refs from operation spec source.\n */\nexport function extractPolicyRefs(code: string): RefInfo[] | undefined {\n const policies: RefInfo[] = [];\n\n // Match policy ref pattern in policies array\n const policyPattern = /\\{\\s*name:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*(\\d+)/g;\n\n // Only look within policy section\n const policySectionMatch = code.match(/policies\\s*:\\s*\\[([\\s\\S]*?)\\]/);\n if (policySectionMatch?.[1]) {\n let match;\n while ((match = policyPattern.exec(policySectionMatch[1])) !== null) {\n if (match[1] && match[2]) {\n policies.push({\n name: match[1],\n version: Number(match[2]),\n });\n }\n }\n }\n\n return policies.length > 0 ? policies : undefined;\n}\n\n/**\n * Extract test spec refs.\n */\nexport function extractTestRefs(code: string): RefInfo[] | undefined {\n const tests: RefInfo[] = [];\n\n // Look for tests array\n const testsSectionMatch = code.match(/tests\\s*:\\s*\\[([\\s\\S]*?)\\]/);\n if (testsSectionMatch?.[1]) {\n const refPattern = /\\{\\s*name:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*(\\d+)/g;\n let match;\n while ((match = refPattern.exec(testsSectionMatch[1])) !== null) {\n if (match[1] && match[2]) {\n tests.push({\n name: match[1],\n version: Number(match[2]),\n });\n }\n }\n }\n\n return tests.length > 0 ? tests : undefined;\n}\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\nfunction matchNumberField(code: string, field: string): number | null {\n const regex = new RegExp(`${escapeRegex(field)}\\\\s*:\\\\s*(\\\\d+)`);\n const match = code.match(regex);\n if (!match?.[1]) return null;\n const parsed = Number(match[1]);\n return Number.isFinite(parsed) ? parsed : null;\n}\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\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\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Infer operation kind from source code.\n * First checks for defineCommand/defineQuery usage (which set kind automatically),\n * then falls back to explicit kind field.\n */\nfunction inferOperationKind(code: string): AnalyzedOperationKind {\n // Check for defineCommand/defineQuery usage first (they set kind automatically)\n if (/defineCommand\\s*\\(/.test(code)) return 'command';\n if (/defineQuery\\s*\\(/.test(code)) return 'query';\n // Fall back to explicit kind field\n const kindRaw = matchStringField(code, 'kind');\n return kindRaw === 'command' || kindRaw === 'query' ? kindRaw : 'unknown';\n}\n\n/**\n * Infer operation kind from a specific code block.\n */\nfunction inferOperationKindFromBlock(block: string): AnalyzedOperationKind {\n if (/defineCommand\\s*\\(/.test(block)) return 'command';\n if (/defineQuery\\s*\\(/.test(block)) return 'query';\n const kindRaw = matchStringField(block, 'kind');\n return kindRaw === 'command' || kindRaw === 'query' ? kindRaw : 'unknown';\n}\n\n/**\n * Extract name and version from a meta block.\n */\nfunction extractMetaFromBlock(\n block: string\n): { name: string; version: number } | null {\n const name = matchStringField(block, 'name');\n const version = matchNumberField(block, 'version');\n if (name && version !== null) {\n return { name, version };\n }\n return null;\n}\n\n/**\n * Define function patterns for all spec types.\n */\nconst DEFINE_FUNCTION_PATTERNS = [\n // Operations\n { pattern: /defineCommand\\s*\\(\\s*\\{/g, type: 'operation' as const },\n { pattern: /defineQuery\\s*\\(\\s*\\{/g, type: 'operation' as const },\n // Events\n { pattern: /defineEvent\\s*\\(\\s*\\{/g, type: 'event' as const },\n // Presentations (both v1 and v2 patterns)\n {\n pattern: /:\\s*PresentationDescriptorV2\\s*=\\s*\\{/g,\n type: 'presentation' as const,\n },\n {\n pattern: /:\\s*PresentationDescriptor\\s*=\\s*\\{/g,\n type: 'presentation' as const,\n },\n { pattern: /definePresentation\\s*\\(\\s*\\{/g, type: 'presentation' as const },\n // Capabilities\n { pattern: /defineCapability\\s*\\(\\s*\\{/g, type: 'capability' as const },\n // Workflows\n { pattern: /defineWorkflow\\s*\\(\\s*\\{/g, type: 'workflow' as const },\n // Experiments\n { pattern: /defineExperiment\\s*\\(\\s*\\{/g, type: 'experiment' as const },\n // Integrations\n { pattern: /defineIntegration\\s*\\(\\s*\\{/g, type: 'integration' as const },\n // Knowledge\n { pattern: /defineKnowledge\\s*\\(\\s*\\{/g, type: 'knowledge' as const },\n // Telemetry\n { pattern: /defineTelemetry\\s*\\(\\s*\\{/g, type: 'telemetry' as const },\n // App config\n { pattern: /defineAppConfig\\s*\\(\\s*\\{/g, type: 'app-config' as const },\n // Policy\n { pattern: /definePolicy\\s*\\(\\s*\\{/g, type: 'policy' as const },\n // Test spec\n { pattern: /defineTestSpec\\s*\\(\\s*\\{/g, type: 'test-spec' as const },\n // Data view\n { pattern: /defineDataView\\s*\\(\\s*\\{/g, type: 'data-view' as const },\n // Form\n { pattern: /defineForm\\s*\\(\\s*\\{/g, type: 'form' as const },\n // Migration\n { pattern: /defineMigration\\s*\\(\\s*\\{/g, type: 'migration' as const },\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 */\nfunction findMatchingBrace(code: string, startIndex: number): 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 === '{') {\n depth++;\n } else if (char === '}') {\n depth--;\n if (depth === 0) {\n return i;\n }\n }\n }\n\n return -1;\n}\n\n/**\n * Scan spec source code to extract ALL specs from a file.\n * This function finds multiple spec definitions in a single file.\n */\nexport function scanAllSpecsFromSource(\n code: string,\n filePath: string\n): SpecScanResult[] {\n const results: SpecScanResult[] = [];\n const baseSpecType = inferSpecTypeFromFilePath(filePath);\n\n // Track positions we've already processed to avoid duplicates\n const processedPositions = new Set<number>();\n\n for (const { pattern, type } of DEFINE_FUNCTION_PATTERNS) {\n // Reset the regex lastIndex\n pattern.lastIndex = 0;\n\n let match;\n while ((match = pattern.exec(code)) !== null) {\n const startPos = match.index;\n\n // Skip if we've already processed this position\n if (processedPositions.has(startPos)) continue;\n processedPositions.add(startPos);\n\n // Find the opening brace position\n const openBracePos = code.indexOf('{', startPos);\n if (openBracePos === -1) continue;\n\n // Find the matching closing brace\n const closeBracePos = findMatchingBrace(code, openBracePos);\n if (closeBracePos === -1) continue;\n\n // Extract the block content\n const block = code.slice(openBracePos, closeBracePos + 1);\n\n // Extract meta information\n const meta = extractMetaFromBlock(block);\n if (!meta) continue;\n\n // Extract additional metadata\n const description = matchStringField(block, 'description');\n const stabilityRaw = matchStringField(block, 'stability');\n const stability = isStability(stabilityRaw) ? stabilityRaw : undefined;\n const owners = matchStringArrayField(block, 'owners');\n const tags = matchStringArrayField(block, 'tags');\n\n const hasMeta = /meta\\s*:\\s*{/.test(block);\n const hasIo = /\\bio\\s*:\\s*{/.test(block);\n const hasPolicy = /\\bpolicy\\s*:\\s*{/.test(block);\n const hasPayload = /\\bpayload\\s*:\\s*{/.test(block);\n const hasContent = /\\bcontent\\s*:\\s*{/.test(block);\n const hasDefinition = /\\bdefinition\\s*:\\s*{/.test(block);\n\n // Infer kind for operations\n const kind =\n type === 'operation' ? inferOperationKindFromBlock(block) : 'unknown';\n\n // Extract references from operations\n const emittedEvents =\n type === 'operation' ? extractEmittedEvents(block) : undefined;\n const policyRefs =\n type === 'operation' ? extractPolicyRefs(block) : undefined;\n const testRefs = extractTestRefs(block);\n\n results.push({\n filePath,\n specType: type,\n name: meta.name,\n version: meta.version,\n description: description ?? undefined,\n stability,\n owners,\n tags,\n kind,\n hasMeta,\n hasIo,\n hasPolicy,\n hasPayload,\n hasContent,\n hasDefinition,\n emittedEvents,\n policyRefs,\n testRefs,\n });\n }\n }\n\n // If no specs found via patterns, fall back to file-type based scanning\n // This handles cases where the file uses non-standard patterns\n if (results.length === 0 && baseSpecType !== 'unknown') {\n const fallback = scanSpecSource(code, filePath);\n if (fallback.name && fallback.version !== undefined) {\n results.push(fallback);\n }\n }\n\n return results;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"spec-scan.js","names":["events: RefInfo[]","policies: RefInfo[]","tests: RefInfo[]","results: SpecScanResult[]"],"sources":["../../src/analysis/spec-scan.ts"],"sourcesContent":["/**\n * Spec source scanning utilities.\n * Extracted from cli-contracts/src/utils/spec-scan.ts\n */\n\nimport type {\n AnalyzedOperationKind,\n AnalyzedSpecType,\n RefInfo,\n SpecScanResult,\n} from '../types/analysis-types';\nimport type { Stability } from '../types/spec-types';\n\n/**\n * Infer spec type from file path based on naming conventions.\n * Supports all contract types from @lssm/lib.contracts.\n */\nexport function inferSpecTypeFromFilePath(filePath: string): AnalyzedSpecType {\n // Check more specific patterns first\n // Operation patterns: .contracts. OR /contracts/ directory\n if (\n filePath.includes('.operations.') ||\n filePath.includes('/operations/') ||\n filePath.includes('.operation.') ||\n filePath.includes('/operation/')\n )\n return 'operation';\n\n // Event patterns: .event. OR /events/ OR /events.ts\n if (\n filePath.includes('.event.') ||\n filePath.includes('/events/') ||\n filePath.endsWith('/events.ts')\n )\n return 'event';\n\n // Presentation patterns: .presentation. OR /presentations/ OR /presentations.ts\n if (\n filePath.includes('.presentation.') ||\n filePath.includes('/presentations/') ||\n filePath.endsWith('/presentations.ts')\n )\n return 'presentation';\n\n if (filePath.includes('.feature.')) return 'feature';\n if (filePath.includes('.capability.')) return 'capability';\n if (filePath.includes('.data-view.')) return 'data-view';\n if (filePath.includes('.form.')) return 'form';\n if (filePath.includes('.migration.')) return 'migration';\n if (filePath.includes('.workflow.')) return 'workflow';\n if (filePath.includes('.experiment.')) return 'experiment';\n if (filePath.includes('.integration.')) return 'integration';\n if (filePath.includes('.knowledge.')) return 'knowledge';\n if (filePath.includes('.telemetry.')) return 'telemetry';\n if (filePath.includes('.app-config.')) return 'app-config';\n if (filePath.includes('.policy.')) return 'policy';\n if (filePath.includes('.test-spec.')) return 'test-spec';\n return 'unknown';\n}\n\n/**\n * Scan spec source code to extract metadata without executing it.\n */\nexport function scanSpecSource(code: string, filePath: string): SpecScanResult {\n const specType = inferSpecTypeFromFilePath(filePath);\n\n const name = matchStringField(code, 'name');\n const description = matchStringField(code, 'description');\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 const version = matchNumberField(code, 'version');\n const kind = inferOperationKind(code);\n\n const hasMeta = /meta\\s*:\\s*{/.test(code);\n const hasIo = /\\bio\\s*:\\s*{/.test(code);\n const hasPolicy = /\\bpolicy\\s*:\\s*{/.test(code);\n const hasPayload = /\\bpayload\\s*:\\s*{/.test(code);\n const hasContent = /\\bcontent\\s*:\\s*{/.test(code);\n const hasDefinition = /\\bdefinition\\s*:\\s*{/.test(code);\n\n // Extract references from operations\n const emittedEvents =\n specType === 'operation' ? extractEmittedEvents(code) : undefined;\n const policyRefs =\n specType === 'operation' ? extractPolicyRefs(code) : undefined;\n const testRefs = extractTestRefs(code);\n\n return {\n filePath,\n specType,\n name: name ?? undefined,\n description: description ?? undefined,\n stability,\n owners,\n tags,\n version: version ?? undefined,\n kind,\n hasMeta,\n hasIo,\n hasPolicy,\n hasPayload,\n hasContent,\n hasDefinition,\n emittedEvents,\n policyRefs,\n testRefs,\n };\n}\n\n/**\n * Extract emitted event refs from operation spec source.\n * Looks for sideEffects.emits array entries.\n */\nexport function extractEmittedEvents(code: string): RefInfo[] | undefined {\n const events: RefInfo[] = [];\n\n // Match inline emit declarations: { name: 'x', version: N, ... }\n const inlinePattern = /\\{\\s*name:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*(\\d+)/g;\n let match;\n while ((match = inlinePattern.exec(code)) !== null) {\n if (match[1] && match[2]) {\n events.push({\n name: match[1],\n version: Number(match[2]),\n });\n }\n }\n\n // Match ref pattern: { ref: SomeEventSpec, ... }\n // We can't fully resolve these without execution, but we can note they exist\n const refPattern = /\\{\\s*ref:\\s*(\\w+)/g;\n while ((match = refPattern.exec(code)) !== null) {\n // Store a placeholder - actual resolution needs runtime\n if (match[1] && !match[1].startsWith('when')) {\n // We can't extract name/version from a variable ref statically\n // This is noted for completeness but won't be fully resolvable\n }\n }\n\n return events.length > 0 ? events : undefined;\n}\n\n/**\n * Extract policy refs from operation spec source.\n */\nexport function extractPolicyRefs(code: string): RefInfo[] | undefined {\n const policies: RefInfo[] = [];\n\n // Match policy ref pattern in policies array\n const policyPattern = /\\{\\s*name:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*(\\d+)/g;\n\n // Only look within policy section\n const policySectionMatch = code.match(/policies\\s*:\\s*\\[([\\s\\S]*?)\\]/);\n if (policySectionMatch?.[1]) {\n let match;\n while ((match = policyPattern.exec(policySectionMatch[1])) !== null) {\n if (match[1] && match[2]) {\n policies.push({\n name: match[1],\n version: Number(match[2]),\n });\n }\n }\n }\n\n return policies.length > 0 ? policies : undefined;\n}\n\n/**\n * Extract test spec refs.\n */\nexport function extractTestRefs(code: string): RefInfo[] | undefined {\n const tests: RefInfo[] = [];\n\n // Look for tests array\n const testsSectionMatch = code.match(/tests\\s*:\\s*\\[([\\s\\S]*?)\\]/);\n if (testsSectionMatch?.[1]) {\n const refPattern = /\\{\\s*name:\\s*['\"]([^'\"]+)['\"]\\s*,\\s*version:\\s*(\\d+)/g;\n let match;\n while ((match = refPattern.exec(testsSectionMatch[1])) !== null) {\n if (match[1] && match[2]) {\n tests.push({\n name: match[1],\n version: Number(match[2]),\n });\n }\n }\n }\n\n return tests.length > 0 ? tests : undefined;\n}\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\nfunction matchNumberField(code: string, field: string): number | null {\n const regex = new RegExp(`${escapeRegex(field)}\\\\s*:\\\\s*(\\\\d+)`);\n const match = code.match(regex);\n if (!match?.[1]) return null;\n const parsed = Number(match[1]);\n return Number.isFinite(parsed) ? parsed : null;\n}\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\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\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Infer operation kind from source code.\n * First checks for defineCommand/defineQuery usage (which set kind automatically),\n * then falls back to explicit kind field.\n */\nfunction inferOperationKind(code: string): AnalyzedOperationKind {\n // Check for defineCommand/defineQuery usage first (they set kind automatically)\n if (/defineCommand\\s*\\(/.test(code)) return 'command';\n if (/defineQuery\\s*\\(/.test(code)) return 'query';\n // Fall back to explicit kind field\n const kindRaw = matchStringField(code, 'kind');\n return kindRaw === 'command' || kindRaw === 'query' ? kindRaw : 'unknown';\n}\n\n/**\n * Infer operation kind from a specific code block.\n */\nfunction inferOperationKindFromBlock(block: string): AnalyzedOperationKind {\n if (/defineCommand\\s*\\(/.test(block)) return 'command';\n if (/defineQuery\\s*\\(/.test(block)) return 'query';\n const kindRaw = matchStringField(block, 'kind');\n return kindRaw === 'command' || kindRaw === 'query' ? kindRaw : 'unknown';\n}\n\n/**\n * Extract name and version from a meta block.\n */\nfunction extractMetaFromBlock(\n block: string\n): { name: string; version: number } | null {\n const name = matchStringField(block, 'name');\n const version = matchNumberField(block, 'version');\n if (name && version !== null) {\n return { name, version };\n }\n return null;\n}\n\n/**\n * Define function patterns for all spec types.\n */\nconst DEFINE_FUNCTION_PATTERNS = [\n // Operations\n { pattern: /defineCommand\\s*\\(\\s*\\{/g, type: 'operation' as const },\n { pattern: /defineQuery\\s*\\(\\s*\\{/g, type: 'operation' as const },\n // Events\n { pattern: /defineEvent\\s*\\(\\s*\\{/g, type: 'event' as const },\n // Presentations (both v1 and v2 patterns)\n {\n pattern: /:\\s*PresentationDescriptorV2\\s*=\\s*\\{/g,\n type: 'presentation' as const,\n },\n {\n pattern: /:\\s*PresentationDescriptor\\s*=\\s*\\{/g,\n type: 'presentation' as const,\n },\n { pattern: /definePresentation\\s*\\(\\s*\\{/g, type: 'presentation' as const },\n // Capabilities\n { pattern: /defineCapability\\s*\\(\\s*\\{/g, type: 'capability' as const },\n // Workflows\n { pattern: /defineWorkflow\\s*\\(\\s*\\{/g, type: 'workflow' as const },\n // Experiments\n { pattern: /defineExperiment\\s*\\(\\s*\\{/g, type: 'experiment' as const },\n // Integrations\n { pattern: /defineIntegration\\s*\\(\\s*\\{/g, type: 'integration' as const },\n // Knowledge\n { pattern: /defineKnowledge\\s*\\(\\s*\\{/g, type: 'knowledge' as const },\n // Telemetry\n { pattern: /defineTelemetry\\s*\\(\\s*\\{/g, type: 'telemetry' as const },\n // App config\n { pattern: /defineAppConfig\\s*\\(\\s*\\{/g, type: 'app-config' as const },\n // Policy\n { pattern: /definePolicy\\s*\\(\\s*\\{/g, type: 'policy' as const },\n // Test spec\n { pattern: /defineTestSpec\\s*\\(\\s*\\{/g, type: 'test-spec' as const },\n // Data view\n { pattern: /defineDataView\\s*\\(\\s*\\{/g, type: 'data-view' as const },\n // Form\n { pattern: /defineForm\\s*\\(\\s*\\{/g, type: 'form' as const },\n // Migration\n { pattern: /defineMigration\\s*\\(\\s*\\{/g, type: 'migration' as const },\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 */\nfunction findMatchingBrace(code: string, startIndex: number): 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 === '{') {\n depth++;\n } else if (char === '}') {\n depth--;\n if (depth === 0) {\n return i;\n }\n }\n }\n\n return -1;\n}\n\n/**\n * Scan spec source code to extract ALL specs from a file.\n * This function finds multiple spec definitions in a single file.\n */\nexport function scanAllSpecsFromSource(\n code: string,\n filePath: string\n): SpecScanResult[] {\n const results: SpecScanResult[] = [];\n const baseSpecType = inferSpecTypeFromFilePath(filePath);\n\n // Track positions we've already processed to avoid duplicates\n const processedPositions = new Set<number>();\n\n for (const { pattern, type } of DEFINE_FUNCTION_PATTERNS) {\n // Reset the regex lastIndex\n pattern.lastIndex = 0;\n\n let match;\n while ((match = pattern.exec(code)) !== null) {\n const startPos = match.index;\n\n // Skip if we've already processed this position\n if (processedPositions.has(startPos)) continue;\n processedPositions.add(startPos);\n\n // Find the opening brace position\n const openBracePos = code.indexOf('{', startPos);\n if (openBracePos === -1) continue;\n\n // Find the matching closing brace\n const closeBracePos = findMatchingBrace(code, openBracePos);\n if (closeBracePos === -1) continue;\n\n // Extract the block content\n const block = code.slice(openBracePos, closeBracePos + 1);\n\n // Extract meta information\n const meta = extractMetaFromBlock(block);\n if (!meta) continue;\n\n // Extract additional metadata\n const description = matchStringField(block, 'description');\n const stabilityRaw = matchStringField(block, 'stability');\n const stability = isStability(stabilityRaw) ? stabilityRaw : undefined;\n const owners = matchStringArrayField(block, 'owners');\n const tags = matchStringArrayField(block, 'tags');\n\n const hasMeta = /meta\\s*:\\s*{/.test(block);\n const hasIo = /\\bio\\s*:\\s*{/.test(block);\n const hasPolicy = /\\bpolicy\\s*:\\s*{/.test(block);\n const hasPayload = /\\bpayload\\s*:\\s*{/.test(block);\n const hasContent = /\\bcontent\\s*:\\s*{/.test(block);\n const hasDefinition = /\\bdefinition\\s*:\\s*{/.test(block);\n\n // Infer kind for operations\n const kind =\n type === 'operation' ? inferOperationKindFromBlock(block) : 'unknown';\n\n // Extract references from operations\n const emittedEvents =\n type === 'operation' ? extractEmittedEvents(block) : undefined;\n const policyRefs =\n type === 'operation' ? extractPolicyRefs(block) : undefined;\n const testRefs = extractTestRefs(block);\n\n results.push({\n filePath,\n specType: type,\n name: meta.name,\n version: meta.version,\n description: description ?? undefined,\n stability,\n owners,\n tags,\n kind,\n hasMeta,\n hasIo,\n hasPolicy,\n hasPayload,\n hasContent,\n hasDefinition,\n emittedEvents,\n policyRefs,\n testRefs,\n });\n }\n }\n\n // If no specs found via patterns, fall back to file-type based scanning\n // This handles cases where the file uses non-standard patterns\n if (results.length === 0 && baseSpecType !== 'unknown') {\n const fallback = scanSpecSource(code, filePath);\n if (fallback.name && fallback.version !== undefined) {\n results.push(fallback);\n }\n }\n\n return results;\n}\n"],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ function validateSpecStructure(code, fileName) {
|
|
|
6
6
|
const errors = [];
|
|
7
7
|
const warnings = [];
|
|
8
8
|
if (!/export\s+(const|let)\s+\w+/.test(code)) errors.push("No exported spec found");
|
|
9
|
-
if (fileName.includes(".contracts.")) validateOperationSpec(code, errors, warnings);
|
|
9
|
+
if (fileName.includes(".contracts.") || fileName.includes(".contract.")) validateOperationSpec(code, errors, warnings);
|
|
10
10
|
if (fileName.includes(".event.")) validateEventSpec(code, errors, warnings);
|
|
11
11
|
if (fileName.includes(".presentation.")) validatePresentationSpec(code, errors, warnings);
|
|
12
12
|
if (fileName.includes(".workflow.")) validateWorkflowSpec(code, errors, warnings);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spec-structure.js","names":["errors: string[]","warnings: string[]"],"sources":["../../../src/analysis/validate/spec-structure.ts"],"sourcesContent":["/**\n * Spec structure validation utilities.\n * Extracted from cli-contracts/src/commands/validate/spec-checker.ts\n */\n\nimport type { ValidationResult } from '../../types/analysis-types';\n\nexport type { ValidationResult };\n\n/**\n * Validate spec structure based on source code and filename.\n */\nexport function validateSpecStructure(\n code: string,\n fileName: string\n): ValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // Check for required exports\n const hasExport = /export\\s+(const|let)\\s+\\w+/.test(code);\n if (!hasExport) {\n errors.push('No exported spec found');\n }\n\n // Validate operation specs\n if (fileName.includes('.contracts.')) {\n validateOperationSpec(code, errors, warnings);\n }\n\n // Validate event specs\n if (fileName.includes('.event.')) {\n validateEventSpec(code, errors, warnings);\n }\n\n // Validate presentation specs\n if (fileName.includes('.presentation.')) {\n validatePresentationSpec(code, errors, warnings);\n }\n\n if (fileName.includes('.workflow.')) {\n validateWorkflowSpec(code, errors, warnings);\n }\n\n if (fileName.includes('.data-view.')) {\n validateDataViewSpec(code, errors, warnings);\n }\n\n if (fileName.includes('.migration.')) {\n validateMigrationSpec(code, errors, warnings);\n }\n\n if (fileName.includes('.telemetry.')) {\n validateTelemetrySpec(code, errors, warnings);\n }\n\n if (fileName.includes('.experiment.')) {\n validateExperimentSpec(code, errors, warnings);\n }\n\n if (fileName.includes('.app-config.')) {\n validateAppConfigSpec(code, errors, warnings);\n }\n\n // Common validations\n validateCommonFields(code, errors, warnings);\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n}\n\n/**\n * Validate operation spec\n */\nfunction validateOperationSpec(\n code: string,\n errors: string[],\n warnings: string[]\n) {\n // Check for defineCommand or defineQuery\n const hasDefine = /define(Command|Query)/.test(code);\n if (!hasDefine) {\n errors.push('Missing defineCommand or defineQuery call');\n }\n\n // Check for required meta fields\n if (!code.includes('meta:')) {\n errors.push('Missing meta section');\n }\n\n if (!code.includes('io:')) {\n errors.push('Missing io section');\n }\n\n if (!code.includes('policy:')) {\n errors.push('Missing policy section');\n }\n\n // Check for name\n if (!code.match(/name:\\s*['\"][^'\"]+['\"]/)) {\n errors.push('Missing or invalid name field');\n }\n\n // Check for version\n if (!code.match(/version:\\s*\\d+/)) {\n errors.push('Missing or invalid version field');\n }\n\n // Check for kind (defineCommand/defineQuery set it automatically, or explicit kind field)\n const hasDefineCommand = /defineCommand\\s*\\(/.test(code);\n const hasDefineQuery = /defineQuery\\s*\\(/.test(code);\n const hasExplicitKind = /kind:\\s*['\"](?:command|query)['\"]/.test(code);\n if (!hasDefineCommand && !hasDefineQuery && !hasExplicitKind) {\n errors.push(\n 'Missing kind: use defineCommand(), defineQuery(), or explicit kind field'\n );\n }\n\n // Warnings\n if (!code.includes('acceptance:')) {\n warnings.push('No acceptance scenarios defined');\n }\n\n if (!code.includes('examples:')) {\n warnings.push('No examples provided');\n }\n\n if (code.includes('TODO')) {\n warnings.push('Contains TODO items that need completion');\n }\n}\n\nfunction validateTelemetrySpec(\n code: string,\n errors: string[],\n warnings: string[]\n) {\n if (!code.match(/:\\s*TelemetrySpec\\s*=/)) {\n errors.push('Missing TelemetrySpec type annotation');\n }\n\n if (!code.match(/meta:\\s*{[\\s\\S]*name:/)) {\n errors.push('TelemetrySpec.meta is required');\n }\n\n if (!code.includes('events:')) {\n errors.push('TelemetrySpec must declare events');\n }\n\n if (!code.match(/privacy:\\s*'(public|internal|pii|sensitive)'/)) {\n warnings.push('No explicit privacy classification found');\n }\n}\n\nfunction validateExperimentSpec(\n code: string,\n errors: string[],\n warnings: string[]\n) {\n if (!code.match(/:\\s*ExperimentSpec\\s*=/)) {\n errors.push('Missing ExperimentSpec type annotation');\n }\n if (!code.includes('controlVariant')) {\n errors.push('ExperimentSpec must declare controlVariant');\n }\n if (!code.includes('variants:')) {\n errors.push('ExperimentSpec must declare variants');\n }\n if (!code.match(/allocation:\\s*{/)) {\n warnings.push('ExperimentSpec missing allocation configuration');\n }\n}\n\nfunction validateAppConfigSpec(\n code: string,\n errors: string[],\n warnings: string[]\n) {\n if (!code.match(/:\\s*AppBlueprintSpec\\s*=/)) {\n errors.push('Missing AppBlueprintSpec type annotation');\n }\n if (!code.includes('meta:')) {\n errors.push('AppBlueprintSpec must define meta');\n }\n if (!code.includes('appId')) {\n warnings.push('AppBlueprint meta missing appId assignment');\n }\n if (!code.includes('capabilities')) {\n warnings.push('App blueprint spec does not declare capabilities');\n }\n}\n\n/**\n * Validate event spec\n */\nfunction validateEventSpec(code: string, errors: string[], warnings: string[]) {\n if (!code.includes('defineEvent')) {\n errors.push('Missing defineEvent call');\n }\n\n if (!code.match(/name:\\s*['\"][^'\"]+['\"]/)) {\n errors.push('Missing or invalid name field');\n }\n\n if (!code.match(/version:\\s*\\d+/)) {\n errors.push('Missing or invalid version field');\n }\n\n if (!code.includes('payload:')) {\n errors.push('Missing payload field');\n }\n\n // Check for past tense naming convention\n const nameMatch = code.match(/name:\\s*['\"]([^'\"]+)['\"]/);\n if (nameMatch?.[1]) {\n const eventName = nameMatch[1].split('.').pop() ?? '';\n if (!eventName.match(/(ed|created|updated|deleted|completed)$/i)) {\n warnings.push(\n 'Event name should use past tense (e.g., \"created\", \"updated\")'\n );\n }\n }\n}\n\n/**\n * Validate presentation spec\n */\nfunction validatePresentationSpec(\n code: string,\n errors: string[],\n _warnings: string[]\n) {\n if (!code.match(/:\\s*PresentationSpec\\s*=/)) {\n errors.push('Missing PresentationSpec type annotation');\n }\n\n if (!code.includes('meta:')) {\n errors.push('Missing meta section');\n }\n\n if (!code.includes('content:')) {\n errors.push('Missing content section');\n }\n\n if (!code.match(/kind:\\s*['\"](?:web_component|markdown|data)['\"]/)) {\n errors.push('Missing or invalid kind field');\n }\n}\n\nfunction validateWorkflowSpec(\n code: string,\n errors: string[],\n warnings: string[]\n) {\n if (!code.match(/:\\s*WorkflowSpec\\s*=/)) {\n errors.push('Missing WorkflowSpec type annotation');\n }\n\n if (!code.includes('definition:')) {\n errors.push('Missing definition section');\n }\n\n if (!code.includes('steps:')) {\n errors.push('Workflow must declare steps');\n }\n\n if (!code.includes('transitions:')) {\n warnings.push(\n 'No transitions declared; workflow will complete after first step.'\n );\n }\n\n if (!code.match(/title:\\s*['\"][^'\"]+['\"]/)) {\n warnings.push('Missing workflow title');\n }\n\n if (!code.match(/domain:\\s*['\"][^'\"]+['\"]/)) {\n warnings.push('Missing domain field');\n }\n\n if (code.includes('TODO')) {\n warnings.push('Contains TODO items that need completion');\n }\n}\n\nfunction validateMigrationSpec(\n code: string,\n errors: string[],\n warnings: string[]\n) {\n if (!code.match(/:\\s*MigrationSpec\\s*=/)) {\n errors.push('Missing MigrationSpec type annotation');\n }\n\n if (!code.includes('plan:')) {\n errors.push('Missing plan section');\n } else {\n if (!code.includes('up:')) {\n errors.push('Migration must define at least one up step');\n }\n }\n\n if (!code.match(/name:\\s*['\"][^'\"]+['\"]/)) {\n errors.push('Missing or invalid migration name');\n }\n\n if (!code.match(/version:\\s*\\d+/)) {\n errors.push('Missing or invalid migration version');\n }\n\n if (code.includes('TODO')) {\n warnings.push('Contains TODO items that need completion');\n }\n}\n\n/**\n * Validate common fields across all spec types\n */\nfunction validateCommonFields(\n code: string,\n errors: string[],\n warnings: string[]\n) {\n // Check for SchemaModel import\n if (\n code.includes('SchemaModel') &&\n !code.includes(\"from '@lssm/lib.schema'\")\n ) {\n errors.push('Missing import for SchemaModel from @lssm/lib.schema');\n }\n\n // Check for contracts import\n if (!code.includes(\"from '@lssm/lib.contracts'\")) {\n errors.push('Missing import from @lssm/lib.contracts');\n }\n\n // Check owners format\n const ownersMatch = code.match(/owners:\\s*\\[(.*?)\\]/s);\n if (ownersMatch?.[1]) {\n const ownersContent = ownersMatch[1];\n if (!ownersContent.includes('@')) {\n warnings.push('Owners should start with @ (e.g., \"@team\")');\n }\n }\n\n // Check for stability\n if (\n !code.match(/stability:\\s*['\"](?:experimental|beta|stable|deprecated)['\"]/)\n ) {\n warnings.push('Missing or invalid stability field');\n }\n}\n\nfunction validateDataViewSpec(\n code: string,\n errors: string[],\n _warnings: string[]\n) {\n if (!code.match(/:\\s*DataViewSpec\\s*=/)) {\n errors.push('Missing DataViewSpec type annotation');\n }\n if (!code.includes('meta:')) {\n errors.push('Missing meta section');\n }\n if (!code.includes('source:')) {\n errors.push('Missing source section');\n }\n if (!code.includes('view:')) {\n errors.push('Missing view section');\n }\n if (!code.match(/kind:\\s*['\"](list|table|detail|grid)['\"]/)) {\n errors.push('Missing or invalid view.kind (list/table/detail/grid)');\n }\n if (!code.match(/fields:\\s*\\[/)) {\n // Note: _warnings unused in this case, but kept for consistency with other validators\n errors.push('No fields defined for data view');\n }\n}\n"],"mappings":";;;;AAYA,SAAgB,sBACd,MACA,UACkB;CAClB,MAAMA,SAAmB,EAAE;CAC3B,MAAMC,WAAqB,EAAE;AAI7B,KAAI,CADc,6BAA6B,KAAK,KAAK,CAEvD,QAAO,KAAK,yBAAyB;AAIvC,KAAI,SAAS,SAAS,cAAc,CAClC,uBAAsB,MAAM,QAAQ,SAAS;AAI/C,KAAI,SAAS,SAAS,UAAU,CAC9B,mBAAkB,MAAM,QAAQ,SAAS;AAI3C,KAAI,SAAS,SAAS,iBAAiB,CACrC,0BAAyB,MAAM,QAAQ,SAAS;AAGlD,KAAI,SAAS,SAAS,aAAa,CACjC,sBAAqB,MAAM,QAAQ,SAAS;AAG9C,KAAI,SAAS,SAAS,cAAc,CAClC,sBAAqB,MAAM,QAAQ,SAAS;AAG9C,KAAI,SAAS,SAAS,cAAc,CAClC,uBAAsB,MAAM,QAAQ,SAAS;AAG/C,KAAI,SAAS,SAAS,cAAc,CAClC,uBAAsB,MAAM,QAAQ,SAAS;AAG/C,KAAI,SAAS,SAAS,eAAe,CACnC,wBAAuB,MAAM,QAAQ,SAAS;AAGhD,KAAI,SAAS,SAAS,eAAe,CACnC,uBAAsB,MAAM,QAAQ,SAAS;AAI/C,sBAAqB,MAAM,QAAQ,SAAS;AAE5C,QAAO;EACL,OAAO,OAAO,WAAW;EACzB;EACA;EACD;;;;;AAMH,SAAS,sBACP,MACA,QACA,UACA;AAGA,KAAI,CADc,wBAAwB,KAAK,KAAK,CAElD,QAAO,KAAK,4CAA4C;AAI1D,KAAI,CAAC,KAAK,SAAS,QAAQ,CACzB,QAAO,KAAK,uBAAuB;AAGrC,KAAI,CAAC,KAAK,SAAS,MAAM,CACvB,QAAO,KAAK,qBAAqB;AAGnC,KAAI,CAAC,KAAK,SAAS,UAAU,CAC3B,QAAO,KAAK,yBAAyB;AAIvC,KAAI,CAAC,KAAK,MAAM,yBAAyB,CACvC,QAAO,KAAK,gCAAgC;AAI9C,KAAI,CAAC,KAAK,MAAM,iBAAiB,CAC/B,QAAO,KAAK,mCAAmC;CAIjD,MAAM,mBAAmB,qBAAqB,KAAK,KAAK;CACxD,MAAM,iBAAiB,mBAAmB,KAAK,KAAK;CACpD,MAAM,kBAAkB,oCAAoC,KAAK,KAAK;AACtE,KAAI,CAAC,oBAAoB,CAAC,kBAAkB,CAAC,gBAC3C,QAAO,KACL,2EACD;AAIH,KAAI,CAAC,KAAK,SAAS,cAAc,CAC/B,UAAS,KAAK,kCAAkC;AAGlD,KAAI,CAAC,KAAK,SAAS,YAAY,CAC7B,UAAS,KAAK,uBAAuB;AAGvC,KAAI,KAAK,SAAS,OAAO,CACvB,UAAS,KAAK,2CAA2C;;AAI7D,SAAS,sBACP,MACA,QACA,UACA;AACA,KAAI,CAAC,KAAK,MAAM,wBAAwB,CACtC,QAAO,KAAK,wCAAwC;AAGtD,KAAI,CAAC,KAAK,MAAM,wBAAwB,CACtC,QAAO,KAAK,iCAAiC;AAG/C,KAAI,CAAC,KAAK,SAAS,UAAU,CAC3B,QAAO,KAAK,oCAAoC;AAGlD,KAAI,CAAC,KAAK,MAAM,+CAA+C,CAC7D,UAAS,KAAK,2CAA2C;;AAI7D,SAAS,uBACP,MACA,QACA,UACA;AACA,KAAI,CAAC,KAAK,MAAM,yBAAyB,CACvC,QAAO,KAAK,yCAAyC;AAEvD,KAAI,CAAC,KAAK,SAAS,iBAAiB,CAClC,QAAO,KAAK,6CAA6C;AAE3D,KAAI,CAAC,KAAK,SAAS,YAAY,CAC7B,QAAO,KAAK,uCAAuC;AAErD,KAAI,CAAC,KAAK,MAAM,kBAAkB,CAChC,UAAS,KAAK,kDAAkD;;AAIpE,SAAS,sBACP,MACA,QACA,UACA;AACA,KAAI,CAAC,KAAK,MAAM,2BAA2B,CACzC,QAAO,KAAK,2CAA2C;AAEzD,KAAI,CAAC,KAAK,SAAS,QAAQ,CACzB,QAAO,KAAK,oCAAoC;AAElD,KAAI,CAAC,KAAK,SAAS,QAAQ,CACzB,UAAS,KAAK,6CAA6C;AAE7D,KAAI,CAAC,KAAK,SAAS,eAAe,CAChC,UAAS,KAAK,mDAAmD;;;;;AAOrE,SAAS,kBAAkB,MAAc,QAAkB,UAAoB;AAC7E,KAAI,CAAC,KAAK,SAAS,cAAc,CAC/B,QAAO,KAAK,2BAA2B;AAGzC,KAAI,CAAC,KAAK,MAAM,yBAAyB,CACvC,QAAO,KAAK,gCAAgC;AAG9C,KAAI,CAAC,KAAK,MAAM,iBAAiB,CAC/B,QAAO,KAAK,mCAAmC;AAGjD,KAAI,CAAC,KAAK,SAAS,WAAW,CAC5B,QAAO,KAAK,wBAAwB;CAItC,MAAM,YAAY,KAAK,MAAM,2BAA2B;AACxD,KAAI,YAAY,IAEd;MAAI,EADc,UAAU,GAAG,MAAM,IAAI,CAAC,KAAK,IAAI,IACpC,MAAM,2CAA2C,CAC9D,UAAS,KACP,oEACD;;;;;;AAQP,SAAS,yBACP,MACA,QACA,WACA;AACA,KAAI,CAAC,KAAK,MAAM,2BAA2B,CACzC,QAAO,KAAK,2CAA2C;AAGzD,KAAI,CAAC,KAAK,SAAS,QAAQ,CACzB,QAAO,KAAK,uBAAuB;AAGrC,KAAI,CAAC,KAAK,SAAS,WAAW,CAC5B,QAAO,KAAK,0BAA0B;AAGxC,KAAI,CAAC,KAAK,MAAM,kDAAkD,CAChE,QAAO,KAAK,gCAAgC;;AAIhD,SAAS,qBACP,MACA,QACA,UACA;AACA,KAAI,CAAC,KAAK,MAAM,uBAAuB,CACrC,QAAO,KAAK,uCAAuC;AAGrD,KAAI,CAAC,KAAK,SAAS,cAAc,CAC/B,QAAO,KAAK,6BAA6B;AAG3C,KAAI,CAAC,KAAK,SAAS,SAAS,CAC1B,QAAO,KAAK,8BAA8B;AAG5C,KAAI,CAAC,KAAK,SAAS,eAAe,CAChC,UAAS,KACP,oEACD;AAGH,KAAI,CAAC,KAAK,MAAM,0BAA0B,CACxC,UAAS,KAAK,yBAAyB;AAGzC,KAAI,CAAC,KAAK,MAAM,2BAA2B,CACzC,UAAS,KAAK,uBAAuB;AAGvC,KAAI,KAAK,SAAS,OAAO,CACvB,UAAS,KAAK,2CAA2C;;AAI7D,SAAS,sBACP,MACA,QACA,UACA;AACA,KAAI,CAAC,KAAK,MAAM,wBAAwB,CACtC,QAAO,KAAK,wCAAwC;AAGtD,KAAI,CAAC,KAAK,SAAS,QAAQ,CACzB,QAAO,KAAK,uBAAuB;UAE/B,CAAC,KAAK,SAAS,MAAM,CACvB,QAAO,KAAK,6CAA6C;AAI7D,KAAI,CAAC,KAAK,MAAM,yBAAyB,CACvC,QAAO,KAAK,oCAAoC;AAGlD,KAAI,CAAC,KAAK,MAAM,iBAAiB,CAC/B,QAAO,KAAK,uCAAuC;AAGrD,KAAI,KAAK,SAAS,OAAO,CACvB,UAAS,KAAK,2CAA2C;;;;;AAO7D,SAAS,qBACP,MACA,QACA,UACA;AAEA,KACE,KAAK,SAAS,cAAc,IAC5B,CAAC,KAAK,SAAS,0BAA0B,CAEzC,QAAO,KAAK,uDAAuD;AAIrE,KAAI,CAAC,KAAK,SAAS,6BAA6B,CAC9C,QAAO,KAAK,0CAA0C;CAIxD,MAAM,cAAc,KAAK,MAAM,uBAAuB;AACtD,KAAI,cAAc,IAEhB;MAAI,CADkB,YAAY,GACf,SAAS,IAAI,CAC9B,UAAS,KAAK,+CAA6C;;AAK/D,KACE,CAAC,KAAK,MAAM,+DAA+D,CAE3E,UAAS,KAAK,qCAAqC;;AAIvD,SAAS,qBACP,MACA,QACA,WACA;AACA,KAAI,CAAC,KAAK,MAAM,uBAAuB,CACrC,QAAO,KAAK,uCAAuC;AAErD,KAAI,CAAC,KAAK,SAAS,QAAQ,CACzB,QAAO,KAAK,uBAAuB;AAErC,KAAI,CAAC,KAAK,SAAS,UAAU,CAC3B,QAAO,KAAK,yBAAyB;AAEvC,KAAI,CAAC,KAAK,SAAS,QAAQ,CACzB,QAAO,KAAK,uBAAuB;AAErC,KAAI,CAAC,KAAK,MAAM,2CAA2C,CACzD,QAAO,KAAK,wDAAwD;AAEtE,KAAI,CAAC,KAAK,MAAM,eAAe,CAE7B,QAAO,KAAK,kCAAkC"}
|
|
1
|
+
{"version":3,"file":"spec-structure.js","names":["errors: string[]","warnings: string[]"],"sources":["../../../src/analysis/validate/spec-structure.ts"],"sourcesContent":["/**\n * Spec structure validation utilities.\n * Extracted from cli-contracts/src/commands/validate/spec-checker.ts\n */\n\nimport type { ValidationResult } from '../../types/analysis-types';\n\nexport type { ValidationResult };\n\n/**\n * Validate spec structure based on source code and filename.\n */\nexport function validateSpecStructure(\n code: string,\n fileName: string\n): ValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // Check for required exports\n const hasExport = /export\\s+(const|let)\\s+\\w+/.test(code);\n if (!hasExport) {\n errors.push('No exported spec found');\n }\n\n // Validate operation specs\n if (fileName.includes('.contracts.') || fileName.includes('.contract.')) {\n validateOperationSpec(code, errors, warnings);\n }\n\n // Validate event specs\n if (fileName.includes('.event.')) {\n validateEventSpec(code, errors, warnings);\n }\n\n // Validate presentation specs\n if (fileName.includes('.presentation.')) {\n validatePresentationSpec(code, errors, warnings);\n }\n\n if (fileName.includes('.workflow.')) {\n validateWorkflowSpec(code, errors, warnings);\n }\n\n if (fileName.includes('.data-view.')) {\n validateDataViewSpec(code, errors, warnings);\n }\n\n if (fileName.includes('.migration.')) {\n validateMigrationSpec(code, errors, warnings);\n }\n\n if (fileName.includes('.telemetry.')) {\n validateTelemetrySpec(code, errors, warnings);\n }\n\n if (fileName.includes('.experiment.')) {\n validateExperimentSpec(code, errors, warnings);\n }\n\n if (fileName.includes('.app-config.')) {\n validateAppConfigSpec(code, errors, warnings);\n }\n\n // Common validations\n validateCommonFields(code, errors, warnings);\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n}\n\n/**\n * Validate operation spec\n */\nfunction validateOperationSpec(\n code: string,\n errors: string[],\n warnings: string[]\n) {\n // Check for defineCommand or defineQuery\n const hasDefine = /define(Command|Query)/.test(code);\n if (!hasDefine) {\n errors.push('Missing defineCommand or defineQuery call');\n }\n\n // Check for required meta fields\n if (!code.includes('meta:')) {\n errors.push('Missing meta section');\n }\n\n if (!code.includes('io:')) {\n errors.push('Missing io section');\n }\n\n if (!code.includes('policy:')) {\n errors.push('Missing policy section');\n }\n\n // Check for name\n if (!code.match(/name:\\s*['\"][^'\"]+['\"]/)) {\n errors.push('Missing or invalid name field');\n }\n\n // Check for version\n if (!code.match(/version:\\s*\\d+/)) {\n errors.push('Missing or invalid version field');\n }\n\n // Check for kind (defineCommand/defineQuery set it automatically, or explicit kind field)\n const hasDefineCommand = /defineCommand\\s*\\(/.test(code);\n const hasDefineQuery = /defineQuery\\s*\\(/.test(code);\n const hasExplicitKind = /kind:\\s*['\"](?:command|query)['\"]/.test(code);\n if (!hasDefineCommand && !hasDefineQuery && !hasExplicitKind) {\n errors.push(\n 'Missing kind: use defineCommand(), defineQuery(), or explicit kind field'\n );\n }\n\n // Warnings\n if (!code.includes('acceptance:')) {\n warnings.push('No acceptance scenarios defined');\n }\n\n if (!code.includes('examples:')) {\n warnings.push('No examples provided');\n }\n\n if (code.includes('TODO')) {\n warnings.push('Contains TODO items that need completion');\n }\n}\n\nfunction validateTelemetrySpec(\n code: string,\n errors: string[],\n warnings: string[]\n) {\n if (!code.match(/:\\s*TelemetrySpec\\s*=/)) {\n errors.push('Missing TelemetrySpec type annotation');\n }\n\n if (!code.match(/meta:\\s*{[\\s\\S]*name:/)) {\n errors.push('TelemetrySpec.meta is required');\n }\n\n if (!code.includes('events:')) {\n errors.push('TelemetrySpec must declare events');\n }\n\n if (!code.match(/privacy:\\s*'(public|internal|pii|sensitive)'/)) {\n warnings.push('No explicit privacy classification found');\n }\n}\n\nfunction validateExperimentSpec(\n code: string,\n errors: string[],\n warnings: string[]\n) {\n if (!code.match(/:\\s*ExperimentSpec\\s*=/)) {\n errors.push('Missing ExperimentSpec type annotation');\n }\n if (!code.includes('controlVariant')) {\n errors.push('ExperimentSpec must declare controlVariant');\n }\n if (!code.includes('variants:')) {\n errors.push('ExperimentSpec must declare variants');\n }\n if (!code.match(/allocation:\\s*{/)) {\n warnings.push('ExperimentSpec missing allocation configuration');\n }\n}\n\nfunction validateAppConfigSpec(\n code: string,\n errors: string[],\n warnings: string[]\n) {\n if (!code.match(/:\\s*AppBlueprintSpec\\s*=/)) {\n errors.push('Missing AppBlueprintSpec type annotation');\n }\n if (!code.includes('meta:')) {\n errors.push('AppBlueprintSpec must define meta');\n }\n if (!code.includes('appId')) {\n warnings.push('AppBlueprint meta missing appId assignment');\n }\n if (!code.includes('capabilities')) {\n warnings.push('App blueprint spec does not declare capabilities');\n }\n}\n\n/**\n * Validate event spec\n */\nfunction validateEventSpec(code: string, errors: string[], warnings: string[]) {\n if (!code.includes('defineEvent')) {\n errors.push('Missing defineEvent call');\n }\n\n if (!code.match(/name:\\s*['\"][^'\"]+['\"]/)) {\n errors.push('Missing or invalid name field');\n }\n\n if (!code.match(/version:\\s*\\d+/)) {\n errors.push('Missing or invalid version field');\n }\n\n if (!code.includes('payload:')) {\n errors.push('Missing payload field');\n }\n\n // Check for past tense naming convention\n const nameMatch = code.match(/name:\\s*['\"]([^'\"]+)['\"]/);\n if (nameMatch?.[1]) {\n const eventName = nameMatch[1].split('.').pop() ?? '';\n if (!eventName.match(/(ed|created|updated|deleted|completed)$/i)) {\n warnings.push(\n 'Event name should use past tense (e.g., \"created\", \"updated\")'\n );\n }\n }\n}\n\n/**\n * Validate presentation spec\n */\nfunction validatePresentationSpec(\n code: string,\n errors: string[],\n _warnings: string[]\n) {\n if (!code.match(/:\\s*PresentationSpec\\s*=/)) {\n errors.push('Missing PresentationSpec type annotation');\n }\n\n if (!code.includes('meta:')) {\n errors.push('Missing meta section');\n }\n\n if (!code.includes('content:')) {\n errors.push('Missing content section');\n }\n\n if (!code.match(/kind:\\s*['\"](?:web_component|markdown|data)['\"]/)) {\n errors.push('Missing or invalid kind field');\n }\n}\n\nfunction validateWorkflowSpec(\n code: string,\n errors: string[],\n warnings: string[]\n) {\n if (!code.match(/:\\s*WorkflowSpec\\s*=/)) {\n errors.push('Missing WorkflowSpec type annotation');\n }\n\n if (!code.includes('definition:')) {\n errors.push('Missing definition section');\n }\n\n if (!code.includes('steps:')) {\n errors.push('Workflow must declare steps');\n }\n\n if (!code.includes('transitions:')) {\n warnings.push(\n 'No transitions declared; workflow will complete after first step.'\n );\n }\n\n if (!code.match(/title:\\s*['\"][^'\"]+['\"]/)) {\n warnings.push('Missing workflow title');\n }\n\n if (!code.match(/domain:\\s*['\"][^'\"]+['\"]/)) {\n warnings.push('Missing domain field');\n }\n\n if (code.includes('TODO')) {\n warnings.push('Contains TODO items that need completion');\n }\n}\n\nfunction validateMigrationSpec(\n code: string,\n errors: string[],\n warnings: string[]\n) {\n if (!code.match(/:\\s*MigrationSpec\\s*=/)) {\n errors.push('Missing MigrationSpec type annotation');\n }\n\n if (!code.includes('plan:')) {\n errors.push('Missing plan section');\n } else {\n if (!code.includes('up:')) {\n errors.push('Migration must define at least one up step');\n }\n }\n\n if (!code.match(/name:\\s*['\"][^'\"]+['\"]/)) {\n errors.push('Missing or invalid migration name');\n }\n\n if (!code.match(/version:\\s*\\d+/)) {\n errors.push('Missing or invalid migration version');\n }\n\n if (code.includes('TODO')) {\n warnings.push('Contains TODO items that need completion');\n }\n}\n\n/**\n * Validate common fields across all spec types\n */\nfunction validateCommonFields(\n code: string,\n errors: string[],\n warnings: string[]\n) {\n // Check for SchemaModel import\n if (\n code.includes('SchemaModel') &&\n !code.includes(\"from '@lssm/lib.schema'\")\n ) {\n errors.push('Missing import for SchemaModel from @lssm/lib.schema');\n }\n\n // Check for contracts import\n if (!code.includes(\"from '@lssm/lib.contracts'\")) {\n errors.push('Missing import from @lssm/lib.contracts');\n }\n\n // Check owners format\n const ownersMatch = code.match(/owners:\\s*\\[(.*?)\\]/s);\n if (ownersMatch?.[1]) {\n const ownersContent = ownersMatch[1];\n if (!ownersContent.includes('@')) {\n warnings.push('Owners should start with @ (e.g., \"@team\")');\n }\n }\n\n // Check for stability\n if (\n !code.match(/stability:\\s*['\"](?:experimental|beta|stable|deprecated)['\"]/)\n ) {\n warnings.push('Missing or invalid stability field');\n }\n}\n\nfunction validateDataViewSpec(\n code: string,\n errors: string[],\n _warnings: string[]\n) {\n if (!code.match(/:\\s*DataViewSpec\\s*=/)) {\n errors.push('Missing DataViewSpec type annotation');\n }\n if (!code.includes('meta:')) {\n errors.push('Missing meta section');\n }\n if (!code.includes('source:')) {\n errors.push('Missing source section');\n }\n if (!code.includes('view:')) {\n errors.push('Missing view section');\n }\n if (!code.match(/kind:\\s*['\"](list|table|detail|grid)['\"]/)) {\n errors.push('Missing or invalid view.kind (list/table/detail/grid)');\n }\n if (!code.match(/fields:\\s*\\[/)) {\n // Note: _warnings unused in this case, but kept for consistency with other validators\n errors.push('No fields defined for data view');\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lssm/module.contractspec-workspace",
|
|
3
|
-
"version": "0.0.0-canary-
|
|
3
|
+
"version": "0.0.0-canary-20251221114240",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
"test": "bun run"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@lssm/lib.contracts": "0.0.0-canary-
|
|
27
|
-
"@lssm/lib.schema": "0.0.0-canary-
|
|
26
|
+
"@lssm/lib.contracts": "0.0.0-canary-20251221114240",
|
|
27
|
+
"@lssm/lib.schema": "0.0.0-canary-20251221114240",
|
|
28
28
|
"zod": "^4.1.13"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@lssm/tool.tsdown": "0.0.0-canary-
|
|
32
|
-
"@lssm/tool.typescript": "0.0.0-canary-
|
|
31
|
+
"@lssm/tool.tsdown": "0.0.0-canary-20251221114240",
|
|
32
|
+
"@lssm/tool.typescript": "0.0.0-canary-20251221114240",
|
|
33
33
|
"tsdown": "^0.18.1",
|
|
34
34
|
"typescript": "^5.9.3"
|
|
35
35
|
},
|