{"record":{"id":"d74bc9eb130067d7","repo":"JuliusBrussee/caveman","slug":"invalid-scoped-package","errorCode":null,"errorMessage":"invalid scoped package","messagePattern":"invalid scoped package","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/source-graph.ts","lineNumber":211,"sourceCode":"async function collectPackageFiles(directory: string, files: Set<string>): Promise<void> {\n  const entries = await opendir(directory);\n  for await (const entry of entries) {\n    if (entry.name === \"node_modules\" || entry.name === \".git\") continue;\n    const path = resolve(directory, entry.name);\n    if (entry.isDirectory()) {\n      await collectPackageFiles(path, files);\n    } else if (entry.isFile()) {\n      files.add(path);\n    } else if (entry.isSymbolicLink()) {\n      throw new Error(`caveman build: package artifact symlink is not lockable: ${JSON.stringify(path)}`);\n    }\n  }\n}\n\nfunction barePackageName(specifier: string): string {\n  const parts = specifier.split(\"/\");\n  if (specifier.startsWith(\"@\")) {\n    if (parts.length < 2 || parts[1] === \"\") throw new Error(\"invalid scoped package\");\n    return `${parts[0]}/${parts[1]}`;\n  }\n  if (parts[0] === \"\") throw new Error(\"invalid package\");\n  return parts[0]!;\n}\n\nfunction resolvePackageExport(exportsValue: unknown, subpath: string): string | undefined {\n  if (typeof exportsValue === \"string\" || Array.isArray(exportsValue)) {\n    return subpath === \".\" ? resolveConditionalExport(exportsValue) : undefined;\n  }\n  if (!isRecord(exportsValue)) return undefined;\n  const keys = Object.keys(exportsValue);\n  if (!keys.some((key) => key.startsWith(\".\"))) {\n    return subpath === \".\" ? resolveConditionalExport(exportsValue) : undefined;\n  }\n  if (Object.hasOwn(exportsValue, subpath)) {\n    return resolveConditionalExport(exportsValue[subpath]);\n  }","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/source-graph.ts#L193-L229","documentation":"barePackageName() parses a bare import specifier and throws \"invalid scoped package\" when a specifier starting with \"@\" does not have a scope and name — i.e. \"@\" alone, \"@scope\", or \"@scope/\". The parser needs a well-formed @scope/name prefix to find the package's package.json; a malformed scoped specifier cannot be resolved.","triggerScenarios":"A source file contains an import like import \"@scope/\" or a string built from a template that collapses to just \"@scope\" or \"@\".","commonSituations":"Template-built import strings with an empty submodule segment; typos like \"@scope//sub\"; refactors that split a specifier incorrectly.","solutions":["Fix the specifier to a complete form: \"@scope/name\" or \"@scope/name/subpath\".","If the specifier is computed at build time, validate it before emitting the import.","Search the codebase for the malformed literal from the error message."],"exampleFix":"// before\nimport x from \"@myorg/\";\n\n// after\nimport x from \"@myorg/pkg\";","handlingStrategy":"type-guard","validationCode":"const SCOPED = /^@[^/\\s]+\\/.+$/;\nfunction isWellFormedBareSpecifier(spec: string): boolean {\n  if (spec.startsWith(\"@\")) return SCOPED.test(spec) && !spec.endsWith(\"/\");\n  return /^[^/]/.test(spec);\n}","typeGuard":"function isScopedPackageName(spec: string): boolean {\n  const parts = spec.split(\"/\");\n  return spec.startsWith(\"@\") && parts.length >= 2 && parts[1] !== \"\";\n}","tryCatchPattern":null,"preventionTips":["Validate template-built import strings before emitting them.","Search for malformed scoped literals like \"@scope/\" after refactors.","Write specifiers as full literals (\"@scope/pkg/sub\") whenever possible."],"tags":["build","specifier","scoped-package","validation"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}