{"version":3,"file":"ownership.cjs","names":[],"sources":["../../../src/services/scaffold/ownership.ts"],"sourcesContent":["/**\n * The authoritative inventory of framework-owned files — the single home for\n * the sync-ownership concept (ADR 0032). `bos upgrade` reconciles exactly the\n * files listed here from the parent template; app-owned files are never\n * considered. Consumers: sync (destToSource), prune (protection), and the\n * structure guardrail test.\n */\n\nexport const FRAMEWORK_OWNED_SYNC_FILES = new Set([\n  \".env.example\",\n  \".gitignore\",\n  \"biome.json\",\n  \"package.json\",\n  \".changeset/config.json\",\n  \".changeset/README.md\",\n  \".github/workflows/ci.yml\",\n  \".github/workflows/deploy.yml\",\n  \".github/workflows/staging.yml\",\n  \".github/workflows/release.yml\",\n  \"ui/package.json\",\n  \"ui/postcss.config.mjs\",\n  \"ui/tsconfig.json\",\n  \"api/package.json\",\n  \"api/plugin.dev.ts\",\n  \"api/rspack.config.js\",\n  \"api/tsconfig.json\",\n  \"api/drizzle.config.ts\",\n  \"api/src/global.d.ts\",\n  \"api/tests/types.d.ts\",\n]);\n\nexport const AGENT_SYNC_GLOBS = [\".agents/skills/**\", \"docs/agents/**\", \"skills-lock.json\"];\n\nexport function isFrameworkOwnedSyncFile(filePath: string): boolean {\n  // Scaffold-template copies (.github/templates/** → their destination path at\n  // init) are framework-owned exactly when their destination is.\n  if (filePath.startsWith(\".github/templates/\")) {\n    return isFrameworkOwnedSyncFile(filePath.slice(\".github/templates/\".length));\n  }\n  if (FRAMEWORK_OWNED_SYNC_FILES.has(filePath)) return true;\n  if (\n    filePath.startsWith(\".agents/skills/\") ||\n    filePath.startsWith(\"docs/agents/\") ||\n    filePath === \"skills-lock.json\"\n  )\n    return true;\n  // lib/auth.ts is NOT sync-owned since #207: the middleware factory lives in\n  // everything-dev/api (framework home); projects import it, they don't own a copy.\n  // db/* and lib/context.ts left ownership in ADR 0033 — the db seam (layer.ts,\n  // schema.ts, migrations/) and the request-context schema are app surface,\n  // scaffolded once by `bos init` and free to swap.\n  if (/^plugins\\/[^/]+\\/rspack\\.config\\.js$/.test(filePath)) return true;\n  if (/^plugins\\/[^/]+\\/drizzle\\.config\\.ts$/.test(filePath)) return true;\n  if (/^plugins\\/[^/]+\\/src\\/global\\.d\\.ts$/.test(filePath)) return true;\n  if (/^plugins\\/[^/]+\\/tests\\/types\\.d\\.ts$/.test(filePath)) return true;\n  if (/^plugins\\/[^/]+\\/tsconfig\\.json$/.test(filePath)) return true;\n  return false;\n}\n\n/**\n * The canonical framework-owned marker (ADR 0032) — the text every commentable\n * sync-owned file carries. Says the three things agents need: what owns the\n * file, that local edits are allowed (with the backup semantics), and the\n * upstream-issue protocol that makes a change permanent.\n *\n * Commentable means parsed leniently: ts/tsx/mjs/js, YAML, env/gitignore\n * hashes, markdown. Strict-JSON files (tsconfigs are JSON.parsed by\n * personalizeConfig; package.json; bos.config.json) stay unmarked — this\n * inventory is their source of truth.\n */\nexport const FRAMEWORK_OWNED_HEADER = [\n  \"FRAMEWORK-OWNED — reconciled from the parent template by `bos upgrade`.\",\n  \"Local edits are allowed: on the next upgrade the template version wins and your\",\n  \"copy is backed up under `.bos/sync-backup/`. To make a change permanent, file an\",\n  \"issue: https://github.com/NEARBuilders/everything-dev/issues\",\n].join(\"\\n\");\n\nexport function frameworkOwnedHeaderComment(syntax: \"block\" | \"hash\"): string {\n  if (syntax === \"hash\") {\n    return `${FRAMEWORK_OWNED_HEADER.split(\"\\n\")\n      .map((line) => `# ${line}`)\n      .join(\"\\n\")}\\n`;\n  }\n  return `/**\\n${FRAMEWORK_OWNED_HEADER.split(\"\\n\")\n    .map((line) => ` * ${line}`)\n    .join(\"\\n\")}\\n */\\n`;\n}\n\n/**\n * The intra-file child-owned block (#538): a framework-owned file may embed a\n * marked span that sync never rewrites — the content between the two marker\n * comments is spliced back out of the child's local copy and re-inserted into\n * the freshly-built template content before any hash is computed, so the\n * markers live in the template (the single source of truth for what sync\n * preserves) while the span's content is child-owned forever. Files: the\n * whole-file marker covers most cases (ADR 0032); this is the two spots where\n * the template used to overwrite child content inside files it owns.\n */\nexport const CHILD_OWNED_BLOCK_START = \"// child-owned-start\";\nexport const CHILD_OWNED_BLOCK_END = \"// child-owned-end\";\n\n/**\n * Extracts the child-authored span from a synced file's local copy: the text\n * between the two marker comments, dedented by the span's common leading\n * indent (splice re-indents with the template's marker indentation, so\n * extract→splice→extract is byte-stable). Files synced before #538 carry no\n * markers; for the api/plugin.dev.ts shape a legacy fallback lifts the entire\n * `variables: { … },` literal so children's existing dev variables (citynode's\n * domain/gatewayDomains) migrate onto the marker protocol on the next sync.\n */\nexport function extractChildOwnedBlock(localContent: string): string | null {\n  const start = localContent.indexOf(CHILD_OWNED_BLOCK_START);\n  const end = localContent.indexOf(CHILD_OWNED_BLOCK_END);\n  let lines: string[];\n  if (start !== -1 && end !== -1 && end > start) {\n    const afterStartMarker = start + CHILD_OWNED_BLOCK_START.length;\n    const afterMarkerNewline = localContent.indexOf(\"\\n\", afterStartMarker) + 1;\n    const endLineStart = localContent.lastIndexOf(\"\\n\", end) + 1;\n    if (endLineStart < afterMarkerNewline) return null;\n    lines = localContent.slice(afterMarkerNewline, endLineStart).split(\"\\n\");\n  } else {\n    const match = /(?:^|\\n)(\\s*)variables: \\{/.exec(localContent);\n    if (!match) return null;\n    const openStart = match.index + match[0].length - \"variables: {\".length;\n    let depth = 0;\n    let i = openStart;\n    for (; i < localContent.length; i++) {\n      if (localContent[i] === \"{\") depth++;\n      else if (localContent[i] === \"}\") {\n        depth--;\n        if (depth === 0) break;\n      }\n    }\n    if (depth !== 0) return null;\n    let cut = localContent.slice(openStart, i + 1);\n    if (localContent[i + 1] === \",\") cut += \",\";\n    // The anchor line starts at its authored indent while inner lines sit one\n    // step deeper — strip the anchor indent so the span joins the marker\n    // protocol at the templated depth (splice re-indents).\n    const anchorIndent = match[1]?.length ?? 0;\n    lines = cut\n      .split(\"\\n\")\n      .map((line, index) =>\n        index === 0\n          ? line\n          : line.startsWith(\" \".repeat(anchorIndent))\n            ? line.slice(anchorIndent)\n            : line.replace(/^ +/, \"\"),\n      );\n  }\n\n  while (lines.length > 0 && lines[lines.length - 1]?.trim() === \"\") lines.pop();\n  if (lines.length === 0) return null;\n  const indents = lines\n    .filter((line) => line.trim() !== \"\")\n    .map((line) => /^ */.exec(line)?.[0].length ?? 0);\n  const common = indents.length > 0 ? Math.min(...indents) : 0;\n  return lines.map((line) => line.slice(Math.min(common, line.length) || 0)).join(\"\\n\");\n}\n\n/**\n * Re-inserts a child-owned span into a template copy between the marker\n * comments, indenting every span line with the template markers' indentation\n * (the mirror of extractChildOwnedBlock's dedent). Returns the template\n * verbatim when it carries no markers — non-marked files keep their\n * whole-file ownership semantics.\n */\nexport function spliceChildOwnedBlock(templateContent: string, childBlock: string): string {\n  const start = templateContent.indexOf(CHILD_OWNED_BLOCK_START);\n  const end = templateContent.indexOf(CHILD_OWNED_BLOCK_END);\n  if (start === -1 || end === -1 || end < start) return templateContent;\n  const markerLineStart = templateContent.lastIndexOf(\"\\n\", start) + 1;\n  const indent = /^ */.exec(templateContent.slice(markerLineStart))?.[0] ?? \"\";\n  const endLineStart = templateContent.lastIndexOf(\"\\n\", end) + 1;\n  const indented = childBlock\n    .split(\"\\n\")\n    .map((line) => (line === \"\" ? line : indent + line))\n    .join(\"\\n\");\n  return `${templateContent.slice(0, start + CHILD_OWNED_BLOCK_START.length)}\\n${indented}\\n${templateContent.slice(endLineStart)}`;\n}\n"],"mappings":"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"}