@clear-capabilities/agentic-security-scanner 0.195.24 → 0.195.26
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 +32 -0
- package/bin/agentic-security.js +9 -0
- package/dist/5752.index.js +16 -3
- package/dist/{daefda0819bb18309b75.json → 60f58f89664e53fc2f96.json} +3 -3
- package/dist/8218.index.js +19 -2
- package/dist/9382.index.js +315 -174
- package/dist/9560.index.js +19 -2
- package/dist/agentic-security.mjs +2 -2
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +3 -3
- package/src/context-intelligence/CLAUDE.md +4 -0
- package/src/context-intelligence/entrypoints.js +141 -9
- package/src/context-intelligence/imports.js +1 -1
- package/src/context-intelligence/review.js +8 -3
- package/src/dataflow/CLAUDE.md +1 -1
- package/src/engine.js +13 -0
- package/src/lineage/CLAUDE.md +17 -0
- package/src/lineage/boundary-join.js +384 -0
- package/src/lineage/coverage.js +4 -1
- package/src/lineage/engine.js +20 -0
- package/src/lineage/graph-builder.js +172 -34
- package/src/lineage/index.js +39 -0
- package/src/lineage/source-seeding.js +11 -1
- package/src/posture/dag-commands.js +15 -2
- package/src/posture/provenance/git-evidence.js +21 -0
- package/src/server/graph-loader.js +19 -2
package/dist/9382.index.js
CHANGED
|
@@ -13,8 +13,8 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
13
13
|
|
|
14
14
|
// EXTERNAL MODULE: ./src/ir/index.js + 11 modules
|
|
15
15
|
var ir = __webpack_require__(98297);
|
|
16
|
-
// EXTERNAL MODULE: ./src/context-intelligence/graph-schema.js
|
|
17
|
-
var graph_schema = __webpack_require__(
|
|
16
|
+
// EXTERNAL MODULE: ./src/context-intelligence/graph-schema.js
|
|
17
|
+
var graph_schema = __webpack_require__(50152);
|
|
18
18
|
;// CONCATENATED MODULE: ./src/context-intelligence/adapters/existing-ir.js
|
|
19
19
|
//
|
|
20
20
|
// Adapter: shared IR (`buildProjectIR`) -> symbol-graph/1.
|
|
@@ -1667,9 +1667,10 @@ function looksLikeTelemetry(name) { return TELEMETRY_NAME.test(String(name || ''
|
|
|
1667
1667
|
/* harmony export */ });
|
|
1668
1668
|
/* unused harmony export DISPOSITIONS */
|
|
1669
1669
|
/* harmony import */ var _canonical_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(44652);
|
|
1670
|
-
/* harmony import */ var
|
|
1670
|
+
/* harmony import */ var _spans_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(77311);
|
|
1671
1671
|
/* harmony import */ var _controls_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(17202);
|
|
1672
1672
|
/* harmony import */ var _posture_entrypoint_inventory_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(11366);
|
|
1673
|
+
/* harmony import */ var _imports_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(86588);
|
|
1673
1674
|
//
|
|
1674
1675
|
// Entry points -> exact handler symbols.
|
|
1675
1676
|
//
|
|
@@ -1689,6 +1690,7 @@ function looksLikeTelemetry(name) { return TELEMETRY_NAME.test(String(name || ''
|
|
|
1689
1690
|
|
|
1690
1691
|
|
|
1691
1692
|
|
|
1693
|
+
|
|
1692
1694
|
const DISPOSITIONS = Object.freeze(['resolved', 'partially_resolved', 'unresolved', 'excluded_by_policy']);
|
|
1693
1695
|
const ENTRYPOINT_KINDS = Object.freeze(['http', 'webhook', 'queue', 'cron', 'upload', 'cli']);
|
|
1694
1696
|
|
|
@@ -1753,15 +1755,18 @@ function collectUses(fileContents) {
|
|
|
1753
1755
|
for (let i = 0; i < lines.length; i++) {
|
|
1754
1756
|
const m = /^\s*([A-Za-z_$][\w$]*)\.use\s*\(/.exec(lines[i]);
|
|
1755
1757
|
if (!m) continue;
|
|
1756
|
-
const st = (0,
|
|
1758
|
+
const st = (0,_spans_js__WEBPACK_IMPORTED_MODULE_4__/* .callStatement */ .yP)(lines, i + 1);
|
|
1757
1759
|
if (!st) continue;
|
|
1758
|
-
const args = (0,
|
|
1760
|
+
const args = (0,_spans_js__WEBPACK_IMPORTED_MODULE_4__/* .splitArgs */ .sG)(st.text);
|
|
1759
1761
|
let prefix = '/';
|
|
1760
1762
|
let rest = args;
|
|
1761
1763
|
const q = args[0] && /^(['"`])(.*)\1$/.exec(args[0]);
|
|
1762
1764
|
if (q) { prefix = q[2]; rest = args.slice(1); }
|
|
1765
|
+
// GA-03.2: a first argument that is a call, a template with interpolation or a concatenation is a prefix
|
|
1766
|
+
// the scan cannot read; the use still records its middleware, but nothing is mounted under a guessed '/'.
|
|
1767
|
+
const prefixDynamic = !q && !!args[0] && (/^[A-Za-z_$][\w$.]*\s*\(/.test(args[0]) && !/^require\s*\(/.test(args[0]) || /\$\{|\+/.test(args[0]));
|
|
1763
1768
|
const names = rest.map(a => { const id = IDENT_CHAIN.exec(a.replace(/^\[|\]$/g, '')); return id ? { name: id[0], text: a } : { name: null, text: a }; });
|
|
1764
|
-
uses.push({ file, line: i + 1, receiver: m[1], prefix, args: names });
|
|
1769
|
+
uses.push({ file, line: i + 1, receiver: m[1], prefix, prefixDynamic, args: names });
|
|
1765
1770
|
}
|
|
1766
1771
|
}
|
|
1767
1772
|
return uses;
|
|
@@ -1805,7 +1810,7 @@ function resolveHandler(route, ctx) {
|
|
|
1805
1810
|
else reasons.push(`handler-${r.resolution}`);
|
|
1806
1811
|
} else if (h.kind === 'inline') {
|
|
1807
1812
|
out.handlerKind = 'inline';
|
|
1808
|
-
out.rootSpan = { file: route.file, ...(0,
|
|
1813
|
+
out.rootSpan = { file: route.file, ...(0,_spans_js__WEBPACK_IMPORTED_MODULE_4__/* .inlineBlockSpan */ .$X)(lines, route.line) };
|
|
1809
1814
|
const enclosing = fileSyms.filter(s => s.kind === 'function' && s.startLine <= route.line && s.endLine >= route.line).sort((a, b) => (b.startLine - a.startLine))[0];
|
|
1810
1815
|
out.handlerSymbol = enclosing ? enclosing.id : null;
|
|
1811
1816
|
out.confidence = enclosing ? 'inline-block' : 'none';
|
|
@@ -1813,7 +1818,7 @@ function resolveHandler(route, ctx) {
|
|
|
1813
1818
|
} else {
|
|
1814
1819
|
reasons.push('handler-not-statically-known');
|
|
1815
1820
|
// the manifest still shows where the route is registered, so the gap is inspectable rather than absent
|
|
1816
|
-
out.rootSpan = { file: route.file, ...(0,
|
|
1821
|
+
out.rootSpan = { file: route.file, ...(0,_spans_js__WEBPACK_IMPORTED_MODULE_4__/* .inlineBlockSpan */ .$X)(lines, route.line) };
|
|
1817
1822
|
}
|
|
1818
1823
|
return out;
|
|
1819
1824
|
}
|
|
@@ -1836,7 +1841,7 @@ function resolveHandler(route, ctx) {
|
|
|
1836
1841
|
return out;
|
|
1837
1842
|
}
|
|
1838
1843
|
|
|
1839
|
-
const st = JS_FILE.test(route.file) ? (0,
|
|
1844
|
+
const st = JS_FILE.test(route.file) ? (0,_spans_js__WEBPACK_IMPORTED_MODULE_4__/* .callStatement */ .yP)(lines, route.line) : null;
|
|
1840
1845
|
if (!st) {
|
|
1841
1846
|
// not a call-style registration we can parse: fall back to the enclosing declaration, flagged
|
|
1842
1847
|
const enclosing = fileSyms.filter(s => s.kind === 'function' && s.startLine <= route.line && s.endLine >= route.line).sort((a, b) => b.startLine - a.startLine)[0];
|
|
@@ -1845,10 +1850,10 @@ function resolveHandler(route, ctx) {
|
|
|
1845
1850
|
return out;
|
|
1846
1851
|
}
|
|
1847
1852
|
out.rootSpan = { file: route.file, startLine: route.line, endLine: st.endLine, precision: 'brace' };
|
|
1848
|
-
const args = (0,
|
|
1853
|
+
const args = (0,_spans_js__WEBPACK_IMPORTED_MODULE_4__/* .splitArgs */ .sG)(st.text).slice(1); // drop the path
|
|
1849
1854
|
const flat = [];
|
|
1850
1855
|
for (const a of args) {
|
|
1851
|
-
if (a.startsWith('[') && a.endsWith(']')) flat.push(...(0,
|
|
1856
|
+
if (a.startsWith('[') && a.endsWith(']')) flat.push(...(0,_spans_js__WEBPACK_IMPORTED_MODULE_4__/* .splitArgs */ .sG)(`(${a.slice(1, -1)})`));
|
|
1852
1857
|
else flat.push(a);
|
|
1853
1858
|
}
|
|
1854
1859
|
if (!flat.length) { reasons.push('registration-has-no-handler-argument'); return out; }
|
|
@@ -1884,6 +1889,107 @@ function resolveHandler(route, ctx) {
|
|
|
1884
1889
|
return out;
|
|
1885
1890
|
}
|
|
1886
1891
|
|
|
1892
|
+
// ── Router mounts (GA-03.2, 0.195.26) ──────────────────────────────────────
|
|
1893
|
+
//
|
|
1894
|
+
// `router.get('/:id', h)` is reachable at `/users/:id` only through `app.use('/users', router)`, and that mount
|
|
1895
|
+
// can sit in another file (`app.use('/users', require('./users'))`, `import users from './users.js'`). Before
|
|
1896
|
+
// this, every route on a router-looking receiver was `partially_resolved` with `router-mount-not-resolved` and
|
|
1897
|
+
// its path was the local one. Now a mount is found when the mounted argument is (a) a receiver with routes in
|
|
1898
|
+
// the same file, (b) a name imported from a file whose `module.exports` / `export default` is that file's
|
|
1899
|
+
// router receiver, or (c) an inline `require('./x')` of such a file. Mounts compose: `app.use('/api', v1)` and
|
|
1900
|
+
// `v1.use('/users', users)` make `/api/users/:id`. A router mounted at several prefixes is reachable at each,
|
|
1901
|
+
// so it yields one entry point per prefix (sorted; at most MAX_MOUNT_PATHS). Depth is bounded; a cycle stops.
|
|
1902
|
+
// A mount's other identifier arguments (`app.use('/api', auth, router)`) are middleware every mounted route
|
|
1903
|
+
// inherits, recorded with the mount's prefix as their scope. What stays unresolved: a router passed through a
|
|
1904
|
+
// variable the import map cannot follow, a mount whose prefix is not a string literal, a dynamic
|
|
1905
|
+
// `app.use(prefixVar, router)`, and anything registered outside the scanned files.
|
|
1906
|
+
const MAX_MOUNT_DEPTH = 4;
|
|
1907
|
+
const MAX_MOUNT_PATHS = 8;
|
|
1908
|
+
const ROUTE_RECEIVER_RE = /([A-Za-z_$][\w$]*)\.(?:get|post|put|patch|delete|all|options|head|route)\s*\(/;
|
|
1909
|
+
const ROUTER_NAME_RE = /^(?:router|routes?|r|api|\w*Router)$/i;
|
|
1910
|
+
|
|
1911
|
+
function routeReceiver(fileContents, route) {
|
|
1912
|
+
if (!JS_FILE.test(route.file)) return null;
|
|
1913
|
+
const line = (fileContents[route.file] || '').split('\n')[route.line - 1] || '';
|
|
1914
|
+
const m = ROUTE_RECEIVER_RE.exec(line);
|
|
1915
|
+
return m ? m[1] : null;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
/** The receiver a file exports as its module (`module.exports = router`, `export default router`), or null. */
|
|
1919
|
+
function exportedReceiver(text) {
|
|
1920
|
+
const t = String(text || '');
|
|
1921
|
+
const m = /\bmodule\.exports\s*=\s*([A-Za-z_$][\w$]*)\s*;?/.exec(t) || /\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*;?/.exec(t);
|
|
1922
|
+
return m ? m[1] : null;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
function joinMountPath(prefix, routePath) {
|
|
1926
|
+
const p = String(prefix || '/').replace(/\/+$/, '');
|
|
1927
|
+
const r = String(routePath || '');
|
|
1928
|
+
if (!p) return r.startsWith('/') ? r : '/' + r;
|
|
1929
|
+
if (!r || r === '/') return p;
|
|
1930
|
+
return p + (r.startsWith('/') ? r : '/' + r);
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
/**
|
|
1934
|
+
* Every mount of a router: key `${file}|${receiver}` -> [{ prefix, file, line, receiver, middleware }], where
|
|
1935
|
+
* `receiver` is the receiver the router was mounted ON (so a chain can be followed upward).
|
|
1936
|
+
*/
|
|
1937
|
+
function buildMountIndex(fileContents, uses, routes) {
|
|
1938
|
+
const files = new Set(Object.keys(fileContents));
|
|
1939
|
+
const imports = (0,_imports_js__WEBPACK_IMPORTED_MODULE_3__/* .buildImportMap */ .kB)(fileContents);
|
|
1940
|
+
const receiversByFile = new Map();
|
|
1941
|
+
for (const r of routes) {
|
|
1942
|
+
const recv = routeReceiver(fileContents, r);
|
|
1943
|
+
if (!recv) continue;
|
|
1944
|
+
if (!receiversByFile.has(r.file)) receiversByFile.set(r.file, new Set());
|
|
1945
|
+
receiversByFile.get(r.file).add(recv);
|
|
1946
|
+
}
|
|
1947
|
+
// a receiver that mounts something is a router too (an intermediate `v1.use('/users', users)`)
|
|
1948
|
+
for (const u of uses) { if (!receiversByFile.has(u.file)) receiversByFile.set(u.file, new Set()); receiversByFile.get(u.file).add(u.receiver); }
|
|
1949
|
+
const index = new Map();
|
|
1950
|
+
const add = (file, receiver, mount) => { const k = `${file}|${receiver}`; if (!index.has(k)) index.set(k, []); index.get(k).push(mount); };
|
|
1951
|
+
// a module is a router module only when its exported receiver registers routes or mounts in that file
|
|
1952
|
+
const isRouter = (file, receiver) => !!receiver && (receiversByFile.get(file) || new Set()).has(receiver);
|
|
1953
|
+
for (const u of uses) {
|
|
1954
|
+
if (u.prefixDynamic) continue;
|
|
1955
|
+
const middleware = [];
|
|
1956
|
+
for (const a of u.args) {
|
|
1957
|
+
let target = null;
|
|
1958
|
+
const req = /^require\(\s*(['"])([^'"]+)\1\s*\)$/.exec(String(a.text || '').trim());
|
|
1959
|
+
if (req) {
|
|
1960
|
+
const res = (0,_imports_js__WEBPACK_IMPORTED_MODULE_3__/* .resolveJsModule */ .iC)(u.file, req[2], files);
|
|
1961
|
+
const recv = res && res.target && files.has(res.target) ? exportedReceiver(fileContents[res.target]) : null;
|
|
1962
|
+
if (recv && isRouter(res.target, recv)) target = { file: res.target, receiver: recv };
|
|
1963
|
+
} else if (a.name && a.name !== u.receiver && isRouter(u.file, a.name)) {
|
|
1964
|
+
target = { file: u.file, receiver: a.name };
|
|
1965
|
+
} else if (a.name) {
|
|
1966
|
+
const via = (0,_imports_js__WEBPACK_IMPORTED_MODULE_3__/* .resolveViaImports */ .xU)(imports, u.file, a.name);
|
|
1967
|
+
const recv = via && via.kind === 'exact-import' && via.targetFile && files.has(via.targetFile) ? exportedReceiver(fileContents[via.targetFile]) : null;
|
|
1968
|
+
if (recv && isRouter(via.targetFile, recv)) target = { file: via.targetFile, receiver: recv };
|
|
1969
|
+
}
|
|
1970
|
+
if (target) add(target.file, target.receiver, { prefix: u.prefix, file: u.file, line: u.line, receiver: u.receiver, middleware: [...middleware] });
|
|
1971
|
+
else if (a.name && !req) middleware.push(a.name);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
return index;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
/** The mount chains (root first) through which (file, receiver) is reachable; [] when it is mounted nowhere. */
|
|
1978
|
+
function mountChains(index, file, receiver, depth = 0, seen = new Set()) {
|
|
1979
|
+
const key = `${file}|${receiver}`;
|
|
1980
|
+
const mounts = index.get(key) || [];
|
|
1981
|
+
if (!mounts.length || depth >= MAX_MOUNT_DEPTH || seen.has(key)) return [];
|
|
1982
|
+
const out = [];
|
|
1983
|
+
const next = new Set(seen); next.add(key);
|
|
1984
|
+
for (const m of mounts) {
|
|
1985
|
+
const parents = mountChains(index, m.file, m.receiver, depth + 1, next);
|
|
1986
|
+
if (!parents.length) out.push([m]);
|
|
1987
|
+
else for (const pc of parents) out.push([...pc, m]);
|
|
1988
|
+
if (out.length >= MAX_MOUNT_PATHS) break;
|
|
1989
|
+
}
|
|
1990
|
+
return out.slice(0, MAX_MOUNT_PATHS);
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1887
1993
|
function pathMatchesPrefix(routePath, prefix) {
|
|
1888
1994
|
if (prefix === '/' || prefix === '') return true;
|
|
1889
1995
|
const p = prefix.replace(/\/+$/, '');
|
|
@@ -1898,23 +2004,24 @@ async function collectEntrypoints({ fileContents, graph, config = {} }) {
|
|
|
1898
2004
|
const kinds = new Set(config.entrypointKinds && config.entrypointKinds.length ? config.entrypointKinds : ['http']);
|
|
1899
2005
|
const idx = symbolIndexes(graph);
|
|
1900
2006
|
const uses = collectUses(fileContents);
|
|
2007
|
+
const routes = (kinds.has('http') || kinds.has('webhook')) ? await collectRoutes(fileContents) : [];
|
|
2008
|
+
const mountIndex = buildMountIndex(fileContents, uses, routes);
|
|
1901
2009
|
const exclude = (config.exclude || []).map(globToRe);
|
|
1902
2010
|
const cap = Number.isInteger(config.maxEntrypoints) && config.maxEntrypoints > 0 ? config.maxEntrypoints : 5000;
|
|
1903
2011
|
const ctx = { fileContents, idx, graph };
|
|
1904
2012
|
const records = [];
|
|
1905
2013
|
|
|
1906
2014
|
if (kinds.has('http') || kinds.has('webhook')) {
|
|
1907
|
-
for (const route of
|
|
2015
|
+
for (const route of routes) {
|
|
1908
2016
|
const isWebhook = /webhook/i.test(route.path || '');
|
|
1909
2017
|
if (isWebhook && !kinds.has('webhook') && !kinds.has('http')) continue;
|
|
1910
2018
|
const h = resolveHandler(route, ctx);
|
|
1911
2019
|
const reasons = [...h.reasons];
|
|
1912
2020
|
const middleware = [...h.middleware];
|
|
2021
|
+
const receiver = routeReceiver(fileContents, route);
|
|
2022
|
+
const chains = receiver ? mountChains(mountIndex, route.file, receiver) : [];
|
|
1913
2023
|
// inherited middleware registered with `X.use(...)` on the same receiver, earlier in the same file
|
|
1914
2024
|
if (JS_FILE.test(route.file)) {
|
|
1915
|
-
const lines = fileContents[route.file].split('\n');
|
|
1916
|
-
const recvM = /([A-Za-z_$][\w$]*)\.(?:get|post|put|patch|delete|all|options|head|route)\s*\(/.exec(lines[route.line - 1] || '');
|
|
1917
|
-
const receiver = recvM ? recvM[1] : null;
|
|
1918
2025
|
for (const u of uses) {
|
|
1919
2026
|
if (u.file !== route.file || u.line >= route.line || u.receiver !== receiver) continue;
|
|
1920
2027
|
if (!pathMatchesPrefix(String(route.path || ''), u.prefix)) continue;
|
|
@@ -1925,17 +2032,35 @@ async function collectEntrypoints({ fileContents, graph, config = {} }) {
|
|
|
1925
2032
|
if (!r.symbol) reasons.push(`inherited-middleware-${r.resolution}:${a.name}`);
|
|
1926
2033
|
}
|
|
1927
2034
|
}
|
|
1928
|
-
if (receiver &&
|
|
1929
|
-
reasons.push('router-mount-not-resolved');
|
|
1930
|
-
}
|
|
2035
|
+
if (receiver && !chains.length && ROUTER_NAME_RE.test(receiver)) reasons.push('router-mount-not-resolved');
|
|
1931
2036
|
}
|
|
2037
|
+
// middleware the mount statements apply to everything mounted under them (`app.use('/api', auth, router)`):
|
|
2038
|
+
// per chain, so a router mounted twice inherits each mount's own middleware and not the other's
|
|
2039
|
+
const chainMiddleware = (chain) => {
|
|
2040
|
+
const mw = []; const why = [];
|
|
2041
|
+
for (const hop of chain) {
|
|
2042
|
+
for (const name of hop.middleware) {
|
|
2043
|
+
const r = resolveName(name, hop.file, idx);
|
|
2044
|
+
mw.push({ name, symbolId: r.symbol ? r.symbol.id : null, resolution: r.resolution, line: hop.line, scope: hop.prefix, inherited: true, viaMount: true });
|
|
2045
|
+
if (!r.symbol) why.push(`inherited-middleware-${r.resolution}:${name}`);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
return { mw, why };
|
|
2049
|
+
};
|
|
1932
2050
|
const authScheme = route.webModel
|
|
1933
2051
|
? { status: route.webModel.auth.status, kind: route.webModel.auth.kind || null, basis: 'haskell-web-model', role: route.webModel.role.status, ownership: route.webModel.ownership || null, idLookups: route.webModel.idLookups || [], csrf: !!route.webModel.csrf }
|
|
1934
2052
|
: { status: route.hasAuth ? 'authenticated' : 'none', basis: 'route-scan-heuristic', note: 'authentication seen near the registration; says nothing about ownership' };
|
|
2053
|
+
const mountsOf = chains.map(chain => ({
|
|
2054
|
+
prefix: chain.reduce((acc, hop) => joinMountPath(acc, hop.prefix), '/'),
|
|
2055
|
+
chain: chain.map(hop => ({ file: hop.file, line: hop.line, receiver: hop.receiver, prefix: hop.prefix })),
|
|
2056
|
+
...chainMiddleware(chain),
|
|
2057
|
+
})).sort((a, b) => (a.prefix < b.prefix ? -1 : a.prefix > b.prefix ? 1 : 0));
|
|
1935
2058
|
const base = {
|
|
1936
2059
|
kind: isWebhook ? 'webhook' : 'http',
|
|
1937
2060
|
method: String(route.method || 'GET'),
|
|
1938
2061
|
pathTemplate: String(route.path || ''),
|
|
2062
|
+
registeredPath: String(route.path || ''),
|
|
2063
|
+
mount: null,
|
|
1939
2064
|
framework: route.framework || null,
|
|
1940
2065
|
language: route.language === 'haskell' ? 'haskell' : (JS_FILE.test(route.file) ? 'javascript' : (route.file.split('.').pop())),
|
|
1941
2066
|
registrationSite: { file: route.file, line: route.line },
|
|
@@ -1948,7 +2073,15 @@ async function collectEntrypoints({ fileContents, graph, config = {} }) {
|
|
|
1948
2073
|
externalDependencies: [],
|
|
1949
2074
|
resolutionReasons: [...new Set(reasons)].sort(),
|
|
1950
2075
|
};
|
|
1951
|
-
records.push(base);
|
|
2076
|
+
if (!mountsOf.length) { records.push(base); continue; }
|
|
2077
|
+
for (const m of mountsOf) {
|
|
2078
|
+
const { mw, why, ...mount } = m;
|
|
2079
|
+
records.push({
|
|
2080
|
+
...base, pathTemplate: joinMountPath(mount.prefix, base.registeredPath), mount: { ...mount, alternatives: mountsOf.length },
|
|
2081
|
+
middleware: [...base.middleware.map(x => ({ ...x })), ...mw],
|
|
2082
|
+
resolutionReasons: [...new Set([...base.resolutionReasons, ...why])].sort(),
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
1952
2085
|
}
|
|
1953
2086
|
}
|
|
1954
2087
|
|
|
@@ -2108,151 +2241,18 @@ function readGitSnapshot(root, ref, { maxFiles = 3000 } = {}) {
|
|
|
2108
2241
|
|
|
2109
2242
|
/***/ }),
|
|
2110
2243
|
|
|
2111
|
-
/***/
|
|
2244
|
+
/***/ 50152:
|
|
2112
2245
|
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
2113
2246
|
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
// EXTERNAL MODULE: ./src/context-intelligence/canonical.js
|
|
2125
|
-
var canonical = __webpack_require__(44652);
|
|
2126
|
-
// EXTERNAL MODULE: ./src/context-intelligence/spans.js
|
|
2127
|
-
var spans = __webpack_require__(77311);
|
|
2128
|
-
// EXTERNAL MODULE: external "node:path"
|
|
2129
|
-
var external_node_path_ = __webpack_require__(76760);
|
|
2130
|
-
;// CONCATENATED MODULE: ./src/context-intelligence/imports.js
|
|
2131
|
-
//
|
|
2132
|
-
// Import maps (JS/TS and Python): which local name in a file refers to which module, and which declaration in it.
|
|
2133
|
-
//
|
|
2134
|
-
// The shared IR resolves a call to a declaration only for the shapes it models; a destructured `require` or a named
|
|
2135
|
-
// ES import is not one of them, so a call such as `getReport(id)` arrives as an edge to nowhere. Left alone, nearly
|
|
2136
|
-
// every Express/Node route would be reported as having an unresolved call. This module recovers those edges from the
|
|
2137
|
-
// import statements themselves and records HOW each was resolved, so the result is still evidence and not a guess:
|
|
2138
|
-
//
|
|
2139
|
-
// exact-import the local name is bound by an import of a module in the repository, and that module declares a
|
|
2140
|
-
// function of that name (or, for a namespace, of the member name)
|
|
2141
|
-
// third-party the module is a package, not a repository file: a boundary, not first-party code
|
|
2142
|
-
// unresolved a relative import that no repository file satisfies
|
|
2143
|
-
//
|
|
2144
|
-
// Scope, stated plainly: this is statement-level pattern matching. Re-export chains, `import()` expressions,
|
|
2145
|
-
// computed `require` arguments and package-relative aliases (tsconfig paths) are not followed.
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
const JS_EXTS = ['', '.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '/index.js', '/index.mjs', '/index.ts', '/index.tsx'];
|
|
2149
|
-
|
|
2150
|
-
function resolveJsModule(fromFile, source, files) {
|
|
2151
|
-
if (!source.startsWith('.')) return { kind: 'third-party', target: null };
|
|
2152
|
-
const base = external_node_path_.posix.normalize(external_node_path_.posix.join(external_node_path_.posix.dirname(fromFile), source));
|
|
2153
|
-
for (const ext of JS_EXTS) if (files.has(base + ext)) return { kind: 'repo', target: base + ext };
|
|
2154
|
-
return { kind: 'unresolved', target: null };
|
|
2155
|
-
}
|
|
2156
|
-
|
|
2157
|
-
function resolvePyModule(fromFile, dotted, files) {
|
|
2158
|
-
let dir = external_node_path_.posix.dirname(fromFile);
|
|
2159
|
-
let mod = dotted;
|
|
2160
|
-
const lead = /^\.+/.exec(mod);
|
|
2161
|
-
const candidatesBase = [];
|
|
2162
|
-
if (lead) {
|
|
2163
|
-
for (let i = 1; i < lead[0].length; i++) dir = external_node_path_.posix.dirname(dir);
|
|
2164
|
-
mod = mod.slice(lead[0].length);
|
|
2165
|
-
candidatesBase.push(dir === '.' ? '' : dir);
|
|
2166
|
-
} else {
|
|
2167
|
-
candidatesBase.push('', external_node_path_.posix.dirname(fromFile) === '.' ? '' : external_node_path_.posix.dirname(fromFile));
|
|
2168
|
-
}
|
|
2169
|
-
for (const b of candidatesBase) {
|
|
2170
|
-
const rel = mod.replace(/\./g, '/');
|
|
2171
|
-
for (const cand of [rel ? `${rel}.py` : '__init__.py', rel ? `${rel}/__init__.py` : '__init__.py']) {
|
|
2172
|
-
const full = b ? external_node_path_.posix.join(b, cand) : cand;
|
|
2173
|
-
if (files.has(full)) return { kind: 'repo', target: full };
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
return { kind: lead ? 'unresolved' : 'third-party', target: null };
|
|
2177
|
-
}
|
|
2178
|
-
|
|
2179
|
-
function stripComments(text) {
|
|
2180
|
-
return text.replace(/\/\*[\s\S]*?\*\//g, m => m.replace(/[^\n]/g, ' ')).replace(/(^|[^:'"`\\])\/\/[^\n]*/g, (m, p1) => p1 + ' '.repeat(m.length - p1.length));
|
|
2181
|
-
}
|
|
2182
|
-
|
|
2183
|
-
function names(list) {
|
|
2184
|
-
return list.split(',').map(s => s.trim()).filter(Boolean).map(s => {
|
|
2185
|
-
const m = /^(?:\w+\s+as\s+)?([\w$]+)$/.exec(s.replace(/^type\s+/, '').replace(/^([\w$]+)\s*:\s*([\w$]+)$/, '$1 as $2'));
|
|
2186
|
-
const parts = s.replace(/^type\s+/, '').split(/\s+as\s+|\s*:\s*/).map(x => x.trim()).filter(Boolean);
|
|
2187
|
-
void m;
|
|
2188
|
-
if (parts.length === 2) return { imported: parts[0], local: parts[1] };
|
|
2189
|
-
if (parts.length === 1) return { imported: parts[0], local: parts[0] };
|
|
2190
|
-
return null;
|
|
2191
|
-
}).filter(Boolean).filter(n => /^[\w$]+$/.test(n.imported) && /^[\w$]+$/.test(n.local));
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
/** Import entries for one JS/TS file. */
|
|
2195
|
-
function jsImports(file, text, files) {
|
|
2196
|
-
const t = stripComments(text);
|
|
2197
|
-
const out = [];
|
|
2198
|
-
const add = (local, source, member) => out.push({ file, local, source, member, ...resolveJsModule(file, source, files) });
|
|
2199
|
-
for (const m of t.matchAll(/\b(?:const|let|var)\s*\{([^}]*)\}\s*=\s*(?:await\s+)?require\(\s*['"]([^'"]+)['"]\s*\)/g)) for (const n of names(m[1])) add(n.local, m[2], n.imported);
|
|
2200
|
-
for (const m of t.matchAll(/\b(?:const|let|var)\s+([\w$]+)\s*=\s*(?:await\s+)?require\(\s*['"]([^'"]+)['"]\s*\)/g)) add(m[1], m[2], '*');
|
|
2201
|
-
for (const m of t.matchAll(/\bimport\s+(?:type\s+)?([\w$]+)?\s*,?\s*(?:\{([^}]*)\}|\*\s+as\s+([\w$]+))?\s*from\s*['"]([^'"]+)['"]/g)) {
|
|
2202
|
-
if (m[1]) add(m[1], m[4], 'default');
|
|
2203
|
-
if (m[2]) for (const n of names(m[2])) add(n.local, m[4], n.imported);
|
|
2204
|
-
if (m[3]) add(m[3], m[4], '*');
|
|
2205
|
-
}
|
|
2206
|
-
return out;
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2209
|
-
function pyImports(file, text, files) {
|
|
2210
|
-
const out = [];
|
|
2211
|
-
const add = (local, source, member) => out.push({ file, local, source, member, ...resolvePyModule(file, source, files) });
|
|
2212
|
-
// the unparenthesised form ends at the line end; only the parenthesised form may span lines
|
|
2213
|
-
for (const m of text.matchAll(/^[ \t]*from[ \t]+([.\w]+)[ \t]+import[ \t]+(?:\(([\w\s,]+)\)|([\w \t,]+))/gm)) {
|
|
2214
|
-
for (const n of (m[2] ?? m[3]).split(',').map(s => s.trim()).filter(Boolean)) {
|
|
2215
|
-
const p = n.split(/\s+as\s+/);
|
|
2216
|
-
add(p[1] || p[0], m[1], p[0]);
|
|
2217
|
-
}
|
|
2218
|
-
}
|
|
2219
|
-
for (const m of text.matchAll(/^[ \t]*import\s+([.\w]+)(?:\s+as\s+(\w+))?\s*$/gm)) add(m[2] || m[1].split('.')[0], m[1], '*');
|
|
2220
|
-
return out;
|
|
2221
|
-
}
|
|
2222
|
-
|
|
2223
|
-
/** All import entries, deterministically ordered. */
|
|
2224
|
-
function buildImportMap(fileContents) {
|
|
2225
|
-
const files = new Set(Object.keys(fileContents));
|
|
2226
|
-
const out = [];
|
|
2227
|
-
for (const file of [...files].sort()) {
|
|
2228
|
-
if (/\.(?:js|jsx|mjs|cjs|ts|tsx)$/i.test(file)) out.push(...jsImports(file, fileContents[file], files));
|
|
2229
|
-
else if (/\.py$/i.test(file)) out.push(...pyImports(file, fileContents[file], files));
|
|
2230
|
-
}
|
|
2231
|
-
return out.sort((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : 0) || (a.local < b.local ? -1 : a.local > b.local ? 1 : 0) || (a.source < b.source ? -1 : 1));
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
/**
|
|
2235
|
-
* Resolve a callee name used in `file` against the import map.
|
|
2236
|
-
* Returns { kind:'exact-import', targetFile, member } | { kind:'third-party' } | { kind:'unresolved-import' } | null.
|
|
2237
|
-
*/
|
|
2238
|
-
function resolveViaImports(imports, file, calleeName) {
|
|
2239
|
-
const parts = String(calleeName || '').split('.');
|
|
2240
|
-
const head = parts[0];
|
|
2241
|
-
const imp = imports.filter(i => i.file === file && i.local === head);
|
|
2242
|
-
if (!imp.length) return null;
|
|
2243
|
-
const i = imp[0];
|
|
2244
|
-
if (i.kind === 'third-party') return { kind: 'third-party' };
|
|
2245
|
-
if (i.kind === 'unresolved') return { kind: 'unresolved-import' };
|
|
2246
|
-
if (parts.length === 1) {
|
|
2247
|
-
const member = i.member === '*' || i.member === 'default' ? head : i.member;
|
|
2248
|
-
return { kind: 'exact-import', targetFile: i.target, member, namespaceCall: i.member === '*' || i.member === 'default' };
|
|
2249
|
-
}
|
|
2250
|
-
if (i.member === '*' || i.member === 'default') return { kind: 'exact-import', targetFile: i.target, member: parts[parts.length - 1], namespaceCall: false };
|
|
2251
|
-
// `db.query()` where `db` is a NAMED import: a method of an imported object, not a function of that module
|
|
2252
|
-
return { kind: 'imported-object-method' };
|
|
2253
|
-
}
|
|
2254
|
-
|
|
2255
|
-
;// CONCATENATED MODULE: ./src/context-intelligence/graph-schema.js
|
|
2247
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
2248
|
+
/* harmony export */ Nk: () => (/* binding */ buildSymbolGraph),
|
|
2249
|
+
/* harmony export */ U2: () => (/* binding */ MATERIAL_REASONS),
|
|
2250
|
+
/* harmony export */ dC: () => (/* binding */ validateGraph)
|
|
2251
|
+
/* harmony export */ });
|
|
2252
|
+
/* unused harmony exports GRAPH_SCHEMA_VERSION, CONTINUATIONS, EDGE_KINDS, RESOLVERS, conditionalityOf */
|
|
2253
|
+
/* harmony import */ var _canonical_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(44652);
|
|
2254
|
+
/* harmony import */ var _spans_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(77311);
|
|
2255
|
+
/* harmony import */ var _imports_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(86588);
|
|
2256
2256
|
//
|
|
2257
2257
|
// Versioned, typed declaration/reference graph (symbol-graph/1).
|
|
2258
2258
|
//
|
|
@@ -2355,8 +2355,8 @@ function buildSymbolGraph({ fileContents, functions, edges }) {
|
|
|
2355
2355
|
ordinal.set(base, k + 1);
|
|
2356
2356
|
const id = !anon && k === 0 ? base : (anon ? base : `${base}#${k + 1}`);
|
|
2357
2357
|
idByQid.set(fn.qid, id);
|
|
2358
|
-
const span = (0,
|
|
2359
|
-
const text = (0,
|
|
2358
|
+
const span = (0,_spans_js__WEBPACK_IMPORTED_MODULE_2__/* .spanFor */ .Wc)(fn.file, lines(fn.file), fn.line, cfgMaxLine(fn));
|
|
2359
|
+
const text = (0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .sliceLines */ .iq)(fileContents[fn.file], span.startLine, span.endLine);
|
|
2360
2360
|
symbols.push({
|
|
2361
2361
|
id,
|
|
2362
2362
|
name: fn.name || '<anon>',
|
|
@@ -2366,7 +2366,7 @@ function buildSymbolGraph({ fileContents, functions, edges }) {
|
|
|
2366
2366
|
endLine: span.endLine,
|
|
2367
2367
|
declLine: fn.line,
|
|
2368
2368
|
spanPrecision: span.precision,
|
|
2369
|
-
spanHash: (0,
|
|
2369
|
+
spanHash: (0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .sha256 */ .sc)(text),
|
|
2370
2370
|
params: Array.isArray(fn.params) ? fn.params.map(String) : [],
|
|
2371
2371
|
resolver: 'source-static',
|
|
2372
2372
|
});
|
|
@@ -2381,7 +2381,7 @@ function buildSymbolGraph({ fileContents, functions, edges }) {
|
|
|
2381
2381
|
byLastName.get(key).push(s.id);
|
|
2382
2382
|
}
|
|
2383
2383
|
|
|
2384
|
-
const imports = buildImportMap(fileContents);
|
|
2384
|
+
const imports = (0,_imports_js__WEBPACK_IMPORTED_MODULE_1__/* .buildImportMap */ .kB)(fileContents);
|
|
2385
2385
|
const symbolsByFileName = new Map();
|
|
2386
2386
|
for (const s of symbols) {
|
|
2387
2387
|
if (s.kind !== 'function' || s.id.includes('::<anon>@')) continue;
|
|
@@ -2429,7 +2429,7 @@ function buildSymbolGraph({ fileContents, functions, edges }) {
|
|
|
2429
2429
|
if (q.length === 1) { graphEdges.push({ ...base, to: q[0], kind: 'call', confidence: 'high', unresolvedReason: null, candidates: [], resolution: 'qualified-name' }); continue; }
|
|
2430
2430
|
}
|
|
2431
2431
|
|
|
2432
|
-
const imp = resolveViaImports(imports, callerFile, name);
|
|
2432
|
+
const imp = (0,_imports_js__WEBPACK_IMPORTED_MODULE_1__/* .resolveViaImports */ .xU)(imports, callerFile, name);
|
|
2433
2433
|
if (imp && imp.kind === 'exact-import') {
|
|
2434
2434
|
const ids = (symbolsByFileName.get(`${imp.targetFile}\0${imp.member}`) || []).filter(id => id !== from);
|
|
2435
2435
|
if (ids.length === 1) { graphEdges.push({ ...base, to: ids[0], kind: 'call', confidence: 'high', unresolvedReason: null, candidates: [], resolution: 'import' }); continue; }
|
|
@@ -2450,9 +2450,9 @@ function buildSymbolGraph({ fileContents, functions, edges }) {
|
|
|
2450
2450
|
graphEdges.sort((a, b) =>
|
|
2451
2451
|
(a.from < b.from ? -1 : a.from > b.from ? 1 : 0) || (a.line - b.line) || (a.calleeName < b.calleeName ? -1 : a.calleeName > b.calleeName ? 1 : 0) || ((a.to || '') < (b.to || '') ? -1 : 1));
|
|
2452
2452
|
|
|
2453
|
-
const snapshotHash = (0,
|
|
2453
|
+
const snapshotHash = (0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .hashOf */ .qQ)(Object.keys(fileContents).sort().map(f => [f, (0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .sha256 */ .sc)(fileContents[f])]));
|
|
2454
2454
|
const body = { schemaVersion: GRAPH_SCHEMA_VERSION, snapshotHash, symbols, edges: graphEdges, imports: imports.map(i => ({ file: i.file, local: i.local, source: i.source, member: i.member, kind: i.kind, target: i.target })) };
|
|
2455
|
-
return { ...body, hash: (0,
|
|
2455
|
+
return { ...body, hash: (0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .hashOf */ .qQ)(body) };
|
|
2456
2456
|
}
|
|
2457
2457
|
|
|
2458
2458
|
/**
|
|
@@ -2469,7 +2469,7 @@ function validateGraph(graph, fileContents) {
|
|
|
2469
2469
|
if (typeof text !== 'string') { problems.push(`missing-file:${s.file}`); continue; }
|
|
2470
2470
|
const n = text.split('\n').length;
|
|
2471
2471
|
if (!(s.startLine >= 1 && s.endLine >= s.startLine && s.endLine <= n)) { problems.push(`span-out-of-range:${s.id}`); continue; }
|
|
2472
|
-
if ((0,
|
|
2472
|
+
if ((0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .sha256 */ .sc)((0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .sliceLines */ .iq)(text, s.startLine, s.endLine)) !== s.spanHash) problems.push(`stale-span-hash:${s.id}`);
|
|
2473
2473
|
}
|
|
2474
2474
|
for (const e of graph.edges) {
|
|
2475
2475
|
if (!ids.has(e.from)) problems.push(`dangling-from:${e.from}`);
|
|
@@ -2477,11 +2477,147 @@ function validateGraph(graph, fileContents) {
|
|
|
2477
2477
|
for (const c of e.candidates || []) if (!ids.has(c)) problems.push(`dangling-candidate:${c}`);
|
|
2478
2478
|
}
|
|
2479
2479
|
const { hash, ...rest } = graph;
|
|
2480
|
-
if ((0,
|
|
2480
|
+
if ((0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .hashOf */ .qQ)(rest) !== hash) problems.push('graph-hash-mismatch');
|
|
2481
2481
|
return problems;
|
|
2482
2482
|
}
|
|
2483
2483
|
|
|
2484
2484
|
|
|
2485
|
+
/***/ }),
|
|
2486
|
+
|
|
2487
|
+
/***/ 86588:
|
|
2488
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
2489
|
+
|
|
2490
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
2491
|
+
/* harmony export */ iC: () => (/* binding */ resolveJsModule),
|
|
2492
|
+
/* harmony export */ kB: () => (/* binding */ buildImportMap),
|
|
2493
|
+
/* harmony export */ xU: () => (/* binding */ resolveViaImports)
|
|
2494
|
+
/* harmony export */ });
|
|
2495
|
+
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(76760);
|
|
2496
|
+
//
|
|
2497
|
+
// Import maps (JS/TS and Python): which local name in a file refers to which module, and which declaration in it.
|
|
2498
|
+
//
|
|
2499
|
+
// The shared IR resolves a call to a declaration only for the shapes it models; a destructured `require` or a named
|
|
2500
|
+
// ES import is not one of them, so a call such as `getReport(id)` arrives as an edge to nowhere. Left alone, nearly
|
|
2501
|
+
// every Express/Node route would be reported as having an unresolved call. This module recovers those edges from the
|
|
2502
|
+
// import statements themselves and records HOW each was resolved, so the result is still evidence and not a guess:
|
|
2503
|
+
//
|
|
2504
|
+
// exact-import the local name is bound by an import of a module in the repository, and that module declares a
|
|
2505
|
+
// function of that name (or, for a namespace, of the member name)
|
|
2506
|
+
// third-party the module is a package, not a repository file: a boundary, not first-party code
|
|
2507
|
+
// unresolved a relative import that no repository file satisfies
|
|
2508
|
+
//
|
|
2509
|
+
// Scope, stated plainly: this is statement-level pattern matching. Re-export chains, `import()` expressions,
|
|
2510
|
+
// computed `require` arguments and package-relative aliases (tsconfig paths) are not followed.
|
|
2511
|
+
|
|
2512
|
+
|
|
2513
|
+
const JS_EXTS = ['', '.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '/index.js', '/index.mjs', '/index.ts', '/index.tsx'];
|
|
2514
|
+
|
|
2515
|
+
function resolveJsModule(fromFile, source, files) {
|
|
2516
|
+
if (!source.startsWith('.')) return { kind: 'third-party', target: null };
|
|
2517
|
+
const base = node_path__WEBPACK_IMPORTED_MODULE_0__.posix.normalize(node_path__WEBPACK_IMPORTED_MODULE_0__.posix.join(node_path__WEBPACK_IMPORTED_MODULE_0__.posix.dirname(fromFile), source));
|
|
2518
|
+
for (const ext of JS_EXTS) if (files.has(base + ext)) return { kind: 'repo', target: base + ext };
|
|
2519
|
+
return { kind: 'unresolved', target: null };
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
function resolvePyModule(fromFile, dotted, files) {
|
|
2523
|
+
let dir = node_path__WEBPACK_IMPORTED_MODULE_0__.posix.dirname(fromFile);
|
|
2524
|
+
let mod = dotted;
|
|
2525
|
+
const lead = /^\.+/.exec(mod);
|
|
2526
|
+
const candidatesBase = [];
|
|
2527
|
+
if (lead) {
|
|
2528
|
+
for (let i = 1; i < lead[0].length; i++) dir = node_path__WEBPACK_IMPORTED_MODULE_0__.posix.dirname(dir);
|
|
2529
|
+
mod = mod.slice(lead[0].length);
|
|
2530
|
+
candidatesBase.push(dir === '.' ? '' : dir);
|
|
2531
|
+
} else {
|
|
2532
|
+
candidatesBase.push('', node_path__WEBPACK_IMPORTED_MODULE_0__.posix.dirname(fromFile) === '.' ? '' : node_path__WEBPACK_IMPORTED_MODULE_0__.posix.dirname(fromFile));
|
|
2533
|
+
}
|
|
2534
|
+
for (const b of candidatesBase) {
|
|
2535
|
+
const rel = mod.replace(/\./g, '/');
|
|
2536
|
+
for (const cand of [rel ? `${rel}.py` : '__init__.py', rel ? `${rel}/__init__.py` : '__init__.py']) {
|
|
2537
|
+
const full = b ? node_path__WEBPACK_IMPORTED_MODULE_0__.posix.join(b, cand) : cand;
|
|
2538
|
+
if (files.has(full)) return { kind: 'repo', target: full };
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
return { kind: lead ? 'unresolved' : 'third-party', target: null };
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
function stripComments(text) {
|
|
2545
|
+
return text.replace(/\/\*[\s\S]*?\*\//g, m => m.replace(/[^\n]/g, ' ')).replace(/(^|[^:'"`\\])\/\/[^\n]*/g, (m, p1) => p1 + ' '.repeat(m.length - p1.length));
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
function names(list) {
|
|
2549
|
+
return list.split(',').map(s => s.trim()).filter(Boolean).map(s => {
|
|
2550
|
+
const m = /^(?:\w+\s+as\s+)?([\w$]+)$/.exec(s.replace(/^type\s+/, '').replace(/^([\w$]+)\s*:\s*([\w$]+)$/, '$1 as $2'));
|
|
2551
|
+
const parts = s.replace(/^type\s+/, '').split(/\s+as\s+|\s*:\s*/).map(x => x.trim()).filter(Boolean);
|
|
2552
|
+
void m;
|
|
2553
|
+
if (parts.length === 2) return { imported: parts[0], local: parts[1] };
|
|
2554
|
+
if (parts.length === 1) return { imported: parts[0], local: parts[0] };
|
|
2555
|
+
return null;
|
|
2556
|
+
}).filter(Boolean).filter(n => /^[\w$]+$/.test(n.imported) && /^[\w$]+$/.test(n.local));
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
/** Import entries for one JS/TS file. */
|
|
2560
|
+
function jsImports(file, text, files) {
|
|
2561
|
+
const t = stripComments(text);
|
|
2562
|
+
const out = [];
|
|
2563
|
+
const add = (local, source, member) => out.push({ file, local, source, member, ...resolveJsModule(file, source, files) });
|
|
2564
|
+
for (const m of t.matchAll(/\b(?:const|let|var)\s*\{([^}]*)\}\s*=\s*(?:await\s+)?require\(\s*['"]([^'"]+)['"]\s*\)/g)) for (const n of names(m[1])) add(n.local, m[2], n.imported);
|
|
2565
|
+
for (const m of t.matchAll(/\b(?:const|let|var)\s+([\w$]+)\s*=\s*(?:await\s+)?require\(\s*['"]([^'"]+)['"]\s*\)/g)) add(m[1], m[2], '*');
|
|
2566
|
+
for (const m of t.matchAll(/\bimport\s+(?:type\s+)?([\w$]+)?\s*,?\s*(?:\{([^}]*)\}|\*\s+as\s+([\w$]+))?\s*from\s*['"]([^'"]+)['"]/g)) {
|
|
2567
|
+
if (m[1]) add(m[1], m[4], 'default');
|
|
2568
|
+
if (m[2]) for (const n of names(m[2])) add(n.local, m[4], n.imported);
|
|
2569
|
+
if (m[3]) add(m[3], m[4], '*');
|
|
2570
|
+
}
|
|
2571
|
+
return out;
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
function pyImports(file, text, files) {
|
|
2575
|
+
const out = [];
|
|
2576
|
+
const add = (local, source, member) => out.push({ file, local, source, member, ...resolvePyModule(file, source, files) });
|
|
2577
|
+
// the unparenthesised form ends at the line end; only the parenthesised form may span lines
|
|
2578
|
+
for (const m of text.matchAll(/^[ \t]*from[ \t]+([.\w]+)[ \t]+import[ \t]+(?:\(([\w\s,]+)\)|([\w \t,]+))/gm)) {
|
|
2579
|
+
for (const n of (m[2] ?? m[3]).split(',').map(s => s.trim()).filter(Boolean)) {
|
|
2580
|
+
const p = n.split(/\s+as\s+/);
|
|
2581
|
+
add(p[1] || p[0], m[1], p[0]);
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
for (const m of text.matchAll(/^[ \t]*import\s+([.\w]+)(?:\s+as\s+(\w+))?\s*$/gm)) add(m[2] || m[1].split('.')[0], m[1], '*');
|
|
2585
|
+
return out;
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
/** All import entries, deterministically ordered. */
|
|
2589
|
+
function buildImportMap(fileContents) {
|
|
2590
|
+
const files = new Set(Object.keys(fileContents));
|
|
2591
|
+
const out = [];
|
|
2592
|
+
for (const file of [...files].sort()) {
|
|
2593
|
+
if (/\.(?:js|jsx|mjs|cjs|ts|tsx)$/i.test(file)) out.push(...jsImports(file, fileContents[file], files));
|
|
2594
|
+
else if (/\.py$/i.test(file)) out.push(...pyImports(file, fileContents[file], files));
|
|
2595
|
+
}
|
|
2596
|
+
return out.sort((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : 0) || (a.local < b.local ? -1 : a.local > b.local ? 1 : 0) || (a.source < b.source ? -1 : 1));
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
/**
|
|
2600
|
+
* Resolve a callee name used in `file` against the import map.
|
|
2601
|
+
* Returns { kind:'exact-import', targetFile, member } | { kind:'third-party' } | { kind:'unresolved-import' } | null.
|
|
2602
|
+
*/
|
|
2603
|
+
function resolveViaImports(imports, file, calleeName) {
|
|
2604
|
+
const parts = String(calleeName || '').split('.');
|
|
2605
|
+
const head = parts[0];
|
|
2606
|
+
const imp = imports.filter(i => i.file === file && i.local === head);
|
|
2607
|
+
if (!imp.length) return null;
|
|
2608
|
+
const i = imp[0];
|
|
2609
|
+
if (i.kind === 'third-party') return { kind: 'third-party' };
|
|
2610
|
+
if (i.kind === 'unresolved') return { kind: 'unresolved-import' };
|
|
2611
|
+
if (parts.length === 1) {
|
|
2612
|
+
const member = i.member === '*' || i.member === 'default' ? head : i.member;
|
|
2613
|
+
return { kind: 'exact-import', targetFile: i.target, member, namespaceCall: i.member === '*' || i.member === 'default' };
|
|
2614
|
+
}
|
|
2615
|
+
if (i.member === '*' || i.member === 'default') return { kind: 'exact-import', targetFile: i.target, member: parts[parts.length - 1], namespaceCall: false };
|
|
2616
|
+
// `db.query()` where `db` is a NAMED import: a method of an imported object, not a function of that module
|
|
2617
|
+
return { kind: 'imported-object-method' };
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
|
|
2485
2621
|
/***/ }),
|
|
2486
2622
|
|
|
2487
2623
|
/***/ 1325:
|
|
@@ -3320,7 +3456,7 @@ function importBatch(root, batchId, resultsText, ctx) {
|
|
|
3320
3456
|
/* unused harmony exports DEFAULT_LIMITS, PROTECTION_DIMENSIONS */
|
|
3321
3457
|
/* harmony import */ var _controls_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(17202);
|
|
3322
3458
|
/* harmony import */ var _canonical_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(44652);
|
|
3323
|
-
/* harmony import */ var _graph_schema_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(
|
|
3459
|
+
/* harmony import */ var _graph_schema_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(50152);
|
|
3324
3460
|
//
|
|
3325
3461
|
// Deterministic, bounded reachability from an entry point's handler.
|
|
3326
3462
|
//
|
|
@@ -3791,8 +3927,13 @@ function resolveCitation(c, segments, fileContents) {
|
|
|
3791
3927
|
if (typeof text !== 'string') return 'file-not-in-snapshot';
|
|
3792
3928
|
const fileLines = text.split('\n');
|
|
3793
3929
|
if (c.endLine > fileLines.length) return 'beyond-end-of-file';
|
|
3794
|
-
const inside = segments.
|
|
3795
|
-
if (!inside) return 'outside-provided-context';
|
|
3930
|
+
const inside = segments.filter(s => s.file === c.file && c.startLine >= s.startLine && c.endLine <= s.endLine);
|
|
3931
|
+
if (!inside.length) return 'outside-provided-context';
|
|
3932
|
+
// GA-03.2 (0.195.26): the citation is bound to the exact lines the bundle was built from. A segment carries the
|
|
3933
|
+
// hash of its span at build time; if the snapshot's lines no longer hash to it, the model reviewed text that is
|
|
3934
|
+
// not what is on disk now, and the citation is stale rather than evidence. A segment with no recorded hash
|
|
3935
|
+
// (a hand-built one) cannot be judged and is not refused for that reason.
|
|
3936
|
+
if (inside.every(s => typeof s.spanHash === 'string' && (0,_canonical_js__WEBPACK_IMPORTED_MODULE_5__/* .sha256 */ .sc)((0,_canonical_js__WEBPACK_IMPORTED_MODULE_5__/* .sliceLines */ .iq)(text, s.startLine, s.endLine)) !== s.spanHash)) return 'stale-context';
|
|
3796
3937
|
return claimGrounded(c.claim, fileLines.slice(c.startLine - 1, c.endLine).join('\n')) ? null : 'claim-not-grounded-in-cited-lines';
|
|
3797
3938
|
}
|
|
3798
3939
|
|