@puls-atlas/cli
Version:
The Puls Atlas CLI tool for managing Atlas projects
342 lines • 14.4 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { createHash } from 'node:crypto';
import { readJsonFile, writeJsonFile, writeTextFile } from '../../utils/file.js';
import { normalizeOptionalString } from '../../utils/value.js';
import { createAtlasAiConsumerImpactSummaryRows, createAtlasAiConsumerQualitySummaryRows, createAtlasAiCopilotInstructions, createAtlasAiSnapshotData, createCoverageFindings, createLockComparison, createVerificationFindingRecord, logVerificationFindingRecords, normalizeAtlasAiRefreshMode, resolveAtlasAiContextState, resolveOutputPaths } from './consumerContextRuntime.js';
const SCHEMA_VERSION = '1.0.0';
const DEFAULT_VERIFY_POLICY_MODE = 'localDevelopment';
const createChecksum = value => createHash('sha256').update(value).digest('hex');
const readTextFile = (filePath, dependencies = {}) => {
const {
fsImpl = fs
} = dependencies;
if (!fsImpl.existsSync(filePath)) {
return null;
}
try {
return fsImpl.readFileSync(filePath, 'utf-8');
} catch (error) {
throw new Error(`Failed to read file: ${filePath}\nError: ${error.message}`);
}
};
export { createCoverageFindings, normalizeAtlasAiRefreshMode };
const createAtlasAiLockData = (state, outputPaths, agentFiles, skillFiles, instructionsChecksum, snapshotChecksum, commandName) => ({
schemaVersion: SCHEMA_VERSION,
framework: 'Atlas',
project: {
name: state.consumerPackage?.name ?? null
},
sharedPackage: {
checksum: state.sharedManifestChecksum,
manifestPath: state.sharedManifestPath,
name: state.sharedManifest?.package?.name ?? state.sharedPackageName,
version: state.sharedPackageVersion ?? null
},
packages: state.packageContexts.map(entry => ({
checksum: entry.checksum,
guidesChecksum: entry.guidesChecksum,
guidesManifestPath: entry.guidesManifestPath,
manifestPath: entry.manifestPath,
name: entry.name,
symbolsChecksum: entry.symbolsChecksum,
symbolsManifestPath: entry.symbolsManifestPath,
version: entry.version
})),
missingContexts: state.missingContexts.map(entry => ({
expectedPath: entry.expectedPath,
name: entry.name,
version: entry.version
})),
generatedFiles: [...agentFiles.map(agentFile => ({
checksum: agentFile.checksum,
path: agentFile.relativeOutputPath
})), ...skillFiles.map(skillFile => ({
checksum: skillFile.checksum,
path: skillFile.relativeOutputPath
})), {
checksum: snapshotChecksum,
path: outputPaths.relativeSnapshotPath
}, {
checksum: instructionsChecksum,
path: outputPaths.relativeInstructionsPath
}],
lockFilePath: outputPaths.relativeLockFilePath,
lastSyncedAt: new Date().toISOString(),
generatedBy: commandName
});
export const createAtlasAiArtifacts = (options = {}, dependencies = {}, cwd = process.cwd(), commandName = 'atlas ai sync') => {
const {
pathImpl = path
} = dependencies;
const state = resolveAtlasAiContextState(options, dependencies, cwd);
const outputPaths = resolveOutputPaths(cwd, options, state.sharedManifest, pathImpl);
const agentFiles = state.sharedAgentPaths.map((sharedAgentPath, index) => {
const content = readTextFile(pathImpl.resolve(cwd, sharedAgentPath), dependencies);
if (content === null) {
throw new Error(`Atlas AI shared agent is missing at ${sharedAgentPath}. ` + 'Reinstall or rebuild the shared Atlas AI package before running this command.');
}
return {
checksum: createChecksum(content),
content,
outputPath: outputPaths.agentPaths[index],
relativeOutputPath: outputPaths.relativeAgentPaths[index],
sharedPath: sharedAgentPath
};
});
const skillFiles = state.sharedSkillPaths.map((sharedSkillPath, index) => {
const content = readTextFile(pathImpl.resolve(cwd, sharedSkillPath), dependencies);
if (content === null) {
throw new Error(`Atlas AI shared skill is missing at ${sharedSkillPath}. ` + 'Reinstall or rebuild the shared Atlas AI package before running this command.');
}
return {
checksum: createChecksum(content),
content,
outputPath: outputPaths.skillPaths[index],
relativeOutputPath: outputPaths.relativeSkillPaths[index],
sharedPath: sharedSkillPath
};
});
const instructionsContent = createAtlasAiCopilotInstructions(state, outputPaths, dependencies, cwd);
const snapshotData = createAtlasAiSnapshotData(state, commandName);
const instructionsChecksum = createChecksum(instructionsContent);
const snapshotChecksum = createChecksum(JSON.stringify(snapshotData));
return {
...state,
agentContent: agentFiles[0]?.content ?? '',
agentFiles,
instructionsContent,
snapshotData,
lockData: createAtlasAiLockData(state, outputPaths, agentFiles, skillFiles, instructionsChecksum, snapshotChecksum, commandName),
outputPaths,
skillFiles
};
};
const createGeneratedFileDriftFindingRecords = (artifacts, dependencies = {}) => {
const issues = [];
const instructionsText = readTextFile(artifacts.outputPaths.instructionsPath, dependencies);
const existingSnapshot = readJsonFile(artifacts.outputPaths.snapshotPath, {
allowMissing: true
}, dependencies);
const existingLock = readJsonFile(artifacts.outputPaths.lockFilePath, {
allowMissing: true
}, dependencies);
for (const agentFile of artifacts.agentFiles) {
const existingAgentText = readTextFile(agentFile.outputPath, dependencies);
if (existingAgentText === null) {
issues.push(createVerificationFindingRecord('error', `Generated Atlas AI agent is missing at ${agentFile.relativeOutputPath}.`, {
code: 'missing-generated-agent'
}));
continue;
}
if (existingAgentText !== agentFile.content) {
issues.push(createVerificationFindingRecord('error', `Generated Atlas AI agent is out of date at ${agentFile.relativeOutputPath}. Run "atlas ai sync".`, {
code: 'stale-generated-agent'
}));
}
}
for (const skillFile of artifacts.skillFiles) {
const existingSkillText = readTextFile(skillFile.outputPath, dependencies);
if (existingSkillText === null) {
issues.push(createVerificationFindingRecord('error', `Generated Atlas AI skill is missing at ${skillFile.relativeOutputPath}.`, {
code: 'missing-generated-skill'
}));
continue;
}
if (existingSkillText !== skillFile.content) {
issues.push(createVerificationFindingRecord('error', `Generated Atlas AI skill is out of date at ${skillFile.relativeOutputPath}. Run "atlas ai sync".`, {
code: 'stale-generated-skill'
}));
}
}
if (instructionsText === null) {
issues.push(createVerificationFindingRecord('error', `Generated Atlas AI instructions are missing at ${artifacts.outputPaths.relativeInstructionsPath}.`, {
code: 'missing-generated-instructions'
}));
} else if (instructionsText !== artifacts.instructionsContent) {
issues.push(createVerificationFindingRecord('error', 'Generated Atlas AI instructions are out of date. Run "atlas ai sync".', {
code: 'stale-generated-instructions'
}));
}
if (existingSnapshot === null) {
issues.push(createVerificationFindingRecord('error', `Atlas AI snapshot is missing at ${artifacts.outputPaths.relativeSnapshotPath}.`, {
code: 'missing-snapshot'
}));
} else if (JSON.stringify(existingSnapshot) !== JSON.stringify(artifacts.snapshotData)) {
issues.push(createVerificationFindingRecord('error', 'Atlas AI snapshot is out of date. Run "atlas ai sync".', {
code: 'stale-snapshot'
}));
}
if (existingLock === null) {
issues.push(createVerificationFindingRecord('error', `Atlas AI lock file is missing at ${artifacts.outputPaths.relativeLockFilePath}.`, {
code: 'missing-lock-file'
}));
} else if (JSON.stringify(createLockComparison(existingLock)) !== JSON.stringify(createLockComparison(artifacts.lockData))) {
issues.push(createVerificationFindingRecord('error', 'Atlas AI lock file is out of date. Run "atlas ai sync".', {
code: 'stale-lock-file'
}));
}
return issues;
};
export const createGeneratedFileDriftIssues = (artifacts, dependencies = {}) => createGeneratedFileDriftFindingRecords(artifacts, dependencies).map(finding => finding.message);
export const ensureInitWriteAllowed = (artifacts, options = {}, dependencies = {}) => {
const {
fsImpl = fs
} = dependencies;
if (options.force) {
return;
}
const existingFiles = [];
for (const [index, agentPath] of artifacts.outputPaths.agentPaths.entries()) {
if (fsImpl.existsSync(agentPath)) {
existingFiles.push(artifacts.outputPaths.relativeAgentPaths[index]);
}
}
for (const [index, skillPath] of artifacts.outputPaths.skillPaths.entries()) {
if (fsImpl.existsSync(skillPath)) {
existingFiles.push(artifacts.outputPaths.relativeSkillPaths[index]);
}
}
if (fsImpl.existsSync(artifacts.outputPaths.instructionsPath)) {
existingFiles.push(artifacts.outputPaths.relativeInstructionsPath);
}
if (fsImpl.existsSync(artifacts.outputPaths.lockFilePath)) {
existingFiles.push(artifacts.outputPaths.relativeLockFilePath);
}
if (fsImpl.existsSync(artifacts.outputPaths.snapshotPath)) {
existingFiles.push(artifacts.outputPaths.relativeSnapshotPath);
}
if (existingFiles.length === 0) {
return;
}
throw new Error(`Atlas AI files already exist: ${existingFiles.join(', ')}. ` + 'Use --force or run "atlas ai sync" instead.');
};
const removeStaleGeneratedFiles = (artifacts, dependencies = {}) => {
const {
fsImpl = fs,
pathImpl = path
} = dependencies;
const existingLock = readJsonFile(artifacts.outputPaths.lockFilePath, {
allowMissing: true
}, dependencies);
if (!existingLock || typeof fsImpl.rmSync !== 'function') {
return;
}
const currentGeneratedFilePaths = new Set(artifacts.lockData.generatedFiles.map(entry => entry.path));
for (const generatedFile of Array.isArray(existingLock.generatedFiles) ? existingLock.generatedFiles : []) {
const relativePath = normalizeOptionalString(generatedFile?.path);
if (!relativePath || currentGeneratedFilePaths.has(relativePath)) {
continue;
}
const staleFilePath = pathImpl.resolve(artifacts.outputPaths.workspaceRootPath, relativePath);
if (fsImpl.existsSync(staleFilePath)) {
fsImpl.rmSync(staleFilePath, {
force: true
});
}
}
};
export const writeAtlasAiArtifacts = (artifacts, dependencies = {}) => {
removeStaleGeneratedFiles(artifacts, dependencies);
for (const agentFile of artifacts.agentFiles) {
writeTextFile(agentFile.outputPath, agentFile.content, {
trailingNewline: false
}, dependencies);
}
for (const skillFile of artifacts.skillFiles) {
writeTextFile(skillFile.outputPath, skillFile.content, {
trailingNewline: false
}, dependencies);
}
writeTextFile(artifacts.outputPaths.instructionsPath, artifacts.instructionsContent, {
trailingNewline: false
}, dependencies);
writeJsonFile(artifacts.outputPaths.snapshotPath, artifacts.snapshotData, {}, dependencies);
writeJsonFile(artifacts.outputPaths.lockFilePath, artifacts.lockData, {}, dependencies);
};
const createAiSummaryRows = (artifacts, actionLabel) => [{
label: 'Action',
value: actionLabel
}, {
label: 'Project',
value: artifacts.consumerPackage?.name ?? '(unnamed project)'
}, {
label: 'Shared package',
value: `${artifacts.sharedManifest?.package?.name ?? artifacts.sharedPackageName} ` + `(${artifacts.sharedPackageVersion ?? 'unknown'})`
}, {
label: 'Atlas package contexts',
value: artifacts.packageContexts.length
}, {
label: 'Symbols catalogs',
value: artifacts.packageContexts.filter(entry => entry.symbolsManifestPath).length
}, {
label: 'Guide catalogs',
value: artifacts.packageContexts.filter(entry => entry.guidesManifestPath).length
}, {
label: 'Missing manifests',
value: artifacts.missingContexts.length
}, {
label: 'Lead agent',
value: artifacts.outputPaths.relativeAgentPath
}, {
label: 'Specialist agents',
value: Math.max(artifacts.outputPaths.relativeAgentPaths.length - 1, 0)
}, {
label: 'Skills',
value: artifacts.outputPaths.relativeSkillPaths.length
}, {
label: 'Instructions',
value: artifacts.outputPaths.relativeInstructionsPath
}, {
label: 'Snapshot',
value: artifacts.outputPaths.relativeSnapshotPath
}, {
label: 'Lock file',
value: artifacts.outputPaths.relativeLockFilePath
}];
export const logAiWriteOutcome = (loggerImpl, artifacts, actionLabel) => {
loggerImpl.summary('Atlas AI summary', createAiSummaryRows(artifacts, actionLabel));
loggerImpl.summary('Atlas AI consumer quality', createAtlasAiConsumerQualitySummaryRows(artifacts));
const consumerImpactSummaryRows = createAtlasAiConsumerImpactSummaryRows(artifacts);
if (consumerImpactSummaryRows.length > 0) {
loggerImpl.summary('Atlas AI consumer impact priorities', consumerImpactSummaryRows);
}
if (artifacts.packageContexts.length > 0) {
loggerImpl.summary('Detected Atlas AI manifests', artifacts.packageContexts.map(entry => ({
label: entry.name,
value: entry.version ?? 'unknown'
})));
}
const coverageFindings = createCoverageFindings(artifacts, {
policyMode: DEFAULT_VERIFY_POLICY_MODE
});
for (const warning of [...coverageFindings.issues, ...coverageFindings.warnings]) {
loggerImpl.warning(warning);
}
};
export const createVerificationFindings = (artifacts, options = {}, dependencies = {}) => {
const coverageFindings = createCoverageFindings(artifacts, options);
const generatedFileFindings = createGeneratedFileDriftFindingRecords(artifacts, dependencies);
const records = [...coverageFindings.records, ...generatedFileFindings];
return {
issues: records.filter(record => record.severity !== 'warning').map(record => record.message),
records,
warnings: records.filter(record => record.severity === 'warning').map(record => record.message)
};
};
export const logAiVerifyOutcome = (loggerImpl, result) => {
loggerImpl.summary('Atlas AI verification', [{
label: 'Project',
value: result.artifacts.consumerPackage?.name ?? '(unnamed project)'
}, {
label: 'Status',
value: result.status
}, {
label: 'Issues',
value: result.issues.length
}, {
label: 'Warnings',
value: result.warnings.length
}]);
logVerificationFindingRecords(loggerImpl, result.findings);
};