pi-lens
Version:
Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo
2,318 lines • 77.2 kB
JavaScript
import { createRequire as __pilensCreateRequire } from "node:module"; const require = __pilensCreateRequire(import.meta.url);
import {
CANONICAL_GLOBAL_CONFIG_FILE,
CANONICAL_PROJECT_CONFIG_FILE,
FRESHNESS_CADENCE_MS,
GLOBAL_CONFIG_LOCATIONS,
LEGACY_ROOT_LSP_KEYS,
LSP_NAMESPACE_KEY,
PROJECT_CONFIG_BASENAMES,
PROJECT_CONFIG_LOCATIONS,
configSearchDirs,
dirMtimesStillFresh,
errorClassName,
findPiLensConfigMarkerInDir,
getPiLensGlobalConfigPath,
isResolvedGlobalConfigPath
} from "./chunk-VNMT7C64.js";
import {
DEGRADATION_ENTRIES_PER_KIND,
GLOBAL_NON_FLAG_CONFIG_SECTIONS,
GLOBAL_ONLY_NON_FLAG_KEYS,
LENS_FLAGS,
LENS_TOOL_NAMES,
PROJECT_FOREIGN_CONFIG_NAMESPACES,
PROJECT_FOREIGN_NAMESPACE_HONORED_KEYS,
PROJECT_NON_FLAG_CONFIG_SECTIONS,
PROJECT_SCOPED_LENS_FLAGS,
assignFlagConfigSection,
flagConfigSectionKeys,
hasFlagConfigPath,
readFlagConfigValue,
readToolConfig,
recordDegradationOnce
} from "./chunk-N3YQJI6O.js";
import {
logExtension
} from "./chunk-O6TQT6RI.js";
import {
CONFIG_SCHEMA_ANCHOR_KEY,
CONFIG_SCHEMA_ID,
DEPRECATED_CONFIG_SURFACES,
STABILITY_TIER_KEY,
withConfigDiagnosticCode
} from "./chunk-5THXD6X5.js";
import {
redactSecrets,
toPositiveFinite
} from "./chunk-UK3CMEAL.js";
import {
BoundedLruCache
} from "./chunk-ABBOA7UD.js";
import {
homeRelativePath,
isAtOrAboveHomeDir,
isWindowsPath,
walkUpDirs
} from "./chunk-Q5U6FMDI.js";
// dist/clients/generated-artifacts.js
import * as fs from "node:fs";
import * as path from "node:path";
var DEFAULT_HEADER_BYTES = 4096;
var GENERATED_DIR_NAMES = /* @__PURE__ */ new Set([
"generated",
"__generated__",
"codegen",
"__codegen__",
"generated-sources",
"generated_sources",
"dist",
"openapi-generated",
".openapi-generator"
]);
var GENERATED_HEADER_PATTERNS = [
/@generated\b/i,
/@ts-nocheck\b/i,
/\bcode generated\b/i,
/\bdo not edit\b/i,
/\bautomatically generated\b/i,
/\bthis file was generated\b/i,
/\bgenerated by protoc\b/i,
/\bgenerated by graphql-code-generator\b/i,
/\bgenerated by openapi-generator\b/i,
/\bgenerated by swagger codegen\b/i
];
var LOCKFILE_NAMES = /* @__PURE__ */ new Set([
"package-lock.json",
"packages.lock.json",
"npm-shrinkwrap.json",
"pnpm-lock.yaml",
"yarn.lock",
"bun.lock",
"cargo.lock",
"composer.lock",
"gemfile.lock",
"poetry.lock",
"uv.lock",
"go.sum"
]);
var GENERATED_FILE_PATTERNS = [
/(^|[._-])generated([._-]|$)/i,
/(^|[._-])gen([._-])/i,
/_generated\.(go|rs|py|rb|java|kt|ts|tsx|js|jsx|mjs|cjs)$/i,
/_gen\.(go|rs|py|rb|java|kt|ts|tsx|js|jsx|mjs|cjs)$/i,
/\.pb\.(go|ts|tsx|js|jsx|mjs|cjs)$/i,
/_pb2(_grpc)?\.py$/i,
/\.sql\.(go|ts|js)$/i,
/_sqlc\.go$/i,
/\.g\.([jt]sx?|mjs|cjs|rs|go)$/i
];
var MINIFIED_BUNDLE_FILE_PATTERNS = [
/\.min\.(js|mjs|cjs|css)$/i,
/\.(bundle|chunk)\.(js|mjs|cjs|css)$/i
];
var DECLARATION_FILE_PATTERNS = [/\.d\.ts$/i, /\.d\.mts$/i, /\.d\.cts$/i];
function pathSegments(filePath) {
return path.normalize(filePath).split(/[\\/]+/).filter(Boolean);
}
function basenameForShape(filePath) {
return isWindowsPath(filePath) ? path.win32.basename(filePath) : path.basename(filePath);
}
function isGeneratedArtifactDirectoryName(dirName) {
return GENERATED_DIR_NAMES.has(dirName.trim().toLowerCase());
}
function isDeclarationFile(filePath) {
const base = basenameForShape(filePath).toLowerCase();
return DECLARATION_FILE_PATTERNS.some((pattern) => pattern.test(base));
}
function hasStrongGeneratedArtifactPath(filePath) {
const segments = pathSegments(filePath);
const dirSegments = segments.slice(0, -1);
if (dirSegments.some((segment) => isGeneratedArtifactDirectoryName(segment))) {
return true;
}
const base = basenameForShape(filePath);
if (LOCKFILE_NAMES.has(base.toLowerCase()))
return true;
return MINIFIED_BUNDLE_FILE_PATTERNS.some((pattern) => pattern.test(base));
}
function hasWeakGeneratedFileNamePattern(filePath) {
const base = basenameForShape(filePath);
return GENERATED_FILE_PATTERNS.some((pattern) => pattern.test(base));
}
function hasGeneratedArtifactContent(content) {
const header = content.slice(0, DEFAULT_HEADER_BYTES);
return GENERATED_HEADER_PATTERNS.some((pattern) => pattern.test(header));
}
var MACHINE_EMITTED_LINE_SHAPE_MIN_CONTENT = 2048;
var MACHINE_EMITTED_LINE_SHAPE_MEAN_THRESHOLD = 2500;
function classifyMachineEmittedLineShape(content) {
const contentLength = content.length;
if (contentLength < MACHINE_EMITTED_LINE_SHAPE_MIN_CONTENT) {
return { generated: false, meanLineLength: 0 };
}
let nonEmptyLineTotal = 0;
let nonEmptyLineCount = 0;
let currentLineLength = 0;
for (let i = 0; i < contentLength; i++) {
const code = content.charCodeAt(i);
if (code === 10) {
if (currentLineLength > 0) {
nonEmptyLineTotal += currentLineLength;
nonEmptyLineCount += 1;
}
currentLineLength = 0;
} else if (code !== 13) {
currentLineLength += 1;
}
}
if (currentLineLength > 0) {
nonEmptyLineTotal += currentLineLength;
nonEmptyLineCount += 1;
}
if (nonEmptyLineCount === 0)
return { generated: false, meanLineLength: 0 };
const meanLineLength = nonEmptyLineTotal / nonEmptyLineCount;
return {
generated: meanLineLength > MACHINE_EMITTED_LINE_SHAPE_MEAN_THRESHOLD,
meanLineLength
};
}
var HEADER_VERDICT_MEMO_CAP = 5e4;
var headerAnalysisMemo = new BoundedLruCache(HEADER_VERDICT_MEMO_CAP);
function readFileHeader(filePath, maxBytes = DEFAULT_HEADER_BYTES) {
let fd;
try {
fd = fs.openSync(filePath, "r");
const buffer = Buffer.alloc(Math.max(0, maxBytes));
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
return buffer.toString("utf8", 0, bytesRead);
} catch {
return void 0;
} finally {
if (fd !== void 0) {
try {
fs.closeSync(fd);
} catch {
}
}
}
}
function analyzeFileHeader(filePath, maxBytes) {
let key;
try {
const st = fs.statSync(filePath);
key = `${st.mtimeMs}:${st.size}:${maxBytes}:${filePath}`;
} catch {
return void 0;
}
const cached = headerAnalysisMemo.get(key);
if (cached !== void 0)
return cached;
const header = readFileHeader(filePath, maxBytes);
const analysis = header === void 0 ? { banner: false, shape: { generated: false, meanLineLength: 0 } } : {
banner: hasGeneratedArtifactContent(header),
shape: classifyMachineEmittedLineShape(header)
};
headerAnalysisMemo.set(key, analysis);
return analysis;
}
function classifyGeneratedOrArtifactDetailed(filePath, options = {}) {
if (hasStrongGeneratedArtifactPath(filePath)) {
return { verdict: "generated", evidence: "strong" };
}
if (options.includeDeclarations && isDeclarationFile(filePath)) {
return { verdict: "generated", evidence: "strong" };
}
const weakNameMatch = hasWeakGeneratedFileNamePattern(filePath);
if (options.content !== void 0) {
if (hasGeneratedArtifactContent(options.content)) {
return { verdict: "generated", evidence: "content" };
}
const shape = classifyMachineEmittedLineShape(options.content);
if (shape.generated) {
return {
verdict: "generated",
evidence: "line-shape",
lineShapeMean: shape.meanLineLength
};
}
return { verdict: weakNameMatch ? "override" : "clean" };
}
if (options.readContentHeader) {
const analysis = analyzeFileHeader(filePath, options.maxHeaderBytes ?? DEFAULT_HEADER_BYTES);
if (analysis?.banner) {
return { verdict: "generated", evidence: "content" };
}
if (analysis?.shape.generated) {
return {
verdict: "generated",
evidence: "line-shape",
lineShapeMean: analysis.shape.meanLineLength
};
}
return { verdict: weakNameMatch ? "override" : "clean" };
}
return weakNameMatch ? { verdict: "generated", evidence: "name-only" } : { verdict: "clean" };
}
function classifyGeneratedOrArtifact(filePath, options = {}) {
return classifyGeneratedOrArtifactDetailed(filePath, options).verdict;
}
function isGeneratedOrArtifact(filePath, options = {}) {
return classifyGeneratedOrArtifact(filePath, options) === "generated";
}
// dist/clients/file-role.js
import { basename as basename2, dirname, win32 as win322 } from "node:path";
var RE_EXPORT_LINE = /^export\s+(type\s+)?\*\s+from\s+|^export\s*\{[^}]*\}\s+from\s+/;
function isReExportBarrel(content) {
const lines = content.split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !l.startsWith("//") && !l.startsWith("*") && !l.startsWith("/*"));
return lines.length > 0 && lines.every((l) => RE_EXPORT_LINE.test(l));
}
function isTypeStub(content) {
const lines = content.split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !l.startsWith("//") && !l.startsWith("*") && !l.startsWith("/*") && l !== "{" && l !== "}");
if (lines.length < 4)
return false;
const typeOnlyLines = lines.filter((l) => /^(export\s+)?(interface|type|abstract\s+class|declare\s+(class|function|const|var|let|module|namespace|enum))\s+/.test(l) || /^(import\s+type\s+|import\s+\{)/.test(l) || /^export\s+type\s+\{/.test(l));
return typeOnlyLines.length / lines.length >= 0.75;
}
function testFileNameMatches(rawBase, base) {
return base.includes(".test.") || base.includes(".spec.") || base.includes("_test.") || base.includes("_spec.") || base.startsWith("test_") || base.startsWith("spec_") || /(Test|Tests|TestCase)\.(java|kt|kts|cs|fs|php)$/.test(rawBase);
}
function isTestFileName(filePath) {
const windowsShaped = isWindowsPath(filePath);
const rawBase = windowsShaped ? win322.basename(filePath) : basename2(filePath);
return testFileNameMatches(rawBase, rawBase.toLowerCase());
}
function detectFileRole(filePath, content) {
const windowsShaped = isWindowsPath(filePath);
const rawBase = windowsShaped ? win322.basename(filePath) : basename2(filePath);
const base = rawBase.toLowerCase();
const dir = (windowsShaped ? win322.dirname(filePath) : dirname(filePath)).replace(/\\/g, "/").toLowerCase();
if (testFileNameMatches(rawBase, base) || /(^|[\\/])__tests__([\\/]|$)/.test(dir) || dir.includes("/test/") || dir.includes("/tests/") || dir.includes("/spec/") || dir.includes("/specs/") || /[/_-]tests?\/?$/.test(dir) || /[/_-]specs?\/?$/.test(dir))
return "test";
if (isGeneratedOrArtifact(filePath, { content, includeDeclarations: false })) {
return "generated";
}
const forwardDir = dir.replace(/\\/g, "/");
if (forwardDir.includes("/migrations/") || forwardDir.includes("/migration/") || /\/\d{4,}_[a-z]/.test(forwardDir + "/" + base))
return "migration";
if (base.endsWith(".config.ts") || base.endsWith(".config.js") || base.endsWith(".config.mjs") || base.endsWith(".config.cjs") || base === "vite.config.ts" || base === "jest.config.ts" || base === "jest.config.js" || base === "webpack.config.js" || base === "rollup.config.js" || base === "tailwind.config.js" || base === "tailwind.config.ts" || base === "next.config.js" || base === "next.config.mjs")
return "config";
if (base.endsWith(".d.ts"))
return "stub";
if (content && isTypeStub(content))
return "stub";
const isIndexFile = base === "index.ts" || base === "index.tsx" || base === "index.js" || base === "index.mjs" || base === "index.cjs" || base === "index.jsx" || base === "__init__.py" || base === "mod.rs";
if (isIndexFile) {
if (content && isReExportBarrel(content))
return "re-export";
return "init";
}
return "source";
}
// dist/clients/user-notify.js
var notifierGetter;
function notifyUserDegradation(message, level = "warning", options) {
try {
const notify = notifierGetter?.();
const coded = options?.code ? withConfigDiagnosticCode(message, options.code) : message;
notify?.(coded, level);
} catch {
}
}
// dist/clients/config-warn.js
var SUBSYSTEM_CONFIG_LABEL = {
"lsp-config": "LSP config",
"lens-config": "global config",
"project-lens-config": "project config"
};
var IGNORED_CONFIG_CODE = "PILENS_CFG_0001";
var IGNORED_CONFIG_KIND = "config-ignored";
var DEPRECATED_CONFIG_KIND = "config-deprecated";
var SUPPRESSED_NOTICE_CODE = "PILENS_CFG_0007";
var SUPPRESSED_NOTICE_KIND = "config-notice-suppressed";
var DEPRECATION_NOUN_BY_CODE = new Map(DEPRECATED_CONFIG_SURFACES.map((row) => [
row.code,
row.kind === "file" ? "location" : "key"
]));
var warnedIgnoredConfigs = /* @__PURE__ */ new Set();
var JSON_PARSE_POSITION = /at position (\d+) \(line (\d+) column (\d+)\)/;
function isSyntaxError(error) {
return typeof error === "object" && error !== null && error.name === "SyntaxError" && typeof error.message === "string";
}
function lineColAt(sourceText, offset) {
let line = 1;
let lastNewline = -1;
for (let i = 0; i < offset; i++) {
if (sourceText[i] === "\n") {
line++;
lastNewline = i;
}
}
return { line, col: offset - lastNewline };
}
function parseUnexpectedTokenMessage(message) {
const prefix = "Unexpected token '";
if (!message.startsWith(prefix))
return void 0;
const closeIdx = message.indexOf("', ", prefix.length);
const token = message.slice(prefix.length, closeIdx);
let rest = message.slice(closeIdx + 3);
if (rest.startsWith("..."))
rest = rest.slice(3);
if (!rest.startsWith('"'))
return void 0;
rest = rest.slice(1);
const truncatedSuffix = '"... is not valid JSON';
const plainSuffix = '" is not valid JSON';
const snippet = rest.endsWith(truncatedSuffix) ? rest.slice(0, -truncatedSuffix.length) : rest.endsWith(plainSuffix) ? rest.slice(0, -plainSuffix.length) : void 0;
return snippet === void 0 ? void 0 : { token, snippet };
}
function locateSyntaxErrorOffset(message, sourceText) {
const stated = JSON_PARSE_POSITION.exec(message);
if (stated)
return Number(stated[1]);
const parsed = parseUnexpectedTokenMessage(message);
if (!parsed)
return void 0;
const { token, snippet } = parsed;
const snippetAt = sourceText.indexOf(snippet);
if (snippetAt === -1 || sourceText.indexOf(snippet, snippetAt + 1) !== -1) {
return void 0;
}
const tokenAt = snippet.indexOf(token);
const onlyTokenOccurrence = tokenAt !== -1 && snippet.indexOf(token, tokenAt + 1) === -1;
return snippetAt + (onlyTokenOccurrence ? tokenAt : 0);
}
function normalizeParseErrorReason(error, options = {}) {
if (options.classOnly) {
return errorClassName(error);
}
if (isSyntaxError(error)) {
if (options.sourceText !== void 0) {
const offset = locateSyntaxErrorOffset(error.message, options.sourceText);
if (offset !== void 0) {
const { line, col } = lineColAt(options.sourceText, offset);
return `${error.name} at line ${line} col ${col}`;
}
return error.name;
}
const match = JSON_PARSE_POSITION.exec(error.message);
return match ? `${error.name} at line ${match[2]} col ${match[3]}` : error.name;
}
const message = error instanceof Error ? error.message : String(error);
return redactSecrets(message);
}
function degradationKindFor(suppressedNotice, deprecationNoun) {
if (suppressedNotice)
return SUPPRESSED_NOTICE_KIND;
return deprecationNoun ? DEPRECATED_CONFIG_KIND : IGNORED_CONFIG_KIND;
}
function noticeMessage(options) {
const label = SUBSYSTEM_CONFIG_LABEL[options.subsystem];
if (options.suppressedNotice) {
return `${label} notices for ${options.file} were summarised: ${options.reason}`;
}
return options.deprecationNoun ? `deprecated ${label} ${options.deprecationNoun} in ${options.file}: ${options.reason}` : `ignoring invalid ${label} ${options.file}: ${options.reason}`;
}
function warnIgnoredConfigOnce(options) {
const { subsystem, file, key } = options;
const code = options.code ?? IGNORED_CONFIG_CODE;
const reason = redactSecrets(typeof options.reason === "string" ? options.reason : normalizeParseErrorReason(options.reason.parseError, {
sourceText: options.reason.sourceText
}));
const suppressedNotice = code === SUPPRESSED_NOTICE_CODE;
const deprecationNoun = suppressedNotice ? void 0 : DEPRECATION_NOUN_BY_CODE.get(code);
recordDegradationOnce({
kind: degradationKindFor(suppressedNotice, deprecationNoun),
subject: key ? `${file}\0${key}` : file,
reason,
metadata: { subsystem, configPath: file },
code
});
const latchKey = `${subsystem}\0${file}\0${key ?? ""}\0${reason}`;
if (warnedIgnoredConfigs.has(latchKey))
return;
warnedIgnoredConfigs.add(latchKey);
const message = noticeMessage({
subsystem,
file,
reason,
suppressedNotice,
deprecationNoun
});
logExtension({
subsystem,
level: "warn",
message: withConfigDiagnosticCode(message, code),
metadata: { configPath: file, reason, code, ...key ? { key } : {} }
});
notifyUserDegradation(`pi-lens: ${message}`, "warning", { code });
}
// dist/clients/project-lens-config.js
import * as fs3 from "node:fs";
import * as os2 from "node:os";
import * as path3 from "node:path";
// dist/clients/config-resolve.js
import * as fs2 from "node:fs";
import * as os from "node:os";
import * as path2 from "node:path";
// dist/clients/config-core/provenance.js
var SOURCE_TIERS = [
"builtin",
"global",
"project",
"nested-project",
"env",
"cli",
"host"
];
var TIER_CLASS = {
builtin: "default",
global: "operator",
project: "repo",
"nested-project": "repo",
env: "operator",
cli: "operator",
host: "operator"
};
function tierPrecedence(tier) {
return SOURCE_TIERS.indexOf(tier);
}
function isOperatorTier(tier) {
return TIER_CLASS[tier] === "operator";
}
function provenanceView(resolved, homeDir) {
const entries = [...resolved.provenance.values()].map((entry) => ({
key: entry.key,
tier: entry.tier,
...entry.file === void 0 ? {} : { file: homeRelativePath(entry.file, homeDir) },
...entry.trust === void 0 ? {} : { trust: entry.trust }
})).sort((left, right) => compareKeys(left.key, right.key));
return { entries };
}
function provenanceFor(resolved, pointer) {
let candidate = pointer;
for (; ; ) {
const entry = resolved.provenance.get(candidate);
if (entry)
return entry;
if (candidate === "")
return void 0;
const cut = candidate.lastIndexOf("/");
candidate = cut <= 0 ? "" : candidate.slice(0, cut);
}
}
function compareKeys(left, right) {
if (left < right)
return -1;
return left > right ? 1 : 0;
}
// dist/clients/config-core/deny.js
function provenanceOfContribution(source, key) {
if (!source)
return void 0;
return {
tier: source.tier,
key,
...source.file === void 0 ? {} : { file: source.file },
...source.trust === void 0 ? {} : { trust: source.trust }
};
}
function denyProvenance(contributions, resolution, key) {
return provenanceOfContribution(contributions[resolution.winner], key);
}
function denyMemberProvenance(contributions, resolution, key) {
const entries = [];
resolution.memberWinners.forEach((index, position) => {
const pointer = `${key}/${position}`;
const entry = provenanceOfContribution(contributions[index], pointer);
if (entry)
entries.push(entry);
});
return entries;
}
var EMPTY_MEMBER_WINNERS = Object.freeze([]);
function byPrecedence(contributions) {
return contributions.map((contribution, index) => ({ contribution, index })).sort((left, right) => tierPrecedence(left.contribution.tier) - tierPrecedence(right.contribution.tier));
}
function resolveBooleanDeny(contributions) {
const ordered = byPrecedence(contributions);
if (ordered.length === 0)
return {
value: void 0,
winner: -1,
denied: false,
memberWinners: EMPTY_MEMBER_WINNERS
};
const denials = ordered.filter((entry) => entry.contribution.value === false);
if (denials.length === 0) {
const last = ordered[ordered.length - 1];
return {
value: last.contribution.value,
winner: last.index,
denied: false,
memberWinners: EMPTY_MEMBER_WINNERS
};
}
const operatorDenial = denials.find((entry) => isOperatorTier(entry.contribution.tier));
if (operatorDenial) {
return {
value: false,
winner: operatorDenial.index,
denied: true,
memberWinners: EMPTY_MEMBER_WINNERS
};
}
const firstDenial = denials[0];
const lift = ordered.find((entry) => entry.contribution.value === true && isOperatorTier(entry.contribution.tier) && tierPrecedence(entry.contribution.tier) > tierPrecedence(firstDenial.contribution.tier));
if (lift) {
return {
value: true,
winner: lift.index,
denied: false,
memberWinners: EMPTY_MEMBER_WINNERS
};
}
return {
value: false,
winner: firstDenial.index,
denied: true,
memberWinners: EMPTY_MEMBER_WINNERS
};
}
function resolveArrayDeny(contributions) {
const ordered = byPrecedence(contributions);
const seen = /* @__PURE__ */ new Set();
const members = [];
const memberWinners = [];
let winner = -1;
for (const entry of ordered) {
const value = entry.contribution.value;
if (!Array.isArray(value))
continue;
for (const member of value) {
const identity = primitiveIdentity(member);
if (identity !== void 0) {
if (seen.has(identity))
continue;
seen.add(identity);
}
members.push(member);
memberWinners.push(entry.index);
if (winner === -1)
winner = entry.index;
}
}
if (winner === -1) {
const last = ordered[ordered.length - 1];
return {
value: members,
winner: last ? last.index : -1,
denied: false,
memberWinners
};
}
return { value: members, winner, denied: members.length > 0, memberWinners };
}
function primitiveIdentity(member) {
if (typeof member === "string")
return `s:${member}`;
if (typeof member === "number" || typeof member === "boolean") {
return `p:${String(member)}`;
}
if (member === null)
return "null";
return void 0;
}
// dist/clients/config-core/safe-object.js
var MAX_CONFIG_DEPTH = 32;
var UNSAFE_CONFIG_KEYS = [
"__proto__",
"constructor",
"prototype"
];
var UNSAFE_KEY_SET = new Set(UNSAFE_CONFIG_KEYS);
function isUnsafeConfigKey(name) {
return UNSAFE_KEY_SET.has(name);
}
var UNSAFE_KEY_REASON = "config key would modify the object prototype; ignored";
function safeAssign(target, name, value) {
if (isUnsafeConfigKey(name))
return false;
Object.defineProperty(target, name, {
value,
writable: true,
enumerable: true,
configurable: true
});
return true;
}
// dist/clients/config-core/records.js
var MAX_MIGRATION_RECORDS = DEGRADATION_ENTRIES_PER_KIND;
var MAX_RECORD_KEY_LENGTH = 120;
var UNBOUNDED_RECORD_CODES = /* @__PURE__ */ new Set(["PILENS_CFG_0008"]);
var SUBJECT_SEPARATOR = String.fromCharCode(0);
function migrationSubject(file, key) {
return key.length > 0 ? `${file}${SUBJECT_SEPARATOR}${key}` : file;
}
function stripControlCharacters(text) {
let out = "";
for (const character of text) {
const code = character.codePointAt(0) ?? 0;
out += code < 32 || code === 127 ? " " : character;
}
return out;
}
function boundedKeyLabel(key) {
const printable = stripControlCharacters(key).trim();
const bounded = printable.length > MAX_RECORD_KEY_LENGTH ? `${printable.slice(0, MAX_RECORD_KEY_LENGTH)}...` : printable;
return redactSecrets(bounded);
}
function jsonTypeName(value) {
if (value === null)
return "null";
if (Array.isArray(value))
return "array";
return typeof value;
}
var UNANCHORED_RECORD_LABEL = "(config resolution)";
var MigrationRecordCollector = class {
kept = [];
dropped;
limit;
/**
* `priorDropped` SEEDS the drop count with records an EARLIER bound already
* discarded before this collector saw the list (#2426 review round 6, F1).
*
* The shared resolution is bounded twice by construction: `resolveConfig`
* runs its own collector across every source, and the loader then finalizes
* what survived plus the deprecation records it composes itself. Only the
* second bound used to be counted, so a `.pi-lens.json` whose 40 refused
* keys were truncated to 19 told the user ONE notice had been suppressed
* when 21 had. A suppression count that undercounts is worse than no count:
* it tells the user the list they were shown is almost complete.
*/
constructor(limit = MAX_MIGRATION_RECORDS, priorDropped = 0) {
this.limit = Math.max(0, Math.trunc(limit));
this.dropped = Math.max(0, Math.trunc(priorDropped));
}
add(record4) {
if (this.kept.length >= this.limit) {
if (UNBOUNDED_RECORD_CODES.has(record4.code)) {
this.kept.push(record4);
return;
}
this.dropped += 1;
return;
}
this.kept.push(record4);
}
/** The retained records, oldest first. */
get records() {
return this.kept;
}
/** How many records the bound discarded. Counted, never silently zero. */
get droppedCount() {
return this.dropped;
}
/**
* The retained records plus, when the bound discarded any, ONE record
* counting them.
*
* The whole of "bound a record list and say so when it truncates" lives
* here, and nowhere else (#2426 review round 5, F-A/F-B/S-A). Three
* producers had grown their own copy of it — the shared resolution, the
* project loader's unknown-key scan, and (by omission, which is how the
* defect showed) the project loader's legacy-document enumeration, which
* shipped its records raw and let one file's key count set the
* notification count. A fourth producer must not have to remember any of
* this: it calls `finalizeRecords` and is bounded.
*
* Two finalized lists that anchor to the SAME file and suppress the SAME
* number of records render one notice, not two. That is the warn-once
* latch doing its documented job: both records state the identical fact
* about the identical file, and the user has one thing to do about it.
*/
finalize(anchor) {
if (this.dropped === 0)
return this.kept;
const file = anchor?.file ?? UNANCHORED_RECORD_LABEL;
return [
...this.kept,
{
code: "PILENS_CFG_0007",
file,
key: "",
subject: migrationSubject(file, ""),
reason: `${this.dropped} further config notices were suppressed by the bound of ${MAX_MIGRATION_RECORDS}`,
...anchor?.tier === void 0 ? {} : { tier: anchor.tier }
}
];
}
};
function finalizeRecords(records, anchor, priorDropped = 0) {
const collector = new MigrationRecordCollector(MAX_MIGRATION_RECORDS - 1, priorDropped);
for (const record4 of records)
collector.add(record4);
return collector.finalize(anchor);
}
// dist/clients/config-core/schema.js
function isConfigObject(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
var MERGE_STRATEGY_KEY = "x-merge-strategy";
var DEFAULT_MERGE_STRATEGY = "replace";
var KEYED_PREFIX = "keyed:";
function isMergeStrategy(value) {
if (typeof value !== "string")
return false;
if (value === "replace" || value === "append")
return true;
return value.startsWith(KEYED_PREFIX) && value.length > KEYED_PREFIX.length;
}
function mergeStrategyOf(node) {
const declared = node?.[MERGE_STRATEGY_KEY];
return isMergeStrategy(declared) ? declared : DEFAULT_MERGE_STRATEGY;
}
function keyedField(strategy) {
return strategy.startsWith(KEYED_PREFIX) ? strategy.slice(KEYED_PREFIX.length) : void 0;
}
var DENY_KEY = "x-deny";
var DENY_POLICIES = ["boolean-false", "array-union"];
function isDenyPolicy(value) {
return typeof value === "string" && DENY_POLICIES.includes(value);
}
function denyPolicyOf(node) {
const declared = node?.[DENY_KEY];
return isDenyPolicy(declared) ? declared : void 0;
}
function isSchemaNode(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isPlainObject(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function schemaType(node) {
const declared = node?.type;
return typeof declared === "string" ? declared : void 0;
}
var SCHEMA_TYPES = [
"object",
"array",
"string",
"number",
"integer",
"boolean",
"null"
];
var SCHEMA_TYPE_SET = new Set(SCHEMA_TYPES);
function isKnownSchemaType(declared) {
return declared !== void 0 && SCHEMA_TYPE_SET.has(declared);
}
function propertySchema(node, name) {
if (!node)
return void 0;
const properties = node.properties;
if (isSchemaNode(properties) && Object.hasOwn(properties, name)) {
const child = properties[name];
if (isSchemaNode(child))
return child;
}
const patterns = node.patternProperties;
if (isSchemaNode(patterns)) {
for (const [pattern, child] of Object.entries(patterns)) {
if (!isSchemaNode(child))
continue;
let matcher;
try {
matcher = new RegExp(pattern);
} catch {
continue;
}
if (matcher.test(name))
return child;
}
}
return void 0;
}
function additionalPropertyPolicy(node) {
const declared = node?.additionalProperties;
if (declared === true)
return { kind: "keep" };
if (isSchemaNode(declared))
return { kind: "validate", schema: declared };
return { kind: "drop" };
}
function itemsSchema(node) {
const declared = node?.items;
return isSchemaNode(declared) ? declared : void 0;
}
// dist/clients/config-core/merge.js
function merge(sources, schema, options = {}) {
const ordered = [...sources].map((source, order) => ({ source, order })).sort((left, right) => {
const byTier = tierPrecedence(left.source.tier) - tierPrecedence(right.source.tier);
return byTier !== 0 ? byTier : left.order - right.order;
}).map((entry, rank) => ({ source: entry.source, rank }));
const contributions = [];
for (const entry of ordered) {
const value2 = entry.source.value;
if (value2 === void 0)
continue;
contributions.push({ source: entry.source, value: value2, rank: entry.rank });
}
const provenance = /* @__PURE__ */ new Map();
const context = {
provenance,
collector: options.collector ?? new MigrationRecordCollector(),
file: options.file ?? ""
};
const value = mergeNode(contributions, schema, "", context, 0);
return { value, provenance };
}
function record(context, entry) {
const key = boundedKeyLabel(entry.key);
context.collector.add({
code: entry.code,
file: context.file,
key,
subject: migrationSubject(context.file, key),
reason: entry.reason
});
}
function provenanceOf(contribution, key) {
const { source } = contribution;
return {
tier: source.tier,
key,
...source.file === void 0 ? {} : { file: source.file },
...source.trust === void 0 ? {} : { trust: source.trust }
};
}
function stamp(context, contribution, key) {
context.provenance.set(key, provenanceOf(contribution, key));
}
function mergeNode(contributions, schema, key, context, depth) {
if (contributions.length === 0)
return void 0;
if (depth > MAX_CONFIG_DEPTH) {
record(context, {
code: "PILENS_CFG_0005",
key,
reason: `config nesting exceeds ${MAX_CONFIG_DEPTH} levels; ignored`
});
return void 0;
}
const denyPolicy = denyPolicyOf(schema);
if (denyPolicy)
return mergeDeny(contributions, denyPolicy, key, context);
if (isObjectNode(schema, contributions)) {
return mergeObject(contributions, schema, key, context, depth);
}
if (isArrayNode(schema, contributions)) {
return mergeArray(contributions, schema, key, context, depth);
}
const winner = contributions[contributions.length - 1];
stamp(context, winner, key);
return winner.value;
}
function isObjectNode(schema, contributions) {
const declared = schemaType(schema);
if (declared === "object")
return true;
if (isKnownSchemaType(declared))
return false;
if (schema && isSchemaNode(schema.properties))
return true;
return contributions.every((entry) => isPlainObject(entry.value));
}
function isArrayNode(schema, contributions) {
const declared = schemaType(schema);
if (declared === "array")
return true;
if (isKnownSchemaType(declared))
return false;
return contributions.every((entry) => Array.isArray(entry.value));
}
function mergeDeny(contributions, policy, key, context) {
const denyContributions = contributions.map((entry2) => ({
tier: entry2.source.tier,
value: entry2.value,
...entry2.source.file === void 0 ? {} : { file: entry2.source.file },
...entry2.source.trust === void 0 ? {} : { trust: entry2.source.trust }
}));
const resolution = policy === "boolean-false" ? resolveBooleanDeny(denyContributions) : resolveArrayDeny(denyContributions);
const entry = denyProvenance(denyContributions, resolution, key);
if (entry)
context.provenance.set(key, entry);
for (const member of denyMemberProvenance(denyContributions, resolution, key)) {
context.provenance.set(member.key, member);
}
return resolution.value;
}
function mergeObject(contributions, schema, key, context, depth) {
const objects = contributions.filter((entry) => isConfigObject(entry.value));
if (objects.length === 0) {
const winner = contributions[contributions.length - 1];
stamp(context, winner, key);
return winner.value;
}
const names = [];
for (const entry of objects) {
for (const name of Object.keys(entry.value)) {
if (names.includes(name))
continue;
if (isUnsafeConfigKey(name)) {
record(context, {
code: "PILENS_CFG_0006",
key: `${key}/${name}`,
reason: UNSAFE_KEY_REASON
});
continue;
}
names.push(name);
}
}
const out = {};
for (const name of names) {
const childKey = `${key}/${name}`;
const childContributions = [];
for (const entry of objects) {
const parent = entry.value;
if (!Object.hasOwn(parent, name))
continue;
childContributions.push({
source: entry.source,
value: parent[name],
rank: entry.rank
});
}
const merged = mergeNode(childContributions, propertySchema(schema, name), childKey, context, depth + 1);
if (merged !== void 0)
safeAssign(out, name, merged);
}
return out;
}
function mergeArray(contributions, schema, key, context, depth) {
const arrays = contributions.filter((entry) => Array.isArray(entry.value));
if (arrays.length === 0) {
const winner2 = contributions[contributions.length - 1];
stamp(context, winner2, key);
return winner2.value;
}
const strategy = mergeStrategyOf(schema);
const keyed = keyedField(strategy);
if (keyed !== void 0) {
return mergeKeyedArray(arrays, schema, keyed, key, context, depth);
}
if (strategy === "append")
return appendArrays(arrays, key, context);
const winner = arrays[arrays.length - 1];
stamp(context, winner, key);
return [...winner.value];
}
function appendArrays(arrays, key, context) {
const out = [];
for (const entry of arrays) {
for (const member of entry.value) {
stamp(context, entry, `${key}/${out.length}`);
out.push(member);
}
}
stamp(context, arrays[0], key);
return out;
}
function mergeKeyedArray(arrays, schema, field, key, context, depth) {
const items = itemsSchema(schema);
const order = [];
const groups = /* @__PURE__ */ new Map();
const unkeyed = [];
for (const entry of arrays) {
for (const member of entry.value) {
const identity = keyIdentity(member, field);
const memberContribution = {
source: entry.source,
value: member,
rank: entry.rank
};
if (identity === void 0) {
unkeyed.push(memberContribution);
continue;
}
const group = groups.get(identity);
if (group) {
group.push(memberContribution);
continue;
}
order.push(identity);
groups.set(identity, [memberContribution]);
}
}
const out = [];
const pushMerged = (group) => {
const merged = mergeNode(group, items, `${key}/${out.length}`, context, depth + 1);
if (merged !== void 0)
out.push(merged);
};
for (const identity of order)
pushMerged(groups.get(identity) ?? []);
for (const entry of unkeyed)
pushMerged([entry]);
stamp(context, arrays[0], key);
return out;
}
function keyIdentity(member, field) {
if (!isConfigObject(member))
return void 0;
const value = member[field];
if (typeof value === "string")
return `s:${value}`;
if (typeof value === "number" && Number.isFinite(value)) {
return `n:${String(value)}`;
}
return void 0;
}
// dist/clients/config-core/normalize.js
var DROPPED = /* @__PURE__ */ Symbol("dropped");
var OPEN_SCHEMA = { additionalProperties: true };
function validate(raw, schema, options = {}) {
const collector = options.collector ?? new MigrationRecordCollector();
const file = options.file ?? "";
const context = {
collector,
file,
tier: options.tier,
path: []
};
let walked;
try {
walked = walk(raw, schema, context, 0, /* @__PURE__ */ new Set());
} catch (error) {
record2(context, {
code: "PILENS_CFG_0005",
key: "",
reason: `config validation failed internally (${errorClassName(error)}); document ignored`
});
walked = DROPPED;
}
return {
value: walked === DROPPED ? void 0 : walked,
records: collector.records,
droppedRecordCount: collector.droppedCount
};
}
function pointerOf(path4) {
return path4.length === 0 ? "" : `/${path4.join("/")}`;
}
function record2(context, entry) {
const key = boundedKeyLabel(entry.key);
context.collector.add({
code: entry.code,
file: context.file,
key,
subject: migrationSubject(context.file, key),
reason: entry.reason,
tier: context.tier
});
}
function walk(value, schema, context, depth, onPath) {
const pointer = pointerOf(context.path);
if (depth > MAX_CONFIG_DEPTH) {
record2(context, {
code: "PILENS_CFG_0005",
key: pointer,
reason: `config nesting exceeds ${MAX_CONFIG_DEPTH} levels; ignored`
});
return DROPPED;
}
if (typeof value === "object" && value !== null) {
if (onPath.has(value)) {
record2(context, {
code: "PILENS_CFG_0005",
key: pointer,
reason: "config value refers to itself; ignored"
});
return DROPPED;
}
}
const declared = schemaType(schema);
if (declared === "object")
return walkObject(value, schema, context, depth, onPath);
if (declared === "array")
return walkArray(value, schema, context, depth, onPath);
if (isKnownSchemaType(declared)) {
return walkScalar(value, schema, context, declared);
}
return walkOpaque(value, schema, context, depth, onPath);
}
function walkOpaque(value, schema, context, depth, onPath) {
if (isPlainObject(value)) {
const effective = claimsChildren(schema) ? schema : OPEN_SCHEMA;
return walkObject(value, effective, context, depth, onPath);
}
if (Array.isArray(value)) {
return walkArray(value, schema, context, depth, onPath);
}
return walkLeaf(value, schema, context);
}
function claimsChildren(schema) {
if (!schema)
return false;
return isSchemaNode(schema.properties) || isSchemaNode(schema.patternProperties) || schema.additionalProperties !== void 0;
}
function walkLeaf(value, schema, context) {
if (!isConfigScalar(value)) {
record2(context, {
code: "PILENS_CFG_0005",
key: pointerOf(context.path),
reason: `config value is not valid JSON data (${jsonTypeName(value)}); ignored`
});
return DROPPED;
}
return checkEnum(value, schema, context) ? value : DROPPED;
}
function isConfigScalar(value) {
if (value === null)
return true;
const type = typeof value;
if (type === "string" || type === "boolean")
return true;
return type === "number" && Number.isFinite(value);
}
function walkObject(value, schema, context, depth, onPath) {
if (!isPlainObject(value)) {
record2(context, {
code: "PILENS_CFG_0005",
key: pointerOf(context.path),
reason: `expected an object, got ${jsonTypeName(value)}; ignored`
});
return DROPPED;
}
const nextPath = onPath.add(value);
const policy = additionalPropertyPolicy(schema);
const out = {};
for (const [name, child] of Object.entries(value)) {
if (rejectUnsafeKey(name, context))
continue;
const childSchema = propertySchema(schema, name);
let effective = childSchema;
if (!childSchema) {
if (policy.kind === "drop") {
context.path.push(name);
record2(context, {
code: "PILENS_CFG_0004",
key: pointerOf(context.path),
reason: "unknown config field; ignored"
});
context.path.pop();
continue;
}
effective = policy.kind === "validate" ? policy.schema : void 0;
}
context.path.push(name);
const walked = walk(child, effective, context, depth + 1, nextPath);
context.path.pop();
if (walked !== DROPPED)
safeAssign(out, name, walked);
}
nextPath.delete(value);
return out;
}
function rejectUnsafeKey(name, context) {
if (!isUnsafeConfigKey(name))
return false;
context.path.push(name);
record2(context, {
code: "PILENS_CFG_0006",
key: pointerOf(context.path),
reason: UNSAFE_KEY_REASON
});
context.path.pop();
return true;
}
function walkArray(value, schema, context, depth, onPath) {
if (!Array.isArray(value)) {
record2(context, {
code: "PILENS_CFG_0005",
key: pointerOf(context.path),
reason: `expected an array, got ${jsonTypeName(value)}; ignored`
});
return DROPPED;
}
const nextPath = onPath.add(value);
const items = itemsSchema(schema);
const out = [];
for (const [index, entry] of value.entries()) {
context.path.push(String(index));
const walked = walk(entry, items, context, depth + 1, nextPath);
context.path.pop();
if (walked !== DROPPED)
out.push(walked);
}
nextPath.delete(value);
return out;
}
var SCALAR_CHECKS = {
string: (value) => typeof value === "string",
number: (value) => typeof value === "number" && Number.isFinite(value),
integer: (value) => typeof value === "number" && Number.isInteger(value),
boolean: (value) => typeof value === "boolean",
null: (value) => value === null
};
function walkScalar(value, schema, context, declared) {
const check = SCALAR_CHECKS[declared];
if (!check(value)) {
record2(context, {
code: "PILENS_CFG_0005",
key: pointerOf(context.path),
reason: `expected ${declared}, got ${jsonTypeName(value)}; ignored`
});
return DROPPED;
}
return checkEnum(value, schema, context) ? value : DROPPED;
}
function checkEnum(value, schema, context) {
const allowed = schema?.enum;
if (!Array.isArray(allowed))
return true;
if (allowed.some((candidate) => Object.is(candidate, value)))
return true;
record2(context, {
code: "PILENS_CFG_0005",
key: pointerOf(context.path),
reason: `value is not one of ${allowed.map((candidate) => JSON.stringify(candidate)).join(", ")}; ignored`
});
return false;
}
// dist/clients/config-core/resolve.js
function anchorSource(sources) {
try {
const last = sources?.[sources.length - 1];
return last === void 0 || typeof last.file !== "string" ? void 0 : { file: last.file, tier: last.tier };
} catch {
return void 0;
}
}
function resolveConfig(options) {
const collector = new MigrationRecordCollector(options.maxRecords);
try {
const normalized = options.sources.map((source) => ({
tier: source.tier,
...source.file === void 0 ? {} : { file: source.file },
...source.trust === void 0 ? {} : { trust: source.trust },
value: validate(source.value, options.schema, {
file: source.file ?? "",
tier: source.tier,
collector
}).value
}));
return {
resolved: merge(normalized, options.schema, { collector }),
records: collector.records,
droppedRecordCount: collector.droppedCount
};
} catch (error) {
const anchor = anchorSource(options.sources);
const file = anchor?.file ?? "";
collector.add({
code: "PILENS_CFG_0008",
file,
key: "",
subject: migrationSubject(file, ""),
reason: `config resolution failed internally (${errorClassName(error)}); configuration ignored`,
...anchor ? { tier: anchor.tier } : {}
});
return {
// The empty resolution, built by the merger from no sources rather than
// asserted into existence: `merge([])` is already "nothing resolved".
resolved: merge([], options.schema),
records: collector.records,
droppedRecordCount: collector.droppedCount
};
}
}
// dist/clients/config-schema.js
var CONFIG_SCHEMA_DIALECT = "https://json-schema.org/draft/2020-12/schema";
function opaque(tier) {
return { [STABILITY_TIER_KEY]: tier };
}
var LSP_KEY_TYPES = {
servers: "object",
serverOverrides: "object",
disabledServers: "array",
warmFiles: "array"
};
var LSP_KEY_DENY = {
disabledServers: "array-union"
};
function denyAnnotation(key) {
const policy = LSP_KEY_DENY[key];
return policy === void 0 ? {} : { [DENY_KEY]: policy };
}
function lspServerEntryNode() {
return {
type: "object",
additionalProperties: true,
[STABILITY_TIER_KEY]: "experimental",
properties: {
covers: {
type: "array",
items: { type: "string", [STABILITY_TIER_KEY]: "experimental" },
[MERGE_STRATEGY_KEY]: "replace",
[STABILITY_TIER_KEY]: "experimental"
}
}
};
}
function lspNamespace() {
const properties = {
// Reserved but deliberately UNTYPED. `lens-config.ts` already rejects a
// non-boolean here with `lsp.enabled must be a boolean` — a message that
// names the key and the expected type — and it is the one every flag in
// the registry shares. Declaring `type: "boolean"` would make `validate()`
// drop the value first and replace that message with the core's generic
// `expected boolean, got string`, which is strictly less useful. The flag
// sections move into the schema when their diagnostics do, not before.
enabled: { [STABILITY_TIER_KEY]: "stable" }
};
for (const key of LEGACY_ROOT_LSP_KEYS) {
const declared = LSP_KEY_TYPES[key];
properties[key] = {
...declared ? { type: declared } : {},
[STABILITY_TIER_KEY]: "experimental",
...declared === "object" ? key === "servers" ? (
// #3968: server entries are TYPED — each carries the covers
// claim node — while staying open on every other field.
{ additionalProperties: lspServerEntryNode() }
) : { additionalProperties: true } : {},
...denyAnnotation(key)
};
}
return {
type: "object",
additionalProperties: true,
[STABILITY_TIER_KEY]: "stable",
properties
};
}
function buildConfigSchema() {
const properties = {};
for (const key of [
...flagConfigSectionKeys(LENS_FLAGS),
...GLOBAL_NON_FLAG_CONFIG_SECTIONS,
...PROJECT_NON_FLAG_CONFIG_SECTIONS
]) {
properties[key] = opaque("stable");
}
properties.tools = {
type: "object",
additionalProperties: true,
properties: Object.fromEntries(LENS_TOOL_NAMES.map((name) => [
name,
{
type: "object",
additionalProperties: true,
properties: {
enabled: { type: "boolean", [STABILITY_TIER_KEY]: "experimental" }
},
[STABILITY_TIER_KEY]: "experimental"
}
])),
[STABILITY_TIER_KEY]: "stable"
};
for (const key of PROJECT_FOREIGN_CONFIG_NAMESPACES) {
properties[key] ??= opaque("experimental");
}
for (const key of [
"knip",
"jscpd",
"madge",
"gitleaks",
"govulncheck",
"deadCode",
"complexity"
]) {
properties[key] = {
type: "object",
additionalProperties: true,
properties: {
enabled: { type: "boolean", [STABILITY_TIER_KEY]: "experimental" }
},
[STABILITY_TIER_KEY]: "stable"
};
}
properties.startup = {
type: "object",
additionalProperties: true,
properties: {
mode: {
type: "string",
enum: ["quick", "full", "minimal"],
[STABILITY_TIER_KEY]: "experimental"
},
scans: {
type: "object",
additionalProperties: true,
properties: {
enabled: { type: "boolean", [STABILITY_TIER_KEY]: "experimental" }
},
[STABILITY_TIER_KEY]: "experimental"
}
},
[STABILITY_TIER_KEY]: "stable"
};
for (const key of LEGACY_ROOT_LSP_KEYS) {
properties[key] = opaque("experimental");
}
properties[LSP_NAMESPACE_KEY] = lspNamespace();
properties[CONFIG_SCHEMA_ANCHOR_KEY] = {
type: "string",
description: "Identity anchor naming the schema version this config was written against.",
[STABILITY_TIER_KEY]: "stable"
};
return {
$schema: CONFIG_SCHEMA_DIALECT,
$id: CONFIG_SCHEMA_ID,
type: "object",
// See shape 1 in the module doc: deliberately open, so the loaders keep
// their own richer unknown-key diagnostics.
additionalProperties: true,
properties
};
}
var PI_LENS_CONFIG_SCHEMA = buildConfigSchema();
// dist/clients/config-resolve.js
function readConfigDocument(file) {
let text;
try {
text = fs2.readFileSync(file, "utf-8");
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
return { status: "missing" };
}
return { status: "error", error };
}
try {
return { status: "ok", value: JSON.parse(text) };
} catch (error) {
return { status: "error", error, sourceText: text };
}
}
function canonicalGlobalFile(options) {
if (options.globalConfigPath !== void 0)
return options.globalConfigPath;
return options.globalDir === void 0 ? void 0 : path2.join(options.globalDir, CANONICAL_GLOBAL_CONFIG_FILE);
}
function collectDocuments(dir, locations, tier, onReadError) {
const documents = [];
for (const location of locations) {
const file = path2.join(dir, location.relativePath);
if (tier !== "global" && isResolvedGlobalConfigPath(file))
continue;
const outcome = readConfigDocument(file);
if (outcome.status === "missing")
continue;
if (outcome.status === "error") {
onReadError?.({
file,
location,
tier,
error: outcome.error,
sourceText: outcome.sourceText
});
continue;
}
documents.push({ tier, file, location, value: outcome.value });
}
return documents;
}
function collectPiLensConfigDocuments(options) {
const documents = [];
const { globalDir, onReadError } = options;
const globalConfigPath = canonicalGlobalFile(options);
if (globalDir !== void 0 || globalConfigPath !== void 0) {
for (const location of GLOBAL_CONFIG_LOCATIONS) {
const file = location.relativePath === CANONICAL_GLOBAL_CONFIG_FILE ? globalConfigPath : globalDir === void 0 ? void 0 : path2.join(globalDir, location.relativePath);
if (file === void 0)
continue;
const outcome = readConfigDocument(file);
if (outcome.status === "missing")
continue;
if (outcome.status === "error") {
onReadError?.({
file,
location,
tier: "global",
error: outcome.error,
sourceText: outcome.sourceText
});
continue;
}
documents.push({ tier: "global", file, location, value: outcome.value });
}
}
if (options.cwd !== void 0) {
const dirs = configSearchDirs(options.cwd, options.homeDir ?? os.homedir()).reverse();
const bearing = dirs.map((dir) => ({
dir,
found: collectDocuments(dir, PROJECT_CONFIG_LOCATIONS, "project", onReadError)
})).filter((entry) => entry.found.length > 0);
bearing.forEach((entry, index) => {
const tier = index === 0 ? "project" : "nested-project";
for (const document of entry.found)
documents.push({ ...document, tier });
});
}
return documents;
}
function emptyTierCounts() {
return Object.fromEntries(SOURCE_TIERS.map((tier) => [tier, 0]));
}
function summarizeConfigResolution(resolution, homeDir) {
const countsByTier = emptyTierCounts();
for (const entry of resolution.provenance.values()) {
countsByTier[entry.tier] += 1;
}
return {
documents: resolution.documents.map((document) => ({
tier: document.tier,
file: homeRelativePath(document.file, homeDir),
legacy: document.location.legacy
})),
countsByTier,
recordCount: resolution.records.length
};
}
function resolvePiLensConfig(options) {
const documents = collectPiLensConfigDocuments(options);
const sources = configSources(documents);
const resolution = resolveConfig({
sources,
schema: PI_LENS_CONFIG_SCHEMA
});
return {
value: resolution.resolved.value ?? {},
provenance: resolution.resolved.provenance,
records: finalizeRecords(
[
...resolution.records,
...deprecationRecords(documents, options.homeDir ?? os.homedir(), canonicalGlobalFile(options))
],
documentAnchor(documents),
// The core ALREADY truncated (#2426 review round 6, F1). Its drop count
// is seeded here so the one `PILENS_CFG_0007` a user reads names the
// whole truncation rather than the part this bound performed.
resolution.droppedRecordCount
),
documents
};
}
function resolveOnePiLensConfigDocument(document, homeDir = os.homedir()) {
const resolution = resolveConfig({
sources: [
{ tier: document.tier, file: document.file, value: document.value }
],
schema: PI_LENS_CONFIG_SCHEMA
});
return {
value: resolution.resolved.value ?? {},
records: finalizeRecords(
[...resolution.records, ...deprecationRecords([document], homeDir)],
documentAnchor([document]),
// Same double bound as the multi-document path above (round 6, F1).
resolution.droppedRecordCount
)
};
}
function documentAnchor(documents) {
const last = documents[documents.length - 1];
return last === void 0 ? void 0 : { file: last.file, tier: last.tier };
}
function projectLocationFor(file) {
const base = path2.basename(file);
return PROJECT_CONFIG_LOCATIONS.find((candidate) => path2.posix.basename(candidate.relativePath) === base) ?? PROJECT_CONFIG_LOCATIONS[PROJECT_CONFIG_LOCATIONS.length - 1];
}
var WINDOW_BY_SURFACE = new Map(DEPRECATED_CONFIG_SURFACES.map((row) => [row.surface, row]));
function windowSuffix(surface) {
const row = surface === void 0 ? void 0 : WINDOW_BY_SURFACE.get(surface);
return row ? ` (deprecated since ${row.deprecatedSince}; read for the last time before ${row.removeNotBefore})` : "";
}
function canonicalDestination(document, homeDir, globalFile) {
if (document.tier === "global") {
return homeRelativePath(globalFile ?? path2.join(path2.dirname(document.file), CANONICAL_GLOBAL_CONFIG_FILE), homeDir);
}
return CANONICAL_PROJECT_CONFIG_FILE;
}
function lspNamespaceKeyFor(document, key) {
if (key === LSP_NAMESPACE_KEY)
return void 0;
if (document.location.lspScoped) {
return recognizedKeysFor(document.location).has(key) ? key : void 0;
}
return LEGACY_ROOT_LSP_KEYS.includes(key) ? key : void 0;
}
function assignRaw(target, key, value) {
Object.defineProperty(target, key, {
value,
writable: true,
enumerable: true,
configurable: true
});
}
function configSources(documents) {
const sources = [];
for (const document of documents) {
const value = document.value;
const namespaced = {};
const rest = {};
for (const key of topLevelKeys(value)) {
const entry = value[key];
const target = lspNamespaceKeyFor(document, key);
if (target === void 0)
assignRaw(rest, key, entry);
else
assignRaw(namespaced, target, entry);
}
const base = { tier: document.tier, file: document.file };
if (Object.keys(namespaced).length === 0) {
sources.push({ ...base, value });
continue;
}
sources.push({ ...base, value: { [LSP_NAMESPACE_KEY]: namespaced } });
sources.push({ ...base, value: rest });
}
return sources;
}
function canonicalKeyFor(document, key) {
const namespaced = lspNamespaceKeyFor(document, key);
return namespaced === void 0 ? key : `${LSP_NAMESPACE_KEY}.${namespaced}`;
}
function topLevelKeys(value) {
return value && typeof value === "object" && !Array.isArray(value) ? Object.keys(value) : [];
}
function schemaPropertyKeys(node) {
const properties = node?.properties;
return properties !== null && typeof properties === "object" && !Array.isArray(properties) ? Object.keys(properties) : [];
}
var CANONICAL_TOP_LEVEL_KEYS = new Set(schemaPropertyKeys(PI_LENS_CONFIG_SCHEMA));
var LSP_DOCUMENT_KEYS = new Set(schemaPropertyKeys(PI_LENS_CONFIG_SCHEMA.properties?.[LSP_NAMESPACE_KEY]));
function recognizedKeysFor(location) {
return location.lspScoped ? LSP_DOCUMENT_KEYS : CANONICAL_TOP_LEVEL_KEYS;
}
function deprecationRecords(documents, homeDir, globalFile) {
const records = [];
for (const document of documents) {
if (document.location.legacy) {
const destination = canonicalDestination(document, homeDir, globalFile);
const recognized = recognizedKeysFor(document.location);
let unrecognized = 0;
for (const key of topLevelKeys(document.value)) {
if (!recognized.has(key)) {
unrecognized += 1;
continue;
}
const canonicalKey = canonicalKeyFor(document, key);
records.push(record3({
code: "PILENS_CFG_0003",
file: document.file,
key,
canonicalKey,
tier: document.tier,
reason: `move "${key}" to ${destination}` + (canonicalKey === key ? "" : ` under "${canonicalKey}"`) + windowSuffix(document.location.surface)
}));
}
if (unrecognized > 0) {
const plural = unrecognized === 1 ? "it was" : "they were";
records.push({
code: "PILENS_CFG_0003",
file: document.file,
key: "",
// An LSP-scoped legacy file moves WHOLESALE into the `lsp`
// namespace, so naming that namespace is literally true here — and
// it is what routes this record to the LSP loader's subsystem, the
// same key-derived ownership every other record uses.
...document.location.lspScoped ? { canonicalKey: LSP_NAMESPACE_KEY } : {},
subject: migrationSubject(document.file, ""),
tier: document.tier,
reason: `this file is at a deprecated location; move it to ${destination}. ` + (unrecognized === 1 ? "1 of its top-level keys is not a recognized pi-lens setting" : `${unrecognized} of its top-level keys are not recognized pi-lens settings`) + `, and ${plural} not migrated${windowSuffix(document.location.surface)}`
});
}
continue;
}
for (const key of topLevelKeys(document.value)) {
if (!LEGACY_ROOT_LSP_KEYS.includes(key))
continue;
const canonicalKey = `${LSP_NAMESPACE_KEY}.${key}`;
records.push(record3({
code: "PILENS_CFG_0002",
file: document.file,
key,
canonicalKey,
tier: document.tier,
reason: `move "${key}" to "${canonicalKey}" in the same file${windowSuffix(key)}`
}));
}
}
return records;
}
function record3(input) {
return {
code: input.code,
file: input.file,
key: input.key,
canonicalKey: input.canonicalKey,
subject: migrationSubject(input.file, input.key),
reason: input.reason,
tier: input.tier
};
}
function configRecordOwner(record4) {
const key = record4.canonicalKey ?? record4.key;
if (key.length === 0)
return void 0;
const head = (key.startsWith("/") ? key.slice(1) : key).split(/[./]/)[0] ?? "";
if (head.length === 0)
return void 0;
return head === LSP_NAMESPACE_KEY || LEGACY_ROOT_LSP_KEYS.includes(head) ? "lsp" : "pi-lens";
}
function piLensSubsystemFor(tier) {
if (tier === "global")
return "lens-config";
if (tier === "project" || tier === "nested-project") {
return "project-lens-config";
}
return void 0;
}
function configRecordSubsystems(record4) {
const owner = configRecordOwner(record4);
const piLens = piLensSubsystemFor(record4.tier);
const piLensSubsystems = piLens ? [piLens] : ["lens-config", "project-lens-config"];
if (owner === "lsp")
return ["lsp-config"];
if (owner === "pi-lens")
return piLensSubsystems;
if (piLens)
return [piLens];
return ["lsp-config", ...piLensSubsystems];
}
function configFileSubsystem(location, tier) {
if (location.lspScoped)
return "lsp-config";
return tier === "global" ? "lens-config" : "project-lens-config";
}
function reportConfigReadFailure(failure) {
warnIgnoredConfigOnce({
subsystem: configFileSubsystem(failure.location, failure.tier),
file: failure.file,
reason: { parseError: failure.error, sourceText: failure.sourceText }
});
}
function reportPiLensConfigRecords(records) {
for (const record4 of records) {
for (const subsystem of configRecordSubsystems(record4)) {
warnIgnoredConfigOnce({
subsystem,
file: record4.file,
key: record4.key.length > 0 ? record4.key : void 0,
reason: record4.reason,
code: record4.code
});
}
}
}
function ignoredRecordCollector(configPath, tier) {
const noted = [];
const note = (reason, code = "PILENS_CFG_0001") => {
let notedRecord = {
code: "PILENS_CFG_0001",
file: configPath,
key: "",
subject: migrationSubject(configPath, ""),
reason: typeof reason === "string" ? reason : normalizeParseErrorReason(reason.parseError, {
sourceText: reason.sourceText
}),
tier
};
if (code !== "PILENS_CFG_0001")
notedRecord.code = code;
noted.push(notedRecord);
};
return {
note,
records: () => finalizeRecords(noted, { file: configPath, tier })
};
}
function lspSectionOf(value) {
const namespace = value[LSP_NAMESPACE_KEY];
return namespace && typeof namespace === "object" && !Array.isArray(namespace) ? namespace : {};
}
// dist/clients/project-lens-config.js
var REVIEW_GRAPH_MAX_FILES_MIN = 100;
var REVIEW_GRAPH_MAX_FILES_MAX = 2e4;
var EMPTY_PROJECT_CONFIG = {
ignore: [],
rules: {},
maxProjectFiles: void 0,
reviewGraph: void 0,
raw: void 0,
configPath: void 0
};
var configCache = /* @__PURE__ */ new Map();
var discoveryCache = /* @__PURE__ */ new Map();
function loadCachedConfigFile(info) {
const cached = configCache.get(info.path);
if (cached && cached.mtimeMs === info.mtimeMs && cached.size === info.size) {
replayConfigNotices(cached);
return cached.config;
}
const parsed = parseConfigFile(info.path);
const entry = {
mtimeMs: info.mtimeMs,
size: info.size,
config: parsed.config,
records: parsed.records,
ignored: parsed.ignored
};
configCache.set(info.path, entry);
replayConfigNotices(entry);
return parsed.config;
}
function replayConfigNotices(entry) {
reportPiLensConfigRecords(entry.records);
for (const record4 of entry.ignored) {
warnIgnoredConfigOnce({
subsystem: "project-lens-config",
file: record4.file,
reason: record4.reason,
code: record4.code
});
}
}
function loadPiLensProjectConfig(startDir, preloadedInfo) {
const entry = discoveryEntryFor(startDir);
const configInfo = preloadedInfo ?? freshInfoFor(entry);
const selectedLegacyPath = configInfo?.path;
reportPiLensConfigRecords(entry.legacyRecords.filter((record4) => record4.file !== selectedLegacyPath));
if (!configInfo)
return EMPTY_PROJECT_CONFIG;
return loadCachedConfigFile(configInfo);
}
function findPiLensConfigInDir(dir) {
const marker = findPiLensConfigMarkerInDir(dir);
if (!marker)
return void 0;
return {
path: marker.path,
dir: marker.dir,
mtimeMs: marker.mtimeMs,
size: marker.size
};
}
function findNestedProjectMutationValue(spec, editedFilePath, projectRoot, homeDir = os2.homedir()) {
const root = path3.resolve(projectRoot);
const start = path3.dirname(path3.resolve(editedFilePath));
for (const dir of walkUpDirs(start)) {
if (isAtOrAboveHomeDir(dir, homeDir))
break;
const rel = path3.relative(root, dir);
if (rel.startsWith("..") || path3.isAbsolute(rel))
break;
const config = loadPiLensConfigInDir(dir);
const enabled = readFlagConfigValue(config, spec.configKey);
if (enabled !== void 0)
return { value: enabled, dir };
if (dir === root)
break;
}
return void 0;
}
function loadPiLensConfigInDir(dir) {
const info = findPiLensConfigInDir(dir);
if (!info)
return EMPTY_PROJECT_CONFIG;
return loadCachedConfigFile(info);
}
function discoveryEntryFor(startDir) {
const cacheKey = path3.resolve(startDir);
const cached = discoveryCache.get(cacheKey);
if (cached && discoveryCacheStillFresh(cached))
return cached;
const discovered = discoverPiLensProjectConfig(cacheKey);
discoveryCache.set(cacheKey, discovered);
return discovered;
}
function findPiLensProjectConfig(startDir) {
return freshInfoFor(discoveryEntryFor(startDir));
}
function freshInfoFor(entry) {
if (!entry.info)
return void 0;
const stat = safeFileStat(entry.info.path);
if (!stat?.isFile())
return entry.info;
return { ...entry.info, mtimeMs: stat.mtimeMs, size: stat.size };
}
function safeFileStat(filePath) {
try {
return fs3.statSync(filePath);
} catch {
return void 0;
}
}
function safeDirMtimeMs(dir) {
try {
return fs3.statSync(dir).mtimeMs;
} catch {
return -1;
}
}
function discoveryCacheStillFresh(entry) {
if (!dirMtimesStillFresh(entry.dirMtimes)) {
return false;
}
if (entry.info === void 0 && Date.now() - entry.checkedAtMs >= FRESHNESS_CADENCE_MS) {
return false;
}
return entry.legacySources.every((source) => {
const stat = safeFileStat(source.path);
return stat?.isFile() === true && stat.mtimeMs === source.mtimeMs && stat.size === source.size;
});
}
var LEGACY_PROJECT_LOCATIONS = PROJECT_CONFIG_LOCATIONS.filter((location) => location.legacy);
function discoverPiLensProjectConfig(startDir) {
const dirMtimes = [];
const legacyDocuments = [];
const legacySources = [];
let info;
for (const dir of configSearchDirs(startDir)) {
dirMtimes.push({ dir, mtimeMs: safeDirMtimeMs(dir) });
if (!info) {
for (const name of PROJECT_CONFIG_BASENAMES) {
const candidate = path3.join(dir, name);
if (isResolvedGlobalConfigPath(candidate))
continue;
const stat = safeFileStat(candidate);
if (stat?.isFile()) {
info = {
path: candidate,
dir,
mtimeMs: stat.mtimeMs,
size: stat.size
};
break;
}
}
}
for (const location of LEGACY_PROJECT_LOCATIONS) {
const file = path3.join(dir, location.relativePath);
if (isResolvedGlobalConfigPath(file))
continue;
const stat = safeFileStat(file);
if (!stat?.isFile())
continue;
legacySources.push({
path: file,
mtimeMs: stat.mtimeMs,
size: stat.size
});
const outcome = readConfigDocument(file);
if (outcome.status === "error") {
reportConfigReadFailure({
file,
location,
tier: "project",
error: outcome.error,
sourceText: outcome.sourceText
});
continue;
}
if (outcome.status !== "ok")
continue;
legacyDocuments.push({
tier: "project",
file,
location,
value: outcome.value
});
}
}
return {
info,
dirMtimes: bearingDirMtimes(dirMtimes, info),
legacyRecords: legacyDeprecationRecords(legacyDocuments),
legacySources,
checkedAtMs: Date.now()
};
}
function legacyDeprecationRecords(documents) {
const home = os2.homedir();
return documents.flatMap((document) => finalizeRecords(deprecationRecords([document], home), {
file: document.file,
tier: document.tier
}));
}
function bearingDirMtimes(dirMtimes, info) {
if (info) {
const bearing = dirMtimes.findIndex((entry) => entry.dir === info.dir);
return bearing < 0 ? [...dirMtimes] : dirMtimes.slice(0, bearing + 1);
}
return dirMtimes.slice(0, 1);
}
function parseRulePolicyList(note, ruleId, key, value) {
if (!Array.isArray(value)) {
note(`rules.${ruleId}.${key} must be an array of strings`);
return { list: [], invalid: true };
}
const list = [];
for (const entry of value) {
if (typeof entry !== "string")
continue;
const trimmed = entry.trim();
if (trimmed.length > 0)
list.push(trimmed);
}
if (list.length === 0) {
if (value.length > 0) {
note(`rules.${ruleId}.${key} must contain at least one non-empty string`);
return { list: [], invalid: true };
}
return { list: [], invalid: false };
}
return { list, invalid: false };
}
var NO_RECORDS = [];
function parseConfigFile(configPath) {
const { note, records: ignoredRecords } = ignoredRecordCollector(configPath, "project");
const outcome = readConfigDocument(configPath);
if (outcome.status === "error") {
note({ parseError: outcome.error, sourceText: outcome.sourceText });
return {
config: EMPTY_PROJECT_CONFIG,
records: NO_RECORDS,
ignored: ignoredRecords()
};
}
if (outcome.status === "missing") {
return {
config: EMPTY_PROJECT_CONFIG,
records: NO_RECORDS,
ignored: NO_RECORDS
};
}
if (!outcome.value || typeof outcome.value !== "object" || Array.isArray(outcome.value)) {
note("top-level value must be an object");
return {
config: EMPTY_PROJECT_CONFIG,
records: NO_RECORDS,
ignored: ignoredRecords()
};
}
const resolved = resolveOnePiLensConfigDocument({
tier: "project",
file: configPath,
location: projectLocationFor(configPath),
value: outcome.value
});
const raw = resolved.value;
const obj = resolved.value;
const ignore = Array.isArray(obj.ignore) ? obj.ignore.filter((p) => typeof p === "string") : [];
const mutations = {};
for (const spec of PROJECT_SCOPED_LENS_FLAGS) {
assignFlagConfigSection(obj, mutations, spec.configKey, note);
}
const toolConfig = readToolConfig(obj, note);
const rules = {};
if (obj.rules && typeof obj.rules === "object" && !Array.isArray(obj.rules)) {
const rawRules = obj.rules;
for (const [ruleId, ruleCfg] of Object.entries(rawRules)) {
if (!ruleCfg || typeof ruleCfg !== "object" || Array.isArray(ruleCfg)) {
note(`rules.${ruleId} must be an object with threshold, disable, or select; ignored`);
continue;
}
const r = ruleCfg;
const entry = {};
if (typeof r.threshold === "number" && Number.isFinite(r.threshold) && r.threshold > 0) {
entry.threshold = r.threshold;
} else if ("threshold" in r) {
note(`rules.${ruleId}.threshold must be a positive finite number`);
}
if ("disable" in r) {
const parsed = parseRulePolicyList(note, ruleId, "disable", r.disable);
if (!parsed.invalid && parsed.list.length > 0)
entry.disable = parsed.list;
}
if ("select" in r) {
const parsed = parseRulePolicyList(note, ruleId, "select", r.select);
if (!parsed.invalid && parsed.list.length > 0)
entry.select = parsed.list;
}
if (entry.threshold !== void 0 || entry.disable || entry.select) {
rules[ruleId] = entry;
} else if (!("threshold" in r) && !("disable" in r) && !("select" in r)) {
note(`rules.${ruleId} has no recognized setting (threshold, disable, select); ignored`);
}
}
}
let maxProjectFiles;
if ("maxProjectFiles" in obj) {
if (typeof obj.maxProjectFiles === "number" && Number.isFinite(obj.maxProjectFiles) && obj.maxProjectFiles > 0) {
maxProjectFiles = obj.maxProjectFiles;
} else {
note("maxProjectFiles must be a positive finite number");
}
}
let reviewGraph;
if (obj.reviewGraph !== void 0) {
if (!obj.reviewGraph || typeof obj.reviewGraph !== "object" || Array.isArray(obj.reviewGraph)) {
note("reviewGraph must be an object");
} else {
const rg = obj.reviewGraph;
if ("maxFiles" in rg) {
const parsed = toPositiveFinite(rg.maxFiles);
if (parsed > 0) {
const clamped = Math.min(REVIEW_GRAPH_MAX_FILES_MAX, Math.max(REVIEW_GRAPH_MAX_FILES_MIN, Math.floor(parsed)));
reviewGraph = { maxFiles: clamped };
} else {
note("reviewGraph.maxFiles must be a positive finite number");
}
}
}
}
let startup;
if (obj.startup !== void 0) {
if (!obj.startup || typeof obj.startup !== "object" || Array.isArray(obj.startup)) {
note("startup must be an object");
} else {
const section = obj.startup;
if (section.mode === "quick" || section.mode === "full" || section.mode === "minimal") {
startup = { mode: section.mode };
} else if ("mode" in section) {
note('startup.mode must be "quick", "full", or "minimal"');
}
if (section.scans !== void 0) {
if (!section.scans || typeof section.scans !== "object" || Array.isArray(section.scans)) {
note("startup.scans must be an object");
} else if (typeof section.scans.enabled === "boolean") {
startup ??= {};
startup.scans = {
enabled: section.scans.enabled
};
} else if ("enabled" in section.scans) {
note("startup.scans.enabled must be a boolean");
}
}
}
}
const knownProjectKeys = /* @__PURE__ */ new Set([
...PROJECT_NON_FLAG_CONFIG_SECTIONS,
...flagConfigSectionKeys(PROJECT_SCOPED_LENS_FLAGS),
...PROJECT_FOREIGN_CONFIG_NAMESPACES
]);
const globalScopeOnlyKeys = new Set([
...flagConfigSectionKeys(LENS_FLAGS),
...GLOBAL_NON_FLAG_CONFIG_SECTIONS
].filter((key) => !knownProjectKeys.has(key)));
const projectScoped = new Set(PROJECT_SCOPED_LENS_FLAGS.map((spec) => spec.configKey));
const globalScopeOnlyFlagKeys = LENS_FLAGS.map((spec) => spec.configKey).filter((configKey) => !projectScoped.has(configKey));
const globalScopeOnlyDottedKeys = [
...globalScopeOnlyFlagKeys,
...GLOBAL_ONLY_NON_FLAG_KEYS
];
const globalConfigLabel = homeRelativePath(getPiLensGlobalConfigPath(), os2.homedir());
const warnUnhonoredKey = (label, globalOnly) => {
if (!globalOnly) {
note(`unknown key "${label}" is not a recognized pi-lens setting (check for a typo); ignored`);
return;
}
const hasCliFlag = LENS_FLAGS.some(({ configKey }) => configKey === label || configKey.startsWith(`${label}.`));
const cliClause = hasCliFlag ? " or pass the matching CLI flag" : "";
note(`"${label}" is a global-only pi-lens setting and is not honored in a project .pi-lens.json (set it in ${globalConfigLabel}${cliClause}); ignored`);
};
for (const key of Object.keys(obj)) {
if (knownProjectKeys.has(key))
continue;
warnUnhonoredKey(key, globalScopeOnlyKeys.has(key));
}
for (const [namespace, honored] of PROJECT_FOREIGN_NAMESPACE_HONORED_KEYS) {
const section = obj[namespace];
if (!section || typeof section !== "object" || Array.isArray(section)) {
continue;
}
for (const key of Object.keys(section)) {
if (honored.includes(key))
continue;
const dotted = `${namespace}.${key}`;
warnUnhonoredKey(dotted, globalScopeOnlyDottedKeys.some((configKey) => configKey === dotted || configKey.startsWith(`${dotted}.`)));
}
}
for (const configKey of globalScopeOnlyDottedKeys) {
const dotIndex = configKey.indexOf(".");
if (dotIndex < 0)
continue;
if (!knownProjectKeys.has(configKey.slice(0, dotIndex)))
continue;
if (!hasFlagConfigPath(obj, configKey))
continue;
warnUnhonoredKey(configKey, true);
}
return {
config: {
...toolConfig === void 0 ? {} : { tools: toolConfig },
...startup === void 0 ? {} : { startup },
ignore,
rules,
...mutations,
maxProjectFiles,
reviewGraph,
raw,
configPath
},
records: resolved.records,
ignored: ignoredRecords()
};
}
export {
isGeneratedArtifactDirectoryName,
isDeclarationFile,
classifyGeneratedOrArtifactDetailed,
isTestFileName,
detectFileRole,
notifyUserDegradation,
warnIgnoredConfigOnce,
provenanceView,
provenanceFor,
compareKeys,
migrationSubject,
readConfigDocument,
summarizeConfigResolution,
resolvePiLensConfig,
resolveOnePiLensConfigDocument,
reportConfigReadFailure,
reportPiLensConfigRecords,
ignoredRecordCollector,
lspSectionOf,
loadPiLensProjectConfig,
findPiLensConfigInDir,
findNestedProjectMutationValue,
loadPiLensConfigInDir,
findPiLensProjectConfig
};