UNPKG

paqad-ai

Version:

Spec-driven development framework — AI agents that think before they type

390 lines (387 loc) • 11.7 kB
import { createRequire as __paqadCreateRequire } from 'module'; const require = __paqadCreateRequire(import.meta.url); import { getProfileDomain } from "./chunk-AK3HYELM.js"; import { init_esm_shims } from "./chunk-IDFRWBMN.js"; // src/core/stack-profile.ts init_esm_shims(); var FRAMEWORK_PACKAGE_MAP = { "laravel/framework": "laravel", flutter: "flutter", react: "react", vue: "vue", nuxt: "vue", next: "nextjs", flask: "flask", "@nestjs/core": "nestjs", "@nestjs/common": "nestjs", "org.springframework.boot:spring-boot": "spring-boot", "org.springframework.boot:spring-boot-starter": "spring-boot", "org.springframework.boot:spring-boot-starter-web": "spring-boot", django: "django", Django: "django", fastapi: "fastapi", FastAPI: "fastapi", rails: "rails" }; var TRAIT_PACKAGE_MAP = { "inertiajs/inertia-laravel": "inertia", "laravel/boost": "boost", "laravel/sail": "sail", "phpunit/phpunit": "phpunit", tailwindcss: "tailwind", next: "next", remix: "remix", "@remix-run/react": "remix", gatsby: "gatsby", nuxt: "nuxt", quasar: "quasar", "@quasar/app-vite": "quasar", vite: "vite-spa", "@vitejs/plugin-react": "vite-spa", "@vitejs/plugin-vue": "vite-spa", "@prisma/client": "prisma", prisma: "prisma", "@trpc/server": "trpc", "@trpc/client": "trpc", "next-auth": "next-auth", "@auth/core": "next-auth", sqlalchemy: "sqlalchemy", celery: "celery", gunicorn: "gunicorn", flask_login: "flask-login", "flask-restx": "flask-restx", "flask-restful": "flask-restx", typeorm: "typeorm", "@nestjs/swagger": "swagger", "@nestjs/graphql": "graphql", "@nestjs/microservices": "microservices", "@nestjs/passport": "passport", "@nestjs/platform-fastify": "fastify", "androidx.room:room-runtime": "room", "androidx.hilt:hilt-navigation-compose": "hilt", "androidx.navigation:navigation-compose": "navigation", "org.jetbrains.kotlinx:kotlinx-coroutines-core": "coroutines", "com.squareup.retrofit2:retrofit": "retrofit", "androidx.datastore:datastore": "datastore", "androidx.compose.ui:ui": "jetpack-compose", vitest: "vitest", "@playwright/test": "playwright", "pestphp/pest": "pest", jest: "jest", pinia: "pinia", vuex: "vuex" }; var VERSIONED_PACKAGES = /* @__PURE__ */ new Set([ "laravel/framework", "react", "vue", "next", "flask", "@nestjs/core", "nuxt", "tailwindcss", "laravel/boost", "flutter" ]); function buildDetectedStackProfile(input) { const frameworks = /* @__PURE__ */ new Set(); const traits = /* @__PURE__ */ new Set([ ...input.fallbackCapabilities ?? [], ...input.detectedTraits ?? [] ]); for (const pkg of input.packages) { const framework = FRAMEWORK_PACKAGE_MAP[pkg.name]; if (framework) { frameworks.add(framework); } const trait = TRAIT_PACKAGE_MAP[pkg.name]; if (trait) { traits.add(trait); } } if (frameworks.size === 0 && input.fallbackStack) { frameworks.add(input.fallbackStack); } if (input.fallbackStack === "laravel") { frameworks.add("laravel"); } if (frameworks.has("nextjs")) { frameworks.delete("react"); } if (frameworks.has("nestjs")) { frameworks.delete("express"); } const versionBands = Array.from( input.packages.filter((pkg) => VERSIONED_PACKAGES.has(pkg.name)).sort(compareVersionBandPackages).reduce( (bands, pkg) => bands.has(pkg.name) ? bands : bands.set(pkg.name, { name: `${pkg.name}:${majorBand(pkg.locked_version || pkg.version_constraint)}`, package_name: pkg.name, range: majorBand(pkg.locked_version || pkg.version_constraint), locked_version: pkg.locked_version, source: pkg.locked_version !== "" && pkg.locked_version !== pkg.version_constraint ? "lockfile" : "manifest" }), /* @__PURE__ */ new Map() ).values() ).sort((left, right) => left.package_name.localeCompare(right.package_name)); return { frameworks: Array.from(frameworks).sort(compareFrameworks), traits: Array.from(traits).sort(), toolchains: input.toolchains, version_bands: versionBands, sources: input.sources }; } function getPrimaryStack(profile) { const frameworks = Array.isArray(profile?.stack_profile?.frameworks) ? profile.stack_profile.frameworks : []; const legacyStack = profile?.routing ? profile.routing.stack : void 0; if (frameworks.length > 0) return frameworks[0]; if (legacyStack) return legacyStack; if (profile && getProfileDomain( profile ) === "content") { return "short-video"; } return "laravel"; } function getLegacyCapabilities(profile) { const legacy = profile?.routing ? profile.routing.capabilities : void 0; if (Array.isArray(legacy)) { return legacy; } return (profile?.stack_profile?.traits ?? []).filter(isCapability); } function directPackageNames(packages) { return new Map((packages ?? []).map((pkg) => [`${pkg.ecosystem}:${pkg.name}`, pkg.name])); } function compareStackProfiles(previous, current, packages) { if (previous === null) { return { generated_at: (/* @__PURE__ */ new Date()).toISOString(), status: "no-drift", previous_profile: null, current_profile: current, material_changes: [], packages_added: [], packages_removed: [], newly_active_rule_bands: [], newly_inactive_rule_bands: [], review_targets: [] }; } const changes = []; const previousFrameworks = new Set(previous.frameworks ?? []); const currentFrameworks = new Set(current.frameworks); const previousTraits = new Set(previous.traits ?? []); const currentTraits = new Set(current.traits); const previousBands = new Map( (previous.version_bands ?? []).map((band) => [band.package_name, band]) ); const currentBands = new Map(current.version_bands.map((band) => [band.package_name, band])); const previousToolchains = JSON.stringify(previous.toolchains ?? []); const currentToolchains = JSON.stringify(current.toolchains); for (const framework of currentFrameworks) { if (!previousFrameworks.has(framework)) { changes.push({ type: "framework-added", key: framework, after: framework }); } } for (const framework of previousFrameworks) { if (!currentFrameworks.has(framework)) { changes.push({ type: "framework-removed", key: framework, before: framework }); } } for (const trait of currentTraits) { if (!previousTraits.has(trait)) { changes.push({ type: "trait-added", key: trait, after: trait }); } } for (const trait of previousTraits) { if (!currentTraits.has(trait)) { changes.push({ type: "trait-removed", key: trait, before: trait }); } } for (const [name, band] of currentBands) { const previousBand = previousBands.get(name); if (previousBand && previousBand.range !== band.range) { changes.push({ type: "version-band-changed", key: name, before: previousBand.range, after: band.range }); } } if (previousToolchains !== currentToolchains) { changes.push({ type: "toolchain-changed", key: "toolchains", before: previousToolchains, after: currentToolchains }); } const packagesAdded = []; const packagesRemoved = []; if (packages !== void 0 && packages.previous !== void 0) { const before = directPackageNames(packages.previous); const after = directPackageNames(packages.current); for (const [key, name] of after) { if (!before.has(key)) { packagesAdded.push(name); changes.push({ type: "package-added", key: name, after: name }); } } for (const [key, name] of before) { if (!after.has(key)) { packagesRemoved.push(name); changes.push({ type: "package-removed", key: name, before: name }); } } } const reviewTargets = deriveReviewTargets(changes); return { generated_at: (/* @__PURE__ */ new Date()).toISOString(), status: changes.length === 0 ? "no-drift" : "drift-detected", previous_profile: previous, current_profile: current, material_changes: changes, packages_added: packagesAdded.sort(), packages_removed: packagesRemoved.sort(), newly_active_rule_bands: current.version_bands.filter((band) => !previousBands.has(band.package_name)).map((band) => band.name), newly_inactive_rule_bands: Array.from(previousBands.values()).filter((band) => !currentBands.has(band.package_name)).map((band) => band.name), review_targets: reviewTargets }; } function summarizeStack(profile) { const frameworks = profile.frameworks.join(", ") || "none"; const traits = profile.traits.join(", ") || "none"; return `frameworks: ${frameworks}; traits: ${traits}`; } function deriveReviewTargets(changes) { const targets = /* @__PURE__ */ new Set(); for (const change of changes) { if (change.type === "framework-added" || change.type === "framework-removed") { targets.add("docs/instructions/architecture/**"); targets.add("docs/modules/*/ui/**"); targets.add("docs/modules/*/api/**"); targets.add("docs/instructions/stack/**"); } if (change.type === "trait-added" || change.type === "trait-removed") { targets.add("docs/instructions/stack/**"); targets.add("docs/modules/*/ui/**"); targets.add("docs/instructions/tools/**"); } if (change.type === "package-added" || change.type === "package-removed") { targets.add("docs/instructions/stack/dependencies.md"); targets.add("docs/instructions/rules/**"); } if (change.type === "version-band-changed") { targets.add("docs/instructions/stack/version-rules.md"); targets.add("docs/instructions/architecture/**"); } } return Array.from(targets).sort(); } function compareFrameworks(left, right) { return frameworkRank(left) - frameworkRank(right) || left.localeCompare(right); } function compareVersionBandPackages(left, right) { return rootDepth(left.root) - rootDepth(right.root) || left.name.localeCompare(right.name); } function rootDepth(root) { if (!root || root === ".") { return 0; } return root.split("/").filter(Boolean).length; } function frameworkRank(name) { switch (name) { case "laravel": return 0; case "flutter": return 1; case "nextjs": return 2; case "react": return 3; case "vue": return 4; case "nestjs": return 5; case "dotnet": return 6; case "flask": return 7; case "kotlin-android": return 8; case "short-video": return 9; default: return 10; } } function isCapability(value) { return [ "inertia", "vue", "react", "tailwind", "boost", "pest", "phpunit", "docker", "compose", "sail", "next", "remix", "vite-spa", "gatsby", "nuxt", "quasar", "blazor", "ef-core", "minimal-api", "mvc", "razor-pages", "signalr", "azure", "identity", "app-router", "pages-router", "prisma", "trpc", "next-auth", "sqlalchemy", "celery", "blueprints", "flask-login", "flask-restx", "gunicorn", "typeorm", "graphql", "microservices", "swagger", "passport", "fastify", "jetpack-compose", "room", "hilt", "retrofit", "coroutines", "navigation", "datastore" ].includes(value); } function majorBand(value) { const match = value.match(/(\d+)/); return match?.[1] ? `^${match[1]}` : value; } export { FRAMEWORK_PACKAGE_MAP, buildDetectedStackProfile, getPrimaryStack, getLegacyCapabilities, compareStackProfiles, summarizeStack };