codebase-snapshot 0.9.12 → 0.9.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +379 -0
- package/README.md +1 -1
- package/dist/bin.js +1313 -633
- package/dist/{chunk-3H45YMBG.js → chunk-DUZK5VL2.js} +1413 -767
- package/dist/{chunk-5OR3WVPW.js → chunk-J4W74N4Y.js} +144 -44
- package/dist/cli.cjs +1305 -628
- package/dist/cli.d.cts +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +43 -54
- package/dist/{generate-worker-D5Xgh27c.d.cts → generate-worker-D_mUjgXw.d.cts} +21 -5
- package/dist/{generate-worker-D5Xgh27c.d.ts → generate-worker-D_mUjgXw.d.ts} +21 -5
- package/dist/index.cjs +1389 -760
- package/dist/index.d.cts +58 -35
- package/dist/index.d.ts +58 -35
- package/dist/index.js +1 -1
- package/dist/mcp.cjs +973 -405
- package/dist/mcp.d.cts +67 -67
- package/dist/mcp.d.ts +67 -67
- package/dist/mcp.js +2 -2
- package/dist/{types-CTewen3J.d.cts → types-DIX6zWSt.d.cts} +15 -0
- package/dist/{types-CTewen3J.d.ts → types-DIX6zWSt.d.ts} +15 -0
- package/docs/cli.md +8 -6
- package/docs/configuration.md +44 -10
- package/docs/mcp.md +16 -3
- package/docs/output.md +13 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1254,9 +1254,18 @@ var DEFAULT_SECRET_PATTERNS = [
|
|
|
1254
1254
|
// The unquoted twin for YAML, docker-compose and `.env`: those formats never
|
|
1255
1255
|
// quote their values, so the rules above cannot see them. `valueGroup` keeps
|
|
1256
1256
|
// `POSTGRES_PASSWORD:` in the output.
|
|
1257
|
+
//
|
|
1258
|
+
// This is the one unquoted rule without `NOT_AFTER_QUOTE`, and the lookbehind is a
|
|
1259
|
+
// string-literal guard rather than a precision rule: a credential assignment *inside* a
|
|
1260
|
+
// literal — `const dsn = "password=hunter2;host=db"`, `curl -d "password=hunter2"` — was
|
|
1261
|
+
// rejected before it could be considered, and a DSN is a credential in a string. What made the
|
|
1262
|
+
// lookbehind redundant is the shape of `UNQUOTED_LITERAL`: a value has to start with a word
|
|
1263
|
+
// character, so `["awsSecret:", "apiKey:"]` — the list of key names the lookbehind was added
|
|
1264
|
+
// for — cannot match here anyway. `redact.ts` still refuses a match whose separator is inside
|
|
1265
|
+
// the literal, which is the case that list actually is.
|
|
1257
1266
|
rule: "credential-assignment-unquoted",
|
|
1258
1267
|
pattern: new RegExp(
|
|
1259
|
-
`${
|
|
1268
|
+
`${UNQUOTED_STRONG_NAME}\\s{0,8}${ASSIGN}\\s{0,8}${NOT_A_LITERAL_VALUE}${UNQUOTED_LITERAL}`,
|
|
1260
1269
|
"i"
|
|
1261
1270
|
),
|
|
1262
1271
|
valueGroup: 1
|
|
@@ -1291,6 +1300,27 @@ var DEFAULT_SECRET_PATTERNS = [
|
|
|
1291
1300
|
pattern: /(?<!["'])(?:api[_-]?key|secret[_-]?key|auth[_-]?token|client[_-]?secret|access[_-]?token|refresh[_-]?token|private[_-]?key|password|passwd|pwd)["']?\s*(?<![=!<>])[:=](?!=)\s*["']([^"'\s]{8,256})["']/i,
|
|
1292
1301
|
valueGroup: 1
|
|
1293
1302
|
},
|
|
1303
|
+
{
|
|
1304
|
+
// `${VAR:-fallback}` — a default value in a shell script, a `.env`, docker-compose or a
|
|
1305
|
+
// GitHub Actions workflow.
|
|
1306
|
+
//
|
|
1307
|
+
// This shape defeated every rule above it, and each refusal was individually reasonable:
|
|
1308
|
+
// the quoted rules need a quote after the separator, and the unquoted ones need the value to
|
|
1309
|
+
// begin with a word character — a fallback begins with `$`. So
|
|
1310
|
+
// `POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-MyProdPassword123!}` was emitted whole, `.env` files
|
|
1311
|
+
// and compose files included, which is where that idiom is the norm.
|
|
1312
|
+
//
|
|
1313
|
+
// The **fallback** is what gets masked, not the whole expansion: the variable name is the
|
|
1314
|
+
// readable context, exactly as `POSTGRES_PASSWORD:` is kept by the rule above. A single
|
|
1315
|
+
// interpolation with no operator (`${DB_PASSWORD}`) still does not match — there is nothing
|
|
1316
|
+
// between the braces to mask, and `isTemplateInterpolation` keeps such a value readable.
|
|
1317
|
+
rule: "interpolation-fallback-secret",
|
|
1318
|
+
pattern: new RegExp(
|
|
1319
|
+
`(?:["']${STRONG_CREDENTIAL_NAME}["']|${NOT_AFTER_QUOTE}${STRONG_CREDENTIAL_NAME})\\s{0,8}${ASSIGN}\\s{0,8}["'\`]?\\$\\{[A-Za-z_][A-Za-z0-9_]*:?[-=?+]([^}"'\`\\r\\n]{2,512})\\}["'\`]?`,
|
|
1320
|
+
"i"
|
|
1321
|
+
),
|
|
1322
|
+
valueGroup: 1
|
|
1323
|
+
},
|
|
1294
1324
|
{
|
|
1295
1325
|
// Covers `.env` / shell assignment syntax, which never quotes its values and
|
|
1296
1326
|
// was therefore invisible to the rule above. Anchored to the line start and
|
|
@@ -1318,8 +1348,8 @@ var DEFAULT_SECRET_PATTERNS = [
|
|
|
1318
1348
|
valueGroup: 1
|
|
1319
1349
|
}
|
|
1320
1350
|
];
|
|
1321
|
-
var PEM_BLOCK_PATTERN = /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?-----END (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----/g;
|
|
1322
|
-
var PEM_UNTERMINATED_PATTERN = /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----(?:\r
|
|
1351
|
+
var PEM_BLOCK_PATTERN = /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----(?:(?!-----BEGIN)[\s\S])*?-----END (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----/g;
|
|
1352
|
+
var PEM_UNTERMINATED_PATTERN = /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----(?:\r\n|\n|\r)(?:(?:[ \t]*(?:Proc-Type|DEK-Info|Comment|Version):[^\r\n]*|[ \t]*)(?:\r\n|\n|\r))*(?:[ \t]*[A-Za-z0-9+/=]{8,}[ \t]*(?:\r\n|\n|\r|$))+/g;
|
|
1323
1353
|
|
|
1324
1354
|
// src/security/redact.ts
|
|
1325
1355
|
var REDACTION_MARKER = "[REDACTED: potential secret-like value omitted from snapshot]";
|
|
@@ -1413,6 +1443,20 @@ function toGlobal(rule) {
|
|
|
1413
1443
|
return new RegExp(rule.pattern.source, [...flags].join(""));
|
|
1414
1444
|
}
|
|
1415
1445
|
var LINE_BREAK = /(\r\n|\n|\r)/;
|
|
1446
|
+
var LINE_TERMINATOR = /\r\n|\n|\r/g;
|
|
1447
|
+
var COMPILED_RULES = /* @__PURE__ */ new WeakMap();
|
|
1448
|
+
function compiledRuleFor(rule) {
|
|
1449
|
+
let compiled = COMPILED_RULES.get(rule);
|
|
1450
|
+
if (compiled === void 0) {
|
|
1451
|
+
compiled = {
|
|
1452
|
+
rule: rule.rule,
|
|
1453
|
+
pattern: toGlobal(rule),
|
|
1454
|
+
...rule.valueGroup === void 0 ? {} : { valueGroup: rule.valueGroup }
|
|
1455
|
+
};
|
|
1456
|
+
COMPILED_RULES.set(rule, compiled);
|
|
1457
|
+
}
|
|
1458
|
+
return compiled;
|
|
1459
|
+
}
|
|
1416
1460
|
function scanAndRedactSecrets(content, rules, filePath) {
|
|
1417
1461
|
const findings = [];
|
|
1418
1462
|
let redacted = content;
|
|
@@ -1424,18 +1468,15 @@ function scanAndRedactSecrets(content, rules, filePath) {
|
|
|
1424
1468
|
blockPattern.lastIndex += 1;
|
|
1425
1469
|
continue;
|
|
1426
1470
|
}
|
|
1427
|
-
const
|
|
1428
|
-
const
|
|
1471
|
+
const before = redacted.slice(0, match.index);
|
|
1472
|
+
const line = (before.match(LINE_TERMINATOR) ?? []).length + 1;
|
|
1473
|
+
const newlines = (match[0].match(LINE_TERMINATOR) ?? []).length;
|
|
1429
1474
|
findings.push({ rules: ["private-key-block"], line, path: filePath });
|
|
1430
|
-
redacted =
|
|
1475
|
+
redacted = before + PEM_REDACTION_MARKER + "\n".repeat(newlines) + redacted.slice(match.index + match[0].length);
|
|
1431
1476
|
blockPattern.lastIndex = match.index + PEM_REDACTION_MARKER.length + newlines;
|
|
1432
1477
|
}
|
|
1433
1478
|
}
|
|
1434
|
-
const compiled = rules.map(
|
|
1435
|
-
rule: rule.rule,
|
|
1436
|
-
pattern: toGlobal(rule),
|
|
1437
|
-
...rule.valueGroup === void 0 ? {} : { valueGroup: rule.valueGroup }
|
|
1438
|
-
}));
|
|
1479
|
+
const compiled = rules.map(compiledRuleFor);
|
|
1439
1480
|
const segments = redacted.split(LINE_BREAK);
|
|
1440
1481
|
let lineNumber = 1;
|
|
1441
1482
|
for (let index = 0; index < segments.length; index += 2) {
|
|
@@ -1453,8 +1494,10 @@ function scanAndRedactSecrets(content, rules, filePath) {
|
|
|
1453
1494
|
}
|
|
1454
1495
|
const group = valueGroup === void 0 ? void 0 : match.indices?.[valueGroup];
|
|
1455
1496
|
if (group && isSelfNamingConstant(line, group[0], group[1])) continue;
|
|
1456
|
-
if (valueGroup !== void 0 && startsInsideStringLiteral(line, match.index))
|
|
1457
|
-
|
|
1497
|
+
if (valueGroup !== void 0 && startsInsideStringLiteral(line, match.index, group?.[0])) {
|
|
1498
|
+
continue;
|
|
1499
|
+
}
|
|
1500
|
+
if (group && isUnquotedValue(line, group[0]) && isDeclaredBinding(line, match.index, filePath)) {
|
|
1458
1501
|
continue;
|
|
1459
1502
|
}
|
|
1460
1503
|
if (group && isTemplateInterpolation(line, group[0], group[1])) continue;
|
|
@@ -1487,14 +1530,20 @@ function expandOverToken(line, start, end) {
|
|
|
1487
1530
|
while (right < line.length && TOKEN_CHARS_RIGHT.test(line[right])) right += 1;
|
|
1488
1531
|
return { start: left, end: right };
|
|
1489
1532
|
}
|
|
1490
|
-
function startsInsideStringLiteral(line, matchIndex) {
|
|
1533
|
+
function startsInsideStringLiteral(line, matchIndex, valueStart) {
|
|
1491
1534
|
let runStart = matchIndex;
|
|
1492
1535
|
while (runStart > 0 && /[A-Za-z0-9_-]/.test(line[runStart - 1])) runStart -= 1;
|
|
1493
1536
|
const before = runStart > 0 ? line[runStart - 1] : "";
|
|
1494
|
-
|
|
1537
|
+
if (before !== '"' && before !== "'" && before !== "`") return false;
|
|
1538
|
+
if (valueStart === void 0) return true;
|
|
1539
|
+
return /["'`]/.test(line.slice(matchIndex, valueStart));
|
|
1495
1540
|
}
|
|
1496
1541
|
function isTemplateInterpolation(line, start, end) {
|
|
1497
|
-
|
|
1542
|
+
if (line[start] !== "$" || line[start + 1] !== "{" || line[end - 1] !== "}") {
|
|
1543
|
+
return false;
|
|
1544
|
+
}
|
|
1545
|
+
const inner = line.slice(start + 2, end - 1).trim();
|
|
1546
|
+
return /^[A-Za-z_$][A-Za-z0-9_$.()[\]]*$/.test(inner);
|
|
1498
1547
|
}
|
|
1499
1548
|
function isSelfNamingConstant(line, start, end) {
|
|
1500
1549
|
const value = line.slice(start, end);
|
|
@@ -1519,9 +1568,19 @@ function isUnquotedValue(line, start) {
|
|
|
1519
1568
|
const before = start > 0 ? line[start - 1] : "";
|
|
1520
1569
|
return before !== '"' && before !== "'" && before !== "`";
|
|
1521
1570
|
}
|
|
1522
|
-
var DECLARATION_KEYWORDS = /\b(?:const|let|var|readonly|public|private|protected|static|
|
|
1571
|
+
var DECLARATION_KEYWORDS = /\b(?:const|let|var|readonly|public|private|protected|static|using)\s+$/;
|
|
1572
|
+
var ASSIGNMENT_FILE = /\.(?:sh|bash|zsh|ksh|fish|env|ya?ml|toml|ini|conf|cfg|properties|tf|tfvars|service)$/i;
|
|
1573
|
+
var ASSIGNMENT_BASENAME = /^(?:\.env(?:\.[^/]*)?|\.envrc|\.profile|\.bashrc|\.zshrc|\.bash_profile)$/i;
|
|
1574
|
+
function isAssignmentFile(filePath) {
|
|
1575
|
+
if (filePath === void 0) return false;
|
|
1576
|
+
const normalised = filePath.replace(/\\/g, "/");
|
|
1577
|
+
if (ASSIGNMENT_FILE.test(normalised)) return true;
|
|
1578
|
+
const base = normalised.slice(normalised.lastIndexOf("/") + 1);
|
|
1579
|
+
return ASSIGNMENT_BASENAME.test(base);
|
|
1580
|
+
}
|
|
1523
1581
|
var DECLARATION_WINDOW = 40;
|
|
1524
|
-
function isDeclaredBinding(line, matchIndex) {
|
|
1582
|
+
function isDeclaredBinding(line, matchIndex, filePath) {
|
|
1583
|
+
if (isAssignmentFile(filePath)) return false;
|
|
1525
1584
|
let runStart = matchIndex;
|
|
1526
1585
|
while (runStart > 0 && /[A-Za-z0-9_$]/.test(line[runStart - 1])) runStart -= 1;
|
|
1527
1586
|
const from = Math.max(0, runStart - DECLARATION_WINDOW);
|
|
@@ -1563,6 +1622,15 @@ function readLinkTarget(linkPath, fallback) {
|
|
|
1563
1622
|
return fallback;
|
|
1564
1623
|
}
|
|
1565
1624
|
}
|
|
1625
|
+
function brokenLinkRecord(linkPath, relativePath, rootReal) {
|
|
1626
|
+
const rawTarget = readLinkTarget(linkPath, linkPath);
|
|
1627
|
+
const resolvedFallback = path2.resolve(path2.dirname(linkPath), rawTarget);
|
|
1628
|
+
return isInside(rootReal, resolvedFallback) ? { path: relativePath, reason: "read-error", detail: "broken symlink" } : {
|
|
1629
|
+
path: relativePath,
|
|
1630
|
+
reason: "outside-root",
|
|
1631
|
+
detail: `link target resolves outside the scanned root (target: ${rawTarget}); not followed`
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1566
1634
|
function isInside(root, candidate) {
|
|
1567
1635
|
const relative12 = path2.relative(root, candidate);
|
|
1568
1636
|
return relative12 === "" || !relative12.startsWith(`..${path2.sep}`) && relative12 !== ".." && !path2.isAbsolute(relative12);
|
|
@@ -1652,6 +1720,13 @@ function isSensitivePath(relativePath) {
|
|
|
1652
1720
|
return matchesSensitiveList(posixPath, base);
|
|
1653
1721
|
}
|
|
1654
1722
|
var GLOBAL_PATTERNS = /* @__PURE__ */ new WeakMap();
|
|
1723
|
+
var GLOB_REGEX_CACHE = /* @__PURE__ */ new Map();
|
|
1724
|
+
var GITIGNORE_REGEX_CACHE = /* @__PURE__ */ new Map();
|
|
1725
|
+
var REGEX_CACHE_LIMIT = 4096;
|
|
1726
|
+
function rememberCompiled(cache, key, value) {
|
|
1727
|
+
if (cache.size >= REGEX_CACHE_LIMIT) cache.clear();
|
|
1728
|
+
cache.set(key, value);
|
|
1729
|
+
}
|
|
1655
1730
|
function globalPattern(pattern) {
|
|
1656
1731
|
let compiled = GLOBAL_PATTERNS.get(pattern);
|
|
1657
1732
|
if (compiled === void 0) {
|
|
@@ -1662,8 +1737,8 @@ function globalPattern(pattern) {
|
|
|
1662
1737
|
}
|
|
1663
1738
|
var DEFAULT_PATH_RULES = [...DEFAULT_SECRET_PATTERNS];
|
|
1664
1739
|
function pathScanRules(extraRules) {
|
|
1665
|
-
if (extraRules === void 0
|
|
1666
|
-
return
|
|
1740
|
+
if (extraRules === void 0) return DEFAULT_PATH_RULES;
|
|
1741
|
+
return extraRules;
|
|
1667
1742
|
}
|
|
1668
1743
|
function findSecretInPath(relativePath, extraRules) {
|
|
1669
1744
|
const posixPath = relativePath.replace(/\\/g, "/");
|
|
@@ -1712,11 +1787,22 @@ function maskSkipPaths(records, extraRules) {
|
|
|
1712
1787
|
});
|
|
1713
1788
|
}
|
|
1714
1789
|
function globToRegExp(glob) {
|
|
1790
|
+
const cached = GLOB_REGEX_CACHE.get(glob);
|
|
1791
|
+
if (cached !== void 0) return cached;
|
|
1715
1792
|
const normalized = glob.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
1716
1793
|
const anchored = normalized.startsWith("/");
|
|
1717
1794
|
const body = anchored ? normalized.slice(1) : normalized;
|
|
1718
1795
|
const prefix = anchored || body.includes("/") ? "^" : "(?:^|.*/)";
|
|
1719
|
-
|
|
1796
|
+
let compiled;
|
|
1797
|
+
try {
|
|
1798
|
+
compiled = new RegExp(`${prefix}${globBodyToRegExp(body, true)}(?:/.*)?$`);
|
|
1799
|
+
} catch (error) {
|
|
1800
|
+
throw new Error(
|
|
1801
|
+
`"${glob}" is not a valid glob: ${error.message}. A character class must be ascending, as in "[a-z]".`
|
|
1802
|
+
);
|
|
1803
|
+
}
|
|
1804
|
+
rememberCompiled(GLOB_REGEX_CACHE, glob, compiled);
|
|
1805
|
+
return compiled;
|
|
1720
1806
|
}
|
|
1721
1807
|
function globBodyToRegExp(body, allowBraces) {
|
|
1722
1808
|
let out = "";
|
|
@@ -1828,6 +1914,14 @@ function escapeRegExpChar(character) {
|
|
|
1828
1914
|
return /[\\^$.*+?()[\]{}|/]/.test(character) ? `\\${character}` : character;
|
|
1829
1915
|
}
|
|
1830
1916
|
function matchGitignorePattern(target, pattern) {
|
|
1917
|
+
let compiled = GITIGNORE_REGEX_CACHE.get(pattern);
|
|
1918
|
+
if (compiled === void 0) {
|
|
1919
|
+
compiled = compileGitignorePattern(pattern);
|
|
1920
|
+
rememberCompiled(GITIGNORE_REGEX_CACHE, pattern, compiled);
|
|
1921
|
+
}
|
|
1922
|
+
return compiled.test(target);
|
|
1923
|
+
}
|
|
1924
|
+
function compileGitignorePattern(pattern) {
|
|
1831
1925
|
const anchored = pattern.startsWith("/");
|
|
1832
1926
|
const body = anchored ? pattern.slice(1) : pattern;
|
|
1833
1927
|
let source = "";
|
|
@@ -1876,7 +1970,11 @@ function matchGitignorePattern(target, pattern) {
|
|
|
1876
1970
|
source += escapeRegExpChar(character);
|
|
1877
1971
|
}
|
|
1878
1972
|
const prefix = anchored || body.includes("/") ? "^" : "(?:^|.*/)";
|
|
1879
|
-
|
|
1973
|
+
try {
|
|
1974
|
+
return new RegExp(`${prefix}${source}(?:/.*)?$`);
|
|
1975
|
+
} catch {
|
|
1976
|
+
return /$^/;
|
|
1977
|
+
}
|
|
1880
1978
|
}
|
|
1881
1979
|
function matchesGitignoreRule(rule, scoped, isDirectory) {
|
|
1882
1980
|
if (!rule.dirOnly) return matchGitignorePattern(scoped, rule.pattern);
|
|
@@ -1906,14 +2004,31 @@ function gitignoreVerdict(layers, relativePath, isDirectory) {
|
|
|
1906
2004
|
}
|
|
1907
2005
|
return verdict;
|
|
1908
2006
|
}
|
|
1909
|
-
|
|
2007
|
+
var DEFAULT_TOOL_DIR_NAMES = ["snapshot"];
|
|
2008
|
+
function toolDirNamesFor(toolOwnedPaths) {
|
|
2009
|
+
const names = new Set(DEFAULT_TOOL_DIR_NAMES);
|
|
2010
|
+
for (const entry of toolOwnedPaths) {
|
|
2011
|
+
const base = entry.replace(/\\/g, "/").replace(/\/+$/, "").split("/").pop();
|
|
2012
|
+
if (base !== void 0 && base.length > 0) names.add(base);
|
|
2013
|
+
}
|
|
2014
|
+
return [...names];
|
|
2015
|
+
}
|
|
2016
|
+
function isToolLeftoverName(name, dirNames) {
|
|
2017
|
+
for (const dirName of dirNames) {
|
|
2018
|
+
if (name.startsWith(`${dirName}.backup-`) || name.startsWith(`${dirName}.partial-`)) {
|
|
2019
|
+
return true;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
return false;
|
|
2023
|
+
}
|
|
2024
|
+
function defaultIgnoreReason(relativePath, toolDirNames = DEFAULT_TOOL_DIR_NAMES) {
|
|
1910
2025
|
const posixPath = relativePath.replace(/\\/g, "/");
|
|
1911
2026
|
const parts = posixPath.split("/");
|
|
1912
2027
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
1913
2028
|
if (DEFAULT_IGNORED_DIRS.includes(parts[i])) {
|
|
1914
2029
|
return `built-in ignore: directory "${parts[i]}"`;
|
|
1915
2030
|
}
|
|
1916
|
-
if (parts[i]
|
|
2031
|
+
if (isToolLeftoverName(parts[i], toolDirNames)) {
|
|
1917
2032
|
return `built-in ignore: leftover snapshot backup "${parts[i]}"`;
|
|
1918
2033
|
}
|
|
1919
2034
|
}
|
|
@@ -1921,6 +2036,9 @@ function defaultIgnoreReason(relativePath) {
|
|
|
1921
2036
|
if (DEFAULT_IGNORED_FILES.includes(base)) {
|
|
1922
2037
|
return `built-in ignore: filename "${base}"`;
|
|
1923
2038
|
}
|
|
2039
|
+
if (isToolLeftoverName(base, toolDirNames)) {
|
|
2040
|
+
return `built-in ignore: leftover snapshot backup "${base}"`;
|
|
2041
|
+
}
|
|
1924
2042
|
return null;
|
|
1925
2043
|
}
|
|
1926
2044
|
function listIgnoredPaths(cwd) {
|
|
@@ -1989,41 +2107,72 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
|
|
|
1989
2107
|
}
|
|
1990
2108
|
const reported = /* @__PURE__ */ new Set();
|
|
1991
2109
|
const kept = [];
|
|
2110
|
+
const report = (linkedPath, record) => {
|
|
2111
|
+
if (reported.has(linkedPath)) return;
|
|
2112
|
+
reported.add(linkedPath);
|
|
2113
|
+
skipped.push(record);
|
|
2114
|
+
};
|
|
1992
2115
|
for (const candidate of candidates) {
|
|
1993
2116
|
const parts = candidate.split("/");
|
|
1994
|
-
let
|
|
2117
|
+
let linkedPath = null;
|
|
1995
2118
|
for (let depth = 1; depth <= parts.length; depth += 1) {
|
|
1996
2119
|
const prefix = parts.slice(0, depth).join("/");
|
|
1997
2120
|
if (isLink(path2.join(cwd, prefix))) {
|
|
1998
|
-
|
|
2121
|
+
linkedPath = prefix;
|
|
1999
2122
|
break;
|
|
2000
2123
|
}
|
|
2001
2124
|
}
|
|
2002
|
-
if (
|
|
2125
|
+
if (linkedPath === null) {
|
|
2003
2126
|
kept.push(candidate);
|
|
2004
2127
|
continue;
|
|
2005
2128
|
}
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
let targetReal = absolute;
|
|
2129
|
+
const absolute = path2.join(cwd, linkedPath);
|
|
2130
|
+
const rawTarget = readLinkTarget(absolute, absolute);
|
|
2131
|
+
let targetReal = null;
|
|
2010
2132
|
try {
|
|
2011
2133
|
targetReal = fs2.realpathSync(absolute);
|
|
2012
2134
|
} catch {
|
|
2135
|
+
targetReal = null;
|
|
2013
2136
|
}
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
} : {
|
|
2022
|
-
path: linkedDir,
|
|
2137
|
+
if (targetReal === null) {
|
|
2138
|
+
report(linkedPath, brokenLinkRecord(absolute, linkedPath, rootReal));
|
|
2139
|
+
continue;
|
|
2140
|
+
}
|
|
2141
|
+
if (!isInside(rootReal, targetReal)) {
|
|
2142
|
+
report(linkedPath, {
|
|
2143
|
+
path: linkedPath,
|
|
2023
2144
|
reason: "outside-root",
|
|
2024
|
-
detail: `link target resolves outside the scanned root (target: ${
|
|
2025
|
-
}
|
|
2026
|
-
|
|
2145
|
+
detail: `link target resolves outside the scanned root (target: ${rawTarget}); not followed`
|
|
2146
|
+
});
|
|
2147
|
+
continue;
|
|
2148
|
+
}
|
|
2149
|
+
let stats = null;
|
|
2150
|
+
try {
|
|
2151
|
+
stats = fs2.statSync(absolute);
|
|
2152
|
+
} catch {
|
|
2153
|
+
stats = null;
|
|
2154
|
+
}
|
|
2155
|
+
if (stats === null) {
|
|
2156
|
+
report(linkedPath, { path: linkedPath, reason: "read-error", detail: "unreadable symlink" });
|
|
2157
|
+
continue;
|
|
2158
|
+
}
|
|
2159
|
+
if (linkedPath !== candidate || stats.isDirectory()) {
|
|
2160
|
+
report(linkedPath, {
|
|
2161
|
+
path: linkedPath,
|
|
2162
|
+
reason: "symlink",
|
|
2163
|
+
detail: `directory link not followed (target: ${rawTarget})`
|
|
2164
|
+
});
|
|
2165
|
+
continue;
|
|
2166
|
+
}
|
|
2167
|
+
if (!stats.isFile()) {
|
|
2168
|
+
report(linkedPath, {
|
|
2169
|
+
path: linkedPath,
|
|
2170
|
+
reason: "symlink",
|
|
2171
|
+
detail: `link to neither a file nor a directory (target: ${rawTarget})`
|
|
2172
|
+
});
|
|
2173
|
+
continue;
|
|
2174
|
+
}
|
|
2175
|
+
kept.push(candidate);
|
|
2027
2176
|
}
|
|
2028
2177
|
return kept;
|
|
2029
2178
|
}
|
|
@@ -2070,6 +2219,7 @@ function collectFiles(cwd, options = {}) {
|
|
|
2070
2219
|
(options.toolOwnedPaths ?? []).map((entry) => entry.replace(/\\/g, "/"))
|
|
2071
2220
|
);
|
|
2072
2221
|
const isToolOwned = makeToolOwnedDecider(cwd, [...toolOwnedPaths]);
|
|
2222
|
+
const toolDirNames = toolDirNamesFor(toolOwnedPaths);
|
|
2073
2223
|
let candidates;
|
|
2074
2224
|
const skipped = [];
|
|
2075
2225
|
const discovery = options.discovery ?? "auto";
|
|
@@ -2098,7 +2248,7 @@ function collectFiles(cwd, options = {}) {
|
|
|
2098
2248
|
}
|
|
2099
2249
|
const files = candidates.filter((file) => {
|
|
2100
2250
|
if (isToolOwned(file)) return false;
|
|
2101
|
-
const ignored = defaultIgnoreReason(file);
|
|
2251
|
+
const ignored = defaultIgnoreReason(file, toolDirNames);
|
|
2102
2252
|
if (ignored) {
|
|
2103
2253
|
skipped.push({ path: file, reason: "default-ignore", detail: ignored });
|
|
2104
2254
|
return false;
|
|
@@ -2153,6 +2303,7 @@ function rootRealFor(root) {
|
|
|
2153
2303
|
function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PURE__ */ new Set(), parentGitignoreLayers = []) {
|
|
2154
2304
|
const results = { files: [], skipped: [] };
|
|
2155
2305
|
const stack = [];
|
|
2306
|
+
const toolDirNames = toolDirNamesFor(reservedPaths);
|
|
2156
2307
|
const relativeToBase = (target) => path2.relative(baseDir, target).replace(/\\/g, "/") || ".";
|
|
2157
2308
|
const enter = (current, layers) => {
|
|
2158
2309
|
if (visited.has(current)) return false;
|
|
@@ -2197,11 +2348,11 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
|
|
|
2197
2348
|
});
|
|
2198
2349
|
continue;
|
|
2199
2350
|
}
|
|
2200
|
-
if (DEFAULT_IGNORED_FILES.includes(entry.name) || entry.name
|
|
2351
|
+
if (DEFAULT_IGNORED_FILES.includes(entry.name) || isToolLeftoverName(entry.name, toolDirNames)) {
|
|
2201
2352
|
results.skipped.push({
|
|
2202
2353
|
path: relPath,
|
|
2203
2354
|
reason: "default-ignore",
|
|
2204
|
-
detail: "built-in ignore: filename"
|
|
2355
|
+
detail: isToolLeftoverName(entry.name, toolDirNames) ? `built-in ignore: leftover snapshot backup "${entry.name}" (not descended)` : "built-in ignore: filename"
|
|
2205
2356
|
});
|
|
2206
2357
|
continue;
|
|
2207
2358
|
}
|
|
@@ -2222,7 +2373,7 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
|
|
|
2222
2373
|
try {
|
|
2223
2374
|
targetReal = fs2.realpathSync(fullPath);
|
|
2224
2375
|
} catch {
|
|
2225
|
-
results.skipped.push(
|
|
2376
|
+
results.skipped.push(brokenLinkRecord(fullPath, relPath, rootReal));
|
|
2226
2377
|
continue;
|
|
2227
2378
|
}
|
|
2228
2379
|
let stats;
|
|
@@ -2322,8 +2473,9 @@ function findConfigFile(cwd) {
|
|
|
2322
2473
|
const pkgJsonPath = path3.join(cwd, "package.json");
|
|
2323
2474
|
if (!fs3.existsSync(pkgJsonPath)) return null;
|
|
2324
2475
|
try {
|
|
2325
|
-
const parsed = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf8"));
|
|
2326
|
-
|
|
2476
|
+
const parsed = JSON.parse(stripJsonComments(fs3.readFileSync(pkgJsonPath, "utf8")));
|
|
2477
|
+
const snapshot = parsed?.snapshot;
|
|
2478
|
+
return typeof snapshot === "object" && snapshot !== null && !Array.isArray(snapshot) ? pkgJsonPath : null;
|
|
2327
2479
|
} catch {
|
|
2328
2480
|
return null;
|
|
2329
2481
|
}
|
|
@@ -2373,15 +2525,19 @@ function normalizeBudgets(budgets, maxPartBytes) {
|
|
|
2373
2525
|
}
|
|
2374
2526
|
function normalizeResolvedConfig(config) {
|
|
2375
2527
|
const discovery = isDiscoveryMode(config.discovery) ? config.discovery : "auto";
|
|
2528
|
+
const monolithWanted = config.includeMonolith === true || config.artifacts?.monolith === true;
|
|
2529
|
+
const artifacts = resolveArtifacts(
|
|
2530
|
+
config.artifacts,
|
|
2531
|
+
void 0,
|
|
2532
|
+
monolithWanted,
|
|
2533
|
+
monolithWanted
|
|
2534
|
+
);
|
|
2376
2535
|
return markResolvedConfig({
|
|
2377
2536
|
...config,
|
|
2378
2537
|
discovery,
|
|
2538
|
+
includeMonolith: artifacts.monolith,
|
|
2379
2539
|
secretRules: compileSecretRules(config.secretRules),
|
|
2380
|
-
artifacts
|
|
2381
|
-
config.artifacts,
|
|
2382
|
-
void 0,
|
|
2383
|
-
config.includeMonolith === true
|
|
2384
|
-
),
|
|
2540
|
+
artifacts,
|
|
2385
2541
|
include: resolveInclude(config.include),
|
|
2386
2542
|
preset: typeof config.preset === "string" ? config.preset : "generic",
|
|
2387
2543
|
presetExclude: [
|
|
@@ -2590,6 +2746,16 @@ async function importConfigModule(fullPath) {
|
|
|
2590
2746
|
url.searchParams.set("codebaseSnapshotLoad", String(moduleConfigLoadToken));
|
|
2591
2747
|
return await import(url.href);
|
|
2592
2748
|
}
|
|
2749
|
+
function attachConfigDir(config, filePath) {
|
|
2750
|
+
if (typeof config === "object" && config !== null) {
|
|
2751
|
+
Object.defineProperty(config, CONFIG_DIR_SYMBOL, {
|
|
2752
|
+
value: path3.dirname(filePath),
|
|
2753
|
+
enumerable: false,
|
|
2754
|
+
configurable: true
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
return config;
|
|
2758
|
+
}
|
|
2593
2759
|
async function loadConfigFileDetailed(cwd) {
|
|
2594
2760
|
const warnings = [];
|
|
2595
2761
|
const errors = [];
|
|
@@ -2605,7 +2771,7 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
2605
2771
|
);
|
|
2606
2772
|
}
|
|
2607
2773
|
assertKnownConfigKeys(config, fullPath);
|
|
2608
|
-
return { config, file: fullPath, warnings, errors };
|
|
2774
|
+
return { config: attachConfigDir(config, fullPath), file: fullPath, warnings, errors };
|
|
2609
2775
|
} catch (err) {
|
|
2610
2776
|
const raw = err.message;
|
|
2611
2777
|
const message = raw.includes(fullPath) ? raw : `${fullPath}: ${raw}`;
|
|
@@ -2624,7 +2790,7 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
2624
2790
|
const imported = await importConfigModule(fullPath);
|
|
2625
2791
|
const hasDefault = Object.prototype.hasOwnProperty.call(imported, "default");
|
|
2626
2792
|
const config = readConfigExport(hasDefault ? imported.default : imported, fullPath);
|
|
2627
|
-
return { config, file: fullPath, warnings, errors };
|
|
2793
|
+
return { config: attachConfigDir(config, fullPath), file: fullPath, warnings, errors };
|
|
2628
2794
|
} catch (err) {
|
|
2629
2795
|
const message = `Could not load ${fullPath}: ${err.message}`;
|
|
2630
2796
|
warnings.push(message);
|
|
@@ -2642,7 +2808,12 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
2642
2808
|
snapshotKey,
|
|
2643
2809
|
`${pkgJsonPath} (the "snapshot" key)`
|
|
2644
2810
|
);
|
|
2645
|
-
return {
|
|
2811
|
+
return {
|
|
2812
|
+
config: attachConfigDir(snapshotKey, pkgJsonPath),
|
|
2813
|
+
file: pkgJsonPath,
|
|
2814
|
+
warnings,
|
|
2815
|
+
errors
|
|
2816
|
+
};
|
|
2646
2817
|
}
|
|
2647
2818
|
if (snapshotKey !== void 0) {
|
|
2648
2819
|
const kind = Array.isArray(snapshotKey) ? "array" : typeof snapshotKey;
|
|
@@ -2681,7 +2852,7 @@ var DEFAULT_ARTIFACTS = {
|
|
|
2681
2852
|
function isArtifactName(value) {
|
|
2682
2853
|
return ARTIFACT_NAMES.includes(value);
|
|
2683
2854
|
}
|
|
2684
|
-
function resolveArtifacts(requested, skipped, includeMonolith, monolithOverride) {
|
|
2855
|
+
function resolveArtifacts(requested, skipped, includeMonolith, monolithOverride, only) {
|
|
2685
2856
|
const artifacts = {
|
|
2686
2857
|
...DEFAULT_ARTIFACTS,
|
|
2687
2858
|
// `includeMonolith` predates this selection and stays the switch for it.
|
|
@@ -2706,6 +2877,21 @@ function resolveArtifacts(requested, skipped, includeMonolith, monolithOverride)
|
|
|
2706
2877
|
if (monolithOverride !== void 0) {
|
|
2707
2878
|
artifacts.monolith = monolithOverride;
|
|
2708
2879
|
}
|
|
2880
|
+
if (only !== void 0 && only.length > 0) {
|
|
2881
|
+
const keep = /* @__PURE__ */ new Set();
|
|
2882
|
+
for (const name of only) {
|
|
2883
|
+
if (!isArtifactName(name)) {
|
|
2884
|
+
throw new Error(
|
|
2885
|
+
`Unknown artifact "${name}". Expected one of: ${ARTIFACT_NAMES.join(", ")}.`
|
|
2886
|
+
);
|
|
2887
|
+
}
|
|
2888
|
+
keep.add(name);
|
|
2889
|
+
}
|
|
2890
|
+
for (const name of ARTIFACT_NAMES) {
|
|
2891
|
+
artifacts[name] = keep.has(name);
|
|
2892
|
+
}
|
|
2893
|
+
return artifacts;
|
|
2894
|
+
}
|
|
2709
2895
|
for (const name of skipped ?? []) {
|
|
2710
2896
|
if (!isArtifactName(name)) {
|
|
2711
2897
|
throw new Error(
|
|
@@ -3006,7 +3192,8 @@ function resolveConfig(userConfig, cliOptions, baseDir) {
|
|
|
3006
3192
|
userConfig?.artifacts,
|
|
3007
3193
|
cliOptions?.skipArtifacts,
|
|
3008
3194
|
includeMonolith,
|
|
3009
|
-
cliMonolith
|
|
3195
|
+
cliMonolith,
|
|
3196
|
+
cliOptions?.onlyArtifacts
|
|
3010
3197
|
);
|
|
3011
3198
|
const explicitMaxPartBytes = cliOptions?.maxPartBytes ?? userConfig?.maxPartBytes;
|
|
3012
3199
|
const maxPartBytes = boundedBytes(
|
|
@@ -3180,7 +3367,18 @@ function configDigest(config) {
|
|
|
3180
3367
|
excludePatterns: config.excludePatterns,
|
|
3181
3368
|
preset: config.preset,
|
|
3182
3369
|
layersMode: config.layersMode,
|
|
3183
|
-
budgets
|
|
3370
|
+
// `budgets.byLayer` is a plain object, and `JSON.stringify` preserves insertion order — so
|
|
3371
|
+
// `{ docs, api }` and `{ api, docs }` hashed differently despite being the same settings, and
|
|
3372
|
+
// `validate` reported configuration drift for a snapshot nobody had changed. The keys are
|
|
3373
|
+
// canonicalised here; nothing else about the object is order-dependent.
|
|
3374
|
+
budgets: {
|
|
3375
|
+
default: config.budgets.default,
|
|
3376
|
+
byLayer: Object.fromEntries(
|
|
3377
|
+
Object.entries(config.budgets.byLayer).sort(
|
|
3378
|
+
([left], [right]) => left < right ? -1 : left > right ? 1 : 0
|
|
3379
|
+
)
|
|
3380
|
+
)
|
|
3381
|
+
},
|
|
3184
3382
|
// The matcher is what makes a layer a layer. Hashing only the names meant that
|
|
3185
3383
|
// moving `{ name: "api", match: "src/api/**" }` to `"src/v2/**"` produced the same
|
|
3186
3384
|
// digest, so `validate` reported "valid and up to date" for a snapshot whose files
|
|
@@ -3288,6 +3486,10 @@ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
3288
3486
|
".mjs",
|
|
3289
3487
|
".cjs"
|
|
3290
3488
|
]);
|
|
3489
|
+
var PYTHON_EXTENSIONS = /* @__PURE__ */ new Set([".py", ".pyi"]);
|
|
3490
|
+
var RUBY_EXTENSIONS = /* @__PURE__ */ new Set([".rb", ".rake", ".gemspec"]);
|
|
3491
|
+
var C_EXTENSIONS = /* @__PURE__ */ new Set([".c", ".h", ".cc", ".cpp", ".cxx", ".hpp", ".hxx", ".m", ".mm"]);
|
|
3492
|
+
var PYTHON_SUFFIXES = [".py", ".pyi", "/__init__.py", "/__init__.pyi"];
|
|
3291
3493
|
function buildImportResolver(files) {
|
|
3292
3494
|
const byPath = /* @__PURE__ */ new Map();
|
|
3293
3495
|
const byPathFolded = /* @__PURE__ */ new Map();
|
|
@@ -3314,11 +3516,45 @@ function buildImportResolver(files) {
|
|
|
3314
3516
|
return { byPath, byPathFolded, byStem, byStemFolded };
|
|
3315
3517
|
}
|
|
3316
3518
|
function resolveSpecifier(fromFile, specifier, resolver) {
|
|
3519
|
+
const extension = path4.posix.extname(fromFile).toLowerCase();
|
|
3520
|
+
const exact = (key) => resolver.byPath.get(key) ?? resolver.byPathFolded.get(key.toLowerCase()) ?? null;
|
|
3521
|
+
const firstOf = (candidates) => {
|
|
3522
|
+
for (const candidate of candidates) {
|
|
3523
|
+
const hit = exact(candidate);
|
|
3524
|
+
if (hit) return hit;
|
|
3525
|
+
}
|
|
3526
|
+
return null;
|
|
3527
|
+
};
|
|
3528
|
+
if (PYTHON_EXTENSIONS.has(extension) && specifier.startsWith(".")) {
|
|
3529
|
+
const dots = /^\.+/.exec(specifier)?.[0].length ?? 0;
|
|
3530
|
+
const rest = specifier.slice(dots).replace(/\./g, "/");
|
|
3531
|
+
let base = path4.posix.dirname(fromFile);
|
|
3532
|
+
for (let up = 1; up < dots; up += 1) base = path4.posix.dirname(base);
|
|
3533
|
+
const joined2 = rest ? path4.posix.normalize(path4.posix.join(base, rest)) : base;
|
|
3534
|
+
return firstOf([joined2, ...PYTHON_SUFFIXES.map((suffix) => `${joined2}${suffix}`)]);
|
|
3535
|
+
}
|
|
3536
|
+
if (RUBY_EXTENSIONS.has(extension) && specifier.startsWith("./")) {
|
|
3537
|
+
const joined2 = path4.posix.normalize(
|
|
3538
|
+
path4.posix.join(path4.posix.dirname(fromFile), specifier)
|
|
3539
|
+
);
|
|
3540
|
+
return firstOf([`${joined2}.rb`, joined2]);
|
|
3541
|
+
}
|
|
3542
|
+
if (C_EXTENSIONS.has(extension) && specifier.startsWith("./")) {
|
|
3543
|
+
const joined2 = path4.posix.normalize(
|
|
3544
|
+
path4.posix.join(path4.posix.dirname(fromFile), specifier)
|
|
3545
|
+
);
|
|
3546
|
+
return exact(joined2);
|
|
3547
|
+
}
|
|
3548
|
+
if (extension === ".dart" && !specifier.includes(":")) {
|
|
3549
|
+
const joined2 = path4.posix.normalize(
|
|
3550
|
+
path4.posix.join(path4.posix.dirname(fromFile), specifier)
|
|
3551
|
+
);
|
|
3552
|
+
return firstOf([joined2, `${joined2}.dart`]);
|
|
3553
|
+
}
|
|
3317
3554
|
if (!RELATIVE_SPECIFIER.test(specifier)) return null;
|
|
3318
3555
|
const joined = path4.posix.normalize(
|
|
3319
3556
|
path4.posix.join(path4.posix.dirname(fromFile), specifier)
|
|
3320
3557
|
);
|
|
3321
|
-
const exact = (key) => resolver.byPath.get(key) ?? resolver.byPathFolded.get(key.toLowerCase()) ?? null;
|
|
3322
3558
|
const direct = exact(joined);
|
|
3323
3559
|
if (direct) return direct;
|
|
3324
3560
|
for (const suffix of IMPORT_CANDIDATE_SUFFIXES) {
|
|
@@ -3450,13 +3686,23 @@ function summarizeTypeScriptSource(relativePath, content) {
|
|
|
3450
3686
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
3451
3687
|
summary.imports.push(node.moduleSpecifier.text);
|
|
3452
3688
|
}
|
|
3689
|
+
if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference) && node.moduleReference.expression !== void 0 && ts.isStringLiteral(node.moduleReference.expression)) {
|
|
3690
|
+
summary.imports.push(node.moduleReference.expression.text);
|
|
3691
|
+
}
|
|
3692
|
+
if (ts.isCallExpression(node) && node.arguments.length >= 1) {
|
|
3693
|
+
const firstArgument = node.arguments[0];
|
|
3694
|
+
if (firstArgument !== void 0 && ts.isStringLiteral(firstArgument)) {
|
|
3695
|
+
const isDynamicImport = node.expression.kind === ts.SyntaxKind.ImportKeyword;
|
|
3696
|
+
const isRequire = ts.isIdentifier(node.expression) && node.expression.text === "require";
|
|
3697
|
+
if (isDynamicImport || isRequire) summary.imports.push(firstArgument.text);
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3453
3700
|
if (ts.isExportDeclaration(node)) {
|
|
3454
3701
|
if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
3455
3702
|
summary.imports.push(node.moduleSpecifier.text);
|
|
3703
|
+
summary.reExports.push(node.moduleSpecifier.text);
|
|
3456
3704
|
if (node.exportClause && ts.isNamespaceExport(node.exportClause)) {
|
|
3457
3705
|
summary.exports.push(node.exportClause.name.text);
|
|
3458
|
-
} else if (!node.exportClause) {
|
|
3459
|
-
summary.reExports.push(node.moduleSpecifier.text);
|
|
3460
3706
|
}
|
|
3461
3707
|
}
|
|
3462
3708
|
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
@@ -3601,7 +3847,7 @@ function isCapitalizedName(name) {
|
|
|
3601
3847
|
}
|
|
3602
3848
|
|
|
3603
3849
|
// src/ast/profiles/cfamily.ts
|
|
3604
|
-
var INCLUDE = /^#\s*include\s*[<"]([^>"]+)[>"]/;
|
|
3850
|
+
var INCLUDE = /^#\s*include\s*([<"])([^>"]+)[>"]/;
|
|
3605
3851
|
var DEFINE = /^#\s*define\s+([A-Za-z_]\w*)/;
|
|
3606
3852
|
var USING = /^using\s+(?:namespace\s+)?([\w:]+)\s*;/;
|
|
3607
3853
|
var TYPE_DECL = /^(?:typedef\s+)?(?:struct|union|class|enum)\s+([A-Za-z_]\w*)/;
|
|
@@ -3620,7 +3866,7 @@ var cfamilyProfile = {
|
|
|
3620
3866
|
}
|
|
3621
3867
|
const included = INCLUDE.exec(trimmed);
|
|
3622
3868
|
if (included) {
|
|
3623
|
-
summary.imports.push(included[1]);
|
|
3869
|
+
summary.imports.push(included[1] === '"' ? `./${included[2]}` : included[2]);
|
|
3624
3870
|
return;
|
|
3625
3871
|
}
|
|
3626
3872
|
const used = USING.exec(trimmed);
|
|
@@ -4024,7 +4270,15 @@ var pythonProfile = {
|
|
|
4024
4270
|
}
|
|
4025
4271
|
const from = FROM_IMPORT.exec(trimmed);
|
|
4026
4272
|
if (from) {
|
|
4027
|
-
|
|
4273
|
+
const module = from[1];
|
|
4274
|
+
if (/^\.+$/.test(module)) {
|
|
4275
|
+
for (const name of from[2].split(",")) {
|
|
4276
|
+
const imported2 = name.replace(/[()]/g, "").trim().split(/\s+as\s+/)[0].trim();
|
|
4277
|
+
if (/^[A-Za-z_]\w*$/.test(imported2)) summary.imports.push(`${module}${imported2}`);
|
|
4278
|
+
}
|
|
4279
|
+
} else {
|
|
4280
|
+
summary.imports.push(module);
|
|
4281
|
+
}
|
|
4028
4282
|
routePending = false;
|
|
4029
4283
|
return;
|
|
4030
4284
|
}
|
|
@@ -4073,7 +4327,8 @@ var pythonProfile = {
|
|
|
4073
4327
|
};
|
|
4074
4328
|
|
|
4075
4329
|
// src/ast/profiles/ruby.ts
|
|
4076
|
-
var
|
|
4330
|
+
var REQUIRE_RELATIVE = /^require_relative\s+["']([^"']+)["']/;
|
|
4331
|
+
var REQUIRE = /^require\s+["']([^"']+)["']/;
|
|
4077
4332
|
var CLASS_OR_MODULE = /^(?:class|module)\s+([A-Z][\w:]*)/;
|
|
4078
4333
|
var METHOD2 = /^def\s+(?:self\.)?([A-Za-z_]\w*[?!=]?)/;
|
|
4079
4334
|
var rubyProfile = {
|
|
@@ -4084,6 +4339,11 @@ var rubyProfile = {
|
|
|
4084
4339
|
eachLine(content, (line) => {
|
|
4085
4340
|
const trimmed = line.trim();
|
|
4086
4341
|
if (!trimmed || trimmed.startsWith("#")) return;
|
|
4342
|
+
const relative12 = REQUIRE_RELATIVE.exec(trimmed);
|
|
4343
|
+
if (relative12) {
|
|
4344
|
+
summary.imports.push(`./${relative12[1]}`);
|
|
4345
|
+
return;
|
|
4346
|
+
}
|
|
4087
4347
|
const required = REQUIRE.exec(trimmed);
|
|
4088
4348
|
if (required) {
|
|
4089
4349
|
summary.imports.push(required[1]);
|
|
@@ -4174,7 +4434,8 @@ var rustProfile = {
|
|
|
4174
4434
|
};
|
|
4175
4435
|
|
|
4176
4436
|
// src/ast/profiles/sfc.ts
|
|
4177
|
-
var SCRIPT_BLOCK = /<script[^>]
|
|
4437
|
+
var SCRIPT_BLOCK = /<script([^>]*)>([\s\S]*?)<\/script>/gi;
|
|
4438
|
+
var JSX_LANG = /\blang\s*=\s*["']?[tj]sx\b/i;
|
|
4178
4439
|
var ASTRO_FRONTMATTER = /^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?=\r?\n|$)/;
|
|
4179
4440
|
function componentName(path22) {
|
|
4180
4441
|
const base = path22.split("/").pop() ?? path22;
|
|
@@ -4185,13 +4446,15 @@ var sfcProfile = {
|
|
|
4185
4446
|
extensions: [".vue", ".svelte", ".astro"],
|
|
4186
4447
|
summarize(path22, content) {
|
|
4187
4448
|
let script = "";
|
|
4449
|
+
let usesJsx = path22.toLowerCase().endsWith(".astro");
|
|
4188
4450
|
if (path22.toLowerCase().endsWith(".astro")) {
|
|
4189
4451
|
const frontmatter = ASTRO_FRONTMATTER.exec(content);
|
|
4190
4452
|
if (frontmatter) script += `${frontmatter[1]}
|
|
4191
4453
|
`;
|
|
4192
4454
|
}
|
|
4193
4455
|
for (const match of content.matchAll(SCRIPT_BLOCK)) {
|
|
4194
|
-
|
|
4456
|
+
if (JSX_LANG.test(match[1] ?? "")) usesJsx = true;
|
|
4457
|
+
script += `${match[2]}
|
|
4195
4458
|
`;
|
|
4196
4459
|
}
|
|
4197
4460
|
const name = componentName(path22);
|
|
@@ -4203,7 +4466,7 @@ var sfcProfile = {
|
|
|
4203
4466
|
}
|
|
4204
4467
|
return { summary: finalizeSummary(summary2), quality: "heuristic" };
|
|
4205
4468
|
}
|
|
4206
|
-
const summary = summarizeTypeScriptSource(`${name}
|
|
4469
|
+
const summary = summarizeTypeScriptSource(`${name}.${usesJsx ? "tsx" : "ts"}`, script);
|
|
4207
4470
|
summary.reactComponents.push(name);
|
|
4208
4471
|
summary.exports.push(name);
|
|
4209
4472
|
return { summary: finalizeSummary(summary), quality: "ast" };
|
|
@@ -4641,18 +4904,22 @@ function isBinaryFile(absolutePath) {
|
|
|
4641
4904
|
}
|
|
4642
4905
|
|
|
4643
4906
|
// src/ast/imports.ts
|
|
4907
|
+
var SUMMARY_MAX_SOURCE_BYTES = 2 * 1024 * 1024;
|
|
4644
4908
|
var TS_SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
4645
4909
|
var isTsSource = (relativePath) => {
|
|
4646
4910
|
const lower = relativePath.toLowerCase();
|
|
4647
4911
|
return TS_SOURCE_EXTENSIONS.some((extension) => lower.endsWith(extension));
|
|
4648
4912
|
};
|
|
4649
4913
|
function importsOfText(relativePath, text2) {
|
|
4914
|
+
if (Buffer.byteLength(text2, "utf8") > SUMMARY_MAX_SOURCE_BYTES) return [];
|
|
4650
4915
|
if (isTsSource(relativePath)) return summarizeTypeScriptSource(relativePath, text2).imports;
|
|
4651
4916
|
return summarizeWithProfile(relativePath, text2)?.summary.imports ?? [];
|
|
4652
4917
|
}
|
|
4653
4918
|
function importsOfFile(relativePath, absolutePath) {
|
|
4654
4919
|
let buffer;
|
|
4655
4920
|
try {
|
|
4921
|
+
const stats = fs5.statSync(absolutePath);
|
|
4922
|
+
if (!stats.isFile() || stats.size > SUMMARY_MAX_SOURCE_BYTES) return [];
|
|
4656
4923
|
buffer = fs5.readFileSync(absolutePath);
|
|
4657
4924
|
} catch {
|
|
4658
4925
|
return [];
|
|
@@ -4783,9 +5050,13 @@ function describeManifestShapeProblem(value) {
|
|
|
4783
5050
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
4784
5051
|
return 'a "parts" entry is not an object';
|
|
4785
5052
|
}
|
|
4786
|
-
|
|
5053
|
+
const filename = entry.filename;
|
|
5054
|
+
if (typeof filename !== "string" || filename.trim() === "") {
|
|
4787
5055
|
return 'a "parts" entry has no "filename"';
|
|
4788
5056
|
}
|
|
5057
|
+
if (filename.includes("/") || filename.includes("\\") || filename.includes("\0") || filename === "." || filename === ".." || path6.isAbsolute(filename)) {
|
|
5058
|
+
return 'a "parts" entry "filename" must be a bare filename inside parts/';
|
|
5059
|
+
}
|
|
4789
5060
|
}
|
|
4790
5061
|
if (!candidate.layers || typeof candidate.layers !== "object" || Array.isArray(candidate.layers)) {
|
|
4791
5062
|
return 'missing "layers" object';
|
|
@@ -4896,7 +5167,7 @@ function validateSnapshot(configInput) {
|
|
|
4896
5167
|
});
|
|
4897
5168
|
for (const diskFile of collection.files) {
|
|
4898
5169
|
const snapshotHash = manifestFileMap.get(diskFile);
|
|
4899
|
-
if (
|
|
5170
|
+
if (snapshotHash === void 0) {
|
|
4900
5171
|
const skipReason = skippedReasonOnDisk(diskFile);
|
|
4901
5172
|
if (skipReason === "unchanged") {
|
|
4902
5173
|
if (unchangedScope !== null) {
|
|
@@ -4910,6 +5181,11 @@ function validateSnapshot(configInput) {
|
|
|
4910
5181
|
drift.missingInSnapshot.push(diskFile);
|
|
4911
5182
|
continue;
|
|
4912
5183
|
}
|
|
5184
|
+
if (unchangedScope !== null && !unchangedScope.has(diskFile) && !becameDependent(diskFile)) {
|
|
5185
|
+
drift.configMismatches.push(
|
|
5186
|
+
`scope: ${maskedPath(diskFile, config.secretRules)} is in the snapshot but is no longer in the changed-files scope`
|
|
5187
|
+
);
|
|
5188
|
+
}
|
|
4913
5189
|
let diskHash;
|
|
4914
5190
|
try {
|
|
4915
5191
|
diskHash = sha256Hex(fs6.readFileSync(path6.join(config.cwd, diskFile)));
|
|
@@ -5050,6 +5326,41 @@ function validateSnapshot(configInput) {
|
|
|
5050
5326
|
`includeSensitiveFiles: manifest ${recorded.includeSensitiveFiles} vs current ${config.includeSensitiveFiles}`
|
|
5051
5327
|
);
|
|
5052
5328
|
}
|
|
5329
|
+
if ((recorded.maxPartTokens ?? void 0) !== (config.maxPartTokens ?? void 0)) {
|
|
5330
|
+
drift.configMismatches.push(
|
|
5331
|
+
`maxPartTokens: manifest ${String(recorded.maxPartTokens ?? "unset")} vs current ${String(config.maxPartTokens ?? "unset")}`
|
|
5332
|
+
);
|
|
5333
|
+
}
|
|
5334
|
+
if ((recorded.maxMonolithBytes ?? null) !== (config.maxMonolithBytes ?? null)) {
|
|
5335
|
+
drift.configMismatches.push(
|
|
5336
|
+
`maxMonolithBytes: manifest ${String(recorded.maxMonolithBytes ?? null)} vs current ${String(config.maxMonolithBytes ?? null)}`
|
|
5337
|
+
);
|
|
5338
|
+
}
|
|
5339
|
+
if (Boolean(recorded.skeleton) !== Boolean(config.skeleton)) {
|
|
5340
|
+
drift.configMismatches.push(
|
|
5341
|
+
`skeleton: manifest ${Boolean(recorded.skeleton)} vs current ${Boolean(config.skeleton)}`
|
|
5342
|
+
);
|
|
5343
|
+
}
|
|
5344
|
+
if (Boolean(recorded.sarif) !== Boolean(config.sarif)) {
|
|
5345
|
+
drift.configMismatches.push(
|
|
5346
|
+
`sarif: manifest ${Boolean(recorded.sarif)} vs current ${Boolean(config.sarif)}`
|
|
5347
|
+
);
|
|
5348
|
+
}
|
|
5349
|
+
if ((recorded.changedSince ?? void 0) !== (config.changedSince ?? void 0)) {
|
|
5350
|
+
drift.configMismatches.push(
|
|
5351
|
+
`changedSince: manifest ${String(recorded.changedSince ?? "unset")} vs current ${String(config.changedSince ?? "unset")}`
|
|
5352
|
+
);
|
|
5353
|
+
}
|
|
5354
|
+
if (Boolean(recorded.staged) !== Boolean(config.staged)) {
|
|
5355
|
+
drift.configMismatches.push(
|
|
5356
|
+
`staged: manifest ${Boolean(recorded.staged)} vs current ${Boolean(config.staged)}`
|
|
5357
|
+
);
|
|
5358
|
+
}
|
|
5359
|
+
if (Boolean(recorded.withDependents) !== Boolean(config.withDependents)) {
|
|
5360
|
+
drift.configMismatches.push(
|
|
5361
|
+
`withDependents: manifest ${Boolean(recorded.withDependents)} vs current ${Boolean(config.withDependents)}`
|
|
5362
|
+
);
|
|
5363
|
+
}
|
|
5053
5364
|
const effectiveDiscovery = config.discovery ?? "auto";
|
|
5054
5365
|
const recordedDiscovery = recorded.discovery;
|
|
5055
5366
|
if (recordedDiscovery !== void 0 && recordedDiscovery !== effectiveDiscovery) {
|
|
@@ -5528,9 +5839,21 @@ import * as fs8 from "fs";
|
|
|
5528
5839
|
import * as path9 from "path";
|
|
5529
5840
|
|
|
5530
5841
|
// src/partitioner/preview.ts
|
|
5842
|
+
var LINE_ENDINGS = /\r\n|\r/g;
|
|
5843
|
+
var normaliseLineEndings = (content) => content.replace(LINE_ENDINGS, "\n");
|
|
5531
5844
|
function countLines(content) {
|
|
5532
5845
|
if (content.length === 0) return 0;
|
|
5533
|
-
|
|
5846
|
+
let breaks = 0;
|
|
5847
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
5848
|
+
const code = content.charCodeAt(index);
|
|
5849
|
+
if (code === 10) {
|
|
5850
|
+
breaks += 1;
|
|
5851
|
+
continue;
|
|
5852
|
+
}
|
|
5853
|
+
if (code === 13 && content.charCodeAt(index + 1) !== 10) breaks += 1;
|
|
5854
|
+
}
|
|
5855
|
+
const last = content.charCodeAt(content.length - 1);
|
|
5856
|
+
return last === 10 || last === 13 ? breaks : breaks + 1;
|
|
5534
5857
|
}
|
|
5535
5858
|
function truncateToBytes(content, maxBytes) {
|
|
5536
5859
|
if (maxBytes <= 0) return { content: "", isTruncated: content.length > 0 };
|
|
@@ -5541,17 +5864,18 @@ function truncateToBytes(content, maxBytes) {
|
|
|
5541
5864
|
return { content: slice, isTruncated: true };
|
|
5542
5865
|
}
|
|
5543
5866
|
function truncatePreview(content, headLines, tailLines) {
|
|
5544
|
-
const
|
|
5545
|
-
const
|
|
5867
|
+
const normalised = normaliseLineEndings(content);
|
|
5868
|
+
const lines = normalised.split("\n");
|
|
5869
|
+
const total = countLines(normalised);
|
|
5546
5870
|
if (total <= headLines + tailLines) {
|
|
5547
5871
|
return {
|
|
5548
|
-
content,
|
|
5872
|
+
content: normalised,
|
|
5549
5873
|
isTruncated: false,
|
|
5550
5874
|
originalLines: total,
|
|
5551
5875
|
includedLines: total
|
|
5552
5876
|
};
|
|
5553
5877
|
}
|
|
5554
|
-
const window =
|
|
5878
|
+
const window = normalised.endsWith("\n") ? lines.slice(0, -1) : lines;
|
|
5555
5879
|
const head = window.slice(0, headLines);
|
|
5556
5880
|
const tail = tailLines > 0 ? window.slice(window.length - tailLines) : [];
|
|
5557
5881
|
const omitted = total - head.length - tail.length;
|
|
@@ -5674,10 +5998,13 @@ function describeMatcher(matcher) {
|
|
|
5674
5998
|
return String(matcher);
|
|
5675
5999
|
}
|
|
5676
6000
|
function normalizeTarget(cwd, target) {
|
|
5677
|
-
const
|
|
5678
|
-
const
|
|
5679
|
-
|
|
5680
|
-
|
|
6001
|
+
const resolvedCwd = path9.resolve(cwd);
|
|
6002
|
+
const absolute = path9.resolve(resolvedCwd, target);
|
|
6003
|
+
const relative12 = path9.relative(resolvedCwd, absolute);
|
|
6004
|
+
if (relative12 === "" || relative12 === ".." || relative12.startsWith(`..${path9.sep}`) || path9.isAbsolute(relative12)) {
|
|
6005
|
+
return { relative: target.replace(/\\/g, "/"), outsideRoot: true };
|
|
6006
|
+
}
|
|
6007
|
+
return { relative: relative12.split(path9.sep).join("/"), outsideRoot: false };
|
|
5681
6008
|
}
|
|
5682
6009
|
function findRuleMatch(rules, relativePath) {
|
|
5683
6010
|
return rules.find(
|
|
@@ -5704,9 +6031,19 @@ function readManifest2(config) {
|
|
|
5704
6031
|
}
|
|
5705
6032
|
}
|
|
5706
6033
|
function explainPath(config, target) {
|
|
5707
|
-
const
|
|
5708
|
-
const
|
|
6034
|
+
const normalized = normalizeTarget(config.cwd, target);
|
|
6035
|
+
const relative12 = normalized.relative;
|
|
5709
6036
|
const maskedPath2 = findSecretInPath(relative12, config.secretRules)?.masked ?? relative12;
|
|
6037
|
+
if (normalized.outsideRoot) {
|
|
6038
|
+
return {
|
|
6039
|
+
path: maskedPath2,
|
|
6040
|
+
exists: false,
|
|
6041
|
+
included: false,
|
|
6042
|
+
reason: "outside-root",
|
|
6043
|
+
detail: "path resolves outside the configured repository root and was not inspected"
|
|
6044
|
+
};
|
|
6045
|
+
}
|
|
6046
|
+
const absolute = path9.join(config.cwd, relative12);
|
|
5710
6047
|
const result = {
|
|
5711
6048
|
path: maskedPath2,
|
|
5712
6049
|
exists: fs8.existsSync(absolute),
|
|
@@ -5861,6 +6198,178 @@ function renderExplainText(result) {
|
|
|
5861
6198
|
import * as fs10 from "fs";
|
|
5862
6199
|
import * as path11 from "path";
|
|
5863
6200
|
|
|
6201
|
+
// src/ast/structured.ts
|
|
6202
|
+
function stripJsonComments2(content) {
|
|
6203
|
+
const text2 = content.replace(/^\uFEFF/, "");
|
|
6204
|
+
const withoutComments = stripComments(text2);
|
|
6205
|
+
return stripTrailingCommas2(withoutComments);
|
|
6206
|
+
}
|
|
6207
|
+
function stripComments(text2) {
|
|
6208
|
+
let out = "";
|
|
6209
|
+
let inString = false;
|
|
6210
|
+
let escaped = false;
|
|
6211
|
+
for (let index = 0; index < text2.length; index += 1) {
|
|
6212
|
+
const char = text2[index];
|
|
6213
|
+
if (inString) {
|
|
6214
|
+
out += char;
|
|
6215
|
+
if (escaped) escaped = false;
|
|
6216
|
+
else if (char === "\\") escaped = true;
|
|
6217
|
+
else if (char === '"') inString = false;
|
|
6218
|
+
continue;
|
|
6219
|
+
}
|
|
6220
|
+
if (char === '"') {
|
|
6221
|
+
inString = true;
|
|
6222
|
+
out += char;
|
|
6223
|
+
continue;
|
|
6224
|
+
}
|
|
6225
|
+
if (char === "/" && text2[index + 1] === "/") {
|
|
6226
|
+
while (index < text2.length && text2[index] !== "\n" && text2[index] !== "\r") index += 1;
|
|
6227
|
+
index -= 1;
|
|
6228
|
+
continue;
|
|
6229
|
+
}
|
|
6230
|
+
if (char === "/" && text2[index + 1] === "*") {
|
|
6231
|
+
index += 2;
|
|
6232
|
+
while (index < text2.length && !(text2[index] === "*" && text2[index + 1] === "/")) {
|
|
6233
|
+
if (text2[index] === "\n" || text2[index] === "\r") out += text2[index];
|
|
6234
|
+
index += 1;
|
|
6235
|
+
}
|
|
6236
|
+
index += 1;
|
|
6237
|
+
continue;
|
|
6238
|
+
}
|
|
6239
|
+
out += char;
|
|
6240
|
+
}
|
|
6241
|
+
return out;
|
|
6242
|
+
}
|
|
6243
|
+
function stripTrailingCommas2(text2) {
|
|
6244
|
+
let out = "";
|
|
6245
|
+
let inString = false;
|
|
6246
|
+
let escaped = false;
|
|
6247
|
+
for (let index = 0; index < text2.length; index += 1) {
|
|
6248
|
+
const char = text2[index];
|
|
6249
|
+
if (inString) {
|
|
6250
|
+
out += char;
|
|
6251
|
+
if (escaped) escaped = false;
|
|
6252
|
+
else if (char === "\\") escaped = true;
|
|
6253
|
+
else if (char === '"') inString = false;
|
|
6254
|
+
continue;
|
|
6255
|
+
}
|
|
6256
|
+
if (char === '"') {
|
|
6257
|
+
inString = true;
|
|
6258
|
+
out += char;
|
|
6259
|
+
continue;
|
|
6260
|
+
}
|
|
6261
|
+
if (char === ",") {
|
|
6262
|
+
let next = index + 1;
|
|
6263
|
+
while (next < text2.length && /\s/.test(text2[next])) next += 1;
|
|
6264
|
+
if (text2[next] === "}" || text2[next] === "]") continue;
|
|
6265
|
+
}
|
|
6266
|
+
out += char;
|
|
6267
|
+
}
|
|
6268
|
+
return out;
|
|
6269
|
+
}
|
|
6270
|
+
function summarizeJson(content) {
|
|
6271
|
+
try {
|
|
6272
|
+
const parsed = JSON.parse(stripJsonComments2(content));
|
|
6273
|
+
if (Array.isArray(parsed)) {
|
|
6274
|
+
const sample = parsed[0];
|
|
6275
|
+
const sampleKeys = sample && typeof sample === "object" && !Array.isArray(sample) ? Object.keys(sample).slice(0, 10).sort() : [];
|
|
6276
|
+
return {
|
|
6277
|
+
shape: "array",
|
|
6278
|
+
itemCount: parsed.length,
|
|
6279
|
+
keys: [],
|
|
6280
|
+
sampleKeys
|
|
6281
|
+
};
|
|
6282
|
+
}
|
|
6283
|
+
if (parsed && typeof parsed === "object") {
|
|
6284
|
+
const keys = Object.keys(parsed).sort();
|
|
6285
|
+
return {
|
|
6286
|
+
shape: "object",
|
|
6287
|
+
itemCount: keys.length,
|
|
6288
|
+
keys: keys.slice(0, 30),
|
|
6289
|
+
sampleKeys: []
|
|
6290
|
+
};
|
|
6291
|
+
}
|
|
6292
|
+
return {
|
|
6293
|
+
shape: typeof parsed,
|
|
6294
|
+
itemCount: null,
|
|
6295
|
+
keys: [],
|
|
6296
|
+
sampleKeys: []
|
|
6297
|
+
};
|
|
6298
|
+
} catch {
|
|
6299
|
+
return {
|
|
6300
|
+
shape: "invalid-json",
|
|
6301
|
+
itemCount: null,
|
|
6302
|
+
keys: [],
|
|
6303
|
+
sampleKeys: []
|
|
6304
|
+
};
|
|
6305
|
+
}
|
|
6306
|
+
}
|
|
6307
|
+
function frontMatterEnd(lines) {
|
|
6308
|
+
if (lines.length < 3 || lines[0].trim() !== "---") return 0;
|
|
6309
|
+
for (let index = 1; index < lines.length; index += 1) {
|
|
6310
|
+
const line = lines[index];
|
|
6311
|
+
const candidate = line.trim();
|
|
6312
|
+
if (candidate === "---" || candidate === "...") {
|
|
6313
|
+
return index > 1 ? index : 0;
|
|
6314
|
+
}
|
|
6315
|
+
if (candidate === "") continue;
|
|
6316
|
+
if (/^#(\s|$)/.test(candidate)) continue;
|
|
6317
|
+
if (/^(`{3,}|~{3,})/.test(candidate)) return 0;
|
|
6318
|
+
if (/^#{1,6}\s+\S/.test(candidate)) return 0;
|
|
6319
|
+
const isYamlMapping = /^[A-Za-z0-9_.-]+\s*:(\s|$)/.test(candidate);
|
|
6320
|
+
const isListItem = /^-(?:(\s|$))/.test(candidate);
|
|
6321
|
+
const isIndented = /^[ \t]/.test(line);
|
|
6322
|
+
if (!isYamlMapping && !isListItem && !isIndented) return 0;
|
|
6323
|
+
}
|
|
6324
|
+
return 0;
|
|
6325
|
+
}
|
|
6326
|
+
function summarizeMarkdown(content) {
|
|
6327
|
+
const headings = [];
|
|
6328
|
+
const frontmatterKeys = [];
|
|
6329
|
+
const lines = content.replace(/^\uFEFF/, "").split(/\r\n|\n|\r/);
|
|
6330
|
+
let inFrontmatter = false;
|
|
6331
|
+
let fence = null;
|
|
6332
|
+
const frontMatterEndLine = frontMatterEnd(lines);
|
|
6333
|
+
for (let index = 0; index < lines.length; index++) {
|
|
6334
|
+
const line = lines[index];
|
|
6335
|
+
if (index === 0 && frontMatterEndLine > 0) {
|
|
6336
|
+
inFrontmatter = true;
|
|
6337
|
+
continue;
|
|
6338
|
+
}
|
|
6339
|
+
if (inFrontmatter) {
|
|
6340
|
+
const trimmedFrontmatter = line.trim();
|
|
6341
|
+
if (trimmedFrontmatter === "---" || trimmedFrontmatter === "...") {
|
|
6342
|
+
inFrontmatter = false;
|
|
6343
|
+
continue;
|
|
6344
|
+
}
|
|
6345
|
+
const match = /^([A-Za-z0-9_.-]+)\s*:/.exec(line);
|
|
6346
|
+
if (match) frontmatterKeys.push(match[1]);
|
|
6347
|
+
continue;
|
|
6348
|
+
}
|
|
6349
|
+
const trimmed = line.trim();
|
|
6350
|
+
const fenceMatch = /^(`{3,}|~{3,})(.*)$/.exec(trimmed);
|
|
6351
|
+
if (fenceMatch) {
|
|
6352
|
+
const run = fenceMatch[1];
|
|
6353
|
+
const rest = fenceMatch[2].trim();
|
|
6354
|
+
if (fence === null) {
|
|
6355
|
+
fence = { marker: run[0], length: run.length };
|
|
6356
|
+
} else if (run[0] === fence.marker && run.length >= fence.length && rest === "") {
|
|
6357
|
+
fence = null;
|
|
6358
|
+
}
|
|
6359
|
+
continue;
|
|
6360
|
+
}
|
|
6361
|
+
if (fence !== null) continue;
|
|
6362
|
+
const headingMatch = /^(#{1,6})\s+(.+)$/.exec(trimmed);
|
|
6363
|
+
if (headingMatch) {
|
|
6364
|
+
headings.push(`${headingMatch[1]} ${headingMatch[2].trim()}`);
|
|
6365
|
+
}
|
|
6366
|
+
}
|
|
6367
|
+
return {
|
|
6368
|
+
headings: headings.slice(0, 40),
|
|
6369
|
+
frontmatterKeys: Array.from(new Set(frontmatterKeys)).sort()
|
|
6370
|
+
};
|
|
6371
|
+
}
|
|
6372
|
+
|
|
5864
6373
|
// src/gitignore.ts
|
|
5865
6374
|
import * as fs9 from "fs";
|
|
5866
6375
|
import * as path10 from "path";
|
|
@@ -5901,7 +6410,7 @@ function gitignorePath(cwd, filePath) {
|
|
|
5901
6410
|
|
|
5902
6411
|
// src/version.ts
|
|
5903
6412
|
var GENERATOR_NAME = "codebase-snapshot";
|
|
5904
|
-
var GENERATOR_VERSION = "0.9.
|
|
6413
|
+
var GENERATOR_VERSION = "0.9.14";
|
|
5905
6414
|
var LEGACY_GENERATOR_NAMES = ["@ankurhalder/codebase-snapshot"];
|
|
5906
6415
|
function isOurGeneratorName(name) {
|
|
5907
6416
|
if (typeof name !== "string") return false;
|
|
@@ -6111,6 +6620,24 @@ function displayWidth(line) {
|
|
|
6111
6620
|
return width;
|
|
6112
6621
|
}
|
|
6113
6622
|
var GUTTER_PATTERN = /^[ \t]*(?:[❯◆◻◼▏✖][ \t]+)*/;
|
|
6623
|
+
function sliceByDisplayWidth(text2, maxCells) {
|
|
6624
|
+
let cells = 0;
|
|
6625
|
+
let lastSpace = -1;
|
|
6626
|
+
let index = 0;
|
|
6627
|
+
for (const character of text2) {
|
|
6628
|
+
const width = isWideCodePoint(character.codePointAt(0) ?? 0) ? 2 : 1;
|
|
6629
|
+
if (cells + width > maxCells) break;
|
|
6630
|
+
if (character === " ") lastSpace = index;
|
|
6631
|
+
cells += width;
|
|
6632
|
+
index += character.length;
|
|
6633
|
+
}
|
|
6634
|
+
if (index >= text2.length) return { head: text2, tail: "" };
|
|
6635
|
+
if (lastSpace > 0) {
|
|
6636
|
+
return { head: text2.slice(0, lastSpace), tail: text2.slice(lastSpace + 1).trimStart() };
|
|
6637
|
+
}
|
|
6638
|
+
const safeIndex = index > 0 ? index : Array.from(text2)[0]?.length ?? 1;
|
|
6639
|
+
return { head: text2.slice(0, safeIndex), tail: text2.slice(safeIndex).trimStart() };
|
|
6640
|
+
}
|
|
6114
6641
|
function wrapOneLine(line, limit) {
|
|
6115
6642
|
if (displayWidth(line) <= limit) return [line];
|
|
6116
6643
|
const gutter = GUTTER_PATTERN.exec(line)?.[0] ?? "";
|
|
@@ -6125,12 +6652,11 @@ function wrapOneLine(line, limit) {
|
|
|
6125
6652
|
rows.push(prefix + rest);
|
|
6126
6653
|
break;
|
|
6127
6654
|
}
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
rest = rest.slice(cut).trimStart();
|
|
6655
|
+
const { head, tail } = sliceByDisplayWidth(rest, room);
|
|
6656
|
+
rows.push(
|
|
6657
|
+
`${prefix}${head.length > 0 ? head : rest.slice(0, 1)}`
|
|
6658
|
+
);
|
|
6659
|
+
rest = tail;
|
|
6134
6660
|
prefix = continuation;
|
|
6135
6661
|
}
|
|
6136
6662
|
return rows;
|
|
@@ -6201,7 +6727,7 @@ function keyReader(input) {
|
|
|
6201
6727
|
next() {
|
|
6202
6728
|
const queued = queue.shift();
|
|
6203
6729
|
if (queued) return Promise.resolve(queued);
|
|
6204
|
-
return new Promise((
|
|
6730
|
+
return new Promise((resolve10) => waiters.push(resolve10));
|
|
6205
6731
|
},
|
|
6206
6732
|
close() {
|
|
6207
6733
|
input.off("keypress", onKey);
|
|
@@ -6418,17 +6944,25 @@ var CONFIG_CANDIDATES = [
|
|
|
6418
6944
|
".snapshotrc.json",
|
|
6419
6945
|
".snapshotrc"
|
|
6420
6946
|
];
|
|
6421
|
-
function
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6947
|
+
function parsePackageJson(raw) {
|
|
6948
|
+
const parsed = JSON.parse(stripJsonComments2(raw));
|
|
6949
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
6950
|
+
throw new Error("package.json is not a JSON object");
|
|
6951
|
+
}
|
|
6952
|
+
return parsed;
|
|
6953
|
+
}
|
|
6954
|
+
function readPackageJson(cwd) {
|
|
6425
6955
|
try {
|
|
6426
|
-
|
|
6427
|
-
return parsed.type === "module" ? "snapshot.config.ts" : "snapshot.config.mjs";
|
|
6956
|
+
return parsePackageJson(fs10.readFileSync(path11.join(cwd, "package.json"), "utf8"));
|
|
6428
6957
|
} catch {
|
|
6429
|
-
return
|
|
6958
|
+
return null;
|
|
6430
6959
|
}
|
|
6431
6960
|
}
|
|
6961
|
+
function chooseConfigFileName(cwd) {
|
|
6962
|
+
if (!supportsTypeScriptImports()) return "snapshot.config.mjs";
|
|
6963
|
+
const parsed = readPackageJson(cwd);
|
|
6964
|
+
return parsed?.type === "module" ? "snapshot.config.ts" : "snapshot.config.mjs";
|
|
6965
|
+
}
|
|
6432
6966
|
var SIZE_CHOICES = [
|
|
6433
6967
|
{ value: "60k", label: "Small", hint: "60 KB \xB7 ~15k tokens \xB7 tight context windows" },
|
|
6434
6968
|
{ value: "120k", label: "Medium", hint: "120 KB \xB7 ~30k tokens \xB7 recommended" },
|
|
@@ -6436,12 +6970,9 @@ var SIZE_CHOICES = [
|
|
|
6436
6970
|
{ value: "custom", label: "Custom\u2026", hint: "type a size like 150k or 1.5mb" }
|
|
6437
6971
|
];
|
|
6438
6972
|
function packageJsonHasConfig(cwd) {
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
} catch {
|
|
6443
|
-
return false;
|
|
6444
|
-
}
|
|
6973
|
+
const parsed = readPackageJson(cwd);
|
|
6974
|
+
if (parsed === null) return false;
|
|
6975
|
+
return typeof parsed.snapshot === "object" && parsed.snapshot !== null && !Array.isArray(parsed.snapshot);
|
|
6445
6976
|
}
|
|
6446
6977
|
function artifactAnswers(options) {
|
|
6447
6978
|
const artifacts = { ...DEFAULT_ANSWERS.artifacts };
|
|
@@ -6482,12 +7013,7 @@ function writeFileOrExplain(target, content) {
|
|
|
6482
7013
|
function canWriteModuleConfig(name, cwd) {
|
|
6483
7014
|
if (name.endsWith(".mjs") || name.endsWith(".ts")) return true;
|
|
6484
7015
|
if (!name.endsWith(".js")) return false;
|
|
6485
|
-
|
|
6486
|
-
const parsed = JSON.parse(fs10.readFileSync(path11.join(cwd, "package.json"), "utf8"));
|
|
6487
|
-
return parsed.type === "module";
|
|
6488
|
-
} catch {
|
|
6489
|
-
return false;
|
|
6490
|
-
}
|
|
7016
|
+
return readPackageJson(cwd)?.type === "module";
|
|
6491
7017
|
}
|
|
6492
7018
|
function configFormatLabel(name) {
|
|
6493
7019
|
if (name.endsWith(".js")) return 'CommonJS `.js` (package.json has no "type": "module")';
|
|
@@ -6515,7 +7041,8 @@ function serializePackageJson(raw, value) {
|
|
|
6515
7041
|
const crlf = raw.includes("\r\n");
|
|
6516
7042
|
const body = JSON.stringify(value, null, spacing);
|
|
6517
7043
|
const text2 = crlf ? body.replace(/\n/g, "\r\n") : body;
|
|
6518
|
-
|
|
7044
|
+
const bom = raw.startsWith("\uFEFF") ? "\uFEFF" : "";
|
|
7045
|
+
return `${bom}${text2}${raw.endsWith("\n") ? crlf ? "\r\n" : "\n" : ""}`;
|
|
6519
7046
|
}
|
|
6520
7047
|
function updatePackageJson(cwd, notes) {
|
|
6521
7048
|
const packagePath = path11.join(cwd, "package.json");
|
|
@@ -6531,7 +7058,7 @@ function updatePackageJson(cwd, notes) {
|
|
|
6531
7058
|
let parsed;
|
|
6532
7059
|
try {
|
|
6533
7060
|
raw = fs10.readFileSync(packagePath, "utf8");
|
|
6534
|
-
parsed =
|
|
7061
|
+
parsed = parsePackageJson(raw);
|
|
6535
7062
|
} catch {
|
|
6536
7063
|
notes.push("Could not parse package.json; npm scripts and the devDependency were not added.");
|
|
6537
7064
|
return unchanged;
|
|
@@ -6949,158 +7476,76 @@ function hasPriorSnapshot(cwd, outDir) {
|
|
|
6949
7476
|
}
|
|
6950
7477
|
if (parsed === null || typeof parsed !== "object") return false;
|
|
6951
7478
|
return isOurGeneratorName(parsed.generator);
|
|
6952
|
-
}
|
|
6953
|
-
|
|
6954
|
-
// src/index.ts
|
|
6955
|
-
import * as crypto4 from "crypto";
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
import
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
if (
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
}
|
|
6985
|
-
if (inString) {
|
|
6986
|
-
out += char;
|
|
6987
|
-
if (char === "\\") {
|
|
6988
|
-
if (next !== void 0) {
|
|
6989
|
-
out += next;
|
|
6990
|
-
i += 1;
|
|
6991
|
-
}
|
|
6992
|
-
} else if (char === '"') {
|
|
6993
|
-
inString = false;
|
|
6994
|
-
}
|
|
6995
|
-
continue;
|
|
6996
|
-
}
|
|
6997
|
-
if (char === '"') {
|
|
6998
|
-
inString = true;
|
|
6999
|
-
out += char;
|
|
7000
|
-
continue;
|
|
7001
|
-
}
|
|
7002
|
-
if (char === "/" && next === "/") {
|
|
7003
|
-
inLineComment = true;
|
|
7004
|
-
i += 1;
|
|
7005
|
-
continue;
|
|
7006
|
-
}
|
|
7007
|
-
if (char === "/" && next === "*") {
|
|
7008
|
-
inBlockComment = true;
|
|
7009
|
-
i += 1;
|
|
7010
|
-
continue;
|
|
7011
|
-
}
|
|
7012
|
-
out += char;
|
|
7013
|
-
}
|
|
7014
|
-
return out;
|
|
7015
|
-
}
|
|
7016
|
-
function summarizeJson(content) {
|
|
7017
|
-
try {
|
|
7018
|
-
const parsed = JSON.parse(stripJsonComments2(content));
|
|
7019
|
-
if (Array.isArray(parsed)) {
|
|
7020
|
-
const sample = parsed[0];
|
|
7021
|
-
const sampleKeys = sample && typeof sample === "object" && !Array.isArray(sample) ? Object.keys(sample).slice(0, 10).sort() : [];
|
|
7022
|
-
return {
|
|
7023
|
-
shape: "array",
|
|
7024
|
-
itemCount: parsed.length,
|
|
7025
|
-
keys: [],
|
|
7026
|
-
sampleKeys
|
|
7027
|
-
};
|
|
7028
|
-
}
|
|
7029
|
-
if (parsed && typeof parsed === "object") {
|
|
7030
|
-
const keys = Object.keys(parsed).sort();
|
|
7031
|
-
return {
|
|
7032
|
-
shape: "object",
|
|
7033
|
-
itemCount: keys.length,
|
|
7034
|
-
keys: keys.slice(0, 30),
|
|
7035
|
-
sampleKeys: []
|
|
7036
|
-
};
|
|
7037
|
-
}
|
|
7038
|
-
return {
|
|
7039
|
-
shape: typeof parsed,
|
|
7040
|
-
itemCount: null,
|
|
7041
|
-
keys: [],
|
|
7042
|
-
sampleKeys: []
|
|
7043
|
-
};
|
|
7044
|
-
} catch {
|
|
7045
|
-
return {
|
|
7046
|
-
shape: "invalid-json",
|
|
7047
|
-
itemCount: null,
|
|
7048
|
-
keys: [],
|
|
7049
|
-
sampleKeys: []
|
|
7050
|
-
};
|
|
7051
|
-
}
|
|
7052
|
-
}
|
|
7053
|
-
function summarizeMarkdown(content) {
|
|
7054
|
-
const headings = [];
|
|
7055
|
-
const frontmatterKeys = [];
|
|
7056
|
-
const lines = content.replace(/^\uFEFF/, "").split("\n");
|
|
7057
|
-
let inFrontmatter = false;
|
|
7058
|
-
let fence = null;
|
|
7059
|
-
let closesFrontmatter = false;
|
|
7060
|
-
for (let index = 1; index < lines.length; index++) {
|
|
7061
|
-
if (lines[index].trim() === "---") {
|
|
7062
|
-
closesFrontmatter = true;
|
|
7063
|
-
break;
|
|
7064
|
-
}
|
|
7065
|
-
}
|
|
7066
|
-
for (let index = 0; index < lines.length; index++) {
|
|
7067
|
-
const line = lines[index];
|
|
7068
|
-
if (index === 0 && line.trim() === "---" && closesFrontmatter) {
|
|
7069
|
-
inFrontmatter = true;
|
|
7070
|
-
continue;
|
|
7071
|
-
}
|
|
7072
|
-
if (inFrontmatter) {
|
|
7073
|
-
if (line.trim() === "---") {
|
|
7074
|
-
inFrontmatter = false;
|
|
7075
|
-
continue;
|
|
7479
|
+
}
|
|
7480
|
+
|
|
7481
|
+
// src/index.ts
|
|
7482
|
+
import * as crypto4 from "crypto";
|
|
7483
|
+
|
|
7484
|
+
// src/stdout.ts
|
|
7485
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
7486
|
+
var hookStdoutScope = new AsyncLocalStorage();
|
|
7487
|
+
var captureScope = new AsyncLocalStorage();
|
|
7488
|
+
var installs = 0;
|
|
7489
|
+
var underlying = null;
|
|
7490
|
+
var router = null;
|
|
7491
|
+
var asText = (chunk) => typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
7492
|
+
var callbackIn = (rest) => {
|
|
7493
|
+
for (const argument of rest) {
|
|
7494
|
+
if (typeof argument === "function") return argument;
|
|
7495
|
+
}
|
|
7496
|
+
return void 0;
|
|
7497
|
+
};
|
|
7498
|
+
function installStdoutRouter() {
|
|
7499
|
+
if (installs === 0) {
|
|
7500
|
+
underlying = process.stdout.write;
|
|
7501
|
+
const realStdout = underlying.bind(process.stdout);
|
|
7502
|
+
const realStderr = process.stderr.write.bind(process.stderr);
|
|
7503
|
+
router = ((...args) => {
|
|
7504
|
+
if (hookStdoutScope.getStore() === true) return realStderr(...args);
|
|
7505
|
+
const capture = captureScope.getStore();
|
|
7506
|
+
if (capture !== void 0) {
|
|
7507
|
+
capture.write(asText(args[0]));
|
|
7508
|
+
const callback = callbackIn(args.slice(1));
|
|
7509
|
+
if (callback) callback(null);
|
|
7510
|
+
return true;
|
|
7076
7511
|
}
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7512
|
+
return realStdout(...args);
|
|
7513
|
+
});
|
|
7514
|
+
process.stdout.write = router;
|
|
7515
|
+
}
|
|
7516
|
+
installs += 1;
|
|
7517
|
+
let released = false;
|
|
7518
|
+
return () => {
|
|
7519
|
+
if (released) return;
|
|
7520
|
+
released = true;
|
|
7521
|
+
installs -= 1;
|
|
7522
|
+
if (installs === 0) {
|
|
7523
|
+
if (process.stdout.write === router && underlying !== null) {
|
|
7524
|
+
process.stdout.write = underlying;
|
|
7090
7525
|
}
|
|
7091
|
-
|
|
7526
|
+
underlying = null;
|
|
7527
|
+
router = null;
|
|
7092
7528
|
}
|
|
7093
|
-
if (fence !== null) continue;
|
|
7094
|
-
const headingMatch = /^(#{1,6})\s+(.+)$/.exec(trimmed);
|
|
7095
|
-
if (headingMatch) {
|
|
7096
|
-
headings.push(`${headingMatch[1]} ${headingMatch[2].trim()}`);
|
|
7097
|
-
}
|
|
7098
|
-
}
|
|
7099
|
-
return {
|
|
7100
|
-
headings: headings.slice(0, 40),
|
|
7101
|
-
frontmatterKeys: Array.from(new Set(frontmatterKeys)).sort()
|
|
7102
7529
|
};
|
|
7103
7530
|
}
|
|
7531
|
+
async function runWithStdoutCapture(fn) {
|
|
7532
|
+
let text2 = "";
|
|
7533
|
+
const release = installStdoutRouter();
|
|
7534
|
+
try {
|
|
7535
|
+
const value = await captureScope.run(
|
|
7536
|
+
{ write: (chunk) => void (text2 += chunk) },
|
|
7537
|
+
fn
|
|
7538
|
+
);
|
|
7539
|
+
return { value, text: text2 };
|
|
7540
|
+
} finally {
|
|
7541
|
+
release();
|
|
7542
|
+
}
|
|
7543
|
+
}
|
|
7544
|
+
|
|
7545
|
+
// src/index.ts
|
|
7546
|
+
import * as fs14 from "fs";
|
|
7547
|
+
import * as os2 from "os";
|
|
7548
|
+
import * as path16 from "path";
|
|
7104
7549
|
|
|
7105
7550
|
// src/ast/skeleton.ts
|
|
7106
7551
|
import ts2 from "typescript";
|
|
@@ -7109,25 +7554,36 @@ function hasCollapsibleBody(node) {
|
|
|
7109
7554
|
const body = node.body;
|
|
7110
7555
|
return body !== void 0 && ts2.isBlock(body);
|
|
7111
7556
|
}
|
|
7112
|
-
function
|
|
7113
|
-
|
|
7557
|
+
function scriptKindFor(fileName) {
|
|
7558
|
+
const lower = fileName.toLowerCase();
|
|
7559
|
+
if (lower.endsWith(".tsx")) return ts2.ScriptKind.TSX;
|
|
7560
|
+
if (lower.endsWith(".jsx")) return ts2.ScriptKind.JSX;
|
|
7561
|
+
if (lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs")) {
|
|
7562
|
+
return ts2.ScriptKind.JS;
|
|
7563
|
+
}
|
|
7564
|
+
return ts2.ScriptKind.TS;
|
|
7114
7565
|
}
|
|
7115
7566
|
function skeletonizeTypeScript(source, fileName = "file.ts") {
|
|
7116
|
-
const scriptKind = isTsxFileName(fileName) ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS;
|
|
7117
7567
|
const sourceFile = ts2.createSourceFile(
|
|
7118
7568
|
fileName,
|
|
7119
7569
|
source,
|
|
7120
7570
|
ts2.ScriptTarget.Latest,
|
|
7121
|
-
|
|
7122
|
-
|
|
7571
|
+
// `setParentNodes: false`. Nothing here reads `.parent` — the one `getStart` call is given the
|
|
7572
|
+
// source file explicitly, which is the reason that argument exists. Asking for parent pointers
|
|
7573
|
+
// makes the parser run a second full pass over every node in the file to set them, on top of
|
|
7574
|
+
// the summary parse this same file already pays for.
|
|
7575
|
+
false,
|
|
7576
|
+
scriptKindFor(fileName)
|
|
7123
7577
|
);
|
|
7124
7578
|
const spans = [];
|
|
7125
7579
|
const visit = (node) => {
|
|
7126
7580
|
if ((ts2.isFunctionDeclaration(node) || ts2.isMethodDeclaration(node) || ts2.isConstructorDeclaration(node) || ts2.isGetAccessorDeclaration(node) || ts2.isSetAccessorDeclaration(node) || ts2.isFunctionExpression(node) || ts2.isArrowFunction(node)) && hasCollapsibleBody(node)) {
|
|
7127
7581
|
const body = node.body;
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7582
|
+
if (body.statements.length > 0) {
|
|
7583
|
+
const start = body.getStart(sourceFile);
|
|
7584
|
+
const end = body.getEnd();
|
|
7585
|
+
if (end > start) spans.push({ start, end });
|
|
7586
|
+
}
|
|
7131
7587
|
}
|
|
7132
7588
|
ts2.forEachChild(node, visit);
|
|
7133
7589
|
};
|
|
@@ -7139,16 +7595,21 @@ function skeletonizeTypeScript(source, fileName = "file.ts") {
|
|
|
7139
7595
|
if (last && span.start < last.end) continue;
|
|
7140
7596
|
outermost.push(span);
|
|
7141
7597
|
}
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7598
|
+
const chunks = [];
|
|
7599
|
+
let cursor = 0;
|
|
7600
|
+
for (const span of outermost) {
|
|
7601
|
+
chunks.push(source.slice(cursor, span.start), "{ /* ... */ }");
|
|
7602
|
+
cursor = span.end;
|
|
7146
7603
|
}
|
|
7147
|
-
|
|
7604
|
+
chunks.push(source.slice(cursor));
|
|
7605
|
+
return { text: chunks.join(""), collapsed: outermost.length };
|
|
7148
7606
|
}
|
|
7149
7607
|
function isSkeletonizable(filePath) {
|
|
7150
7608
|
const lower = filePath.toLowerCase();
|
|
7151
|
-
|
|
7609
|
+
if (lower.endsWith(".d.ts") || lower.endsWith(".d.mts") || lower.endsWith(".d.cts")) {
|
|
7610
|
+
return false;
|
|
7611
|
+
}
|
|
7612
|
+
return lower.endsWith(".ts") || lower.endsWith(".tsx") || lower.endsWith(".mts") || lower.endsWith(".cts") || lower.endsWith(".js") || lower.endsWith(".jsx") || lower.endsWith(".mjs") || lower.endsWith(".cjs");
|
|
7152
7613
|
}
|
|
7153
7614
|
|
|
7154
7615
|
// src/generator/audit.ts
|
|
@@ -7422,23 +7883,55 @@ function truncateWithBudgetMarker(content, maxBytes, budget, originalLines) {
|
|
|
7422
7883
|
};
|
|
7423
7884
|
}
|
|
7424
7885
|
function partitionFiles(files, maxPartBytes, contentFingerprint) {
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
const
|
|
7429
|
-
|
|
7886
|
+
return partitionFilesForBuckets([{ files, budgetBytes: maxPartBytes }], contentFingerprint);
|
|
7887
|
+
}
|
|
7888
|
+
function partitionFilesForBuckets(buckets, contentFingerprint) {
|
|
7889
|
+
const groups = [];
|
|
7890
|
+
for (const bucket of buckets) {
|
|
7891
|
+
if (bucket.files.length === 0) continue;
|
|
7892
|
+
const budget = Math.max(
|
|
7893
|
+
MIN_SECTION_BUDGET_BYTES + HEADER_RESERVE_BYTES,
|
|
7894
|
+
bucket.budgetBytes
|
|
7895
|
+
);
|
|
7896
|
+
const sectionBudget = budget - HEADER_RESERVE_BYTES;
|
|
7897
|
+
const prepared = bucket.files.map((file) => prepareSection(file, sectionBudget));
|
|
7898
|
+
for (const sections of greedyGroups(prepared, budget)) groups.push({ sections, budget });
|
|
7899
|
+
}
|
|
7900
|
+
if (groups.length === 0) return [];
|
|
7901
|
+
const measurements = /* @__PURE__ */ new Map();
|
|
7902
|
+
const widthOf = (total) => Math.max(3, String(total).length);
|
|
7903
|
+
const measure = (group, index, total) => {
|
|
7904
|
+
const cached = measurements.get(group);
|
|
7905
|
+
const width = widthOf(total);
|
|
7906
|
+
if (cached !== void 0 && cached.index === index && cached.width === width) {
|
|
7907
|
+
return cached.size;
|
|
7908
|
+
}
|
|
7909
|
+
const { content } = assembleGroup(group, index, total, contentFingerprint);
|
|
7910
|
+
const size2 = Buffer.byteLength(content, "utf8");
|
|
7911
|
+
measurements.set(group, { index, width, size: size2 });
|
|
7912
|
+
return size2;
|
|
7913
|
+
};
|
|
7430
7914
|
for (let pass = 0; pass < MAX_SPLIT_PASSES; pass++) {
|
|
7431
|
-
const
|
|
7432
|
-
|
|
7433
|
-
|
|
7915
|
+
const total = groups.length;
|
|
7916
|
+
let oversized = -1;
|
|
7917
|
+
for (let index = 0; index < total; index += 1) {
|
|
7918
|
+
const group2 = groups[index];
|
|
7919
|
+
if (measure(group2, index, total) > group2.budget) {
|
|
7920
|
+
oversized = index;
|
|
7921
|
+
break;
|
|
7922
|
+
}
|
|
7923
|
+
}
|
|
7924
|
+
if (oversized === -1) return renderGroups(groups, contentFingerprint);
|
|
7434
7925
|
const group = groups[oversized];
|
|
7435
|
-
if (group.length > 1) {
|
|
7436
|
-
const moved = group.pop();
|
|
7437
|
-
groups.splice(oversized + 1, 0, [moved]);
|
|
7926
|
+
if (group.sections.length > 1) {
|
|
7927
|
+
const moved = group.sections.pop();
|
|
7928
|
+
groups.splice(oversized + 1, 0, { sections: [moved], budget: group.budget });
|
|
7929
|
+
measurements.delete(group);
|
|
7438
7930
|
continue;
|
|
7439
7931
|
}
|
|
7440
|
-
const [section] = group;
|
|
7441
|
-
|
|
7932
|
+
const [section] = group.sections;
|
|
7933
|
+
if (section === void 0) return renderGroups(groups, contentFingerprint);
|
|
7934
|
+
const overflow = measure(group, oversized, total) - group.budget;
|
|
7442
7935
|
const currentContent = section.file.content ?? "";
|
|
7443
7936
|
const shrinkTo = Math.max(
|
|
7444
7937
|
0,
|
|
@@ -7447,26 +7940,33 @@ function partitionFiles(files, maxPartBytes, contentFingerprint) {
|
|
|
7447
7940
|
const shrunk = truncateWithBudgetMarker(
|
|
7448
7941
|
currentContent,
|
|
7449
7942
|
shrinkTo,
|
|
7450
|
-
budget,
|
|
7943
|
+
group.budget,
|
|
7451
7944
|
section.file.originalLines
|
|
7452
7945
|
);
|
|
7453
7946
|
if (shrunk.isTruncated) {
|
|
7454
7947
|
applyTruncatedContent(section.file, shrunk.content);
|
|
7455
7948
|
section.text = renderFileSection(section.file);
|
|
7456
7949
|
section.byteSize = Buffer.byteLength(section.text, "utf8");
|
|
7950
|
+
measurements.delete(group);
|
|
7457
7951
|
continue;
|
|
7458
7952
|
}
|
|
7953
|
+
const rendered = renderGroups(groups, contentFingerprint);
|
|
7459
7954
|
const marker2 = TRUNCATION_MARKER;
|
|
7460
|
-
assertBudgetHoldsMarker(budget);
|
|
7955
|
+
assertBudgetHoldsMarker(group.budget);
|
|
7461
7956
|
const target = Math.max(
|
|
7462
7957
|
0,
|
|
7463
|
-
budget - Buffer.byteLength(marker2, "utf8") - TOKEN_LINE_SLACK
|
|
7958
|
+
group.budget - Buffer.byteLength(marker2, "utf8") - TOKEN_LINE_SLACK
|
|
7464
7959
|
);
|
|
7465
7960
|
const clipped = truncateToBytes(rendered[oversized].markdown, target);
|
|
7466
|
-
rendered[oversized] = rehashPart(
|
|
7961
|
+
rendered[oversized] = rehashPart(
|
|
7962
|
+
rendered[oversized],
|
|
7963
|
+
clipped.content + marker2
|
|
7964
|
+
);
|
|
7467
7965
|
return rendered;
|
|
7468
7966
|
}
|
|
7469
|
-
return
|
|
7967
|
+
return renderGroups(groups, contentFingerprint).flatMap(
|
|
7968
|
+
(part) => enforceBudget([part], part.metadata.budgetBytes)
|
|
7969
|
+
);
|
|
7470
7970
|
}
|
|
7471
7971
|
var TRUNCATION_MARKER = "\n... [part truncated: cannot fit budget] ...\n";
|
|
7472
7972
|
function assertBudgetHoldsMarker(budget) {
|
|
@@ -7489,69 +7989,7 @@ function enforceBudget(parts, budget) {
|
|
|
7489
7989
|
return rehashPart(part, clipped.content + TRUNCATION_MARKER);
|
|
7490
7990
|
});
|
|
7491
7991
|
}
|
|
7492
|
-
|
|
7493
|
-
const total = parts.length;
|
|
7494
|
-
if (total === 0) return [];
|
|
7495
|
-
const zeroPad = (index) => String(index).padStart(Math.max(3, String(total).length), "0");
|
|
7496
|
-
const nameFor = (position) => `part-${zeroPad(position)}.md`;
|
|
7497
|
-
const marker2 = "## Contents\n";
|
|
7498
|
-
const renumbered = parts.map((part, index) => {
|
|
7499
|
-
const markerIndex = part.markdown.indexOf(marker2);
|
|
7500
|
-
const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex + marker2.length);
|
|
7501
|
-
const included = part.metadata.includedFiles;
|
|
7502
|
-
if (markerIndex === -1) {
|
|
7503
|
-
throw new Error(`${part.filename} has no "## Contents" section to re-number.`);
|
|
7504
|
-
}
|
|
7505
|
-
const layerCounts = {};
|
|
7506
|
-
const kindCounts = {};
|
|
7507
|
-
for (const file of included) {
|
|
7508
|
-
layerCounts[file.layer] = (layerCounts[file.layer] || 0) + 1;
|
|
7509
|
-
kindCounts[file.kind] = (kindCounts[file.kind] || 0) + 1;
|
|
7510
|
-
}
|
|
7511
|
-
const pathLines = included.map((file) => displayPath(file.relativePath));
|
|
7512
|
-
const listFence = fenceForText(pathLines.join("\n"), 3);
|
|
7513
|
-
const header = [
|
|
7514
|
-
`# Codebase Snapshot Part ${zeroPad(index + 1)} of ${zeroPad(total)}`,
|
|
7515
|
-
"",
|
|
7516
|
-
`- Previous: ${index === 0 ? "none" : nameFor(index)}`,
|
|
7517
|
-
`- Next: ${index === total - 1 ? "none" : nameFor(index + 2)}`,
|
|
7518
|
-
`- Files: ${included.length}`,
|
|
7519
|
-
`- Content Fingerprint: ${contentFingerprint || "none"}`,
|
|
7520
|
-
`- Layers: ${topEntries(layerCounts, 6) || "none"}`,
|
|
7521
|
-
`- Kinds: ${topEntries(kindCounts, 8) || "none"}`,
|
|
7522
|
-
"",
|
|
7523
|
-
UNTRUSTED_CONTENT_NOTICE,
|
|
7524
|
-
"",
|
|
7525
|
-
"## Files",
|
|
7526
|
-
"",
|
|
7527
|
-
`${listFence}text`,
|
|
7528
|
-
...pathLines,
|
|
7529
|
-
listFence,
|
|
7530
|
-
"",
|
|
7531
|
-
"## Contents",
|
|
7532
|
-
""
|
|
7533
|
-
].join("\n");
|
|
7534
|
-
const assembled = (header + body).replace(/\r\n/g, "\n");
|
|
7535
|
-
const estimatedTokens = estimateTokens(assembled);
|
|
7536
|
-
const markdown = insertTokenLine(assembled, estimatedTokens);
|
|
7537
|
-
const metadata = {
|
|
7538
|
-
...part.metadata,
|
|
7539
|
-
index: index + 1,
|
|
7540
|
-
filename: nameFor(index + 1),
|
|
7541
|
-
byteSize: Buffer.byteLength(markdown, "utf8"),
|
|
7542
|
-
estimatedTokens,
|
|
7543
|
-
sha256: crypto3.createHash("sha256").update(markdown).digest("hex"),
|
|
7544
|
-
lines: countLines(markdown),
|
|
7545
|
-
fileCount: included.length,
|
|
7546
|
-
layersCovered: Object.keys(layerCounts).sort()
|
|
7547
|
-
};
|
|
7548
|
-
return { filename: metadata.filename, markdown, metadata };
|
|
7549
|
-
});
|
|
7550
|
-
return renumbered.map(
|
|
7551
|
-
(part) => enforceBudget([part], part.metadata.budgetBytes)[0] ?? part
|
|
7552
|
-
);
|
|
7553
|
-
}
|
|
7554
|
-
var SECTION_TRUNCATION_NOTICE = "\n... [section truncated: metadata alone exceeds the part budget] ...\n";
|
|
7992
|
+
var SECTION_TRUNCATION_NOTICE = "... [section truncated: the fixed metadata and a code fence did not fit the part budget] ...";
|
|
7555
7993
|
var METADATA_VALUE_MAX = 200;
|
|
7556
7994
|
var DISPLAY_PATH_MAX = 300;
|
|
7557
7995
|
function capValue(value, max) {
|
|
@@ -7593,13 +8031,57 @@ function prepareSection(file, sectionBudget) {
|
|
|
7593
8031
|
shrinkBody();
|
|
7594
8032
|
}
|
|
7595
8033
|
if (byteSize > sectionBudget) {
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
8034
|
+
applyTruncatedContent(file, SECTION_TRUNCATION_NOTICE);
|
|
8035
|
+
file.summary = void 0;
|
|
8036
|
+
file.summaryOmitted = true;
|
|
8037
|
+
text2 = renderFileSection(file);
|
|
7599
8038
|
byteSize = Buffer.byteLength(text2, "utf8");
|
|
8039
|
+
if (byteSize > sectionBudget) {
|
|
8040
|
+
text2 = truncateRenderedSection(text2, sectionBudget, fenceForContent(file.content ?? ""));
|
|
8041
|
+
byteSize = Buffer.byteLength(text2, "utf8");
|
|
8042
|
+
}
|
|
7600
8043
|
}
|
|
7601
8044
|
return { file, text: text2, byteSize };
|
|
7602
8045
|
}
|
|
8046
|
+
function truncateRenderedSection(text2, sectionBudget, fence) {
|
|
8047
|
+
const closing = `
|
|
8048
|
+
${SECTION_TRUNCATION_NOTICE}
|
|
8049
|
+
${fence}
|
|
8050
|
+
`;
|
|
8051
|
+
const bodyRoom = sectionBudget - Buffer.byteLength(closing, "utf8");
|
|
8052
|
+
if (bodyRoom > 0) {
|
|
8053
|
+
const cut2 = truncateToBytes(text2, bodyRoom).content;
|
|
8054
|
+
const lastBreak2 = cut2.lastIndexOf("\n");
|
|
8055
|
+
const kept = lastBreak2 >= 0 ? cut2.slice(0, lastBreak2 + 1) : "";
|
|
8056
|
+
if (fenceIsOpen(kept, fence)) return `${kept}${closing}`;
|
|
8057
|
+
}
|
|
8058
|
+
const probe = text2.slice(0, 512);
|
|
8059
|
+
const emptyFence = fenceForText(probe.slice(0, Math.max(0, probe.lastIndexOf("\n") + 1)), 4);
|
|
8060
|
+
const block = `
|
|
8061
|
+
${emptyFence}
|
|
8062
|
+
${SECTION_TRUNCATION_NOTICE}
|
|
8063
|
+
${emptyFence}
|
|
8064
|
+
`;
|
|
8065
|
+
const prefixRoom = sectionBudget - Buffer.byteLength(block, "utf8");
|
|
8066
|
+
const cut = prefixRoom > 0 ? truncateToBytes(text2, prefixRoom).content : "";
|
|
8067
|
+
const lastBreak = cut.lastIndexOf("\n");
|
|
8068
|
+
const prefix = lastBreak >= 0 ? cut.slice(0, lastBreak + 1) : "";
|
|
8069
|
+
return `${dropTrailingOpenFence(prefix, fence)}${block}`;
|
|
8070
|
+
}
|
|
8071
|
+
function dropTrailingOpenFence(text2, fence) {
|
|
8072
|
+
if (!fenceIsOpen(text2, fence)) return text2;
|
|
8073
|
+
const lines = text2.split("\n");
|
|
8074
|
+
const opening = lines.findIndex((line) => line.startsWith(fence));
|
|
8075
|
+
if (opening < 0) return text2;
|
|
8076
|
+
return lines.slice(0, opening).join("\n") + (opening > 0 ? "\n" : "");
|
|
8077
|
+
}
|
|
8078
|
+
function fenceIsOpen(text2, fence) {
|
|
8079
|
+
let open = false;
|
|
8080
|
+
for (const line of text2.split("\n")) {
|
|
8081
|
+
if (line.startsWith(fence)) open = !open;
|
|
8082
|
+
}
|
|
8083
|
+
return open;
|
|
8084
|
+
}
|
|
7603
8085
|
function applyTruncatedContent(file, content) {
|
|
7604
8086
|
file.content = content;
|
|
7605
8087
|
file.isPreviewTruncated = true;
|
|
@@ -7647,52 +8129,64 @@ function greedyGroups(sections, budget) {
|
|
|
7647
8129
|
if (current.length > 0) groups.push(current);
|
|
7648
8130
|
return groups;
|
|
7649
8131
|
}
|
|
7650
|
-
function
|
|
8132
|
+
function assembleGroup(group, index, total, contentFingerprint) {
|
|
8133
|
+
const width = Math.max(3, String(total).length);
|
|
8134
|
+
const zeroPad = (value) => String(value).padStart(width, "0");
|
|
8135
|
+
const filename = `part-${zeroPad(index + 1)}.md`;
|
|
8136
|
+
const prev = index === 0 ? "none" : `part-${zeroPad(index)}.md`;
|
|
8137
|
+
const next = index === total - 1 ? "none" : `part-${zeroPad(index + 2)}.md`;
|
|
8138
|
+
for (const section of group.sections) {
|
|
8139
|
+
section.file.part = filename;
|
|
8140
|
+
}
|
|
8141
|
+
const layerCounts = {};
|
|
8142
|
+
const kindCounts = {};
|
|
8143
|
+
for (const section of group.sections) {
|
|
8144
|
+
layerCounts[section.file.layer] = (layerCounts[section.file.layer] || 0) + 1;
|
|
8145
|
+
kindCounts[section.file.kind] = (kindCounts[section.file.kind] || 0) + 1;
|
|
8146
|
+
}
|
|
8147
|
+
const pathLines = group.sections.map((s) => displayPath(s.file.relativePath));
|
|
8148
|
+
const listFence = fenceForText(pathLines.join("\n"), 3);
|
|
8149
|
+
const header = [
|
|
8150
|
+
`# Codebase Snapshot Part ${zeroPad(index + 1)} of ${zeroPad(total)}`,
|
|
8151
|
+
"",
|
|
8152
|
+
`- Previous: ${prev}`,
|
|
8153
|
+
`- Next: ${next}`,
|
|
8154
|
+
`- Files: ${group.sections.length}`,
|
|
8155
|
+
`- Content Fingerprint: ${contentFingerprint || "none"}`,
|
|
8156
|
+
`- Layers: ${topEntries(layerCounts, 6) || "none"}`,
|
|
8157
|
+
`- Kinds: ${topEntries(kindCounts, 8) || "none"}`,
|
|
8158
|
+
"",
|
|
8159
|
+
UNTRUSTED_CONTENT_NOTICE,
|
|
8160
|
+
"",
|
|
8161
|
+
"## Files",
|
|
8162
|
+
"",
|
|
8163
|
+
`${listFence}text`,
|
|
8164
|
+
...pathLines,
|
|
8165
|
+
listFence,
|
|
8166
|
+
"",
|
|
8167
|
+
"## Contents",
|
|
8168
|
+
""
|
|
8169
|
+
].join("\n");
|
|
8170
|
+
const assembled = (header + group.sections.map((s) => s.text).join("")).replace(
|
|
8171
|
+
/\r\n|\r/g,
|
|
8172
|
+
"\n"
|
|
8173
|
+
);
|
|
8174
|
+
const estimatedTokens = estimateTokens(assembled);
|
|
8175
|
+
return { filename, content: insertTokenLine(assembled, estimatedTokens), estimatedTokens };
|
|
8176
|
+
}
|
|
8177
|
+
function renderGroups(groups, contentFingerprint) {
|
|
7651
8178
|
const total = groups.length;
|
|
7652
|
-
const zeroPad = (index) => String(index).padStart(Math.max(3, String(total).length), "0");
|
|
7653
8179
|
return groups.map((group, index) => {
|
|
7654
|
-
const filename =
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
const layersCovered = Array.from(new Set(group.map((s) => s.file.layer))).sort();
|
|
7661
|
-
const layerCounts = {};
|
|
7662
|
-
const kindCounts = {};
|
|
7663
|
-
for (const section of group) {
|
|
7664
|
-
layerCounts[section.file.layer] = (layerCounts[section.file.layer] || 0) + 1;
|
|
7665
|
-
kindCounts[section.file.kind] = (kindCounts[section.file.kind] || 0) + 1;
|
|
7666
|
-
}
|
|
7667
|
-
const pathLines = group.map((s) => displayPath(s.file.relativePath));
|
|
7668
|
-
const listFence = fenceForText(pathLines.join("\n"), 3);
|
|
7669
|
-
const header = [
|
|
7670
|
-
`# Codebase Snapshot Part ${zeroPad(index + 1)} of ${zeroPad(total)}`,
|
|
7671
|
-
"",
|
|
7672
|
-
`- Previous: ${prev}`,
|
|
7673
|
-
`- Next: ${next}`,
|
|
7674
|
-
`- Files: ${group.length}`,
|
|
7675
|
-
`- Content Fingerprint: ${contentFingerprint || "none"}`,
|
|
7676
|
-
`- Layers: ${topEntries(layerCounts, 6) || "none"}`,
|
|
7677
|
-
`- Kinds: ${topEntries(kindCounts, 8) || "none"}`,
|
|
7678
|
-
"",
|
|
7679
|
-
UNTRUSTED_CONTENT_NOTICE,
|
|
7680
|
-
"",
|
|
7681
|
-
"## Files",
|
|
7682
|
-
"",
|
|
7683
|
-
`${listFence}text`,
|
|
7684
|
-
...pathLines,
|
|
7685
|
-
listFence,
|
|
7686
|
-
"",
|
|
7687
|
-
"## Contents",
|
|
7688
|
-
""
|
|
7689
|
-
].join("\n");
|
|
7690
|
-
const assembled = (header + group.map((s) => s.text).join("")).replace(/\r\n/g, "\n");
|
|
7691
|
-
const estimatedTokens = estimateTokens(assembled);
|
|
7692
|
-
const content = insertTokenLine(assembled, estimatedTokens);
|
|
8180
|
+
const { filename, content, estimatedTokens } = assembleGroup(
|
|
8181
|
+
group,
|
|
8182
|
+
index,
|
|
8183
|
+
total,
|
|
8184
|
+
contentFingerprint
|
|
8185
|
+
);
|
|
7693
8186
|
const byteSize = Buffer.byteLength(content, "utf8");
|
|
7694
8187
|
const sha256 = crypto3.createHash("sha256").update(content).digest("hex");
|
|
7695
|
-
const
|
|
8188
|
+
const layersCovered = Array.from(new Set(group.sections.map((s) => s.file.layer))).sort();
|
|
8189
|
+
const includedFiles = group.sections.map(({ file }) => ({
|
|
7696
8190
|
relativePath: file.relativePath,
|
|
7697
8191
|
sourceBytes: file.sizeBytes,
|
|
7698
8192
|
includedBytes: file.includedBytes,
|
|
@@ -7705,10 +8199,10 @@ function renderGroups(groups, budget, contentFingerprint) {
|
|
|
7705
8199
|
filename,
|
|
7706
8200
|
byteSize,
|
|
7707
8201
|
estimatedTokens,
|
|
7708
|
-
budgetBytes: budget,
|
|
8202
|
+
budgetBytes: group.budget,
|
|
7709
8203
|
sha256,
|
|
7710
8204
|
lines: countLines(content),
|
|
7711
|
-
fileCount: group.length,
|
|
8205
|
+
fileCount: group.sections.length,
|
|
7712
8206
|
layersCovered,
|
|
7713
8207
|
includedFiles
|
|
7714
8208
|
};
|
|
@@ -7716,7 +8210,7 @@ function renderGroups(groups, budget, contentFingerprint) {
|
|
|
7716
8210
|
});
|
|
7717
8211
|
}
|
|
7718
8212
|
function emittedBody(content) {
|
|
7719
|
-
const normalised = content.replace(/\r\n/g, "\n");
|
|
8213
|
+
const normalised = content.replace(/\r\n|\r/g, "\n");
|
|
7720
8214
|
return normalised.endsWith("\n") ? normalised.slice(0, -1) : normalised;
|
|
7721
8215
|
}
|
|
7722
8216
|
function compactMetadata(file) {
|
|
@@ -7784,7 +8278,7 @@ function generateMonolithJson(manifest, filesWithContent) {
|
|
|
7784
8278
|
}
|
|
7785
8279
|
function generateMonolithMarkdown(manifest, filesWithContent) {
|
|
7786
8280
|
const sections = filesWithContent.map((file) => {
|
|
7787
|
-
const content = (file.content ?? "").replace(/\r\n/g, "\n");
|
|
8281
|
+
const content = (file.content ?? "").replace(/\r\n|\r/g, "\n");
|
|
7788
8282
|
const fence = fenceForContent(content);
|
|
7789
8283
|
return [
|
|
7790
8284
|
`## File: ${escapeControls(file.relativePath)}`,
|
|
@@ -8026,6 +8520,7 @@ function generateProjectTree(filePaths) {
|
|
|
8026
8520
|
|
|
8027
8521
|
// src/update-check.ts
|
|
8028
8522
|
import * as fs12 from "fs";
|
|
8523
|
+
import * as http from "http";
|
|
8029
8524
|
import * as https from "https";
|
|
8030
8525
|
import * as os from "os";
|
|
8031
8526
|
import * as path13 from "path";
|
|
@@ -8050,22 +8545,28 @@ function updateCommand(kind) {
|
|
|
8050
8545
|
if (kind === "local") return "npm install codebase-snapshot@latest";
|
|
8051
8546
|
return "npm install -g codebase-snapshot";
|
|
8052
8547
|
}
|
|
8053
|
-
function
|
|
8054
|
-
const
|
|
8055
|
-
|
|
8548
|
+
function parseSemver(value) {
|
|
8549
|
+
const cleaned = value.trim().replace(/^v/i, "").split("+")[0] ?? "";
|
|
8550
|
+
const dash = cleaned.indexOf("-");
|
|
8551
|
+
const corePart = dash === -1 ? cleaned : cleaned.slice(0, dash);
|
|
8552
|
+
const prerelease = dash === -1 ? null : cleaned.slice(dash + 1);
|
|
8553
|
+
const core = corePart.split(".").map((part) => {
|
|
8056
8554
|
const parsed = Number(part);
|
|
8057
8555
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
8058
8556
|
});
|
|
8557
|
+
return { core, prerelease };
|
|
8059
8558
|
}
|
|
8060
8559
|
function isNewer(candidate, current) {
|
|
8061
|
-
const left =
|
|
8062
|
-
const right =
|
|
8063
|
-
|
|
8560
|
+
const left = parseSemver(candidate);
|
|
8561
|
+
const right = parseSemver(current);
|
|
8562
|
+
if (left.prerelease !== null && right.prerelease === null) return false;
|
|
8563
|
+
const length = Math.max(left.core.length, right.core.length);
|
|
8064
8564
|
for (let index = 0; index < length; index += 1) {
|
|
8065
|
-
const a = left[index] ?? 0;
|
|
8066
|
-
const b = right[index] ?? 0;
|
|
8565
|
+
const a = left.core[index] ?? 0;
|
|
8566
|
+
const b = right.core[index] ?? 0;
|
|
8067
8567
|
if (a !== b) return a > b;
|
|
8068
8568
|
}
|
|
8569
|
+
if (left.prerelease === null && right.prerelease !== null) return true;
|
|
8069
8570
|
return false;
|
|
8070
8571
|
}
|
|
8071
8572
|
function cacheDirectory(env = process.env, platform = process.platform) {
|
|
@@ -8133,14 +8634,22 @@ function writeCache(file, entry) {
|
|
|
8133
8634
|
}
|
|
8134
8635
|
}
|
|
8135
8636
|
function fetchLatestVersion(url = PACKAGE_LATEST_URL, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
8136
|
-
return new Promise((
|
|
8637
|
+
return new Promise((resolve10) => {
|
|
8137
8638
|
let settled = false;
|
|
8639
|
+
let request = null;
|
|
8138
8640
|
const finish = (value) => {
|
|
8139
8641
|
if (settled) return;
|
|
8140
8642
|
settled = true;
|
|
8141
|
-
|
|
8643
|
+
clearTimeout(deadline);
|
|
8644
|
+
resolve10(value);
|
|
8142
8645
|
};
|
|
8143
|
-
const
|
|
8646
|
+
const deadline = setTimeout(() => {
|
|
8647
|
+
request?.destroy();
|
|
8648
|
+
finish(null);
|
|
8649
|
+
}, timeoutMs);
|
|
8650
|
+
deadline.unref();
|
|
8651
|
+
const transport = url.startsWith("http:") ? http : https;
|
|
8652
|
+
request = transport.get(
|
|
8144
8653
|
url,
|
|
8145
8654
|
{
|
|
8146
8655
|
headers: {
|
|
@@ -8159,7 +8668,7 @@ function fetchLatestVersion(url = PACKAGE_LATEST_URL, timeoutMs = REQUEST_TIMEOU
|
|
|
8159
8668
|
response.on("data", (chunk) => {
|
|
8160
8669
|
body += chunk;
|
|
8161
8670
|
if (body.length > 64 * 1024) {
|
|
8162
|
-
request
|
|
8671
|
+
request?.destroy();
|
|
8163
8672
|
finish(null);
|
|
8164
8673
|
}
|
|
8165
8674
|
});
|
|
@@ -8174,10 +8683,6 @@ function fetchLatestVersion(url = PACKAGE_LATEST_URL, timeoutMs = REQUEST_TIMEOU
|
|
|
8174
8683
|
}
|
|
8175
8684
|
);
|
|
8176
8685
|
request.on("error", () => finish(null));
|
|
8177
|
-
request.setTimeout(timeoutMs, () => {
|
|
8178
|
-
request.destroy();
|
|
8179
|
-
finish(null);
|
|
8180
|
-
});
|
|
8181
8686
|
});
|
|
8182
8687
|
}
|
|
8183
8688
|
async function checkForUpdate(options) {
|
|
@@ -8345,7 +8850,10 @@ const { parentPort, workerData } = require("node:worker_threads");
|
|
|
8345
8850
|
parentPort.postMessage({ ok: false, message: loaded.errors.join("; ") });
|
|
8346
8851
|
return;
|
|
8347
8852
|
}
|
|
8348
|
-
|
|
8853
|
+
// \`hooks: {}\` is a value, not an absence: the config is applied with the hook list emptied,
|
|
8854
|
+
// so the run is the documented MCP run rather than the CLI run with one setting removed.
|
|
8855
|
+
const base = loaded.config ? { ...loaded.config, hooks: {} } : null;
|
|
8856
|
+
const config = mod.resolveConfig(base, workerData.cliOptions, workerData.cwd);
|
|
8349
8857
|
const manifest = mod.generateSnapshot(config);
|
|
8350
8858
|
parentPort.postMessage({
|
|
8351
8859
|
ok: true,
|
|
@@ -8368,14 +8876,15 @@ const { parentPort, workerData } = require("node:worker_threads");
|
|
|
8368
8876
|
})();
|
|
8369
8877
|
`;
|
|
8370
8878
|
function generateInWorker(cwd, cliOptions) {
|
|
8371
|
-
return new Promise((
|
|
8879
|
+
return new Promise((resolve10) => {
|
|
8372
8880
|
let settled = false;
|
|
8881
|
+
let worker = null;
|
|
8373
8882
|
const finish = (result) => {
|
|
8374
8883
|
if (settled) return;
|
|
8375
8884
|
settled = true;
|
|
8376
|
-
|
|
8885
|
+
if (worker !== null) void worker.terminate();
|
|
8886
|
+
resolve10(result);
|
|
8377
8887
|
};
|
|
8378
|
-
let worker;
|
|
8379
8888
|
const isCjs = typeof __dirname !== "undefined";
|
|
8380
8889
|
const directory = isCjs ? __dirname : path15.dirname(fileURLToPath(import.meta.url));
|
|
8381
8890
|
try {
|
|
@@ -8408,7 +8917,100 @@ function generateInWorker(cwd, cliOptions) {
|
|
|
8408
8917
|
|
|
8409
8918
|
// src/index.ts
|
|
8410
8919
|
var SCHEMA_VERSION = "3.0.0";
|
|
8411
|
-
var
|
|
8920
|
+
var SUMMARY_MAX_SOURCE_BYTES2 = SUMMARY_MAX_SOURCE_BYTES;
|
|
8921
|
+
var FILE_DERIVATIONS = /* @__PURE__ */ new Map();
|
|
8922
|
+
var FILE_DERIVATION_LIMIT_BYTES = 64 * 1024 * 1024;
|
|
8923
|
+
var fileDerivationBytes = 0;
|
|
8924
|
+
function takeDerivation(path22, hash, settings) {
|
|
8925
|
+
const cached = FILE_DERIVATIONS.get(path22);
|
|
8926
|
+
if (cached === void 0 || cached.hash !== hash || cached.settings !== settings) return void 0;
|
|
8927
|
+
return cached;
|
|
8928
|
+
}
|
|
8929
|
+
function rememberDerivation(path22, hash, settings, derivation) {
|
|
8930
|
+
const bodyBytes = derivation.finalContent === derivation.redacted ? derivation.redacted.length : derivation.redacted.length + derivation.finalContent.length;
|
|
8931
|
+
const bytes = bodyBytes * 2 + 512;
|
|
8932
|
+
if (bytes > FILE_DERIVATION_LIMIT_BYTES / 4) return;
|
|
8933
|
+
while (fileDerivationBytes + bytes > FILE_DERIVATION_LIMIT_BYTES) {
|
|
8934
|
+
const oldest = FILE_DERIVATIONS.keys().next();
|
|
8935
|
+
if (oldest.done === true) break;
|
|
8936
|
+
const evicted = FILE_DERIVATIONS.get(oldest.value);
|
|
8937
|
+
FILE_DERIVATIONS.delete(oldest.value);
|
|
8938
|
+
if (evicted !== void 0) fileDerivationBytes -= evicted.bytes;
|
|
8939
|
+
}
|
|
8940
|
+
FILE_DERIVATIONS.set(path22, { ...derivation, hash, settings, bytes });
|
|
8941
|
+
fileDerivationBytes += bytes;
|
|
8942
|
+
}
|
|
8943
|
+
function derivationSettings(config) {
|
|
8944
|
+
const rules = config.secretRules.map(
|
|
8945
|
+
(rule) => `${rule.rule}\0${rule.pattern.source}\0${rule.pattern.flags}\0${rule.valueGroup ?? ""}`
|
|
8946
|
+
).join("");
|
|
8947
|
+
return `${GENERATOR_VERSION}${configDigest(config)}${sha256Hex2(rules)}`;
|
|
8948
|
+
}
|
|
8949
|
+
function deriveFile(relativePath, rawContent, sizeBytes, config) {
|
|
8950
|
+
const lineCount = countLines(rawContent);
|
|
8951
|
+
const { redacted, findings } = scanAndRedactSecrets(rawContent, config.secretRules, relativePath);
|
|
8952
|
+
const isLarge = sizeBytes > config.largeFileBytes;
|
|
8953
|
+
let finalContent = redacted;
|
|
8954
|
+
let isPreviewTruncated = false;
|
|
8955
|
+
let includedLines = lineCount;
|
|
8956
|
+
let inclusionMode = "full";
|
|
8957
|
+
if (config.skeleton === true && isSkeletonizable(relativePath)) {
|
|
8958
|
+
const skeleton = skeletonizeTypeScript(redacted, relativePath);
|
|
8959
|
+
if (skeleton.collapsed > 0) {
|
|
8960
|
+
finalContent = skeleton.text;
|
|
8961
|
+
inclusionMode = "signatures";
|
|
8962
|
+
includedLines = countLines(skeleton.text);
|
|
8963
|
+
}
|
|
8964
|
+
}
|
|
8965
|
+
if (isLarge && inclusionMode !== "signatures") {
|
|
8966
|
+
const preview = truncatePreview(redacted, config.previewHeadLines, config.previewTailLines);
|
|
8967
|
+
finalContent = preview.content;
|
|
8968
|
+
isPreviewTruncated = preview.isTruncated;
|
|
8969
|
+
includedLines = preview.includedLines;
|
|
8970
|
+
inclusionMode = isPreviewTruncated ? "preview" : "full";
|
|
8971
|
+
}
|
|
8972
|
+
let summary;
|
|
8973
|
+
let summaryQuality;
|
|
8974
|
+
let summarySkip = null;
|
|
8975
|
+
let summaryWarning = null;
|
|
8976
|
+
let dependentImports = null;
|
|
8977
|
+
if (Buffer.byteLength(rawContent, "utf8") > SUMMARY_MAX_SOURCE_BYTES2) {
|
|
8978
|
+
summarySkip = "oversize";
|
|
8979
|
+
dependentImports = [];
|
|
8980
|
+
} else if (SKIP_PREVIEW_SUMMARIES && isPreviewTruncated && hasStructuralSummary(relativePath)) {
|
|
8981
|
+
summarySkip = "preview";
|
|
8982
|
+
dependentImports = importsOfText(relativePath, rawContent);
|
|
8983
|
+
} else {
|
|
8984
|
+
try {
|
|
8985
|
+
const outcome = summarizeFile(relativePath, rawContent);
|
|
8986
|
+
summary = outcome?.summary ? maskSummaryStrings(outcome.summary, config.secretRules) : void 0;
|
|
8987
|
+
summaryQuality = outcome?.quality;
|
|
8988
|
+
if (isSourceSummary(summary) && summary.parseErrors) {
|
|
8989
|
+
summaryWarning = `Parse errors in ${maskPathText(relativePath, config.secretRules)} (${summary.parseErrors}); its summary is incomplete.`;
|
|
8990
|
+
}
|
|
8991
|
+
} catch (err) {
|
|
8992
|
+
summaryWarning = `Summary skipped for ${maskPathText(relativePath, config.secretRules)}: ${err.message}`.replace(
|
|
8993
|
+
/\s+/g,
|
|
8994
|
+
" "
|
|
8995
|
+
);
|
|
8996
|
+
}
|
|
8997
|
+
}
|
|
8998
|
+
return {
|
|
8999
|
+
redacted,
|
|
9000
|
+
findings,
|
|
9001
|
+
lineCount,
|
|
9002
|
+
finalContent,
|
|
9003
|
+
inclusionMode,
|
|
9004
|
+
isPreviewTruncated,
|
|
9005
|
+
includedLines,
|
|
9006
|
+
isLarge,
|
|
9007
|
+
summary,
|
|
9008
|
+
summaryQuality,
|
|
9009
|
+
summarySkip,
|
|
9010
|
+
summaryWarning,
|
|
9011
|
+
dependentImports
|
|
9012
|
+
};
|
|
9013
|
+
}
|
|
8412
9014
|
function sha256Hex2(input) {
|
|
8413
9015
|
return crypto4.createHash("sha256").update(input).digest("hex");
|
|
8414
9016
|
}
|
|
@@ -8953,10 +9555,7 @@ function acquireRunLock(stagingRoot) {
|
|
|
8953
9555
|
};
|
|
8954
9556
|
} catch (err) {
|
|
8955
9557
|
if (err.code !== "EEXIST") throw err;
|
|
8956
|
-
if (attempt === 0 &&
|
|
8957
|
-
fs14.rmSync(lockPath, { force: true });
|
|
8958
|
-
continue;
|
|
8959
|
-
}
|
|
9558
|
+
if (attempt === 0 && reclaimStaleLock(lockPath)) continue;
|
|
8960
9559
|
throw new Error(
|
|
8961
9560
|
`Another generate run is already using the staging directory ${stagingRoot}. Wait for it to finish, or delete ${lockPath} if no other run is active.`
|
|
8962
9561
|
);
|
|
@@ -8965,14 +9564,25 @@ function acquireRunLock(stagingRoot) {
|
|
|
8965
9564
|
throw new Error(`Could not acquire the generate lock for ${stagingRoot}.`);
|
|
8966
9565
|
}
|
|
8967
9566
|
function lockKeyFor(stagingRoot) {
|
|
8968
|
-
let key = stagingRoot;
|
|
8969
|
-
try {
|
|
8970
|
-
key = fs14.realpathSync(stagingRoot);
|
|
8971
|
-
} catch {
|
|
8972
|
-
}
|
|
9567
|
+
let key = canonicalLockTarget(stagingRoot);
|
|
8973
9568
|
if (process.platform === "win32" || process.platform === "darwin") key = key.toLowerCase();
|
|
8974
9569
|
return sha256Hex2(key).slice(0, 16);
|
|
8975
9570
|
}
|
|
9571
|
+
function canonicalLockTarget(targetPath2) {
|
|
9572
|
+
const resolved = path16.resolve(targetPath2);
|
|
9573
|
+
let existing = resolved;
|
|
9574
|
+
const tail = [];
|
|
9575
|
+
for (; ; ) {
|
|
9576
|
+
try {
|
|
9577
|
+
return path16.join(fs14.realpathSync(existing), ...tail);
|
|
9578
|
+
} catch {
|
|
9579
|
+
const parent = path16.dirname(existing);
|
|
9580
|
+
if (parent === existing) return resolved;
|
|
9581
|
+
tail.unshift(path16.basename(existing));
|
|
9582
|
+
existing = parent;
|
|
9583
|
+
}
|
|
9584
|
+
}
|
|
9585
|
+
}
|
|
8976
9586
|
function lockPathFor(stagingRoot) {
|
|
8977
9587
|
return path16.join(os2.tmpdir(), `codebase-snapshot-${lockKeyFor(stagingRoot)}.lock`);
|
|
8978
9588
|
}
|
|
@@ -8997,6 +9607,24 @@ function lockOwnerIsGone(lockPath) {
|
|
|
8997
9607
|
return err.code === "ESRCH";
|
|
8998
9608
|
}
|
|
8999
9609
|
}
|
|
9610
|
+
function reclaimStaleLock(lockPath) {
|
|
9611
|
+
let before;
|
|
9612
|
+
try {
|
|
9613
|
+
before = fs14.statSync(lockPath);
|
|
9614
|
+
} catch {
|
|
9615
|
+
return true;
|
|
9616
|
+
}
|
|
9617
|
+
if (!lockOwnerIsGone(lockPath)) return false;
|
|
9618
|
+
try {
|
|
9619
|
+
const current = fs14.statSync(lockPath);
|
|
9620
|
+
const sameFile = current.ino === before.ino && current.birthtimeMs === before.birthtimeMs && current.mtimeMs === before.mtimeMs;
|
|
9621
|
+
if (!sameFile) return false;
|
|
9622
|
+
fs14.rmSync(lockPath, { force: true });
|
|
9623
|
+
return true;
|
|
9624
|
+
} catch {
|
|
9625
|
+
return false;
|
|
9626
|
+
}
|
|
9627
|
+
}
|
|
9000
9628
|
function lockFileIsAncient(lockPath) {
|
|
9001
9629
|
try {
|
|
9002
9630
|
return Date.now() - fs14.statSync(lockPath).mtimeMs > STALE_RUN_MS;
|
|
@@ -9054,7 +9682,6 @@ function swapOnce(stagingDir, outDir) {
|
|
|
9054
9682
|
throw copyErr;
|
|
9055
9683
|
}
|
|
9056
9684
|
}
|
|
9057
|
-
if (hadPrevious) fs14.rmSync(backupDir, { recursive: true, force: true });
|
|
9058
9685
|
} catch (err) {
|
|
9059
9686
|
if (hadPrevious && fs14.existsSync(backupDir)) {
|
|
9060
9687
|
if (fs14.existsSync(outDir)) fs14.rmSync(outDir, { recursive: true, force: true });
|
|
@@ -9062,6 +9689,16 @@ function swapOnce(stagingDir, outDir) {
|
|
|
9062
9689
|
}
|
|
9063
9690
|
throw err;
|
|
9064
9691
|
}
|
|
9692
|
+
if (hadPrevious) {
|
|
9693
|
+
try {
|
|
9694
|
+
fs14.rmSync(backupDir, { recursive: true, force: true });
|
|
9695
|
+
} catch (err) {
|
|
9696
|
+
process.stderr.write(
|
|
9697
|
+
`\u26A0 The previous snapshot could not be removed from ${backupDir} (${err.message}). The new snapshot is in place; delete that directory by hand when convenient.
|
|
9698
|
+
`
|
|
9699
|
+
);
|
|
9700
|
+
}
|
|
9701
|
+
}
|
|
9065
9702
|
}
|
|
9066
9703
|
function sleepSync(milliseconds) {
|
|
9067
9704
|
const buffer = new Int32Array(new SharedArrayBuffer(4));
|
|
@@ -9084,22 +9721,18 @@ function artifactWarnings(artifacts, sarif) {
|
|
|
9084
9721
|
}
|
|
9085
9722
|
return messages;
|
|
9086
9723
|
}
|
|
9724
|
+
function installHookStdoutRedirect() {
|
|
9725
|
+
return installStdoutRouter();
|
|
9726
|
+
}
|
|
9087
9727
|
function runAfterGenerate(config, manifest) {
|
|
9088
9728
|
const hook = config.hooks.afterGenerate;
|
|
9089
9729
|
if (!hook) return null;
|
|
9090
|
-
const
|
|
9091
|
-
const stderrWrite = process.stderr.write.bind(process.stderr);
|
|
9092
|
-
const hookScope = new AsyncLocalStorage();
|
|
9093
|
-
const patchedWrite = ((...args) => hookScope.getStore() === true ? stderrWrite(...args) : stdoutWrite(...args));
|
|
9094
|
-
const restoreStdout = () => {
|
|
9095
|
-
if (process.stdout.write === patchedWrite) process.stdout.write = stdoutWrite;
|
|
9096
|
-
};
|
|
9730
|
+
const releaseRedirect = installHookStdoutRedirect();
|
|
9097
9731
|
let returned;
|
|
9098
9732
|
try {
|
|
9099
|
-
|
|
9100
|
-
returned = hookScope.run(true, () => hook(manifest));
|
|
9733
|
+
returned = hookStdoutScope.run(true, () => hook(manifest));
|
|
9101
9734
|
} catch (err) {
|
|
9102
|
-
|
|
9735
|
+
releaseRedirect();
|
|
9103
9736
|
const message = maskPathText(err.message, config.secretRules);
|
|
9104
9737
|
manifest.warnings.push(`hooks.afterGenerate failed: ${message}`);
|
|
9105
9738
|
manifest.totals.warningCount = manifest.warnings.length;
|
|
@@ -9107,10 +9740,10 @@ function runAfterGenerate(config, manifest) {
|
|
|
9107
9740
|
}
|
|
9108
9741
|
const thenable = returned;
|
|
9109
9742
|
if (!thenable || typeof thenable.then !== "function") {
|
|
9110
|
-
|
|
9743
|
+
releaseRedirect();
|
|
9111
9744
|
return null;
|
|
9112
9745
|
}
|
|
9113
|
-
return Promise.resolve(returned).finally(
|
|
9746
|
+
return Promise.resolve(returned).finally(releaseRedirect);
|
|
9114
9747
|
}
|
|
9115
9748
|
function partitionByBudget(files, config, fingerprint) {
|
|
9116
9749
|
const budgetFor = (file) => config.budgets.byLayer[file.layer] ?? config.budgets.default;
|
|
@@ -9121,23 +9754,14 @@ function partitionByBudget(files, config, fingerprint) {
|
|
|
9121
9754
|
if (bucket) bucket.push(file);
|
|
9122
9755
|
else buckets.set(budget, [file]);
|
|
9123
9756
|
}
|
|
9124
|
-
if (buckets.size
|
|
9125
|
-
|
|
9126
|
-
return partitionFiles(files,
|
|
9757
|
+
if (buckets.size === 0) return [];
|
|
9758
|
+
if (buckets.size === 1) {
|
|
9759
|
+
return partitionFiles(files, Array.from(buckets.keys())[0], fingerprint);
|
|
9127
9760
|
}
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
const merged = renumberParts(parts, fingerprint);
|
|
9133
|
-
const byPath = new Map(files.map((file) => [file.relativePath, file]));
|
|
9134
|
-
for (const part of merged) {
|
|
9135
|
-
for (const included of part.metadata.includedFiles) {
|
|
9136
|
-
const file = byPath.get(included.relativePath);
|
|
9137
|
-
if (file) file.part = part.filename;
|
|
9138
|
-
}
|
|
9139
|
-
}
|
|
9140
|
-
return merged;
|
|
9761
|
+
return partitionFilesForBuckets(
|
|
9762
|
+
Array.from(buckets.entries()).sort(([left], [right]) => left - right).map(([budgetBytes, bucketFiles]) => ({ files: bucketFiles, budgetBytes })),
|
|
9763
|
+
fingerprint
|
|
9764
|
+
);
|
|
9141
9765
|
}
|
|
9142
9766
|
function generateSnapshot(configInput, options) {
|
|
9143
9767
|
assertUsableConfigInput(configInput);
|
|
@@ -9169,6 +9793,7 @@ function generateSnapshot(configInput, options) {
|
|
|
9169
9793
|
return false;
|
|
9170
9794
|
}) : collection.files;
|
|
9171
9795
|
const files = [];
|
|
9796
|
+
const derivationKey = derivationSettings(config);
|
|
9172
9797
|
const monolithBodies = /* @__PURE__ */ new Map();
|
|
9173
9798
|
const skipped = maskSkipRecords([...collection.skipped], config.secretRules);
|
|
9174
9799
|
const secretFindings = [];
|
|
@@ -9181,6 +9806,7 @@ function generateSnapshot(configInput, options) {
|
|
|
9181
9806
|
const fileWarnings = [];
|
|
9182
9807
|
const oversizeSummaryPaths = [];
|
|
9183
9808
|
const previewSummaryPaths = [];
|
|
9809
|
+
const skippedSummaryImports = /* @__PURE__ */ new Map();
|
|
9184
9810
|
const layerCounts = {};
|
|
9185
9811
|
const kindCounts = {};
|
|
9186
9812
|
let totalLines = 0;
|
|
@@ -9245,8 +9871,6 @@ function generateSnapshot(configInput, options) {
|
|
|
9245
9871
|
}
|
|
9246
9872
|
const hashSha256 = sha256Hex2(buffer);
|
|
9247
9873
|
const rawContent = decoded.text;
|
|
9248
|
-
const lineCount = countLines(rawContent);
|
|
9249
|
-
totalLines += lineCount;
|
|
9250
9874
|
totalSizeBytes += stats.size;
|
|
9251
9875
|
if (decoded.encoding !== "utf-8") {
|
|
9252
9876
|
encodingNotes.push({
|
|
@@ -9260,66 +9884,25 @@ function generateSnapshot(configInput, options) {
|
|
|
9260
9884
|
const language = detectLanguage(relativePath);
|
|
9261
9885
|
bump(layerCounts, layer2);
|
|
9262
9886
|
bump(kindCounts, kind);
|
|
9263
|
-
const
|
|
9264
|
-
rawContent,
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
);
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
}
|
|
9283
|
-
if (isLarge && inclusionMode !== "signatures") {
|
|
9284
|
-
const preview = truncatePreview(
|
|
9285
|
-
redacted,
|
|
9286
|
-
config.previewHeadLines,
|
|
9287
|
-
config.previewTailLines
|
|
9288
|
-
);
|
|
9289
|
-
finalContent = preview.content;
|
|
9290
|
-
isPreviewTruncated = preview.isTruncated;
|
|
9291
|
-
includedLines = preview.includedLines;
|
|
9292
|
-
inclusionMode = isPreviewTruncated ? "preview" : "full";
|
|
9293
|
-
}
|
|
9294
|
-
if (config.includeMonolith && (isPreviewTruncated || inclusionMode === "signatures")) {
|
|
9295
|
-
monolithBodies.set(relativePath, redacted);
|
|
9296
|
-
}
|
|
9297
|
-
let summary;
|
|
9298
|
-
let summaryQuality;
|
|
9299
|
-
if (Buffer.byteLength(rawContent, "utf8") > SUMMARY_MAX_SOURCE_BYTES) {
|
|
9300
|
-
oversizeSummaryPaths.push(relativePath);
|
|
9301
|
-
} else if (SKIP_PREVIEW_SUMMARIES && isPreviewTruncated && hasStructuralSummary(relativePath)) {
|
|
9302
|
-
previewSummaryPaths.push(relativePath);
|
|
9303
|
-
} else {
|
|
9304
|
-
try {
|
|
9305
|
-
const outcome = summarizeFile(relativePath, rawContent);
|
|
9306
|
-
summary = outcome?.summary ? maskSummaryStrings(outcome.summary, config.secretRules) : void 0;
|
|
9307
|
-
summaryQuality = outcome?.quality;
|
|
9308
|
-
if (isSourceSummary(summary) && summary.parseErrors) {
|
|
9309
|
-
fileWarnings.push({
|
|
9310
|
-
path: relativePath,
|
|
9311
|
-
message: `Parse errors in ${maskPathText(relativePath, config.secretRules)} (${summary.parseErrors}); its summary is incomplete.`
|
|
9312
|
-
});
|
|
9313
|
-
}
|
|
9314
|
-
} catch (err) {
|
|
9315
|
-
fileWarnings.push({
|
|
9316
|
-
path: relativePath,
|
|
9317
|
-
message: `Summary skipped for ${maskPathText(relativePath, config.secretRules)}: ${err.message}`.replace(
|
|
9318
|
-
/\s+/g,
|
|
9319
|
-
" "
|
|
9320
|
-
)
|
|
9321
|
-
});
|
|
9322
|
-
}
|
|
9887
|
+
const derivation = takeDerivation(absolutePath, hashSha256, derivationKey) ?? (() => {
|
|
9888
|
+
const fresh = deriveFile(relativePath, rawContent, stats.size, config);
|
|
9889
|
+
rememberDerivation(absolutePath, hashSha256, derivationKey, fresh);
|
|
9890
|
+
return fresh;
|
|
9891
|
+
})();
|
|
9892
|
+
const { lineCount } = derivation;
|
|
9893
|
+
totalLines += lineCount;
|
|
9894
|
+
secretFindings.push(...derivation.findings);
|
|
9895
|
+
if (derivation.isLarge) largeFileCount += 1;
|
|
9896
|
+
if (derivation.summarySkip === "oversize") oversizeSummaryPaths.push(relativePath);
|
|
9897
|
+
else if (derivation.summarySkip === "preview") previewSummaryPaths.push(relativePath);
|
|
9898
|
+
if (derivation.dependentImports !== null) {
|
|
9899
|
+
skippedSummaryImports.set(relativePath, derivation.dependentImports);
|
|
9900
|
+
}
|
|
9901
|
+
if (derivation.summaryWarning !== null) {
|
|
9902
|
+
fileWarnings.push({ path: relativePath, message: derivation.summaryWarning });
|
|
9903
|
+
}
|
|
9904
|
+
if (config.includeMonolith && (derivation.isPreviewTruncated || derivation.inclusionMode === "signatures")) {
|
|
9905
|
+
monolithBodies.set(relativePath, derivation.redacted);
|
|
9323
9906
|
}
|
|
9324
9907
|
files.push({
|
|
9325
9908
|
relativePath,
|
|
@@ -9330,24 +9913,25 @@ function generateSnapshot(configInput, options) {
|
|
|
9330
9913
|
layer: layer2,
|
|
9331
9914
|
kind,
|
|
9332
9915
|
language,
|
|
9333
|
-
inclusionMode,
|
|
9334
|
-
isLarge,
|
|
9335
|
-
isPreviewTruncated,
|
|
9916
|
+
inclusionMode: derivation.inclusionMode,
|
|
9917
|
+
isLarge: derivation.isLarge,
|
|
9918
|
+
isPreviewTruncated: derivation.isPreviewTruncated,
|
|
9336
9919
|
originalLines: lineCount,
|
|
9337
|
-
includedLines,
|
|
9338
|
-
includedBytes: Buffer.byteLength(finalContent, "utf8"),
|
|
9339
|
-
summary,
|
|
9340
|
-
summaryQuality,
|
|
9920
|
+
includedLines: derivation.includedLines,
|
|
9921
|
+
includedBytes: Buffer.byteLength(derivation.finalContent, "utf8"),
|
|
9922
|
+
summary: derivation.summary,
|
|
9923
|
+
summaryQuality: derivation.summaryQuality,
|
|
9341
9924
|
...decoded.encoding === "utf-8" ? {} : { encoding: decoded.encoding },
|
|
9342
|
-
content: finalContent
|
|
9925
|
+
content: derivation.finalContent
|
|
9343
9926
|
});
|
|
9344
9927
|
}
|
|
9345
9928
|
if (changedScope !== null && changedPaths !== null && config.withDependents === true) {
|
|
9346
9929
|
const keep = new Set(changedPaths);
|
|
9347
9930
|
const resolver = buildImportResolver(files);
|
|
9348
9931
|
for (const file of files) {
|
|
9349
|
-
|
|
9350
|
-
if (
|
|
9932
|
+
const fileImports = isSourceSummary(file.summary) ? file.summary.imports : skippedSummaryImports.get(file.relativePath) ?? [];
|
|
9933
|
+
if (fileImports.length === 0) continue;
|
|
9934
|
+
if (importsChangedFile(file.relativePath, fileImports, changedPaths, resolver)) {
|
|
9351
9935
|
keep.add(file.relativePath);
|
|
9352
9936
|
}
|
|
9353
9937
|
}
|
|
@@ -9378,6 +9962,9 @@ function generateSnapshot(configInput, options) {
|
|
|
9378
9962
|
for (const path22 of [...monolithBodies.keys()]) {
|
|
9379
9963
|
if (!keep.has(path22)) monolithBodies.delete(path22);
|
|
9380
9964
|
}
|
|
9965
|
+
for (const path22 of [...skippedSummaryImports.keys()]) {
|
|
9966
|
+
if (!keep.has(path22)) skippedSummaryImports.delete(path22);
|
|
9967
|
+
}
|
|
9381
9968
|
}
|
|
9382
9969
|
if (changedScope !== null) {
|
|
9383
9970
|
for (const relativePath of unchangedPaths) {
|
|
@@ -9413,7 +10000,7 @@ function generateSnapshot(configInput, options) {
|
|
|
9413
10000
|
const shown = oversizeSummaryPaths.slice(0, ENCODING_PATHS_SHOWN).map((entry) => maskPathText(entry, config.secretRules));
|
|
9414
10001
|
const hidden = oversizeSummaryPaths.length - ENCODING_PATHS_SHOWN;
|
|
9415
10002
|
warnings.push(
|
|
9416
|
-
`${oversizeSummaryPaths.length} file(s) exceed ${Math.round(
|
|
10003
|
+
`${oversizeSummaryPaths.length} file(s) exceed ${Math.round(SUMMARY_MAX_SOURCE_BYTES2 / (1024 * 1024))} MB and were not parsed for a structural summary: ${shown.join(", ")}${hidden > 0 ? ` and ${hidden} more` : ""}. Their bodies are emitted as head/tail previews.`
|
|
9417
10004
|
);
|
|
9418
10005
|
}
|
|
9419
10006
|
for (const [encoding, bucket] of encodingBuckets) {
|
|
@@ -9437,11 +10024,13 @@ function generateSnapshot(configInput, options) {
|
|
|
9437
10024
|
).join("\n")
|
|
9438
10025
|
);
|
|
9439
10026
|
const fullContents = /* @__PURE__ */ new Map();
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
10027
|
+
if (config.includeMonolith) {
|
|
10028
|
+
for (const file of files) {
|
|
10029
|
+
fullContents.set(
|
|
10030
|
+
file.relativePath,
|
|
10031
|
+
monolithBodies.get(file.relativePath) ?? file.content ?? ""
|
|
10032
|
+
);
|
|
10033
|
+
}
|
|
9445
10034
|
}
|
|
9446
10035
|
const parts = partitionByBudget(files, config, fingerprint);
|
|
9447
10036
|
const truncatedPreviewCount = files.filter((file) => file.isPreviewTruncated).length;
|
|
@@ -9702,7 +10291,7 @@ function generateSnapshot(configInput, options) {
|
|
|
9702
10291
|
};
|
|
9703
10292
|
shipHookState();
|
|
9704
10293
|
if (pendingHook) {
|
|
9705
|
-
pendingHook.then(
|
|
10294
|
+
const tracked = pendingHook.then(
|
|
9706
10295
|
() => {
|
|
9707
10296
|
try {
|
|
9708
10297
|
shipHookState();
|
|
@@ -9721,8 +10310,10 @@ function generateSnapshot(configInput, options) {
|
|
|
9721
10310
|
} catch {
|
|
9722
10311
|
}
|
|
9723
10312
|
}
|
|
9724
|
-
)
|
|
9725
|
-
|
|
10313
|
+
).finally(() => {
|
|
10314
|
+
if (pendingHooks.get(outDir) === tracked) pendingHooks.delete(outDir);
|
|
10315
|
+
});
|
|
10316
|
+
pendingHooks.set(outDir, tracked);
|
|
9726
10317
|
}
|
|
9727
10318
|
return manifest;
|
|
9728
10319
|
} catch (err) {
|
|
@@ -9904,12 +10495,35 @@ async function openWorkspace(root, overrides = {}) {
|
|
|
9904
10495
|
}
|
|
9905
10496
|
cliOptions.maxPartBytes = parsed;
|
|
9906
10497
|
}
|
|
10498
|
+
if (overrides.maxPartTokens !== void 0) {
|
|
10499
|
+
const parsed = parseByteSize(overrides.maxPartTokens);
|
|
10500
|
+
if (parsed === void 0) {
|
|
10501
|
+
return {
|
|
10502
|
+
ok: false,
|
|
10503
|
+
message: '"maxPartTokens" must be a token count, for example 32000 or "32k".'
|
|
10504
|
+
};
|
|
10505
|
+
}
|
|
10506
|
+
cliOptions.maxPartTokens = parsed;
|
|
10507
|
+
}
|
|
9907
10508
|
if (overrides.outDir !== void 0) {
|
|
9908
10509
|
if (typeof overrides.outDir !== "string" || overrides.outDir.trim().length === 0) {
|
|
9909
10510
|
return { ok: false, message: '"outDir" must be a non-empty string.' };
|
|
9910
10511
|
}
|
|
9911
10512
|
cliOptions.outDir = overrides.outDir.trim();
|
|
9912
10513
|
}
|
|
10514
|
+
if (overrides.changedSince !== void 0) {
|
|
10515
|
+
if (typeof overrides.changedSince !== "string" || overrides.changedSince.trim().length === 0) {
|
|
10516
|
+
return {
|
|
10517
|
+
ok: false,
|
|
10518
|
+
message: '"changedSince" must be a git ref such as "main" or "HEAD~3".'
|
|
10519
|
+
};
|
|
10520
|
+
}
|
|
10521
|
+
cliOptions.changedSince = overrides.changedSince.trim();
|
|
10522
|
+
}
|
|
10523
|
+
if (overrides.skeleton === true) cliOptions.skeleton = true;
|
|
10524
|
+
if (overrides.sarif === true) cliOptions.sarif = true;
|
|
10525
|
+
if (overrides.staged === true) cliOptions.staged = true;
|
|
10526
|
+
if (overrides.withDependents === true) cliOptions.withDependents = true;
|
|
9913
10527
|
try {
|
|
9914
10528
|
const config = resolveConfig(loaded.config, cliOptions, root);
|
|
9915
10529
|
return { ok: true, workspace: { root, config, configFile: loaded.file, cliOptions } };
|
|
@@ -9921,6 +10535,16 @@ var manifestCache = /* @__PURE__ */ new Map();
|
|
|
9921
10535
|
function manifestPathFor(workspace) {
|
|
9922
10536
|
return path17.resolve(workspace.root, workspace.config.outDir, "manifest.json");
|
|
9923
10537
|
}
|
|
10538
|
+
function partPathFor(workspace, filename) {
|
|
10539
|
+
if (filename.length === 0 || filename.includes("\0")) return null;
|
|
10540
|
+
const partsDir = path17.resolve(workspace.root, workspace.config.outDir, "parts");
|
|
10541
|
+
const resolved = path17.resolve(partsDir, filename);
|
|
10542
|
+
const relative12 = path17.relative(partsDir, resolved);
|
|
10543
|
+
if (relative12.length === 0 || relative12.includes(path17.sep) || relative12.includes("/") || relative12.includes("\\") || path17.isAbsolute(relative12)) {
|
|
10544
|
+
return null;
|
|
10545
|
+
}
|
|
10546
|
+
return resolved;
|
|
10547
|
+
}
|
|
9924
10548
|
function readManifest3(workspace) {
|
|
9925
10549
|
const file = manifestPathFor(workspace);
|
|
9926
10550
|
const shown = path17.relative(workspace.root, file).split(path17.sep).join("/") || file;
|
|
@@ -9991,6 +10615,44 @@ var OUT_DIR_PROPERTY = {
|
|
|
9991
10615
|
description: "Snapshot directory, relative to the root. Defaults to the project configuration's outDir."
|
|
9992
10616
|
};
|
|
9993
10617
|
var outDirOverride = (args) => ({ outDir: args.outDir });
|
|
10618
|
+
var SCOPE_PROPERTIES = {
|
|
10619
|
+
maxPartTokens: {
|
|
10620
|
+
type: "string",
|
|
10621
|
+
description: 'Token ceiling per part, for example 32000 or "32k". Narrower than maxPartBytes; it converts at four bytes per token.'
|
|
10622
|
+
},
|
|
10623
|
+
skeleton: {
|
|
10624
|
+
type: "boolean",
|
|
10625
|
+
description: "Replace every function body with `{ /* ... */ }`, keeping signatures, imports and exports. Applies to TypeScript, JavaScript and JSX."
|
|
10626
|
+
},
|
|
10627
|
+
sarif: {
|
|
10628
|
+
type: "boolean",
|
|
10629
|
+
description: "Also write reports/audit.sarif beside reports/audit.md, for CI code scanning."
|
|
10630
|
+
},
|
|
10631
|
+
changedSince: {
|
|
10632
|
+
type: "string",
|
|
10633
|
+
description: 'Only files changed since this git ref, for example "main" or "HEAD~3".'
|
|
10634
|
+
},
|
|
10635
|
+
staged: {
|
|
10636
|
+
type: "boolean",
|
|
10637
|
+
description: "Only files staged in the git index."
|
|
10638
|
+
},
|
|
10639
|
+
withDependents: {
|
|
10640
|
+
type: "boolean",
|
|
10641
|
+
description: "With staged or changedSince, also include the files that import the changed ones (one hop)."
|
|
10642
|
+
}
|
|
10643
|
+
};
|
|
10644
|
+
function workspaceOverrides(args) {
|
|
10645
|
+
return {
|
|
10646
|
+
outDir: args.outDir,
|
|
10647
|
+
maxPartBytes: args.maxPartBytes,
|
|
10648
|
+
maxPartTokens: args.maxPartTokens,
|
|
10649
|
+
skeleton: args.skeleton,
|
|
10650
|
+
sarif: args.sarif,
|
|
10651
|
+
changedSince: args.changedSince,
|
|
10652
|
+
staged: args.staged,
|
|
10653
|
+
withDependents: args.withDependents
|
|
10654
|
+
};
|
|
10655
|
+
}
|
|
9994
10656
|
function failure(message) {
|
|
9995
10657
|
return { text: message, isError: true };
|
|
9996
10658
|
}
|
|
@@ -10021,12 +10683,13 @@ function summarizeDrift(drift) {
|
|
|
10021
10683
|
}
|
|
10022
10684
|
function toRelativePath(root, value) {
|
|
10023
10685
|
const cleaned = value.trim();
|
|
10024
|
-
if (cleaned.length === 0) return null;
|
|
10025
|
-
|
|
10026
|
-
|
|
10686
|
+
if (cleaned.length === 0 || cleaned.includes("\0")) return null;
|
|
10687
|
+
const resolvedRoot = path18.resolve(root);
|
|
10688
|
+
const absolute = path18.resolve(resolvedRoot, cleaned);
|
|
10689
|
+
const relative12 = path18.relative(resolvedRoot, absolute);
|
|
10690
|
+
if (relative12 === "" || relative12 === ".." || relative12.startsWith(`..${path18.sep}`) || path18.isAbsolute(relative12)) {
|
|
10691
|
+
return null;
|
|
10027
10692
|
}
|
|
10028
|
-
const relative12 = path18.relative(root, cleaned);
|
|
10029
|
-
if (relative12.startsWith("..") || path18.isAbsolute(relative12)) return null;
|
|
10030
10693
|
return relative12.split(path18.sep).join("/");
|
|
10031
10694
|
}
|
|
10032
10695
|
function symbolsOf(entry) {
|
|
@@ -10162,7 +10825,8 @@ var planTool = {
|
|
|
10162
10825
|
maxPartBytes: {
|
|
10163
10826
|
type: "string",
|
|
10164
10827
|
description: 'Byte budget per part, for example 307200 or "300k". Defaults to the project configuration.'
|
|
10165
|
-
}
|
|
10828
|
+
},
|
|
10829
|
+
...SCOPE_PROPERTIES
|
|
10166
10830
|
},
|
|
10167
10831
|
additionalProperties: false
|
|
10168
10832
|
},
|
|
@@ -10179,7 +10843,7 @@ var planTool = {
|
|
|
10179
10843
|
},
|
|
10180
10844
|
annotations: READ_ONLY,
|
|
10181
10845
|
async handler(args, context) {
|
|
10182
|
-
const prepared = await prepare(args, context,
|
|
10846
|
+
const prepared = await prepare(args, context, workspaceOverrides(args));
|
|
10183
10847
|
if (!prepared.ok) return prepared.outcome;
|
|
10184
10848
|
const workspace = prepared.workspace;
|
|
10185
10849
|
let manifest;
|
|
@@ -10237,7 +10901,8 @@ var generateTool = {
|
|
|
10237
10901
|
maxPartBytes: {
|
|
10238
10902
|
type: "string",
|
|
10239
10903
|
description: 'Byte budget per part, for example 307200 or "300k". Defaults to the project configuration.'
|
|
10240
|
-
}
|
|
10904
|
+
},
|
|
10905
|
+
...SCOPE_PROPERTIES
|
|
10241
10906
|
},
|
|
10242
10907
|
additionalProperties: false
|
|
10243
10908
|
},
|
|
@@ -10253,10 +10918,7 @@ var generateTool = {
|
|
|
10253
10918
|
},
|
|
10254
10919
|
annotations: WRITES,
|
|
10255
10920
|
async handler(args, context) {
|
|
10256
|
-
const prepared = await prepare(args, context,
|
|
10257
|
-
maxPartBytes: args.maxPartBytes,
|
|
10258
|
-
outDir: args.outDir
|
|
10259
|
-
});
|
|
10921
|
+
const prepared = await prepare(args, context, workspaceOverrides(args));
|
|
10260
10922
|
if (!prepared.ok) return prepared.outcome;
|
|
10261
10923
|
const workspace = prepared.workspace;
|
|
10262
10924
|
const result = await generateInWorker(workspace.root, workspace.cliOptions);
|
|
@@ -10295,7 +10957,7 @@ var validateTool = {
|
|
|
10295
10957
|
description: "Re-read the repository and report whether the snapshot on disk still matches it. Use it before trusting context that came from an earlier session, or after changing the config.",
|
|
10296
10958
|
inputSchema: {
|
|
10297
10959
|
type: "object",
|
|
10298
|
-
properties: { root: ROOT_PROPERTY, outDir: OUT_DIR_PROPERTY },
|
|
10960
|
+
properties: { root: ROOT_PROPERTY, outDir: OUT_DIR_PROPERTY, ...SCOPE_PROPERTIES },
|
|
10299
10961
|
additionalProperties: false
|
|
10300
10962
|
},
|
|
10301
10963
|
outputSchema: {
|
|
@@ -10307,7 +10969,7 @@ var validateTool = {
|
|
|
10307
10969
|
},
|
|
10308
10970
|
annotations: READ_ONLY,
|
|
10309
10971
|
async handler(args, context) {
|
|
10310
|
-
const prepared = await prepare(args, context,
|
|
10972
|
+
const prepared = await prepare(args, context, workspaceOverrides(args));
|
|
10311
10973
|
if (!prepared.ok) return prepared.outcome;
|
|
10312
10974
|
const workspace = prepared.workspace;
|
|
10313
10975
|
let result;
|
|
@@ -10652,7 +11314,12 @@ var readTool = {
|
|
|
10652
11314
|
`${relativePath} is not in the snapshot. Call snapshot_locate to find the path, or snapshot_explain to learn why it was excluded.`
|
|
10653
11315
|
);
|
|
10654
11316
|
}
|
|
10655
|
-
const partPath =
|
|
11317
|
+
const partPath = partPathFor(workspace, entry.part);
|
|
11318
|
+
if (partPath === null) {
|
|
11319
|
+
return failure(
|
|
11320
|
+
`${entry.part} is not a part of this snapshot: its name leaves ${workspace.config.outDir}/parts. Run snapshot_generate to rebuild it.`
|
|
11321
|
+
);
|
|
11322
|
+
}
|
|
10656
11323
|
let partText;
|
|
10657
11324
|
try {
|
|
10658
11325
|
partText = fs16.readFileSync(partPath, "utf8");
|
|
@@ -10732,7 +11399,8 @@ var explainTool = {
|
|
|
10732
11399
|
description: "Path relative to the repository root. Directories are explained as a whole."
|
|
10733
11400
|
},
|
|
10734
11401
|
root: ROOT_PROPERTY,
|
|
10735
|
-
outDir: OUT_DIR_PROPERTY
|
|
11402
|
+
outDir: OUT_DIR_PROPERTY,
|
|
11403
|
+
...SCOPE_PROPERTIES
|
|
10736
11404
|
},
|
|
10737
11405
|
required: ["path"],
|
|
10738
11406
|
additionalProperties: false
|
|
@@ -10747,7 +11415,7 @@ var explainTool = {
|
|
|
10747
11415
|
},
|
|
10748
11416
|
annotations: READ_ONLY,
|
|
10749
11417
|
async handler(args, context) {
|
|
10750
|
-
const prepared = await prepare(args, context,
|
|
11418
|
+
const prepared = await prepare(args, context, workspaceOverrides(args));
|
|
10751
11419
|
if (!prepared.ok) return prepared.outcome;
|
|
10752
11420
|
const workspace = prepared.workspace;
|
|
10753
11421
|
if (typeof args.path !== "string") return failure('"path" must be a string.');
|
|
@@ -11589,7 +12257,7 @@ function buildAgentContract() {
|
|
|
11589
12257
|
{ key: "preset", type: "string", default: "detected", summary: "Force a preset. Unknown names are an error." },
|
|
11590
12258
|
{ key: "maxPartBytes", type: "number | string", default: 307200, summary: "Per-part byte budget." },
|
|
11591
12259
|
{ key: "maxPartTokens", type: "number | string | null", default: null, summary: "Per-part token ceiling; narrows maxPartBytes. Minimum 1024." },
|
|
11592
|
-
{ key: "skeleton", type: "boolean", default: false, summary: "Collapse function bodies to `{ /* ... */ }` in
|
|
12260
|
+
{ key: "skeleton", type: "boolean", default: false, summary: "Collapse function bodies to `{ /* ... */ }` in TS, JS, TSX and JSX files." },
|
|
11593
12261
|
{ key: "sarif", type: "boolean", default: false, summary: "Also write reports/audit.sarif." },
|
|
11594
12262
|
{ key: "changedSince", type: "string | null", default: null, summary: "Snapshot only files that differ from this git ref." },
|
|
11595
12263
|
{ key: "staged", type: "boolean", default: false, summary: "Snapshot only files staged in the index." },
|
|
@@ -12533,9 +13201,9 @@ function buildResourceProvider() {
|
|
|
12533
13201
|
const index = Number(partMatch[1]);
|
|
12534
13202
|
const part = read.manifest.parts.find((candidate) => candidate.index === index);
|
|
12535
13203
|
if (!part) return null;
|
|
12536
|
-
const
|
|
12537
|
-
|
|
12538
|
-
);
|
|
13204
|
+
const partPath = partPathFor(workspace, part.filename);
|
|
13205
|
+
if (partPath === null) return null;
|
|
13206
|
+
const text2 = readFileIfPresent(partPath);
|
|
12539
13207
|
if (text2 === null) return null;
|
|
12540
13208
|
return { uri, mimeType: "text/markdown", text: capUtf8(text2, MAX_RESOURCE_BYTES).text };
|
|
12541
13209
|
}
|
|
@@ -12567,11 +13235,11 @@ var MAX_LINE_BYTES = 32 * 1024 * 1024;
|
|
|
12567
13235
|
function createWriter(output) {
|
|
12568
13236
|
return {
|
|
12569
13237
|
write(value) {
|
|
12570
|
-
return new Promise((
|
|
13238
|
+
return new Promise((resolve10, reject) => {
|
|
12571
13239
|
output.write(`${JSON.stringify(value)}
|
|
12572
13240
|
`, (error) => {
|
|
12573
13241
|
if (error) reject(error);
|
|
12574
|
-
else
|
|
13242
|
+
else resolve10();
|
|
12575
13243
|
});
|
|
12576
13244
|
});
|
|
12577
13245
|
}
|
|
@@ -12579,6 +13247,7 @@ function createWriter(output) {
|
|
|
12579
13247
|
}
|
|
12580
13248
|
function createLineReader(input, options) {
|
|
12581
13249
|
let buffer = "";
|
|
13250
|
+
let bufferBytes = 0;
|
|
12582
13251
|
let discarding = false;
|
|
12583
13252
|
let discardedBytes = 0;
|
|
12584
13253
|
const deliver = (line) => {
|
|
@@ -12591,6 +13260,13 @@ function createLineReader(input, options) {
|
|
|
12591
13260
|
if (trimmed.trim().length === 0) return;
|
|
12592
13261
|
options.onLine(trimmed);
|
|
12593
13262
|
};
|
|
13263
|
+
const discardBuffered = () => {
|
|
13264
|
+
discarding = true;
|
|
13265
|
+
discardedBytes += bufferBytes;
|
|
13266
|
+
buffer = "";
|
|
13267
|
+
bufferBytes = 0;
|
|
13268
|
+
options.onOverflow?.(discardedBytes);
|
|
13269
|
+
};
|
|
12594
13270
|
const drain = () => {
|
|
12595
13271
|
let index = buffer.indexOf("\n");
|
|
12596
13272
|
while (index >= 0) {
|
|
@@ -12598,42 +13274,57 @@ function createLineReader(input, options) {
|
|
|
12598
13274
|
buffer = buffer.slice(index + 1);
|
|
12599
13275
|
if (discarding) {
|
|
12600
13276
|
deliver(line);
|
|
12601
|
-
} else if (line.length > MAX_LINE_BYTES) {
|
|
12602
|
-
options.onOverflow?.(line.length);
|
|
12603
13277
|
} else {
|
|
12604
|
-
|
|
13278
|
+
const lineBytes = Buffer.byteLength(line, "utf8");
|
|
13279
|
+
if (lineBytes > MAX_LINE_BYTES) options.onOverflow?.(lineBytes);
|
|
13280
|
+
else deliver(line);
|
|
12605
13281
|
}
|
|
12606
13282
|
index = buffer.indexOf("\n");
|
|
12607
13283
|
}
|
|
13284
|
+
bufferBytes = Buffer.byteLength(buffer, "utf8");
|
|
13285
|
+
if (!discarding && bufferBytes > MAX_LINE_BYTES) discardBuffered();
|
|
12608
13286
|
};
|
|
12609
13287
|
const onData = (chunk) => {
|
|
13288
|
+
const chunkBytes = Buffer.byteLength(chunk, "utf8");
|
|
12610
13289
|
if (discarding) {
|
|
12611
13290
|
const index = chunk.indexOf("\n");
|
|
12612
13291
|
if (index < 0) {
|
|
12613
|
-
discardedBytes +=
|
|
13292
|
+
discardedBytes += chunkBytes;
|
|
12614
13293
|
return;
|
|
12615
13294
|
}
|
|
12616
13295
|
buffer = chunk.slice(index + 1);
|
|
13296
|
+
bufferBytes = Buffer.byteLength(buffer, "utf8");
|
|
12617
13297
|
deliver("");
|
|
12618
13298
|
drain();
|
|
12619
13299
|
return;
|
|
12620
13300
|
}
|
|
12621
|
-
|
|
12622
|
-
drain();
|
|
12623
|
-
if (buffer.length > MAX_LINE_BYTES) {
|
|
13301
|
+
if (chunk.indexOf("\n") < 0 && bufferBytes + chunkBytes > MAX_LINE_BYTES) {
|
|
12624
13302
|
discarding = true;
|
|
12625
|
-
discardedBytes =
|
|
13303
|
+
discardedBytes = bufferBytes + chunkBytes;
|
|
12626
13304
|
buffer = "";
|
|
13305
|
+
bufferBytes = 0;
|
|
12627
13306
|
options.onOverflow?.(discardedBytes);
|
|
12628
13307
|
return;
|
|
12629
13308
|
}
|
|
13309
|
+
buffer += chunk;
|
|
13310
|
+
drain();
|
|
12630
13311
|
};
|
|
12631
13312
|
const onEnd = () => {
|
|
12632
|
-
|
|
12633
|
-
|
|
12634
|
-
|
|
12635
|
-
|
|
13313
|
+
const rest = buffer;
|
|
13314
|
+
const restBytes = bufferBytes || Buffer.byteLength(rest, "utf8");
|
|
13315
|
+
buffer = "";
|
|
13316
|
+
bufferBytes = 0;
|
|
13317
|
+
if (rest.length === 0) return;
|
|
13318
|
+
if (discarding) {
|
|
13319
|
+
discarding = false;
|
|
13320
|
+
discardedBytes = 0;
|
|
13321
|
+
return;
|
|
12636
13322
|
}
|
|
13323
|
+
if (restBytes > MAX_LINE_BYTES) {
|
|
13324
|
+
options.onOverflow?.(restBytes);
|
|
13325
|
+
return;
|
|
13326
|
+
}
|
|
13327
|
+
deliver(rest);
|
|
12637
13328
|
};
|
|
12638
13329
|
input.setEncoding("utf8");
|
|
12639
13330
|
input.on("data", onData);
|
|
@@ -12684,8 +13375,8 @@ async function runStdioServer(options) {
|
|
|
12684
13375
|
let inFlight = 0;
|
|
12685
13376
|
let inputEnded = false;
|
|
12686
13377
|
let resolveDone = () => void 0;
|
|
12687
|
-
const finished = new Promise((
|
|
12688
|
-
resolveDone =
|
|
13378
|
+
const finished = new Promise((resolve10) => {
|
|
13379
|
+
resolveDone = resolve10;
|
|
12689
13380
|
});
|
|
12690
13381
|
const finishIfIdle = () => {
|
|
12691
13382
|
if (inputEnded && inFlight === 0) resolveDone(0);
|
|
@@ -13197,7 +13888,9 @@ function nextStepHint(cwd, scriptsAddedNow) {
|
|
|
13197
13888
|
let hasScript = scriptsAddedNow;
|
|
13198
13889
|
if (!hasScript) {
|
|
13199
13890
|
try {
|
|
13200
|
-
const parsed = JSON.parse(
|
|
13891
|
+
const parsed = JSON.parse(
|
|
13892
|
+
stripJsonComments2(fs19.readFileSync(path21.join(cwd, "package.json"), "utf8"))
|
|
13893
|
+
);
|
|
13201
13894
|
hasScript = typeof parsed.scripts?.snapshot === "string";
|
|
13202
13895
|
} catch {
|
|
13203
13896
|
hasScript = false;
|
|
@@ -13299,18 +13992,8 @@ function installDependencies(cwd) {
|
|
|
13299
13992
|
return { manager: manager.name, command, ok: true, detail: seconds };
|
|
13300
13993
|
}
|
|
13301
13994
|
async function captureStdout(fn) {
|
|
13302
|
-
const
|
|
13303
|
-
|
|
13304
|
-
process.stdout.write = ((chunk) => {
|
|
13305
|
-
text2 += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
13306
|
-
return true;
|
|
13307
|
-
});
|
|
13308
|
-
try {
|
|
13309
|
-
const code = await fn();
|
|
13310
|
-
return { code, text: text2 };
|
|
13311
|
-
} finally {
|
|
13312
|
-
process.stdout.write = original;
|
|
13313
|
-
}
|
|
13995
|
+
const { value, text: text2 } = await runWithStdoutCapture(fn);
|
|
13996
|
+
return { code: value, text: text2 };
|
|
13314
13997
|
}
|
|
13315
13998
|
async function runSetupCommand(invocation, options = {}) {
|
|
13316
13999
|
try {
|
|
@@ -13425,31 +14108,33 @@ async function runSetupCommand(invocation, options = {}) {
|
|
|
13425
14108
|
if (invocation.watch) generateArgs.push("--watch");
|
|
13426
14109
|
if (invocation.format === "json") {
|
|
13427
14110
|
const generated = await captureStdout(() => main(generateArgs));
|
|
13428
|
-
let
|
|
14111
|
+
let parsed = null;
|
|
13429
14112
|
try {
|
|
13430
|
-
|
|
14113
|
+
parsed = JSON.parse(generated.text);
|
|
13431
14114
|
} catch {
|
|
13432
|
-
|
|
14115
|
+
parsed = null;
|
|
14116
|
+
}
|
|
14117
|
+
const setup = {
|
|
14118
|
+
configPath: init.configPath,
|
|
14119
|
+
configReused: init.reused,
|
|
14120
|
+
preset: init.preset,
|
|
14121
|
+
scripts: init.scripts,
|
|
14122
|
+
dependency: init.dependency,
|
|
14123
|
+
gitignoreEntries: init.gitignoreEntries,
|
|
14124
|
+
install,
|
|
14125
|
+
notes: init.notes,
|
|
14126
|
+
hook
|
|
14127
|
+
};
|
|
14128
|
+
if (generated.code !== 0) {
|
|
14129
|
+
const inner = parsed && typeof parsed.error === "object" && parsed.error !== null ? parsed.error : { message: "Snapshot generation failed during setup.", exitCode: generated.code };
|
|
14130
|
+
process.stdout.write(
|
|
14131
|
+
`${JSON.stringify({ error: inner, setup, manifest: null }, null, 2)}
|
|
14132
|
+
`
|
|
14133
|
+
);
|
|
14134
|
+
return generated.code;
|
|
13433
14135
|
}
|
|
13434
14136
|
process.stdout.write(
|
|
13435
|
-
`${JSON.stringify(
|
|
13436
|
-
{
|
|
13437
|
-
setup: {
|
|
13438
|
-
configPath: init.configPath,
|
|
13439
|
-
configReused: init.reused,
|
|
13440
|
-
preset: init.preset,
|
|
13441
|
-
scripts: init.scripts,
|
|
13442
|
-
dependency: init.dependency,
|
|
13443
|
-
gitignoreEntries: init.gitignoreEntries,
|
|
13444
|
-
install,
|
|
13445
|
-
notes: init.notes,
|
|
13446
|
-
hook
|
|
13447
|
-
},
|
|
13448
|
-
manifest
|
|
13449
|
-
},
|
|
13450
|
-
null,
|
|
13451
|
-
2
|
|
13452
|
-
)}
|
|
14137
|
+
`${JSON.stringify({ setup, manifest: parsed }, null, 2)}
|
|
13453
14138
|
`
|
|
13454
14139
|
);
|
|
13455
14140
|
return generated.code;
|
|
@@ -13642,6 +14327,10 @@ ${USAGE}
|
|
|
13642
14327
|
largeFileBytes: invocation.largeFileBytes,
|
|
13643
14328
|
maxMonolithBytes: invocation.maxMonolithBytes,
|
|
13644
14329
|
skipArtifacts: invocation.skipArtifacts,
|
|
14330
|
+
// `--only-artifacts` is forwarded as itself, not only as the derived suppression list:
|
|
14331
|
+
// suppression cannot switch an artifact *on*, so a config that had turned one of the
|
|
14332
|
+
// requested artifacts off left the run with nothing to write.
|
|
14333
|
+
onlyArtifacts: invocation.onlyArtifacts,
|
|
13645
14334
|
discovery: invocation.discovery,
|
|
13646
14335
|
changedSince: invocation.changedSince,
|
|
13647
14336
|
staged: invocation.staged,
|
|
@@ -13653,19 +14342,6 @@ ${USAGE}
|
|
|
13653
14342
|
);
|
|
13654
14343
|
if (invocation.command === "validate") {
|
|
13655
14344
|
const result = validateSnapshot(resolved);
|
|
13656
|
-
if (result.valid) {
|
|
13657
|
-
const manifestPath = path21.resolve(resolved.cwd, resolved.outDir, "manifest.json");
|
|
13658
|
-
let manifest;
|
|
13659
|
-
try {
|
|
13660
|
-
manifest = JSON.parse(fs19.readFileSync(manifestPath, "utf8"));
|
|
13661
|
-
} catch {
|
|
13662
|
-
manifest = void 0;
|
|
13663
|
-
}
|
|
13664
|
-
const shapeError = manifest === void 0 ? null : describeManifestShapeProblem(manifest);
|
|
13665
|
-
if (shapeError) {
|
|
13666
|
-
return fail(invocation.format, `${manifestPath} is not a snapshot manifest: ${shapeError}`);
|
|
13667
|
-
}
|
|
13668
|
-
}
|
|
13669
14345
|
emit(invocation.format, result, () => renderValidateText(result));
|
|
13670
14346
|
return result.valid ? 0 : 1;
|
|
13671
14347
|
}
|
|
@@ -13860,10 +14536,13 @@ function isGeneratedWatchPath(candidate, paths) {
|
|
|
13860
14536
|
const outDir = comparablePath(path21.resolve(paths.cwd, paths.outDir));
|
|
13861
14537
|
const tmpDir = comparablePath(path21.resolve(paths.cwd, paths.tmpDir));
|
|
13862
14538
|
if (isSameOrInside(outDir, absolute) || isSameOrInside(tmpDir, absolute)) return true;
|
|
13863
|
-
const
|
|
13864
|
-
|
|
13865
|
-
|
|
13866
|
-
|
|
14539
|
+
const offsets = [path21.basename(outDir), path21.basename(tmpDir)].filter((name) => name.length > 0);
|
|
14540
|
+
const leftover = absolute.split(/[\\/]+/).some(
|
|
14541
|
+
(segment) => offsets.some(
|
|
14542
|
+
(name) => segment.startsWith(`${name}.backup-`) || segment.startsWith(`${name}.partial-`)
|
|
14543
|
+
)
|
|
14544
|
+
);
|
|
14545
|
+
if (leftover) return true;
|
|
13867
14546
|
return absolute.split(/[\\/]+/).some((segment) => VCS_SEGMENTS.has(segment));
|
|
13868
14547
|
}
|
|
13869
14548
|
function decideWatchEvent(state, event, paths) {
|
|
@@ -13938,6 +14617,7 @@ async function runWatchLoop(invocation, resolved, options) {
|
|
|
13938
14617
|
largeFileBytes: invocation.largeFileBytes,
|
|
13939
14618
|
maxMonolithBytes: invocation.maxMonolithBytes,
|
|
13940
14619
|
skipArtifacts: invocation.skipArtifacts,
|
|
14620
|
+
onlyArtifacts: invocation.onlyArtifacts,
|
|
13941
14621
|
discovery: invocation.discovery,
|
|
13942
14622
|
changedSince: invocation.changedSince,
|
|
13943
14623
|
staged: invocation.staged,
|
|
@@ -13995,12 +14675,12 @@ async function runWatchLoop(invocation, resolved, options) {
|
|
|
13995
14675
|
info(`\u26A0 Watch error: ${err.message}`);
|
|
13996
14676
|
info(" The watcher may have missed changes. Restart with Ctrl+C if they stop arriving.");
|
|
13997
14677
|
});
|
|
13998
|
-
await new Promise((
|
|
14678
|
+
await new Promise((resolve10) => {
|
|
13999
14679
|
const stop = () => {
|
|
14000
14680
|
stopped = true;
|
|
14001
14681
|
if (timer) clearTimeout(timer);
|
|
14002
14682
|
watcher.close();
|
|
14003
|
-
|
|
14683
|
+
resolve10();
|
|
14004
14684
|
};
|
|
14005
14685
|
process.once("SIGINT", stop);
|
|
14006
14686
|
process.once("SIGTERM", stop);
|