abhigyanpatwari/GitNexus · warning
[node] package : candidate source entries ( ) — none…
Error message
[node] package ${name}: ${discovered.ambiguous.length} candidate source entries (${discovered.ambiguous.join(', ')}) — none adopted; declare `source` or a single lib entry What it means
This is a logged warning (not a thrown error) emitted while reading a workspace package's manifest. GitNexus tries to infer a package's source entry point for import resolution; when the package has multiple ambiguous candidate entries (e.g. both `src/index` and `lib/index` exist, or several discovery sources produced candidates) it refuses to guess, adopts none, and warns. Imports into that package may then resolve less precisely until the package declares an explicit entry.
Solutions
- Add an explicit `source` field to the package's package.json pointing at the canonical entry (e.g. "source": "./src/index.ts").
- Declare `exports` in package.json so the resolver has an unambiguous entry map instead of falling back to conventional discovery.
- Remove or rename the extra candidate entry (e.g. delete a stale duplicate `index.ts` at the package root or an obsolete `lib/index`) so only one candidate remains.
- If the ambiguity is intentional (multi-entry package), accept the warning; resolution will fall back to other heuristics for that package.
Example fix
// before: package.json with ambiguous entries on disk (src/index.ts AND lib/index.js)
{ "name": "my-pkg", "main": "./lib/index.js" }
// after: declare an explicit source entry
{ "name": "my-pkg", "main": "./lib/index.js", "source": "./src/index.ts" } Defensive patterns
Strategy: validation
Validate before calling
// pre-flight check per workspace package
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
const ambiguous = ['src/index.ts','index.ts','lib/index.js'].filter(f => fs.existsSync(f));
if (!pkg.exports && !pkg.source && ambiguous.length > 1) {
console.warn(`declare \`source\` or exports; ambiguous entries: ${ambiguous.join(', ')}`);
} Prevention
- Always declare `exports` or `source` in workspace package manifests
- Avoid shipping both src/index and lib/index in the same package
- Add a lint check that each publishable package has exactly one entry convention
When it happens
Trigger: Analyzing a workspace package whose package.json declares neither `exports` nor an unambiguous `source`/single-lib entry, while more than one conventional candidate source file exists on disk (discovered.ambiguous non-empty). Raised from readManifest during `gitnexus analyze` of a Node workspace.
Common situations: Packages with both a compiled `lib/index.js` and a `src/index.ts`; packages publishing multiple top-level bundles; legacy packages migrated from a build layout but never declaring `exports` or `source` in package.json.
Related errors
- [node] package.json scan of
- [node-workspace-extractor] duplicate package name
- Multiple registered repos match
- Multiple registered repos match
- Multiple repositories indexed. Specify which one with the…
AI-assisted analysis of abhigyanpatwari/GitNexus@ac9a4e9abd (2026-09-15).
Data as JSON: /api/errors/fe7782936447135a.
Report an issue: GitHub.
Appendix: source
Thrown at gitnexus/src/core/ingestion/import-resolvers/node-workspace-packages.ts:649
// resolved calls against ~845 raw. Tried in a fixed order, and ONLY appended
// (declared entries keep precedence): `source` / `publishConfig.source`, the
// vite `lib.entry`, then `src/main` / `src/index`. More than one candidate
// that exists on disk is recorded as ambiguous rather than picked — a wrong
// entry binds every import of the package to the wrong file.
// A package whose `exports` map has no `"."` (only subpaths) refuses the bare
// specifier outright; discovery must not manufacture a `src/index` root for it.
const rootlessExports = declaresExports && rootExports.length === 0;
const discovered = rootlessExports
? { entries: [], ambiguous: [], allowConventional: false }
: await discoverSourceEntries(parsed, dir, repoRoot);
for (const entry of discovered.entries) push(entries, entry);
if (!declaresExports && discovered.allowConventional) {
for (const conventional of ['src/index', 'index', 'lib/index']) {
push(entries, joinRepoPath(packageDir, conventional));
}
}
if (discovered.ambiguous.length > 0) {
logger.warn(
`[node] package ${name}: ${discovered.ambiguous.length} candidate source entries (${discovered.ambiguous.join(', ')}) — none adopted; declare \`source\` or a single lib entry`,
);
}
const subpathImports = new Map<string, readonly string[]>();
collectImports(parsed.imports, subpathImports, rebase);
return { name, package: { dir: packageDir, entries, subpathExports, subpathImports } };
}
/**
* Walk an `exports` value into the root-entry list and the subpath map.
*
* `exports` nests three ways at once — a bare string, a subpath map, and
* condition maps (`import` / `require` / `types` / `default`) at any depth — so
* this collects string leaves per subpath rather than assuming a shape.
*/
function collectExports(View on GitHub (pinned to ac9a4e9abd)