"use strict";
// Repo Map — lightweight persistent snapshot of a project's structure.
//
// Captures the things the agent needs to know *before* it starts working:
//   - detected language / framework stack
//   - package manager
//   - test, check, and build commands
//   - paths the agent must never touch
//   - known entry points and key directories
//
// Deliberately lightweight: no AST, no import graph — just filesystem
// probing + package.json parsing. Builds in < 100 ms for typical projects.
// Persisted to <projectDir>/.harness/repo-map.json so repeated runs do not
// re-scan unless the map is stale (default staleness threshold: 24 h).
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildRepoMap = buildRepoMap;
exports.saveRepoMap = saveRepoMap;
exports.loadRepoMap = loadRepoMap;
exports.getOrBuildRepoMap = getOrBuildRepoMap;
exports.isStale = isStale;
exports.renderRepoMapBlock = renderRepoMapBlock;
const crypto = __importStar(require("crypto"));
const fs = __importStar(require("fs"));
const fsp = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
// ─── Constants ──────────────────────────────────────────────────────
const SCHEMA_VERSION = 1;
const REPO_MAP_FILENAME = 'repo-map.json';
const HARNESS_DIR = '.harness';
/** Default staleness threshold: 24 hours. */
const DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1000;
const ALWAYS_DO_NOT_EDIT = [
    'node_modules',
    '.git',
    'dist',
    'build',
    'out',
    '.harness',
    'package-lock.json',
    'yarn.lock',
    'pnpm-lock.yaml',
    'Pipfile.lock',
    'poetry.lock',
    'Cargo.lock',
    'go.sum',
];
const KEY_DIR_NAMES = new Set([
    'src', 'lib', 'app', 'core', 'api', 'server',
    'client', 'ui', 'web', 'frontend', 'backend',
    'tests', 'test', 'spec', '__tests__',
    'docs', 'scripts', 'tools', 'utils', 'config',
    'integrations', 'services', 'types', 'models',
    'middleware', 'helpers', 'hooks', 'components',
]);
// ─── Build ──────────────────────────────────────────────────────────
/**
 * Scan `projectDir` and produce a `RepoMap`.
 * This is the main entry point for building a map from scratch.
 */
async function buildRepoMap(projectDir) {
    const root = path.resolve(projectDir);
    const entries = await safeReaddir(root);
    const frameworks = [];
    const configFiles = [];
    const keyDirs = [];
    // ── Detect config files ────────────────────────────────────────────
    const configFileNames = new Set([
        'package.json', 'tsconfig.json', 'jsconfig.json',
        'jest.config.js', 'jest.config.ts', 'jest.config.mjs',
        'vite.config.ts', 'vite.config.js',
        'webpack.config.js', 'rollup.config.js',
        '.eslintrc', '.eslintrc.js', '.eslintrc.json', '.eslintrc.yml',
        '.prettierrc', '.prettierrc.json',
        'babel.config.js', 'babel.config.json',
        'Makefile', 'makefile',
        'requirements.txt', 'setup.py', 'setup.cfg', 'pyproject.toml', 'Pipfile',
        'go.mod',
        'Cargo.toml',
        'pom.xml', 'build.gradle', 'build.gradle.kts',
        'Dockerfile', 'docker-compose.yml', 'docker-compose.yaml',
        '.env.example',
    ]);
    for (const entry of entries) {
        if (configFileNames.has(entry.name)) {
            configFiles.push(entry.name);
        }
        if (entry.isDirectory() && KEY_DIR_NAMES.has(entry.name)) {
            keyDirs.push(entry.name);
        }
    }
    // ── Detect framework / language ─────────────────────────────────────
    const hasFile = (name) => configFiles.includes(name);
    const hasTsConfig = hasFile('tsconfig.json') || hasFile('jsconfig.json');
    const hasPkg = hasFile('package.json');
    const hasPython = hasFile('requirements.txt') || hasFile('setup.py') || hasFile('pyproject.toml') || hasFile('Pipfile');
    const hasGo = hasFile('go.mod');
    const hasRust = hasFile('Cargo.toml');
    const hasJava = hasFile('pom.xml') || hasFile('build.gradle') || hasFile('build.gradle.kts');
    if (hasTsConfig)
        frameworks.push('typescript');
    if (hasPkg && !hasTsConfig)
        frameworks.push('javascript');
    if (hasPkg)
        frameworks.push('node');
    if (hasPython)
        frameworks.push('python');
    if (hasGo)
        frameworks.push('go');
    if (hasRust)
        frameworks.push('rust');
    if (hasJava)
        frameworks.push('java');
    if (frameworks.length === 0)
        frameworks.push('unknown');
    // ── Package manager ─────────────────────────────────────────────────
    const lockFiles = {
        'pnpm-lock.yaml': 'pnpm',
        'yarn.lock': 'yarn',
        'package-lock.json': 'npm',
        Pipfile: 'pipenv',
        'requirements.txt': 'pip',
        'pyproject.toml': 'poetry',
        'go.mod': 'go',
        'Cargo.toml': 'cargo',
    };
    let packageManager;
    for (const entry of entries) {
        if (lockFiles[entry.name]) {
            packageManager = lockFiles[entry.name];
            break;
        }
    }
    // ── Extract commands from package.json ──────────────────────────────
    let testCommand;
    let checkCommand;
    let buildCommand;
    if (hasPkg) {
        const pkg = await readJson(path.join(root, 'package.json'));
        const scripts = (pkg?.scripts ?? {});
        testCommand = scripts['test'] ? `npm test` : undefined;
        // Prefer explicit typecheck script, fall back to tsc
        checkCommand = scripts['typecheck'] ? `npm run typecheck`
            : scripts['type-check'] ? `npm run type-check`
                : scripts['lint'] ? `npm run lint`
                    : hasTsConfig ? `npx tsc --noEmit`
                        : undefined;
        buildCommand = scripts['build'] ? `npm run build` : undefined;
    }
    if (hasPython && !testCommand) {
        testCommand = 'pytest';
    }
    if (hasGo && !testCommand) {
        testCommand = 'go test ./...';
    }
    if (hasRust && !testCommand) {
        testCommand = 'cargo test';
    }
    // ── Entry points ─────────────────────────────────────────────────────
    const entryPointCandidates = [
        'src/index.ts', 'src/main.ts', 'src/index.js', 'src/main.js',
        'src/app.ts', 'src/app.js',
        'index.ts', 'index.js', 'main.ts', 'main.js',
        'main.py', 'app.py', '__main__.py',
        'cmd/main.go', 'main.go',
        'src/main.rs', 'main.rs',
    ];
    const entryPoints = [];
    for (const candidate of entryPointCandidates) {
        if (fs.existsSync(path.join(root, candidate))) {
            entryPoints.push(candidate);
        }
    }
    // ── Do-not-edit paths ─────────────────────────────────────────────────
    const doNotEdit = [...ALWAYS_DO_NOT_EDIT].filter(p => fs.existsSync(path.join(root, p)) || p.includes('*'));
    const fileListForHash = [...configFiles].sort().join('\n');
    const contentHash = crypto.createHash('sha256').update(fileListForHash).digest('hex').slice(0, 16);
    return {
        version: SCHEMA_VERSION,
        root,
        frameworks,
        packageManager,
        testCommand,
        checkCommand,
        buildCommand,
        doNotEdit,
        entryPoints,
        keyDirs: keyDirs.sort(),
        configFiles: configFiles.sort(),
        builtAt: new Date().toISOString(),
        contentHash,
    };
}
// ─── Persistence ────────────────────────────────────────────────────
/** Save a repo map to `<projectDir>/.harness/repo-map.json`. */
async function saveRepoMap(repoMap, projectDir) {
    const harnessDir = path.join(path.resolve(projectDir), HARNESS_DIR);
    await fsp.mkdir(harnessDir, { recursive: true });
    const filePath = path.join(harnessDir, REPO_MAP_FILENAME);
    await fsp.writeFile(filePath, JSON.stringify(repoMap, null, 2), 'utf8');
}
/**
 * Load a previously-saved repo map from `<projectDir>/.harness/repo-map.json`.
 * Returns `undefined` if the file does not exist or cannot be parsed.
 */
async function loadRepoMap(projectDir) {
    const filePath = path.join(path.resolve(projectDir), HARNESS_DIR, REPO_MAP_FILENAME);
    try {
        const raw = await fsp.readFile(filePath, 'utf8');
        const parsed = JSON.parse(raw);
        if (parsed.version !== SCHEMA_VERSION)
            return undefined;
        // Verify content hash if present
        if (parsed.contentHash) {
            const fileListForHash = [...(parsed.configFiles ?? [])].sort().join('\n');
            const expected = crypto.createHash('sha256').update(fileListForHash).digest('hex').slice(0, 16);
            if (expected !== parsed.contentHash) {
                return undefined; // tampered — force rebuild
            }
        }
        return parsed;
    }
    catch {
        return undefined;
    }
}
/**
 * Load map from disk; build and save a fresh one if absent or stale.
 * This is the recommended high-level entry point for callers that want
 * caching behaviour.
 */
async function getOrBuildRepoMap(projectDir, opts = {}) {
    const existing = await loadRepoMap(projectDir);
    if (existing && !isStale(existing, opts.maxAgeMs)) {
        return existing;
    }
    const fresh = await buildRepoMap(projectDir);
    await saveRepoMap(fresh, projectDir);
    return fresh;
}
// ─── Staleness ──────────────────────────────────────────────────────
/** Returns true when the map is older than `maxAgeMs` (default 24 h). */
function isStale(repoMap, maxAgeMs = DEFAULT_MAX_AGE_MS) {
    const ageMs = Date.now() - new Date(repoMap.builtAt).getTime();
    return ageMs > maxAgeMs;
}
// ─── System prompt rendering ─────────────────────────────────────────
/**
 * Render a concise Markdown block for injection into the agent system prompt.
 * Kept compact so it doesn't crowd out the real task context.
 */
function renderRepoMapBlock(map) {
    const lines = ['## Project Snapshot'];
    lines.push(`- **Root:** \`${map.root}\``);
    lines.push(`- **Stack:** ${map.frameworks.join(', ')}`);
    if (map.packageManager) {
        lines.push(`- **Package manager:** ${map.packageManager}`);
    }
    if (map.testCommand) {
        lines.push(`- **Test command:** \`${map.testCommand}\``);
    }
    if (map.checkCommand) {
        lines.push(`- **Check command:** \`${map.checkCommand}\``);
    }
    if (map.buildCommand) {
        lines.push(`- **Build command:** \`${map.buildCommand}\``);
    }
    if (map.entryPoints.length > 0) {
        lines.push(`- **Entry points:** ${map.entryPoints.map(p => `\`${p}\``).join(', ')}`);
    }
    if (map.keyDirs.length > 0) {
        lines.push(`- **Key dirs:** ${map.keyDirs.map(d => `\`${d}/\``).join(', ')}`);
    }
    if (map.doNotEdit.length > 0) {
        lines.push(`- **Do not edit:** ${map.doNotEdit.map(p => `\`${p}\``).join(', ')}`);
    }
    if (map.notes) {
        lines.push('');
        lines.push(`> ${map.notes.replace(/\n/g, '\n> ')}`);
    }
    return lines.join('\n');
}
// ─── Helpers ─────────────────────────────────────────────────────────
async function safeReaddir(dir) {
    try {
        return await fsp.readdir(dir, { withFileTypes: true });
    }
    catch {
        return [];
    }
}
async function readJson(filePath) {
    try {
        const raw = await fsp.readFile(filePath, 'utf8');
        return JSON.parse(raw);
    }
    catch {
        return undefined;
    }
}
//# sourceMappingURL=repoMap.js.map