"use strict";
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.go = void 0;
exports.parseGoModDirectDeps = parseGoModDirectDeps;
exports.buildGoTopLevelDepIndex = buildGoTopLevelDepIndex;
exports.parseGoCoverProfile = parseGoCoverProfile;
exports.extractGoImportsRaw = extractGoImportsRaw;
exports.resolveGoImportRaw = resolveGoImportRaw;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const coverage_1 = require("../analyzers/tools/coverage");
const walk_source_files_1 = require("../analyzers/tools/walk-source-files");
const osv_1 = require("../analyzers/tools/osv");
const runner_1 = require("../analyzers/tools/runner");
const run_tests_helper_1 = require("../analyzers/tools/run-tests-helper");
const tool_registry_1 = require("../analyzers/tools/tool-registry");
/**
 * Tier a golangci-lint finding by the linter that produced it.
 *
 * golangci-lint bundles ~60 linters with different character. The
 * `FromLinter` field identifies which one fired. `Severity` (often empty)
 * is used only as a fallback floor for unknown linters.
 */
function mapGolangciLinterSeverity(linter) {
    if (!linter)
        return 'low';
    // Security — gosec exclusively flags vuln patterns.
    if (linter === 'gosec')
        return 'critical';
    // Correctness bugs — go vet, staticcheck analyses, type errors, etc.
    if (linter === 'govet' ||
        linter === 'staticcheck' ||
        linter === 'typecheck' ||
        linter === 'errorlint' ||
        linter === 'ineffassign' ||
        linter === 'unused' ||
        linter === 'nilerr' ||
        linter === 'bodyclose' ||
        linter === 'rowserrcheck' ||
        linter === 'sqlclosecheck' ||
        linter === 'noctx') {
        return 'high';
    }
    // Best practices / maintenance
    if (linter === 'errcheck' ||
        linter === 'gocritic' ||
        linter === 'revive' ||
        linter === 'goconst' ||
        linter === 'gocyclo' ||
        linter === 'funlen' ||
        linter === 'dupl' ||
        linter === 'gosimple' ||
        linter === 'unconvert' ||
        linter === 'unparam' ||
        linter === 'prealloc' ||
        linter === 'gocognit') {
        return 'medium';
    }
    // Style / formatting
    if (linter === 'gofmt' ||
        linter === 'gofumpt' ||
        linter === 'goimports' ||
        linter === 'stylecheck' ||
        linter === 'whitespace' ||
        linter === 'misspell' ||
        linter === 'godot' ||
        linter === 'lll') {
        return 'low';
    }
    return 'low';
}
function tierGolangciIssue(issue) {
    const byLinter = mapGolangciLinterSeverity(issue.FromLinter);
    // For unknown linters we fell through to 'low' — but golangci-lint's
    // own Severity field may say otherwise. Use it as a floor.
    if (byLinter === 'low' && issue.FromLinter) {
        const sev = (issue.Severity || '').toLowerCase();
        if (sev === 'error')
            return 'high';
        if (sev === 'warning')
            return 'medium';
    }
    return byLinter;
}
/**
 * Pure parser for `go.mod`, extracting module paths declared in
 * `require` blocks WITHOUT a `// indirect` marker. Those are the
 * user's direct deps — the ones that show up as Snyk top-levels.
 *
 * Handles both syntaxes:
 *   require foo.com/bar v1.0                        (single-line)
 *   require ( foo.com/bar v1.0                      (block)
 *             baz.com/qux v2.0 // indirect )        (indirect-marked)
 *
 * Lines with `// indirect` (or `//indirect`) are skipped since those
 * were added by `go mod tidy` to pin transitive versions, not because
 * the user declared them. Go's direct/indirect distinction maps
 * cleanly onto the top-level concept.
 */
function parseGoModDirectDeps(raw) {
    const direct = [];
    let inRequireBlock = false;
    for (const rawLine of raw.split('\n')) {
        // Strip line comments first so `// indirect` detection is accurate.
        // Preserve the original for the indirect check — which looks at the
        // comment we just stripped.
        const indirect = /\/\/\s*indirect\b/.test(rawLine);
        const line = rawLine.replace(/\/\/.*$/, '').trim();
        if (!line)
            continue;
        if (line === 'require (') {
            inRequireBlock = true;
            continue;
        }
        if (inRequireBlock && line === ')') {
            inRequireBlock = false;
            continue;
        }
        if (inRequireBlock) {
            if (indirect)
                continue;
            // Block-form entries are `<modpath> <version>`; we only want the path.
            const parts = line.split(/\s+/);
            if (parts.length >= 1 && parts[0])
                direct.push(parts[0]);
            continue;
        }
        // Single-line require outside a block: `require <modpath> <version>`.
        const m = line.match(/^require\s+(\S+)\s+\S+/);
        if (m && !indirect)
            direct.push(m[1]);
    }
    return direct;
}
/**
 * Pure parser for `go mod graph`, returning a per-module index of the
 * top-level manifest deps (root's direct children, filtered against
 * `go.mod`'s direct-dep list when supplied) that transitively pull
 * each module.
 *
 * `go mod graph` output is line-oriented: `<src> <dst>` where each
 * token is `module@version` except the root module which appears
 * without `@version`. The root's direct deps are the BFS seeds;
 * attribution collapses at the module-name level so the same package
 * appearing at multiple versions maps to a single name.
 *
 * When `directDeps` is supplied, seeds are intersected with that set —
 * excludes indirect-but-in-graph modules (every dependency of a
 * direct dep is also a direct child of root in the graph). Without
 * this filter, Go projects with 5 direct + 30 indirect deps would
 * show 35 "top-levels," inflating attribution.
 */
function buildGoTopLevelDepIndex(raw, directDeps) {
    const nameOf = (tok) => {
        const at = tok.indexOf('@');
        return at < 0 ? tok : tok.slice(0, at);
    };
    // Root is identified as the only source token without `@version`.
    // All lines whose source lacks `@` share the same root name (barring
    // workspace multi-module repos, which we approximate as a single
    // root — accurate enough for Snyk-style grouping).
    let root = null;
    const edges = new Map();
    for (const line of raw.split('\n')) {
        const trimmed = line.trim();
        if (!trimmed)
            continue;
        const parts = trimmed.split(/\s+/);
        if (parts.length < 2)
            continue;
        const srcName = nameOf(parts[0]);
        const dstName = nameOf(parts[1]);
        if (!parts[0].includes('@')) {
            // Source token lacks @version → this is the root module.
            if (root === null)
                root = srcName;
        }
        const bucket = edges.get(srcName) ?? new Set();
        bucket.add(dstName);
        edges.set(srcName, bucket);
    }
    if (!root)
        return new Map();
    let rootChildren = [...(edges.get(root) ?? new Set())];
    if (directDeps && directDeps.length > 0) {
        const directSet = new Set(directDeps);
        rootChildren = rootChildren.filter((name) => directSet.has(name));
    }
    const result = new Map();
    for (const top of rootChildren) {
        const visited = new Set();
        const queue = [top];
        while (queue.length > 0) {
            const name = queue.shift();
            if (visited.has(name))
                continue;
            visited.add(name);
            const bucket = result.get(name) ?? new Set();
            bucket.add(top);
            result.set(name, bucket);
            for (const child of edges.get(name) ?? []) {
                if (!visited.has(child))
                    queue.push(child);
            }
        }
    }
    const sorted = new Map();
    for (const [name, parents] of result) {
        sorted.set(name, [...parents].sort());
    }
    return sorted;
}
/**
 * Invoke `go mod graph` + read `go.mod` and build the top-level index.
 * Returns an empty map when either is missing — attribution stays
 * unset so dep-vuln gather still succeeds. Degrades safely on
 * toolchain-less environments (e.g. containerized CI lacking go).
 */
function loadGoTopLevelDepIndex(cwd) {
    const graphRaw = (0, runner_1.run)('go mod graph', cwd, 60000);
    if (!graphRaw)
        return new Map();
    let directDeps = [];
    try {
        const modRaw = fs.readFileSync(path.join(cwd, 'go.mod'), 'utf-8');
        directDeps = parseGoModDirectDeps(modRaw);
    }
    catch {
        // go.mod missing/unreadable — fall back to pre-filter behavior
        // (all root children become top-levels). Consistent with the
        // pre-10h.4.4.a semantics rather than blocking attribution.
    }
    return buildGoTopLevelDepIndex(graphRaw, directDeps);
}
/**
 * Single source of truth for the go pack's dep-vuln gathering.
 * Consumed by `goDepVulnsProvider` (capability dispatcher).
 *
 * govulncheck only emits `finding` records when call analysis confirms
 * the vulnerable code is reachable from the project's main package, so
 * every emitted DepVulnFinding sets `reachable: true` — Tier-1 output
 * with Tier-4 reachability semantics by virtue of the upstream tool's
 * design.
 *
 * Findings are grouped by (osvId, module, installedVersion). One
 * advisory hitting multiple modules yields multiple findings; multiple
 * call paths through the same (advisory, module, version) collapse to
 * one. govulncheck's per-call-path output (102 raw findings on the
 * Tickit smoke) is intentional internal detail; the bom/render layer
 * wants one row per (vuln × installed package).
 */
async function gatherGoDepVulnsResult(cwd) {
    const vuln = (0, tool_registry_1.findTool)(tool_registry_1.TOOL_DEFS.govulncheck, cwd);
    if (!vuln.available || !vuln.path) {
        return { kind: 'unavailable', reason: 'govulncheck not installed' };
    }
    const raw = (0, runner_1.run)(`${vuln.path} -json ./...`, cwd, 120000);
    if (!raw)
        return { kind: 'unavailable', reason: 'govulncheck produced no output' };
    try {
        // govulncheck -json emits a stream of *pretty-printed* JSON objects
        // (each spanning many lines), not single-line ndjson — so we can't
        // JSON.parse line-by-line. Accumulate lines into a buffer and try to
        // parse after each newline; success resets the buffer for the next
        // record. Three relevant top-level shapes:
        //   { "osv": { ...full OSV record... } }   — the advisory detail
        //   { "finding": { "osv": "GO-YYYY-NNNN", "trace": [...] } }  — a call-site hit
        //   { "config": ... } / { "progress": ... } / { "SBOM": ... } — ignored
        const groupedFindings = new Map();
        const embeddedOsv = new Map();
        for (const obj of (0, runner_1.parseJsonStream)(raw)) {
            if (obj.finding?.osv) {
                const trace0 = obj.finding.trace?.[0] ?? {};
                const key = `${obj.finding.osv}|${trace0.module ?? ''}|${trace0.version ?? ''}`;
                if (!groupedFindings.has(key))
                    groupedFindings.set(key, obj.finding);
            }
            if (obj.osv?.id)
                embeddedOsv.set(obj.osv.id, obj.osv);
        }
        // Prefer severity from the embedded OSV record (already in the
        // govulncheck output, no extra API call). Fall back to an OSV.dev
        // lookup for IDs without embedded data. Fall back to 'high' (the
        // legacy govulncheck default) for anything still unknown.
        const uniqueIds = [
            ...new Set([...groupedFindings.values()].map((f) => f.osv).filter(Boolean)),
        ];
        const needsLookup = uniqueIds.filter((id) => {
            const rec = embeddedOsv.get(id);
            if (!rec)
                return true;
            return (0, osv_1.classifyOsvSeverity)(rec) === 'unknown';
        });
        const lookedUp = needsLookup.length > 0 ? await (0, osv_1.enrichOsv)(needsLookup) : new Map();
        let critical = 0;
        let high = 0;
        let medium = 0;
        let low = 0;
        let enrichedCount = 0;
        const severityById = new Map();
        for (const id of uniqueIds) {
            const rec = embeddedOsv.get(id);
            let sev = rec ? (0, osv_1.classifyOsvSeverity)(rec) : 'unknown';
            if (sev === 'unknown')
                sev = lookedUp.get(id)?.severity ?? 'unknown';
            let bucket;
            if (sev !== 'unknown') {
                enrichedCount++;
                if (sev === 'critical')
                    bucket = 'critical';
                else if (sev === 'high')
                    bucket = 'high';
                else if (sev === 'medium')
                    bucket = 'medium';
                else
                    bucket = 'low';
            }
            else {
                bucket = 'high'; // govulncheck legacy default
            }
            severityById.set(id, bucket);
            if (bucket === 'critical')
                critical++;
            else if (bucket === 'high')
                high++;
            else if (bucket === 'medium')
                medium++;
            else
                low++;
        }
        const topLevelIndex = loadGoTopLevelDepIndex(cwd);
        const findings = [];
        for (const grouped of groupedFindings.values()) {
            if (!grouped.osv)
                continue;
            const trace0 = grouped.trace?.[0] ?? {};
            const osvRec = embeddedOsv.get(grouped.osv);
            // Prefer the trace module (project-specific), fall back to the OSV
            // record's affected package name, then 'unknown' if neither shipped.
            const pkgName = trace0.module ?? osvRec?.affected?.[0]?.package?.name ?? 'unknown';
            const finding = {
                id: grouped.osv,
                package: pkgName,
                installedVersion: trace0.version,
                tool: 'govulncheck',
                packId: 'go',
                severity: severityById.get(grouped.osv) ?? 'high',
                reachable: true,
            };
            if (grouped.fixed_version)
                finding.fixedVersion = grouped.fixed_version;
            // Capture the embedded CVSS (govulncheck bundles severity vectors
            // for many third-party deps but rarely for stdlib). Alias-fallback
            // happens in the batched resolveCvssScores pass after this loop.
            const embeddedScore = osvRec ? (0, osv_1.extractOsvCvssScore)(osvRec) : null;
            if (embeddedScore !== null)
                finding.cvssScore = embeddedScore;
            if (osvRec) {
                const aliases = (osvRec.aliases ?? []).filter((a) => a && a.length > 0);
                if (aliases.length > 0)
                    finding.aliases = aliases;
                if (osvRec.summary)
                    finding.summary = osvRec.summary;
                else if (osvRec.details)
                    finding.summary = osvRec.details;
                const refs = (osvRec.references ?? []).map((r) => r.url).filter(Boolean);
                if (refs.length > 0)
                    finding.references = refs;
            }
            // Synthesize an OSV.dev URL when the embedded record lacked references.
            if (!finding.references) {
                finding.references = [`https://osv.dev/vulnerability/${grouped.osv}`];
            }
            const parents = topLevelIndex.get(pkgName);
            if (parents && parents.length > 0)
                finding.topLevelDep = parents;
            findings.push(finding);
        }
        // Alias-fallback CVSS pass: stdlib advisories (GO-YYYY-NNNN) often
        // carry no CVSS in either govulncheck's embedded data or OSV.dev's
        // GO-* record, while the corresponding CVE alias does (e.g.
        // GO-2025-3750 → CVE-2025-0913). resolveCvssScores re-queries each
        // alias for findings still missing a score, batched.
        if (findings.length > 0) {
            const cvssInputs = findings.map((f) => ({
                primaryId: f.id,
                embeddedCvss: f.cvssScore ?? null,
                aliases: f.aliases ?? [],
            }));
            const resolved = await (0, osv_1.resolveCvssScores)(cvssInputs);
            for (const f of findings) {
                const score = resolved.get(f.id);
                if (score !== null && score !== undefined)
                    f.cvssScore = score;
            }
        }
        const envelope = {
            schemaVersion: 1,
            tool: 'govulncheck',
            // OSV is "used" only when we made an actual API lookup AND it
            // produced enrichment. Embedded-only severity isn't an OSV call.
            enrichment: enrichedCount > 0 && needsLookup.length > 0 ? 'osv.dev' : null,
            counts: { critical, high, medium, low },
            findings,
        };
        return { kind: 'success', envelope };
    }
    catch (err) {
        return { kind: 'unavailable', reason: `govulncheck parse error: ${err.message}` };
    }
}
const goDepVulnsProvider = {
    source: 'go',
    async gather(cwd) {
        const outcome = await gatherGoDepVulnsResult(cwd);
        return outcome.kind === 'success' ? outcome.envelope : null;
    },
    async gatherOutcome(cwd) {
        return gatherGoDepVulnsResult(cwd);
    },
};
/**
 * Single source of truth for the go pack's lint gathering.
 * Consumed by `goLintProvider` (capability dispatcher).
 */
function gatherGoLintResult(cwd) {
    const lint = (0, tool_registry_1.findTool)(tool_registry_1.TOOL_DEFS['golangci-lint'], cwd);
    if (!lint.available || !lint.path) {
        return { kind: 'unavailable', reason: 'not installed' };
    }
    const raw = (0, runner_1.run)(`${lint.path} run --out-format json ./...`, cwd, 120000);
    const counts = { critical: 0, high: 0, medium: 0, low: 0 };
    if (!raw) {
        // Empty output = golangci-lint ran with no issues. Matches prior behavior.
        const envelope = { schemaVersion: 1, tool: 'golangci-lint', counts };
        return { kind: 'success', envelope };
    }
    try {
        const data = JSON.parse(raw);
        for (const issue of data.Issues || []) {
            counts[tierGolangciIssue(issue)]++;
        }
        const envelope = { schemaVersion: 1, tool: 'golangci-lint', counts };
        return { kind: 'success', envelope };
    }
    catch {
        return { kind: 'unavailable', reason: 'parse error' };
    }
}
const goLintProvider = {
    source: 'go',
    async gather(cwd) {
        const outcome = gatherGoLintResult(cwd);
        return outcome.kind === 'success' ? outcome.envelope : null;
    },
};
// ─── Coverage parser ────────────────────────────────────────────────────────
// Moved from `src/analyzers/tools/coverage.ts` in Phase 10i.0-LP.4.
/**
 * Go coverprofile paths look like `github.com/user/repo/pkg/foo.go`. If that
 * file doesn't exist at cwd, try stripping leading segments until we find one
 * that does. Fall back to the original string if nothing resolves.
 */
function resolveGoPath(p, cwd) {
    const norm = p.replace(/\\/g, '/');
    if (fs.existsSync(path.join(cwd, norm)))
        return norm;
    const parts = norm.split('/');
    for (let i = 1; i < parts.length; i++) {
        const candidate = parts.slice(i).join('/');
        if (fs.existsSync(path.join(cwd, candidate)))
            return candidate;
    }
    return norm;
}
/**
 * Go coverprofile:
 *   mode: set
 *   pkg/path/file.go:5.2,10.1 3 1
 *
 * Each line: `file:startLine.col,endLine.col numStatements count`. A
 * block is covered iff `count > 0`.
 */
function parseGoCoverProfile(raw, sourceFile, cwd) {
    const perFile = new Map();
    let totalCovered = 0;
    let totalStatements = 0;
    for (const line of raw.split('\n')) {
        if (!line || line.startsWith('mode:'))
            continue;
        const m = line.match(/^(.+):\d+\.\d+,\d+\.\d+\s+(\d+)\s+(\d+)$/);
        if (!m)
            continue;
        const file = m[1];
        const stmts = parseInt(m[2], 10);
        const count = parseInt(m[3], 10);
        const bucket = perFile.get(file) ?? { covered: 0, total: 0 };
        bucket.total += stmts;
        if (count > 0)
            bucket.covered += stmts;
        perFile.set(file, bucket);
        totalStatements += stmts;
        if (count > 0)
            totalCovered += stmts;
    }
    const files = new Map();
    for (const [key, { covered, total }] of perFile) {
        // Go coverprofile uses module-relative paths (e.g. github.com/user/repo/pkg/foo.go).
        // Strip the module prefix if it resolves to a real file in cwd.
        const rel = resolveGoPath(key, cwd);
        files.set(rel, {
            path: rel,
            covered,
            total,
            pct: (0, coverage_1.round1)(total > 0 ? (covered / total) * 100 : 0),
        });
    }
    return {
        source: 'go',
        sourceFile,
        linePercent: (0, coverage_1.round1)(totalStatements > 0 ? (totalCovered / totalStatements) * 100 : 0),
        files,
    };
}
/**
 * Single source of truth for the go pack's coverage gathering.
 * Consumed by `goCoverageProvider` (capability dispatcher).
 */
function gatherGoCoverageResult(cwd) {
    for (const file of ['coverage.out', 'cover.out']) {
        const abs = path.join(cwd, file);
        let raw;
        try {
            raw = fs.readFileSync(abs, 'utf-8');
        }
        catch {
            continue;
        }
        try {
            const coverage = parseGoCoverProfile(raw, file, cwd);
            return { schemaVersion: 1, tool: `coverage:${coverage.source}`, coverage };
        }
        catch {
            continue;
        }
    }
    return null;
}
/**
 * Run `go test -coverprofile=coverage.out ./...` from cwd (D021).
 *
 * Universal Go convention — works on any Go module. The output file
 * `coverage.out` is the canonical artifact that `gatherGoCoverageResult`
 * already reads on the next dispatcher pass.
 *
 * Preflight: detect a `go.mod` or any `.go` source. If neither exists,
 * return `unavailable` rather than waste the spawn cost (Go's own
 * "no Go files" error is fine but unhelpful framing for the user).
 */
function runGoTestsWithCoverage(cwd) {
    return Promise.resolve((0, run_tests_helper_1.runTestsWithCoverage)({
        pack: 'go',
        cmd: 'go test -coverprofile=coverage.out ./...',
        cwd,
        artifact: 'coverage.out',
        preflight: (cwd) => {
            if (!(0, runner_1.fileExists)(cwd, 'go.mod')) {
                return 'no go.mod in this directory — not a Go module';
            }
            // `go` itself missing surfaces via the spawn ENOENT path; no
            // need to pre-check.
            return null;
        },
    }));
}
const goCoverageProvider = {
    source: 'go',
    async gather(cwd) {
        return gatherGoCoverageResult(cwd);
    },
    async runTests(cwd) {
        return runGoTestsWithCoverage(cwd);
    },
};
/**
 * Capture Go module specifiers from source text. Handles both single-line
 * `import "fmt"` and parenthesized multi-line blocks, including aliased
 * imports. Exported for unit tests; the imports capability batches it
 * across all .go files in the repo.
 */
function extractGoImportsRaw(content) {
    const out = [];
    const singleRe = /^\s*import\s+(?:[a-zA-Z_]\w*\s+)?"([^"]+)"/gm;
    let m;
    while ((m = singleRe.exec(content)) !== null) {
        out.push(m[1]);
    }
    const blockRe = /import\s*\(([\s\S]*?)\)/g;
    while ((m = blockRe.exec(content)) !== null) {
        const block = m[1];
        const lineRe = /(?:[a-zA-Z_]\w*\s+)?"([^"]+)"/g;
        let lm;
        while ((lm = lineRe.exec(block)) !== null) {
            if (!out.includes(lm[1])) {
                out.push(lm[1]);
            }
        }
    }
    return out;
}
/**
 * Resolve a Go import specifier to the in-project package directory,
 * or null for stdlib / external modules. A resolved edge target is a
 * directory (not a file), which naturally dead-ends the import-graph
 * BFS. Exported for unit tests.
 */
function resolveGoImportRaw(_fromFile, spec, cwd) {
    let goMod;
    try {
        goMod = fs.readFileSync(path.join(cwd, 'go.mod'), 'utf-8');
    }
    catch {
        return null;
    }
    const moduleMatch = goMod.match(/^module\s+(\S+)/m);
    if (!moduleMatch)
        return null;
    const modulePath = moduleMatch[1];
    if (!spec.startsWith(modulePath + '/'))
        return null;
    const rel = spec.slice(modulePath.length + 1);
    const dir = path.join(cwd, rel);
    try {
        if (fs.statSync(dir).isDirectory()) {
            return rel;
        }
    }
    catch {
        // not found
    }
    return null;
}
/**
 * Enumerate .go source files and pre-compute the pack's per-file imports
 * + resolved edges. Edges point at package directories (Go's model), not
 * individual files — consumers BFS-ing over them see the same dead-ends
 * as the legacy path.
 */
function gatherGoImportsResult(cwd) {
    const files = (0, walk_source_files_1.walkSourceFiles)(cwd, {
        extensions: ['.go'],
        includeTests: true,
        includeAutogen: true,
    });
    if (files.length === 0)
        return null;
    const extracted = new Map();
    const edges = new Map();
    for (const rel of files) {
        let content;
        try {
            content = fs.readFileSync(path.join(cwd, rel), 'utf-8');
        }
        catch {
            continue;
        }
        const specs = extractGoImportsRaw(content);
        extracted.set(rel, specs);
        const targets = new Set();
        for (const spec of specs) {
            const resolved = resolveGoImportRaw(rel, spec, cwd);
            if (resolved)
                targets.add(resolved);
        }
        if (targets.size > 0)
            edges.set(rel, targets);
    }
    if (extracted.size === 0)
        return null;
    return {
        schemaVersion: 1,
        tool: 'go-imports',
        sourceExtensions: ['.go'],
        extracted,
        edges,
    };
}
const goImportsProvider = {
    source: 'go',
    async gather(cwd) {
        return gatherGoImportsResult(cwd);
    },
};
/**
 * Go ships with a single test runner (`go test`) and every module
 * using it is identified by `go.mod` at the root — detection is a
 * single file-existence check.
 */
function gatherGoTestFrameworkResult(cwd) {
    if (!(0, runner_1.fileExists)(cwd, 'go.mod'))
        return null;
    return { schemaVersion: 1, tool: 'go', name: 'go-test' };
}
const goTestFrameworkProvider = {
    source: 'go',
    async gather(cwd) {
        return gatherGoTestFrameworkResult(cwd);
    },
};
/**
 * Parse the NDJSON-ish stream emitted by `go list -m -json all`. The tool
 * outputs concatenated `{...}` objects separated by no delimiter; each
 * object starts with '{' and ends with '}' at column 0. Match each with
 * a multiline regex and JSON.parse individually.
 */
function parseGoListModuleStream(raw) {
    const out = [];
    const re = /^\{[\s\S]*?^\}/gm;
    let m;
    while ((m = re.exec(raw)) !== null) {
        try {
            out.push(JSON.parse(m[0]));
        }
        catch {
            /* skip malformed block */
        }
    }
    return out;
}
/**
 * Find the module whose path is the longest prefix of a Go package path.
 * go-licenses reports at the package level (`github.com/x/y/subpkg`)
 * while module-keyed maps (go list -m all, the top-level index) key on
 * module paths (`github.com/x/y`); longest-prefix match bridges the two.
 * Returns an empty string when no module prefixes the package.
 */
function goModuleForPackage(pkgPath, modules) {
    let best = '';
    for (const mod of modules) {
        if ((pkgPath === mod || pkgPath.startsWith(mod + '/')) && mod.length > best.length) {
            best = mod;
        }
    }
    return best;
}
function goVersionForPackage(pkgPath, modules) {
    const mod = goModuleForPackage(pkgPath, modules.keys());
    return modules.get(mod) ?? '';
}
/**
 * Single source of truth for the go pack's license gathering. Consumed
 * by `goLicensesProvider` (capability dispatcher).
 *
 * Two-step merge: `go-licenses report .` emits per-package CSV
 * (path,url,license) and `go list -m -json all` emits per-module
 * versions. Modules and packages have different granularity in the
 * Go module system, so we longest-prefix-match the package path
 * against the module list. Returns null cleanly on any Go module
 * without go-licenses installed, without go.mod, or when the tool
 * fails (commonly because `go mod download` hasn't been run).
 */
function gatherGoLicensesResult(cwd) {
    if (!(0, runner_1.fileExists)(cwd, 'go.mod')) {
        return { kind: 'no-manifest', reason: 'no go.mod' };
    }
    const status = (0, tool_registry_1.findTool)(tool_registry_1.TOOL_DEFS['go-licenses'], cwd);
    if (!status.available || !status.path) {
        return { kind: 'unavailable', reason: 'go-licenses not installed' };
    }
    const csvRaw = (0, runner_1.run)(`${status.path} report .`, cwd, 180000);
    if (!csvRaw)
        return { kind: 'unavailable', reason: 'go-licenses produced no output' };
    const listRaw = (0, runner_1.run)('go list -m -json all', cwd, 60000);
    const versions = new Map();
    if (listRaw) {
        for (const mod of parseGoListModuleStream(listRaw)) {
            if (mod.Main)
                continue;
            if (mod.Path && mod.Version)
                versions.set(mod.Path, mod.Version);
        }
    }
    // go-licenses reports packages (`github.com/x/y/subpkg`); the top-level
    // index keys modules (`github.com/x/y`). Project the CSV row's package
    // path down to its owning module, then check the self-parent invariant.
    const topLevelIndex = loadGoTopLevelDepIndex(cwd);
    const hasIndex = topLevelIndex.size > 0;
    const findings = [];
    for (const line of csvRaw.split('\n')) {
        const trimmed = line.trim();
        if (!trimmed)
            continue;
        const parts = trimmed.split(',');
        if (parts.length < 3)
            continue;
        const pkg = parts[0].trim();
        const url = parts[1].trim();
        const license = parts[2].trim();
        let isTopLevel;
        if (hasIndex) {
            const mod = goModuleForPackage(pkg, topLevelIndex.keys());
            const parents = mod ? topLevelIndex.get(mod) : undefined;
            isTopLevel = mod ? (parents?.includes(mod) ?? false) : false;
        }
        findings.push({
            package: pkg,
            version: goVersionForPackage(pkg, versions),
            licenseType: license || 'UNKNOWN',
            sourceUrl: url || undefined,
            isTopLevel,
        });
    }
    const envelope = {
        schemaVersion: 1,
        tool: 'go-licenses',
        findings,
    };
    return { kind: 'success', envelope };
}
const goLicensesProvider = {
    source: 'go',
    async gather(cwd) {
        const outcome = gatherGoLicensesResult(cwd);
        return outcome.kind === 'success' ? outcome.envelope : null;
    },
    async gatherOutcome(cwd) {
        return gatherGoLicensesResult(cwd);
    },
};
exports.go = {
    id: 'go',
    displayName: 'Go',
    commentSyntax: { lineComment: '//', blockCommentStart: '/*', blockCommentEnd: '*/' },
    sourceExtensions: ['.go'],
    testFilePatterns: ['*_test.go'],
    extraExcludes: ['vendor'],
    // D027 (2.4.7): godoc convention is a comment starting with the
    // exported symbol's name in sentence case (e.g. "// HandlerFunc
    // serves..."). Approximate with "// " followed by capital letter
    // at line start; matches the dominant godoc pattern across
    // standard library + idiomatic projects.
    docCommentPatterns: ['^// [A-Z]'],
    exportDetection: {
        reliability: 'full',
        strategy: 'Capitalized first letter on top-level identifiers (Go idiomatic export convention)',
    },
    // D034 (2.4.7): `tls.Config{InsecureSkipVerify: true}` is the
    // canonical Go pattern for disabling cert verification — applied
    // to `http.Transport.TLSClientConfig`, gRPC `credentials.NewTLS`,
    // and similar. Pre-D034 it never matched (Go wasn't in the include
    // list at all).
    tlsBypassPatterns: ['InsecureSkipVerify[[:space:]]*:[[:space:]]*true'],
    upgradeCommand(name, version) {
        return `go get ${name}@v${version}`;
    },
    // Go's stdlib layout (`cmd/`, `internal/`, `pkg/`) is too broad to
    // call "primary architecture" — those directories typically contain
    // every Go file in the repo. Narrow declarations target the
    // handler/service/controller paths that gin/echo/chi/fiber web apps
    // and standard library `net/http` servers organize their HTTP
    // surface under.
    architecturalShape: {
        primaryComponentPaths: ['/handlers/', '/services/', '/controllers/', '/jobs/'],
        routePaths: ['/handlers/', '/controllers/', '/routes/'],
        modelPaths: ['/models/', '/entities/'],
        vocabulary: {
            components: 'handlers/services',
            models: 'models',
            routes: 'routes',
        },
        testGapPriority: {
            high: ['/handlers/', '/services/', '/controllers/', '/jobs/'],
        },
    },
    clocLanguageNames: ['Go'],
    detect(cwd) {
        return (0, runner_1.fileExists)(cwd, 'go.mod');
    },
    tools: ['golangci-lint', 'govulncheck', 'go-licenses'],
    semgrepRulesets: ['p/gosec'],
    capabilities: {
        depVulns: goDepVulnsProvider,
        lint: goLintProvider,
        coverage: goCoverageProvider,
        imports: goImportsProvider,
        testFramework: goTestFrameworkProvider,
        licenses: goLicensesProvider,
    },
    mapLintSeverity: mapGolangciLinterSeverity,
    permissions: ['Bash(go test:*)', 'Bash(go build:*)', 'Bash(go vet:*)', 'Bash(golangci-lint:*)'],
    ruleFile: 'go.md',
    defaultVersion: '1.24.0',
    cliBinaries: ['go', 'golangci-lint'],
    devcontainerFeature: {
        name: 'ghcr.io/devcontainers/features/go:1',
        opts: { version: '1.21' },
    },
    devcontainerExtensions: ['golang.go'],
};
//# sourceMappingURL=go.js.map