{"record":{"id":"98343324e3d70b9e","repo":"pbakaus/impeccable","slug":"surface-brief-requires-a-concrete-project-relative","errorCode":null,"errorMessage":"surface brief requires a concrete project-relative primary target or URL","messagePattern":"surface brief requires a concrete project-relative primary target or URL","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/skills/impeccable/scripts/lib/surface-briefs.mjs","lineNumber":134,"sourceCode":"  const exactPath = surfaceBriefPathForTarget(normalized, { projectRoot });\n  const exact = briefs.find((brief) => brief.path === exactPath && (!brief.targets.length || brief.targets.includes(normalized)));\n  if (exact) return { brief: exact, candidates: briefs, reason: 'slug' };\n  const mapped = briefs.filter((brief) => brief.targets.includes(normalized));\n  return {\n    brief: mapped.length === 1 ? mapped[0] : null,\n    candidates: mapped.length > 1 ? mapped : briefs,\n    reason: mapped.length === 1 ? 'mapping' : mapped.length > 1 ? 'ambiguous-target' : 'not-found',\n  };\n}\n\nexport function writeSurfaceBrief({\n  projectRoot = process.cwd(),\n  primaryTarget,\n  relatedTargets = [],\n  body,\n}) {\n  const normalizedPrimary = normalizeSurfaceTarget(primaryTarget, { projectRoot });\n  if (!normalizedPrimary) throw new Error('surface brief requires a concrete project-relative primary target or URL');\n  const normalizedRelated = [...new Set(relatedTargets\n    .map((target) => normalizeSurfaceTarget(target, { projectRoot }))\n    .filter((target) => target && target !== normalizedPrimary))];\n  const slug = slugFromTarget(normalizedPrimary, { cwd: projectRoot });\n  const filePath = surfaceBriefPathForTarget(normalizedPrimary, { projectRoot });\n  fs.mkdirSync(path.dirname(filePath), { recursive: true });\n  const frontmatter = [\n    '---',\n    `version: ${SURFACE_BRIEF_VERSION}`,\n    `slug: ${JSON.stringify(slug)}`,\n    `primary_target: ${JSON.stringify(normalizedPrimary)}`,\n    `related_targets: ${JSON.stringify(normalizedRelated)}`,\n    '---',\n  ].join('\\n');\n  fs.writeFileSync(filePath, `${frontmatter}\\n\\n${String(body || '').trim()}\\n`, 'utf-8');\n  return filePath;\n}\n","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/pbakaus/impeccable/blob/d14711ae3d1a1dd62dee61a358d27f107c51ccd0/plugin/skills/impeccable/scripts/lib/surface-briefs.mjs#L116-L152","documentation":"Thrown by writeSurfaceBrief() when normalizeSurfaceTarget(primaryTarget) returns null. normalizeSurfaceTarget rejects empty/non-string values, malformed URLs (catch falls to null), route: strings that don't start with '/' or contain '..', and filesystem paths whose path.relative() to projectRoot escapes the project (starts with '..'). The brief writer needs one concrete, resolvable primary target so it can slug the file and emit frontmatter.","triggerScenarios":"Calling writeSurfaceBrief({ primaryTarget: '' }), with undefined, with 'route:../x', with 'https:// bad url' (URL constructor throws -> null), or with an absolute path like '/etc/passwd' that resolves outside projectRoot. Also when a relative path equals '.' or resolves to the projectRoot itself (rel === '.' -> null).","commonSituations":"Programmatically building a brief from user/agent input without pre-validating; passing a DOM href that is a bare fragment ('#foo') or protocol-relative ('//host'); wrong cwd/projectRoot so a legitimately in-project file computes as outside; passing an empty string after trimming. URL inputs with stray whitespace or invalid characters hit the URL constructor catch.","solutions":["Pass a non-empty string that is either an http(s) URL, a route: string starting with '/', an absolute path inside the project, or a relative path that stays within projectRoot.","Pre-validate with the exported normalizeSurfaceTarget() before calling writeSurfaceBrief; if it returns null, surface a user-facing error instead of letting the throw propagate.","For URL inputs, new URL() it yourself first to catch malformed URLs early; trim whitespace and drop fragments/search before passing.","Confirm projectRoot matches the directory the target path is relative to (the default is process.cwd())."],"exampleFix":"// before\nwriteSurfaceBrief({ primaryTarget: userHref, body });\n\n// after\nimport { normalizeSurfaceTarget, writeSurfaceBrief } from './lib/surface-briefs.mjs';\nconst normalized = normalizeSurfaceTarget(userHref, { projectRoot });\nif (!normalized) throw new Error(`Invalid primary target: ${JSON.stringify(userHref)}`);\nwriteSurfaceBrief({ projectRoot, primaryTarget: normalized, body });","handlingStrategy":"validation","validationCode":"import { normalizeSurfaceTarget } from './lib/surface-briefs.mjs';\nfunction isValidSurfaceTarget(target, projectRoot) {\n  return typeof target === 'string'\n    && target.trim().length > 0\n    && normalizeSurfaceTarget(target, { projectRoot }) !== null;\n}\n// call before writeSurfaceBrief\nif (!isValidSurfaceTarget(primaryTarget, projectRoot)) {\n  throw new Error(`Refusing to write brief: invalid primary target ${JSON.stringify(primaryTarget)}`);\n}","typeGuard":"/** Narrowing guard for surface brief targets. */\nfunction isSurfaceTarget(value, projectRoot) {\n  if (typeof value !== 'string' || !value.trim()) return false;\n  // Reuse the library's own normalizer as the source of truth.\n  try { return normalizeSurfaceTarget(value, { projectRoot }) !== null; }\n  catch { return false; }\n}","tryCatchPattern":"try {\n  writeSurfaceBrief({ projectRoot, primaryTarget, relatedTargets, body });\n} catch (err) {\n  if (/concrete project-relative primary target/.test(err.message)) {\n    // prompt the user/agent for a valid target instead of crashing\n    return { ok: false, error: 'invalid_primary_target', hint: 'Pass a project-relative path, route:/x, or http(s) URL.' };\n  }\n  throw err;\n}","preventionTips":["Always normalise through the exported normalizeSurfaceTarget() — it encodes every rejection rule (URL parse, route '..', path escape).","When forwarding user/agent hrefs, trim and new URL() them first so malformed URLs surface as your own error.","Keep projectRoot explicit rather than relying on process.cwd() so path-relative checks match your intent."],"tags":["validation","surface-briefs","input-validation"],"backgroundTag":null,"analyzedSha":"d14711ae3d1a1dd62dee61a358d27f107c51ccd0","analyzedAt":"2026-08-13T00:52:25.771Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}