UNPKG

paqad-ai

Version:

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

282 lines (279 loc) • 10 kB
import { createRequire as __paqadCreateRequire } from 'module'; const require = __paqadCreateRequire(import.meta.url); import { writeProjectProfile } from "./chunk-AK3HYELM.js"; import { toPosixPath } from "./chunk-LYNDVGNE.js"; import { FrameworkError } from "./chunk-D6VEUSB6.js"; import { PATHS } from "./chunk-IHMBVS6H.js"; import { init_esm_shims } from "./chunk-IDFRWBMN.js"; // src/onboarding/manifest-writer.ts init_esm_shims(); import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; import { homedir } from "os"; import { dirname, join, relative, sep } from "path"; function writeProjectProfile2(projectRoot, profile) { return writeProjectProfile(projectRoot, profile); } function writeDetectionReport(projectRoot, report) { const path = join(projectRoot, PATHS.DETECTION_REPORT); mkdirSync(dirname(path), { recursive: true }); const sanitized = sanitizeDetectionReport(projectRoot, report); writeJsonPreservingTimestamp(path, sanitized, "timestamp"); return path; } function writeFrameworkMetadata(projectRoot, version) { mkdirSync(dirname(join(projectRoot, PATHS.FRAMEWORK_VERSION)), { recursive: true }); writeFrameworkVersionPreservingTimestamp( join(projectRoot, PATHS.FRAMEWORK_VERSION), version, (/* @__PURE__ */ new Date()).toISOString() ); writeFileSync(join(projectRoot, PATHS.FRAMEWORK_PATH), `${resolveFrameworkInstallReference()} `); } function readExistingOnboardingManifest(projectRoot) { const path = join(projectRoot, PATHS.ONBOARDING_MANIFEST); if (!existsSync(path)) { return null; } try { return JSON.parse(readFileSync(path, "utf8")); } catch (error) { throw new FrameworkError( "Project registry corrupted \u2014 the onboarding manifest is not valid JSON. Contact support.", { code: "REGISTRY_CORRUPTED", cause: error, details: { manifest_path: PATHS.ONBOARDING_MANIFEST } } ); } } function writeOnboardingManifest(projectRoot, manifest) { const path = join(projectRoot, PATHS.ONBOARDING_MANIFEST); mkdirSync(dirname(path), { recursive: true }); const previous = readPreviousJson(path); const removedAgents = previous?.removed_stage_agents; const sanitized = sanitizeOnboardingManifest(projectRoot, { ...manifest, ...manifest.removed_stage_agents === void 0 && Array.isArray(removedAgents) ? { removed_stage_agents: removedAgents } : {} }); writeJsonPreservingTimestamp(path, sanitized, ["generated_at", "detected.timestamp"]); return path; } function writeJsonPreservingTimestamp(path, value, timestampFields) { const fields = typeof timestampFields === "string" ? [timestampFields] : timestampFields; const previous = readPreviousJson(path); if (previous !== null) { const candidate = JSON.parse(JSON.stringify(value)); for (const field of fields) { const previousTimestamp = readNestedString(previous, field); if (previousTimestamp !== null) { writeNestedString(candidate, field, previousTimestamp); } } const candidateJson = `${JSON.stringify(candidate, null, 2)} `; const existing = readFileSync(path, "utf8"); if (candidateJson === existing) { return; } } writeFileSync(path, `${JSON.stringify(value, null, 2)} `); } function writeFrameworkVersionPreservingTimestamp(path, version, now) { const previous = readPreviousFrameworkVersion(path); const timestamp = previous && previous.version === version ? previous.updatedAt : now; writeFileSync(path, `version=${version} updated_at=${timestamp} `); } function readPreviousJson(path) { if (!existsSync(path)) { return null; } try { return JSON.parse(readFileSync(path, "utf8")); } catch { return null; } } function readNestedString(value, path) { let current = value; for (const segment of path.split(".")) { if (!isSafePropertySegment(segment) || !current || typeof current !== "object" || Array.isArray(current) || !Object.hasOwn(current, segment)) { return null; } current = current[segment]; } return typeof current === "string" ? current : null; } function writeNestedString(value, path, next) { const segments = path.split("."); let current = value; for (const segment of segments.slice(0, -1)) { if (!isSafePropertySegment(segment) || !Object.hasOwn(current, segment)) { return; } const child = current[segment]; if (!child || typeof child !== "object" || Array.isArray(child)) { return; } current = child; } const leaf = segments.at(-1); if (leaf && isSafePropertySegment(leaf)) { Object.defineProperty(current, leaf, { value: next, enumerable: true, configurable: true, writable: true }); } } function isSafePropertySegment(segment) { return segment !== "__proto__" && segment !== "prototype" && segment !== "constructor"; } function readFrameworkVersion(path) { return readPreviousFrameworkVersion(path)?.version ?? null; } function readPreviousFrameworkVersion(path) { if (!existsSync(path)) { return null; } try { const content = readFileSync(path, "utf8"); const versionMatch = content.match(/^version=(.*)$/m); const updatedMatch = content.match(/^updated_at=(.*)$/m); if (!versionMatch || !updatedMatch) { return null; } return { version: versionMatch[1].trim(), updatedAt: updatedMatch[1].trim() }; } catch { return null; } } function resolveFrameworkInstallPath() { return process.env.PAQAD_FRAMEWORK_HOME ?? toPosixPath(join(homedir(), ".paqad-ai/current")); } function resolveFrameworkInstallReference() { return process.env.PAQAD_FRAMEWORK_HOME ? "$PAQAD_FRAMEWORK_HOME" : "~/.paqad-ai/current"; } function sanitizeDetectionReport(projectRoot, report) { return { ...report, signals: (report.signals ?? []).map((signal) => ({ ...signal, file: sanitizePersistedPath(projectRoot, signal.file) })), repository: report.repository ? sanitizeRepositoryContext(projectRoot, report.repository) : void 0 }; } function sanitizeOnboardingManifest(projectRoot, manifest) { return { ...manifest, project_root: typeof manifest.project_root === "string" ? sanitizePersistedPath(projectRoot, manifest.project_root) : manifest.project_root, detected: manifest.detected ? sanitizeDetectionReport(projectRoot, manifest.detected) : null, repository: manifest.repository ? sanitizeRepositoryContext(projectRoot, manifest.repository) : void 0, generated_artifacts: [...manifest.generated_artifacts ?? []].map((artifact) => ({ ...artifact, path: sanitizePersistedPath(projectRoot, artifact.path) })).sort((left, right) => comparePaths(left.path, right.path)), planning_artifacts: manifest.planning_artifacts ? { ...manifest.planning_artifacts, compiled_rules_path: sanitizePersistedPath( projectRoot, manifest.planning_artifacts.compiled_rules_path ) } : manifest.planning_artifacts }; } function sanitizeStackSnapshotRepository(projectRoot, snapshot) { if (!snapshot.repository) return snapshot; return { ...snapshot, repository: sanitizeRepositoryContext(projectRoot, snapshot.repository) }; } function sanitizeRepositoryContext(projectRoot, repository) { return { ...repository, selected_root: sanitizePersistedPath(projectRoot, repository.selected_root), ignored_paths: repository.ignored_paths.map((path) => sanitizePersistedPath(projectRoot, path)).filter((path) => !isDeveloperLocalNoise(path)).sort(comparePaths), projects: repository.projects.map((project) => sanitizeRepositoryProject(projectRoot, project)), applications: repository.applications.map( (application) => sanitizeRepositoryApplication(projectRoot, application) ), primary_project_root: repository.primary_project_root === null ? null : sanitizePersistedPath(projectRoot, repository.primary_project_root) }; } var DEVELOPER_LOCAL_PATH_SEGMENTS = /* @__PURE__ */ new Set([".DS_Store", ".idea", ".vscode"]); function isDeveloperLocalNoise(path) { return toPosixPath(path).split("/").some((segment) => DEVELOPER_LOCAL_PATH_SEGMENTS.has(segment)); } function comparePaths(left, right) { return left < right ? -1 : left > right ? 1 : 0; } function sanitizeRepositoryProject(projectRoot, project) { return { ...project, root: sanitizePersistedPath(projectRoot, project.root), parent_root: project.parent_root === null ? null : sanitizePersistedPath(projectRoot, project.parent_root) }; } function sanitizeRepositoryApplication(projectRoot, application) { return { ...application, root: sanitizePersistedPath(projectRoot, application.root), component_roots: application.component_roots.map( (root) => sanitizePersistedPath(projectRoot, root) ) }; } function sanitizePersistedPath(projectRoot, value) { if (value === ".") { return value; } const normalizedValue = toPosixPath(value); const normalizedRoot = toPosixPath(projectRoot); if (normalizedValue === normalizedRoot) { return "."; } const prefix = normalizedRoot.endsWith("/") ? normalizedRoot : `${normalizedRoot}/`; if (normalizedValue.startsWith(prefix)) { return normalizedValue.slice(prefix.length); } const isAbsoluteWindows = /^[a-zA-Z]:\//.test(normalizedValue); const isAbsolutePosix = normalizedValue.startsWith("/"); if (!isAbsoluteWindows && !isAbsolutePosix) { return normalizedValue; } const relativePath = relative(projectRoot, value); if (relativePath === "") { return "."; } if (relativePath !== ".." && !relativePath.startsWith(`..${sep}`)) { return toPosixPath(relativePath); } return normalizedValue; } export { writeProjectProfile2 as writeProjectProfile, writeDetectionReport, writeFrameworkMetadata, readExistingOnboardingManifest, writeOnboardingManifest, writeJsonPreservingTimestamp, writeFrameworkVersionPreservingTimestamp, readFrameworkVersion, resolveFrameworkInstallPath, sanitizeStackSnapshotRepository };