UNPKG

paqad-ai

Version:

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

382 lines (378 loc) • 10.1 kB
import { createRequire as __paqadCreateRequire } from 'module'; const require = __paqadCreateRequire(import.meta.url); import { applyFrameworkConfigToProfile, readTextIfExistsSync, require_dist, resolveFrameworkConfig, stripFrameworkConfigFromProfile } from "./chunk-ACJQW7SB.js"; import { renameWithRetrySync } from "./chunk-Z3NGWHFX.js"; import { PATHS } from "./chunk-IHMBVS6H.js"; import { __toESM, init_esm_shims } from "./chunk-IDFRWBMN.js"; // src/core/types/domain.ts init_esm_shims(); var DOMAINS = ["coding", "content"]; var STACKS = [ "laravel", "flutter", "react", "vue", "django", "fastapi", "rails", "spring-boot", "express", "angular", "svelte", "astro", "go-web", "rust-web", "dotnet", "nextjs", "flask", "nestjs", "kotlin-android", "node-cli", "node-library", "node-service", "short-video" ]; var CAPABILITIES = [ "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" ]; var ACTIVE_CAPABILITIES = ["content", "coding", "planning", "security"]; var DOMAIN_STACK_MAP = { coding: [ "laravel", "flutter", "react", "vue", "django", "fastapi", "rails", "spring-boot", "express", "angular", "svelte", "astro", "go-web", "rust-web", "dotnet", "nextjs", "flask", "nestjs", "kotlin-android" ], content: ["short-video"] }; var STACK_CAPABILITIES_MAP = { laravel: [ "inertia", "vue", "react", "tailwind", "boost", "pest", "phpunit", "docker", "compose", "sail" ], flutter: ["docker", "compose"], react: ["next", "remix", "vite-spa", "gatsby", "tailwind", "docker", "compose"], vue: ["nuxt", "vite-spa", "quasar", "tailwind", "docker", "compose"], django: ["docker", "compose"], fastapi: ["docker", "compose"], rails: ["docker", "compose"], "spring-boot": ["docker", "compose"], express: ["docker", "compose"], angular: ["docker", "compose"], svelte: ["docker", "compose"], astro: ["docker", "compose"], "go-web": ["docker", "compose"], "rust-web": ["docker", "compose"], dotnet: [ "blazor", "ef-core", "minimal-api", "mvc", "razor-pages", "signalr", "identity", "azure", "docker" ], nextjs: ["app-router", "pages-router", "tailwind", "prisma", "trpc", "next-auth", "docker"], flask: ["sqlalchemy", "celery", "blueprints", "flask-login", "flask-restx", "gunicorn", "docker"], nestjs: [ "prisma", "typeorm", "graphql", "microservices", "swagger", "passport", "fastify", "docker" ], "kotlin-android": [ "jetpack-compose", "room", "hilt", "retrofit", "coroutines", "navigation", "datastore", "docker" ], "node-cli": ["docker", "compose"], "node-library": ["docker", "compose"], "node-service": ["docker", "compose"], "short-video": ["docker", "compose"] }; // src/core/project-profile.ts init_esm_shims(); var import_yaml = __toESM(require_dist(), 1); import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; import { dirname, join } from "path"; import { isDeepStrictEqual } from "util"; function readProjectProfile(projectRoot, options = {}) { const path = join(projectRoot, PATHS.PROJECT_PROFILE); if (!existsSync(path)) { return null; } try { const parsed = import_yaml.default.parse(readFileSync(path, "utf8")); const migration = migrateProjectProfile(parsed); if (migration.migrated && options.persistMigration !== false) { writeProjectProfile(projectRoot, migration.profile, migration.audit_message); } return applyFrameworkConfigToProfile(migration.profile, resolveFrameworkConfig(projectRoot)); } catch { return null; } } function writeProjectProfile(projectRoot, profile, auditMessage) { return syncProjectProfile(projectRoot, profile, auditMessage).path; } function syncProjectProfile(projectRoot, profile, auditMessage) { const migration = migrateProjectProfile(profile); const path = join(projectRoot, PATHS.PROJECT_PROFILE); const desired = toPlainData(stripFrameworkConfigFromProfile(migration.profile)); const current = readProfileText(path); const next = renderProfile(current, desired); if (next === null || next === current) { return { path, written: false }; } mkdirSync(dirname(path), { recursive: true }); const staging = `${path}.${process.pid}.tmp`; writeFileSync(staging, next); renameWithRetrySync(staging, path); const audit = auditMessage ?? migration.audit_message; if (audit) { appendAuditLog(projectRoot, audit); } return { path, written: true }; } function readProfileText(path) { try { return readFileSync(path, "utf8"); } catch (error) { if (error.code === "ENOENT") return null; throw error; } } function toPlainData(value) { return JSON.parse(JSON.stringify(value)); } function renderProfile(current, desired) { const fresh = () => import_yaml.default.stringify(desired, { lineWidth: 0 }); if (current === null) return fresh(); const doc = import_yaml.default.parseDocument(current); if (doc.errors.length > 0 || !import_yaml.default.isMap(doc.contents)) return fresh(); if (isDeepStrictEqual(doc.toJS(), desired)) return null; syncMap(doc, doc.contents, desired); return doc.toString({ lineWidth: 0 }); } function syncMap(doc, map, desired) { for (const pair of [...map.items]) { const key = import_yaml.default.isScalar(pair.key) ? pair.key.value : pair.key; if (typeof key !== "string" || !Object.hasOwn(desired, key)) { map.delete(pair.key); } } for (const [key, value] of Object.entries(desired)) { const node = map.get(key, true); if (import_yaml.default.isNode(node) && isDeepStrictEqual(node.toJS(doc), value)) continue; if (import_yaml.default.isMap(node) && isPlainObject(value)) { syncMap(doc, node, value); continue; } map.set(key, doc.createNode(value)); } } function isPlainObject(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } function migrateProjectProfile(input) { const legacy = input; const activeCapabilities = normalizeActiveCapabilities( legacy.active_capabilities, legacy.routing, legacy.stack_profile ); const stackProfile = normalizeStackProfile(legacy.stack_profile, activeCapabilities); const { active_capabilities: legacyActiveCapabilities, routing: legacyRouting, stack_profile: legacyStackProfile, ...rest } = legacy; void legacyActiveCapabilities; void legacyRouting; void legacyStackProfile; const profile = { ...rest, active_capabilities: activeCapabilities, stack_profile: stackProfile }; const migrated = !Array.isArray(legacy.active_capabilities) || legacy.routing !== void 0 || legacy.stack_profile?.domain !== void 0 || JSON.stringify(legacy.active_capabilities ?? []) !== JSON.stringify(activeCapabilities) || JSON.stringify(legacy.stack_profile ?? null) !== JSON.stringify(stackProfile ?? null); const audit_message = migrated ? `[${(/* @__PURE__ */ new Date()).toISOString()}] Migrated project profile to canonical capabilities model` : void 0; return { profile, migrated, audit_message }; } function getProfileDomain(profile) { if (profile.active_capabilities?.includes("coding")) { return "coding"; } if (profile.routing?.domain === "coding") { return "coding"; } if ((profile.stack_profile?.frameworks ?? []).length > 0) { return "coding"; } return "content"; } function normalizeActiveCapabilities(raw, routing, stackProfile) { const values = /* @__PURE__ */ new Set(); values.add("content"); if (Array.isArray(raw)) { for (const value of raw) { if (typeof value === "string" && ACTIVE_CAPABILITIES.includes(value)) { values.add(value); } } } const frameworks = (stackProfile?.frameworks ?? []).filter( (framework) => framework !== "short-video" ); const hasCodingSignals = frameworks.length > 0 || routing?.domain === "coding"; if (hasCodingSignals) { values.add("coding"); } if (values.has("coding")) { values.add("security"); } else { values.delete("security"); } return Array.from(values).sort(compareActiveCapabilities); } function normalizeStackProfile(stackProfile, activeCapabilities) { if (!activeCapabilities.includes("coding") || !stackProfile) { return void 0; } const frameworks = (stackProfile.frameworks ?? []).filter( (framework) => framework !== "short-video" ); if (frameworks.length === 0) { return void 0; } return { frameworks: frameworks.sort(), traits: [...stackProfile.traits ?? []].sort(), toolchains: [...stackProfile.toolchains ?? []], version_bands: [...stackProfile.version_bands ?? []], sources: [...stackProfile.sources ?? []] }; } function appendAuditLog(projectRoot, entry) { const path = join(projectRoot, ".paqad", "audit.log"); mkdirSync(dirname(path), { recursive: true }); const current = readTextIfExistsSync(path) ?? ""; const normalized = current.endsWith("\n") || current.length === 0 ? current : `${current} `; writeFileSync(path, `${normalized}${entry} `); } function compareActiveCapabilities(left, right) { return ACTIVE_CAPABILITIES.indexOf(left) - ACTIVE_CAPABILITIES.indexOf(right); } export { DOMAINS, STACKS, CAPABILITIES, ACTIVE_CAPABILITIES, DOMAIN_STACK_MAP, STACK_CAPABILITIES_MAP, readProjectProfile, writeProjectProfile, syncProjectProfile, migrateProjectProfile, getProfileDomain };