UNPKG

@autobe/agent

Version:

AI backend server code generator

614 lines 25.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildFileStateFieldConflictMap = exports.detectStateFieldConflicts = exports.buildFilePermissionConflictMap = exports.detectPermissionConflicts = exports.buildFileEnumConflictMap = exports.detectEnumConflicts = exports.buildFileAttributeDuplicateMap = exports.detectAttributeDuplicates = exports.buildFileConflictMap = exports.detectConstraintConflicts = exports.buildConstraintConsistencyReport = void 0; const yaml_1 = __importDefault(require("yaml")); const YAML_CODE_BLOCK_REGEX = /```yaml\n([\s\S]*?)```/g; const buildConstraintConsistencyReport = (props) => { const constraints = new Map(); let totalConstraints = 0; for (const { file, sectionEvents } of props.files) { for (const sectionsForModule of sectionEvents) { for (const sectionEvent of sectionsForModule) { for (const section of sectionEvent.sectionSections) { const pairs = extractConstraints(section.content); for (const { key, value } of pairs) { totalConstraints++; const normalized = normalizeValue(value); if (!constraints.has(key)) { constraints.set(key, { key, values: new Map(), }); } const entry = constraints.get(key); if (!entry.values.has(normalized)) { entry.values.set(normalized, { normalized, display: value.trim(), sources: [], }); } entry.values.get(normalized).sources.push({ file, sectionTitle: section.title, }); } } } } } const conflicts = [...constraints.values()].filter((entry) => entry.values.size > 1); if (conflicts.length === 0) { return [ "No numeric constraint conflicts detected.", `Scanned ${totalConstraints} numeric constraints from YAML spec blocks.`, ].join("\n"); } const lines = [ `Detected ${conflicts.length} numeric constraint conflict(s).`, `Scanned ${totalConstraints} numeric constraints from YAML spec blocks.`, "", "Conflicts:", ]; for (const entry of conflicts) { lines.push(`- ${entry.key}:`); for (const value of entry.values.values()) { const sources = value.sources .map((s) => `${s.file.filename}${s.sectionTitle}`) .slice(0, 6) .join("; "); lines.push(` - ${value.display} (e.g., ${sources})`); } } return lines.join("\n"); }; exports.buildConstraintConsistencyReport = buildConstraintConsistencyReport; /** * Extract numeric constraints from YAML spec blocks. * * Parses YAML code blocks and extracts Entity.attribute constraints that * contain numeric values (e.g., length limits, quantity limits). */ const extractConstraints = (content) => { var _a, _b; if (!content) return []; const results = []; const yamlMatches = content.matchAll(YAML_CODE_BLOCK_REGEX); for (const match of yamlMatches) { const yamlContent = (_a = match[1]) !== null && _a !== void 0 ? _a : ""; try { const parsed = yaml_1.default.parse(yamlContent); if (!parsed || typeof parsed !== "object") continue; // Handle entity attribute YAML blocks if (typeof parsed.entity === "string" && Array.isArray(parsed.attributes)) { for (const attr of parsed.attributes) { if (!attr || typeof attr.name !== "string") continue; const constraintStr = String((_b = attr.constraints) !== null && _b !== void 0 ? _b : ""); if (!hasNumeric(constraintStr)) continue; results.push({ key: `${parsed.entity}.${attr.name}`, value: constraintStr, }); } } // Handle error code YAML blocks (HTTP status codes) if (Array.isArray(parsed.errors)) { for (const err of parsed.errors) { if (!err || typeof err.code !== "string") continue; if (typeof err.http === "number") { results.push({ key: `error.${err.code}.http`, value: String(err.http), }); } } } } catch (_c) { // skip parse errors } } return results; }; const normalizeValue = (value) => value .toLowerCase() .replace(/[–—]/g, "-") .replace(/`/g, "") .replace(/\s+/g, " ") .trim(); const hasNumeric = (value) => /\d/.test(value); /** * Detect numeric constraint conflicts across files as structured data. * * Returns an array of conflicts where the same constraint key has different * normalized values across files. */ const detectConstraintConflicts = (props) => { const constraints = new Map(); for (const { file, sectionEvents } of props.files) { for (const sectionsForModule of sectionEvents) { for (const sectionEvent of sectionsForModule) { for (const section of sectionEvent.sectionSections) { const pairs = extractConstraints(section.content); for (const { key, value } of pairs) { const normalized = normalizeValue(value); if (!constraints.has(key)) { constraints.set(key, { key, values: new Map() }); } const entry = constraints.get(key); if (!entry.values.has(normalized)) { entry.values.set(normalized, { normalized, display: value.trim(), sources: [], }); } entry.values.get(normalized).sources.push({ file, sectionTitle: section.title, }); } } } } } return [...constraints.values()] .filter((entry) => entry.values.size > 1) .map((entry) => ({ key: entry.key, values: [...entry.values.values()].map((v) => ({ display: v.display, files: [...new Set(v.sources.map((s) => s.file.filename))], })), })); }; exports.detectConstraintConflicts = detectConstraintConflicts; /** Build a map from filename → list of conflict feedback strings. */ const buildFileConflictMap = (conflicts) => { const map = new Map(); for (const conflict of conflicts) { const allFiles = new Set(conflict.values.flatMap((v) => v.files)); const feedback = `${conflict.key} has conflicting values: ` + conflict.values .map((v) => `"${v.display}" in [${v.files.join(", ")}]`) .join(" vs "); for (const filename of allFiles) { if (!map.has(filename)) map.set(filename, []); map.get(filename).push(feedback); } } return map; }; exports.buildFileConflictMap = buildFileConflictMap; /** * Detect cross-file attribute duplication from YAML spec blocks. * * Returns attributes that are defined in YAML blocks across multiple files. */ const detectAttributeDuplicates = (props) => { // key → { normalized spec → { display, files } } const attributes = new Map(); const allFilesByKey = new Map(); for (const { file, sectionEvents } of props.files) { for (const sectionsForModule of sectionEvents) { for (const sectionEvent of sectionsForModule) { for (const section of sectionEvent.sectionSections) { const specs = extractAttributeSpecs(section.content); for (const { key, specification } of specs) { if (!allFilesByKey.has(key)) allFilesByKey.set(key, new Set()); allFilesByKey.get(key).add(file.filename); if (!attributes.has(key)) attributes.set(key, new Map()); const specMap = attributes.get(key); const normalized = normalizeValue(specification); if (!specMap.has(normalized)) { specMap.set(normalized, { display: specification.trim(), files: new Set(), }); } specMap.get(normalized).files.add(file.filename); } } } } } return [...allFilesByKey.entries()] .filter(([, files]) => files.size > 1) .map(([key, files]) => { const specMap = attributes.get(key); const hasValueConflict = specMap.size > 1; return Object.assign({ key, files: [...files], hasValueConflict }, (hasValueConflict ? { values: [...specMap.values()].map((v) => ({ specification: v.display, files: [...v.files], })), } : {})); }); }; exports.detectAttributeDuplicates = detectAttributeDuplicates; const buildFileAttributeDuplicateMap = (duplicates) => { const map = new Map(); for (const dup of duplicates) { let feedback; if (dup.hasValueConflict && dup.values) { feedback = `${dup.key} has conflicting specifications across files: ` + dup.values .map((v) => `"${v.specification}" in [${v.files.join(", ")}]`) .join(" vs ") + `. Align to ONE canonical definition.`; } else { feedback = `${dup.key} is fully specified in multiple files: [${dup.files.join(", ")}]. ` + `Only ONE file should contain the full spec.`; } for (const filename of dup.files) { if (!map.has(filename)) map.set(filename, []); map.get(filename).push(feedback); } } return map; }; exports.buildFileAttributeDuplicateMap = buildFileAttributeDuplicateMap; /** * Detect enum value conflicts from YAML spec blocks. * * Scans YAML attribute blocks for enum-like constraints and detects when * different files define different enum value sets for the same attribute. */ const detectEnumConflicts = (props) => { const enums = new Map(); for (const { file, sectionEvents } of props.files) { for (const sectionsForModule of sectionEvents) { for (const sectionEvent of sectionsForModule) { for (const section of sectionEvent.sectionSections) { const specs = extractEnumSpecs(section.content); for (const { key, enumSet, display } of specs) { if (!enums.has(key)) enums.set(key, new Map()); const entry = enums.get(key); if (!entry.has(enumSet)) { entry.set(enumSet, { enumSet, display, files: new Set() }); } entry.get(enumSet).files.add(file.filename); } } } } } return [...enums.entries()] .filter(([, values]) => values.size > 1) .map(([key, values]) => ({ key, values: [...values.values()].map((v) => ({ enumSet: v.enumSet, display: v.display, files: [...v.files], })), })); }; exports.detectEnumConflicts = detectEnumConflicts; const buildFileEnumConflictMap = (conflicts) => { const map = new Map(); for (const conflict of conflicts) { const allFiles = new Set(conflict.values.flatMap((v) => v.files)); const feedback = `${conflict.key} has conflicting enum values: ` + conflict.values .map((v) => `enum(${v.enumSet}) in [${v.files.join(", ")}]`) .join(" vs "); for (const filename of allFiles) { if (!map.has(filename)) map.set(filename, []); map.get(filename).push(feedback); } } return map; }; exports.buildFileEnumConflictMap = buildFileEnumConflictMap; /** * Detect permission rule conflicts from YAML spec blocks. * * A conflict occurs when one YAML block allows an action but another doesn't * include it for the same actor+resource. */ const detectPermissionConflicts = (props) => { // actor:resource → action → Set<filename> const ruleMap = new Map(); for (const { file, sectionEvents } of props.files) { for (const sectionsForModule of sectionEvents) { for (const sectionEvent of sectionsForModule) { for (const section of sectionEvent.sectionSections) { const rules = extractPermissionRulesFromYaml(section.content); for (const { actor, resource, actions } of rules) { const key = `${actor.toLowerCase()}:${resource}`; if (!ruleMap.has(key)) ruleMap.set(key, new Map()); const actionMap = ruleMap.get(key); for (const action of actions) { const normAction = action.toLowerCase(); if (!actionMap.has(normAction)) actionMap.set(normAction, new Set()); actionMap.get(normAction).add(file.filename); } } } } } } // Permission conflicts are rare in YAML-based approach since // 01-actors-and-auth is the canonical source. Return empty for now. return []; }; exports.detectPermissionConflicts = detectPermissionConflicts; const buildFilePermissionConflictMap = (conflicts) => { const map = new Map(); for (const conflict of conflicts) { const allFiles = new Set(conflict.rules.flatMap((r) => r.files)); const feedback = `Permission conflict for "${conflict.actorOperation}": ` + conflict.rules .map((r) => `"${r.condition}" in [${r.files.join(", ")}]`) .join(" vs "); for (const filename of allFiles) { if (!map.has(filename)) map.set(filename, []); map.get(filename).push(feedback); } } return map; }; exports.buildFilePermissionConflictMap = buildFilePermissionConflictMap; /** * Detect state field conflicts from YAML spec blocks. * * Known contradiction patterns: * * 1. Same entity has both `deletedAt` (datetime) and `isDeleted` (boolean) * 2. Same entity has `status` (enum) and semantically equivalent `is*` booleans */ const detectStateFieldConflicts = (props) => { var _a, _b; // entity → { fieldName → { specification, files } } const entityFields = new Map(); for (const { file, sectionEvents } of props.files) { for (const sectionsForModule of sectionEvents) { for (const sectionEvent of sectionsForModule) { for (const section of sectionEvent.sectionSections) { const specs = extractAttributeSpecs(section.content); for (const { key, specification } of specs) { const dotIndex = key.indexOf("."); if (dotIndex < 0) continue; const entity = key.slice(0, dotIndex); const field = key.slice(dotIndex + 1).toLowerCase(); if (!entityFields.has(entity)) entityFields.set(entity, new Map()); const fields = entityFields.get(entity); if (!fields.has(field)) fields.set(field, { specification, files: new Set() }); fields.get(field).files.add(file.filename); } } } } } const conflicts = []; for (const [entity, fields] of entityFields) { const fieldNames = [...fields.keys()]; // Pattern 1: deletedAt + isDeleted on same entity const hasDeletedAt = fieldNames.some((f) => f === "deletedat" || f === "deleted_at"); const hasIsDeleted = fieldNames.some((f) => f === "isdeleted" || f === "is_deleted"); if (hasDeletedAt && hasIsDeleted) { const deletedAtField = (_a = fields.get("deletedat")) !== null && _a !== void 0 ? _a : fields.get("deleted_at"); const isDeletedField = (_b = fields.get("isdeleted")) !== null && _b !== void 0 ? _b : fields.get("is_deleted"); if (deletedAtField && isDeletedField) { conflicts.push({ entity, conflictType: "deletedAt vs isDeleted", fields: [ { fieldName: "deletedAt", specification: deletedAtField.specification, files: [...deletedAtField.files], }, { fieldName: "isDeleted", specification: isDeletedField.specification, files: [...isDeletedField.files], }, ], }); } } // Pattern 2: status (enum) + is* booleans const statusField = fields.get("status"); if (statusField && /enum/i.test(statusField.specification)) { const isBooleans = fieldNames.filter((f) => f.startsWith("is") && /boolean/i.test(fields.get(f).specification)); for (const boolField of isBooleans) { const concept = boolField.slice(2).toLowerCase(); if (statusField.specification.toLowerCase().includes(concept)) { const boolEntry = fields.get(boolField); conflicts.push({ entity, conflictType: `status enum includes "${concept}" but separate is${concept.charAt(0).toUpperCase() + concept.slice(1)} boolean also exists`, fields: [ { fieldName: "status", specification: statusField.specification, files: [...statusField.files], }, { fieldName: boolField, specification: boolEntry.specification, files: [...boolEntry.files], }, ], }); } } } } return conflicts; }; exports.detectStateFieldConflicts = detectStateFieldConflicts; const buildFileStateFieldConflictMap = (conflicts) => { const map = new Map(); for (const conflict of conflicts) { const allFiles = new Set(conflict.fields.flatMap((f) => f.files)); const feedback = `State field conflict for "${conflict.entity}": ${conflict.conflictType}. ` + conflict.fields .map((f) => `"${f.fieldName}: ${f.specification}" in [${f.files.join(", ")}]`) .join(" vs ") + `. Use ONE canonical approach.`; for (const filename of allFiles) { if (!map.has(filename)) map.set(filename, []); map.get(filename).push(feedback); } } return map; }; exports.buildFileStateFieldConflictMap = buildFileStateFieldConflictMap; // ─── YAML-based Attribute Specs Extraction (shared) ─── const ENUM_PATTERN = /enum\s*[\(\[\{]([^)\]\}]+)[\)\]\}]/i; /** * Extract attribute specs from YAML code blocks. * * Parses YAML blocks with `entity` + `attributes` structure and returns * Entity.attribute → constraints pairs. */ const extractAttributeSpecs = (content) => { var _a; if (!content) return []; const results = []; const yamlMatches = content.matchAll(YAML_CODE_BLOCK_REGEX); for (const match of yamlMatches) { const yamlContent = (_a = match[1]) !== null && _a !== void 0 ? _a : ""; try { const parsed = yaml_1.default.parse(yamlContent); if (!parsed || typeof parsed !== "object" || typeof parsed.entity !== "string" || !Array.isArray(parsed.attributes)) continue; for (const attr of parsed.attributes) { if (!attr || typeof attr.name !== "string") continue; const spec = [ attr.type ? String(attr.type) : "", attr.constraints ? String(attr.constraints) : "", ] .filter(Boolean) .join(", "); if (!spec) continue; results.push({ key: `${parsed.entity}.${attr.name}`, specification: spec, }); } } catch (_b) { // skip parse errors } } return results; }; /** Extract enum specs from YAML attribute blocks. */ const extractEnumSpecs = (content) => { var _a, _b, _c; if (!content) return []; const results = []; const yamlMatches = content.matchAll(YAML_CODE_BLOCK_REGEX); for (const match of yamlMatches) { const yamlContent = (_a = match[1]) !== null && _a !== void 0 ? _a : ""; try { const parsed = yaml_1.default.parse(yamlContent); if (!parsed || typeof parsed !== "object" || typeof parsed.entity !== "string" || !Array.isArray(parsed.attributes)) continue; for (const attr of parsed.attributes) { if (!attr || typeof attr.name !== "string") continue; const typeStr = String((_b = attr.type) !== null && _b !== void 0 ? _b : ""); const constraintStr = String((_c = attr.constraints) !== null && _c !== void 0 ? _c : ""); const combined = `${typeStr} ${constraintStr}`; const enumMatch = combined.match(ENUM_PATTERN); if (!enumMatch) continue; const rawEnumValues = enumMatch[1]; const enumSet = [ ...new Set(rawEnumValues .split(/[|,]/) .map((v) => v.trim().toLowerCase()) .filter((v) => v.length > 0)), ] .sort() .join("|"); results.push({ key: `${parsed.entity}.${attr.name}`, enumSet, display: combined.trim(), }); } } catch (_d) { // skip parse errors } } return results; }; /** Extract permission rules from YAML spec blocks. */ const extractPermissionRulesFromYaml = (content) => { var _a; if (!content) return []; const results = []; const yamlMatches = content.matchAll(YAML_CODE_BLOCK_REGEX); for (const match of yamlMatches) { const yamlContent = (_a = match[1]) !== null && _a !== void 0 ? _a : ""; try { const parsed = yaml_1.default.parse(yamlContent); if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.permissions)) continue; for (const perm of parsed.permissions) { if (!perm || typeof perm.actor !== "string" || typeof perm.resource !== "string" || !Array.isArray(perm.actions)) continue; results.push({ actor: perm.actor, resource: perm.resource, actions: perm.actions.map(String), }); } } catch (_b) { // skip parse errors } } return results; }; //# sourceMappingURL=buildConstraintConsistencyReport.js.map