@contentrain/mcp 3.6.1 → 3.6.3
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/README.md +3 -1
- package/dist/{apply-manager-eZ6zA99Y.mjs → apply-manager-llmSyK5K.mjs} +7 -7
- package/dist/{apply-manager-eZ6zA99Y.mjs.map → apply-manager-llmSyK5K.mjs.map} +1 -1
- package/dist/branch-lifecycle-DGlgjnC7.d.mts.map +1 -1
- package/dist/{branch-lifecycle-DUOoyNWn.mjs → branch-lifecycle-f4gI_eGo.mjs} +71 -5
- package/dist/branch-lifecycle-f4gI_eGo.mjs.map +1 -0
- package/dist/{config-B8a4JBT1.mjs → config-BLwZtwlI.mjs} +2 -2
- package/dist/{config-B8a4JBT1.mjs.map → config-BLwZtwlI.mjs.map} +1 -1
- package/dist/{context-BHfO4WBv.mjs → context-fJCKH_qv.mjs} +6 -6
- package/dist/{context-BHfO4WBv.mjs.map → context-fJCKH_qv.mjs.map} +1 -1
- package/dist/core/apply-manager.mjs +8 -8
- package/dist/core/config.mjs +2 -2
- package/dist/core/content-manager.mjs +3 -3
- package/dist/core/context.mjs +5 -5
- package/dist/core/doctor.mjs +7 -7
- package/dist/core/graph-builder.mjs +3 -3
- package/dist/core/meta-manager.mjs +2 -2
- package/dist/core/model-manager.mjs +3 -3
- package/dist/core/ops/index.mjs +8 -8
- package/dist/core/scan-config.mjs +2 -2
- package/dist/core/scanner.mjs +3 -3
- package/dist/core/validator/index.mjs +5 -5
- package/dist/{detect-CI32ynpq.mjs → detect-DUYFv3-O.mjs} +2 -2
- package/dist/{detect-CI32ynpq.mjs.map → detect-DUYFv3-O.mjs.map} +1 -1
- package/dist/{doctor-VnOzDTbk.mjs → doctor-fIG4TtsO.mjs} +6 -6
- package/dist/{doctor-VnOzDTbk.mjs.map → doctor-fIG4TtsO.mjs.map} +1 -1
- package/dist/{fs-DJv5G8uL.mjs → fs-BFTuXbbw.mjs} +2 -2
- package/dist/{fs-DJv5G8uL.mjs.map → fs-BFTuXbbw.mjs.map} +1 -1
- package/dist/git/branch-lifecycle.mjs +3 -3
- package/dist/git/reconcile.d.mts.map +1 -1
- package/dist/git/reconcile.mjs +10 -10
- package/dist/git/transaction.d.mts +4 -0
- package/dist/git/transaction.d.mts.map +1 -1
- package/dist/git/transaction.mjs +7 -7
- package/dist/{graph-builder-C9NS7y-a.mjs → graph-builder-CNswWNtb.mjs} +3 -3
- package/dist/{graph-builder-C9NS7y-a.mjs.map → graph-builder-CNswWNtb.mjs.map} +1 -1
- package/dist/index.mjs +19 -19
- package/dist/{local-DgjDad1x.mjs → local-DkDTrGt_.mjs} +140 -13
- package/dist/local-DkDTrGt_.mjs.map +1 -0
- package/dist/{meta-manager-Cd6AfuGS.mjs → meta-manager-CSpKtjnP.mjs} +2 -2
- package/dist/{meta-manager-Cd6AfuGS.mjs.map → meta-manager-CSpKtjnP.mjs.map} +1 -1
- package/dist/{model-manager-CfwJWvz7.mjs → model-manager-DPXulg7z.mjs} +4 -4
- package/dist/{model-manager-CfwJWvz7.mjs.map → model-manager-DPXulg7z.mjs.map} +1 -1
- package/dist/{ops-PfMNR0nx.mjs → ops-DxhCCs_Z.mjs} +4 -4
- package/dist/{ops-PfMNR0nx.mjs.map → ops-DxhCCs_Z.mjs.map} +1 -1
- package/dist/providers/local/index.d.mts.map +1 -1
- package/dist/providers/local/index.mjs +11 -11
- package/dist/{reconcile-B55dZqhg.mjs → reconcile-CPDGFQ8H.mjs} +5 -5
- package/dist/{reconcile-B55dZqhg.mjs.map → reconcile-CPDGFQ8H.mjs.map} +1 -1
- package/dist/{reconcile-CwS4LUbo.mjs → reconcile-Cq60cldB.mjs} +11 -7
- package/dist/reconcile-Cq60cldB.mjs.map +1 -0
- package/dist/{scan-config-C5zzygeH.mjs → scan-config-DkRKu_ON.mjs} +2 -2
- package/dist/{scan-config-C5zzygeH.mjs.map → scan-config-DkRKu_ON.mjs.map} +1 -1
- package/dist/{scanner-CEiT8Tgx.mjs → scanner-BLo3ssiF.mjs} +3 -3
- package/dist/{scanner-CEiT8Tgx.mjs.map → scanner-BLo3ssiF.mjs.map} +1 -1
- package/dist/server/http/index.mjs +19 -19
- package/dist/{server-BRTztDnc.mjs → server-Ioqkq_v-.mjs} +21 -21
- package/dist/server-Ioqkq_v-.mjs.map +1 -0
- package/dist/server.mjs +19 -19
- package/dist/{transaction-BR2jg4uj.mjs → transaction-BWJt1KQb.mjs} +77 -39
- package/dist/transaction-BWJt1KQb.mjs.map +1 -0
- package/dist/util/detect.mjs +2 -2
- package/dist/util/fs.mjs +1 -1
- package/dist/{validator-BD0Z0O0_.mjs → validator-C08p1OpM.mjs} +6 -6
- package/dist/{validator-BD0Z0O0_.mjs.map → validator-C08p1OpM.mjs.map} +1 -1
- package/package.json +2 -2
- package/dist/branch-lifecycle-DUOoyNWn.mjs.map +0 -1
- package/dist/local-DgjDad1x.mjs.map +0 -1
- package/dist/reconcile-CwS4LUbo.mjs.map +0 -1
- package/dist/server-BRTztDnc.mjs.map +0 -1
- package/dist/transaction-BR2jg4uj.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile-Cq60cldB.mjs","names":[],"sources":["../src/git/ref-reader.ts","../src/git/reconcile.ts"],"sourcesContent":["import { type SimpleGit } from 'simple-git'\nimport { createGit } from './identity.js'\nimport type { RepoReader } from '../core/contracts/index.js'\n\n/**\n * GitRefReader — `RepoReader` over an arbitrary local git ref, without a\n * checkout. `git show <ref>:<path>` reads blobs, `ls-tree` lists a tree\n * level, `cat-file -e` answers existence. This is what lets a reconcile\n * planner read three trees (merge-base, contentrain, base branch) side by\n * side while the developer's working tree stays untouched.\n *\n * The ref is fixed at construction; the per-call `ref?` parameter of the\n * `RepoReader` contract is ignored — a bound reader answering for a\n * different ref than it was built for would be a lie. Bind another instance\n * instead.\n */\nexport class GitRefReader implements RepoReader {\n private readonly git: SimpleGit\n\n constructor(\n public readonly projectRoot: string,\n public readonly ref: string,\n ) {\n this.git = createGit(projectRoot)\n }\n\n async readFile(path: string, _ref?: string): Promise<string> {\n return this.git.show([`${this.ref}:${normalize(path)}`])\n }\n\n async listDirectory(path: string, _ref?: string): Promise<string[]> {\n try {\n // Trailing slash = \"list the tree at this path\", bare names via\n // --name-only; ls-tree is non-recursive by default, matching the\n // contract's \"does not recurse\".\n const dir = normalize(path)\n const args = dir && dir !== '.'\n ? ['ls-tree', '--name-only', this.ref, `${dir}/`]\n : ['ls-tree', '--name-only', this.ref]\n const raw = await this.git.raw(args)\n return raw\n .split('\\n')\n .map(line => line.trim())\n .filter(Boolean)\n .map(entry => entry.slice(entry.lastIndexOf('/') + 1))\n } catch {\n return []\n }\n }\n\n async fileExists(path: string, _ref?: string): Promise<boolean> {\n try {\n await this.git.raw(['cat-file', '-e', `${this.ref}:${normalize(path)}`])\n return true\n } catch {\n return false\n }\n }\n}\n\n/** Strip a trailing slash — `<ref>:dir/` and `<ref>:dir` name different things to git. */\nfunction normalize(path: string): string {\n return path.endsWith('/') ? path.slice(0, -1) : path\n}\n","import { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { randomUUID } from 'node:crypto'\nimport { CONTENTRAIN_BRANCH } from '@contentrain/types'\nimport type { BaseAdvance, ConflictResolution, ContextSource, RemotePush, SyncResult } from '@contentrain/types'\nimport { createGit, authorConfig } from './identity.js'\nimport { networkGit } from './branch-lifecycle.js'\nimport { GitRefReader } from './ref-reader.js'\nimport { resolveBaseBranch } from './base-branch.js'\nimport {\n NETWORK_TIMEOUT_MS,\n ensureContentBranch,\n isAncestor,\n pushContentBranches,\n selectiveSync,\n} from './transaction.js'\nimport { readConfig } from '../core/config.js'\nimport { planReconcile } from '../core/ops/reconcile/index.js'\nimport type { ReconcilePlan } from '../core/ops/reconcile/index.js'\nimport { applyChangesToWorktree } from '../core/ops/apply-to-worktree.js'\n\nexport interface ReconcileBranchesOptions {\n /** Preview only — plan, never touch a ref. Default true. */\n dryRun?: boolean\n /** Decisions for previously reported conflicts, matched by id. */\n resolutions?: ConflictResolution[]\n /** Stamped into the regenerated context.json. */\n source?: ContextSource\n}\n\nexport interface ReconcileBranchesResult {\n /**\n * `in_sync` — nothing to reconcile (base already contained in contentrain\n * and no content differences; a base merely behind was fast-forwarded).\n * `preview` — dry-run: the plan says what WOULD happen.\n * `conflicts` — unresolved conflicts remain; nothing was written.\n * `reconciled` — the merge commit landed and the base branch advanced.\n */\n action: 'in_sync' | 'preview' | 'conflicts' | 'reconciled'\n plan: ReconcilePlan\n base: string\n merge_base: string | null\n commit?: string\n base_advance?: BaseAdvance\n sync?: SyncResult\n remote_push?: RemotePush\n}\n\n/**\n * Content-aware reconcile of a diverged `contentrain` ↔ base pair, local\n * executor. Plans with three `GitRefReader`s (no checkout, developer tree\n * untouched), then — when clean or fully resolved — performs a REAL\n * `git merge --no-commit` in a temp worktree and writes the ENTIRE plan\n * output over it before committing: for content-owned paths the planner is\n * authoritative, never git's textual auto-merge (its line-level result can\n * be non-canonical or wrong without ever surfacing as a conflict). Git\n * still resolves everything the planner does not own — source files, lock\n * files — exactly as a manual merge would. The commit joins both parents,\n * so afterwards the base branch is an ancestor again and the ordinary\n * fast-forward advance works.\n *\n * Reconcile never runs inside a write path: it is an explicit, separate\n * step (tool, CLI, or Studio background job).\n */\nexport async function reconcileBranches(\n projectRoot: string,\n options: ReconcileBranchesOptions = {},\n): Promise<ReconcileBranchesResult> {\n const dryRun = options.dryRun ?? true\n const git = createGit(projectRoot)\n const config = await readConfig(projectRoot)\n const remoteName = process.env['CONTENTRAIN_REMOTE'] ?? 'origin'\n\n const currentBranch = (await git.raw(['branch', '--show-current']).catch(() => '')).trim()\n const baseBranch = await resolveBaseBranch(git, config, { remoteName, currentBranch })\n if (currentBranch === CONTENTRAIN_BRANCH) {\n throw Object.assign(new Error(\n `The '${CONTENTRAIN_BRANCH}' branch is checked out in your working directory. `\n + `Switch to your working branch and retry.`,\n ), {\n code: 'CONTENT_BRANCH_CHECKED_OUT',\n agent_hint: 'Ask the developer to switch to their working branch, then retry.',\n developer_action: `git checkout ${baseBranch}`,\n })\n }\n\n await ensureContentBranch(projectRoot)\n\n let hasRemote = false\n try {\n const remotes = await git.getRemotes()\n hasRemote = remotes.some(r => r.name === remoteName)\n } catch {\n hasRemote = false\n }\n if (hasRemote) {\n const net = networkGit(projectRoot, NETWORK_TIMEOUT_MS)\n await Promise.all([\n net.fetch(remoteName, baseBranch).catch(() => {}),\n net.fetch(remoteName, CONTENTRAIN_BRANCH).catch(() => {}),\n ])\n }\n\n const [contentrainTip, baseTip] = await Promise.all([\n git.raw(['rev-parse', CONTENTRAIN_BRANCH]).then(s => s.trim()),\n git.raw(['rev-parse', baseBranch]).then(s => s.trim()),\n ])\n\n let mergeBase: string | null = null\n try {\n mergeBase = (await git.raw(['merge-base', CONTENTRAIN_BRANCH, baseBranch])).trim() || null\n } catch {\n mergeBase = null\n }\n if (!mergeBase) {\n throw Object.assign(new Error(\n `\"${CONTENTRAIN_BRANCH}\" and \"${baseBranch}\" share no history — reconcile needs a common ancestor.`,\n ), {\n code: 'RECONCILE_NO_MERGE_BASE',\n agent_hint: 'The branches are unrelated. This needs a human decision about which history is canonical.',\n })\n }\n\n const plan = await planReconcile({\n base: new GitRefReader(projectRoot, mergeBase),\n ours: new GitRefReader(projectRoot, CONTENTRAIN_BRANCH),\n theirs: new GitRefReader(projectRoot, baseBranch),\n resolutions: options.resolutions,\n source: options.source ?? 'mcp-local',\n })\n\n // Base already contained in contentrain: there is no divergence. A base\n // merely BEHIND is fast-forwarded on the spot (that is what the ordinary\n // write path would do next anyway).\n if (await isAncestor(git, baseTip, contentrainTip)) {\n if (!dryRun && baseTip !== contentrainTip) {\n await git.raw(['update-ref', `refs/heads/${baseBranch}`, contentrainTip])\n try {\n await git.raw(['read-tree', 'HEAD'])\n } catch {\n try { await git.raw(['reset', 'HEAD']) } catch { /* ignore */ }\n }\n return { action: 'reconciled', plan, base: baseBranch, merge_base: mergeBase, commit: contentrainTip, base_advance: 'advanced' }\n }\n return { action: 'in_sync', plan, base: baseBranch, merge_base: mergeBase }\n }\n\n if (dryRun) {\n return { action: 'preview', plan, base: baseBranch, merge_base: mergeBase }\n }\n if (plan.conflicts.length > 0) {\n return { action: 'conflicts', plan, base: baseBranch, merge_base: mergeBase }\n }\n\n // Execute: real merge in a temp worktree, planner output written on top.\n const worktreePath = join(tmpdir(), `cr-reconcile-${randomUUID()}`)\n await git.raw(['worktree', 'add', worktreePath, CONTENTRAIN_BRANCH])\n const wtGit = createGit(worktreePath, { config: authorConfig() })\n\n try {\n try {\n await wtGit.merge([baseBranch, '--no-commit', '--no-ff'])\n } catch {\n // Conflicts inside the worktree are expected — the planner's output\n // overwrites every content-owned path next. Anything left unmerged\n // after that is genuinely outside the planner's scope.\n }\n\n await applyChangesToWorktree(worktreePath, plan.changes)\n await wtGit.raw(['add', '-A'])\n\n const unmerged = (await wtGit.raw(['ls-files', '-u'])).trim()\n if (unmerged) {\n const paths = [...new Set(unmerged.split('\\n').map(l => l.split('\\t')[1]).filter(Boolean))]\n try { await wtGit.merge(['--abort']) } catch { /* not in merge state */ }\n throw Object.assign(new Error(\n `Reconcile stopped: ${paths.length} non-content file(s) conflict between \"${CONTENTRAIN_BRANCH}\" and \"${baseBranch}\": ${paths.join(', ')}. `\n + `These are outside the content planner's scope and need a manual merge.`,\n ), {\n code: 'RECONCILE_SOURCE_CONFLICT',\n agent_hint: 'Non-content files conflict. Ask the developer to merge the branches manually for those paths.',\n developer_action: `git checkout ${CONTENTRAIN_BRANCH} && git merge ${baseBranch}`,\n })\n }\n\n await wtGit.commit(\n `[contentrain] reconcile: merge ${baseBranch} into ${CONTENTRAIN_BRANCH}`,\n { '--no-verify': null },\n )\n\n const [newTip, previousBaseRef, statusBeforeUpdate] = await Promise.all([\n wtGit.raw(['rev-parse', 'HEAD']).then(s => s.trim()),\n git.raw(['rev-parse', baseBranch]).then(s => s.trim()),\n git.status(),\n ])\n const dirtyFilesBeforeUpdate = new Set(statusBeforeUpdate.files.map(f => f.path))\n\n // The merge commit has baseBranch as a parent, so this is a true\n // fast-forward again — the whole point of the exercise.\n await git.raw(['update-ref', `refs/heads/${baseBranch}`, newTip])\n try {\n await git.raw(['read-tree', 'HEAD'])\n } catch {\n try { await git.raw(['reset', 'HEAD']) } catch { /* ignore */ }\n }\n const sync = await selectiveSync(projectRoot, worktreePath, newTip, previousBaseRef, dirtyFilesBeforeUpdate)\n\n let remotePush: RemotePush = 'no-remote'\n if (hasRemote) {\n remotePush = await pushContentBranches(projectRoot, worktreePath, wtGit, remoteName, baseBranch, true)\n }\n\n return {\n action: 'reconciled',\n plan,\n base: baseBranch,\n merge_base: mergeBase,\n commit: newTip,\n base_advance: 'advanced',\n sync,\n remote_push: remotePush,\n }\n } finally {\n try {\n await git.raw(['worktree', 'remove', worktreePath, '--force'])\n } catch {\n // worktree may already be cleaned up\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as pathExists } from "./fs-
|
|
1
|
+
import { r as pathExists } from "./fs-BFTuXbbw.mjs";
|
|
2
2
|
import { basename, extname, join } from "node:path";
|
|
3
3
|
import { readdir } from "node:fs/promises";
|
|
4
4
|
//#region src/core/scan-config.ts
|
|
@@ -156,4 +156,4 @@ async function discoverFiles(projectRoot, options) {
|
|
|
156
156
|
//#endregion
|
|
157
157
|
export { classifyFile as a, autoDetectSourceDirs as i, SCAN_EXTENSIONS as n, discoverFiles as o, SCAN_IGNORE_DIRS as r, MAX_SCAN_FILES as t };
|
|
158
158
|
|
|
159
|
-
//# sourceMappingURL=scan-config-
|
|
159
|
+
//# sourceMappingURL=scan-config-DkRKu_ON.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan-config-
|
|
1
|
+
{"version":3,"file":"scan-config-DkRKu_ON.mjs","names":[],"sources":["../src/core/scan-config.ts"],"sourcesContent":["import { readdir } from 'node:fs/promises'\nimport { join, extname, basename } from 'node:path'\nimport { pathExists } from '../util/fs.js'\n\n// ─── Shared Scan Constants ───\n\n/** File extensions to scan across all JS/TS ecosystem projects */\nexport const SCAN_EXTENSIONS = new Set([\n '.tsx', '.jsx', '.vue', '.ts', '.js', '.mjs', '.astro', '.svelte',\n])\n\n/** Directory names to always exclude from scanning */\nexport const SCAN_IGNORE_DIRS = new Set([\n // Package managers / deps\n 'node_modules', '.pnpm',\n // Build outputs\n 'dist', 'build', 'out', '.output',\n // Framework caches\n '.nuxt', '.next', '.svelte-kit', '.expo', '.turbo', '.parcel-cache', '.vercel', '.netlify',\n // Test / coverage\n 'coverage', '__tests__', '__mocks__',\n // VCS / IDE\n '.git', '.vscode', '.idea',\n // Contentrain\n '.contentrain',\n])\n\n/** Max files per scan operation */\nexport const MAX_SCAN_FILES = 500\n\n/** File patterns to skip regardless of extension */\nconst SKIP_FILE_RE = /\\.(test|spec)\\.[^.]+$|\\.d\\.ts$|\\.min\\.[^.]+$/\n\n// ─── File Classification ───\n// Covers: React, Next.js, Nuxt, Vue, Astro, SvelteKit, Remix,\n// React Native/Expo, NestJS, Express, Fastify, Koa, Hapi\n\n/** Directories that represent entry points / pages / screens */\nconst PAGE_DIR_NAMES = new Set([\n // Frontend routing\n 'pages', 'routes', 'screens', 'views',\n // Backend entry points\n 'controllers', 'handlers', 'resolvers',\n])\n\n/** Directories that represent reusable components / modules */\nconst COMPONENT_DIR_NAMES = new Set([\n // UI components\n 'components', 'ui', 'widgets', 'elements',\n // Feature modules\n 'features', 'modules',\n // Backend services\n 'services', 'providers',\n // Shared / common\n 'shared', 'common',\n])\n\n/** Directories that represent layouts / templates */\nconst LAYOUT_DIR_NAMES = new Set([\n 'layouts', 'templates',\n])\n\n/** Next.js App Router special files that are page-like */\nconst NEXTJS_PAGE_FILES = new Set([\n 'page.tsx', 'page.jsx', 'page.ts', 'page.js',\n 'layout.tsx', 'layout.jsx', 'layout.ts', 'layout.js',\n 'error.tsx', 'error.jsx',\n 'loading.tsx', 'loading.jsx',\n 'not-found.tsx', 'not-found.jsx',\n])\n\n/** Classify a file into page/component/layout/other based on path heuristics */\nexport function classifyFile(relPath: string): 'page' | 'component' | 'layout' | 'other' {\n const parts = relPath.split('/')\n const fileName = parts[parts.length - 1] ?? ''\n\n // Next.js App Router: files in app/ with special names are pages\n if (parts.includes('app') && NEXTJS_PAGE_FILES.has(fileName)) {\n return fileName.startsWith('layout') ? 'layout' : 'page'\n }\n\n // Check directory names in path (check layout first — more specific)\n for (const part of parts) {\n const lower = part.toLowerCase()\n if (LAYOUT_DIR_NAMES.has(lower)) return 'layout'\n if (PAGE_DIR_NAMES.has(lower)) return 'page'\n if (COMPONENT_DIR_NAMES.has(lower)) return 'component'\n }\n\n return 'other'\n}\n\n// ─── Source Directory Detection ───\n\n/** Common source directories across all JS/TS project types */\nconst AUTO_DETECT_DIRS = [\n // Standard source\n 'src', 'app', 'lib',\n // Frontend specific\n 'pages', 'components', 'layouts', 'views',\n // Mobile\n 'screens',\n // Backend\n 'modules', 'routes', 'controllers', 'services',\n // Shared\n 'features', 'shared', 'common',\n // Hooks / composables\n 'hooks', 'composables',\n // Stores\n 'stores',\n]\n\n/** Auto-detect which source directories exist in the project */\nexport async function autoDetectSourceDirs(projectRoot: string): Promise<string[]> {\n const found: string[] = []\n for (const dir of AUTO_DETECT_DIRS) {\n if (await pathExists(join(projectRoot, dir))) {\n found.push(dir)\n }\n }\n return found.length > 0 ? found : ['.']\n}\n\n// ─── File Discovery ───\n\nexport interface DiscoverFilesOptions {\n paths?: string[]\n include?: string[]\n exclude?: string[]\n}\n\n/**\n * Discover source files matching scan criteria.\n * Returns relative paths (relative to projectRoot).\n */\nexport async function discoverFiles(\n projectRoot: string,\n options?: DiscoverFilesOptions,\n): Promise<string[]> {\n const extensions = options?.include\n ? new Set(options.include.map(e => e.startsWith('.') ? e : `.${e}`))\n : SCAN_EXTENSIONS\n const extraExcludes = new Set(options?.exclude ?? [])\n const scanDirs = options?.paths ?? await autoDetectSourceDirs(projectRoot)\n\n const files: string[] = []\n\n for (const dir of scanDirs) {\n const absDir = join(projectRoot, dir)\n if (!(await pathExists(absDir))) continue\n\n let entries: string[]\n try {\n entries = await readdir(absDir, { recursive: true }) as unknown as string[]\n } catch {\n continue\n }\n\n for (const entry of entries) {\n if (files.length >= MAX_SCAN_FILES) break\n\n const fileName = basename(entry)\n\n // Check if any path segment is excluded\n const pathSegments = entry.split('/')\n if (pathSegments.some(seg => SCAN_IGNORE_DIRS.has(seg) || extraExcludes.has(seg))) continue\n\n // Check extension\n if (!extensions.has(extname(fileName))) continue\n\n // Skip test/spec/declaration/minified files\n if (SKIP_FILE_RE.test(fileName)) continue\n\n files.push(join(dir, entry))\n }\n\n if (files.length >= MAX_SCAN_FILES) break\n }\n\n return files.toSorted((a, b) => a.localeCompare(b)).slice(0, MAX_SCAN_FILES)\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { o as readText } from "./fs-
|
|
2
|
-
import { i as autoDetectSourceDirs, o as discoverFiles } from "./scan-config-
|
|
1
|
+
import { o as readText } from "./fs-BFTuXbbw.mjs";
|
|
2
|
+
import { i as autoDetectSourceDirs, o as discoverFiles } from "./scan-config-DkRKu_ON.mjs";
|
|
3
3
|
import { t as parseTsx } from "./tsx-parser-go5V9zoc.mjs";
|
|
4
4
|
import { extname, join } from "node:path";
|
|
5
5
|
//#region src/core/ast-scanner/pre-filter.ts
|
|
@@ -601,4 +601,4 @@ async function scanSummary(projectRoot, options) {
|
|
|
601
601
|
//#endregion
|
|
602
602
|
export { scanSummary as n, scanCandidates as t };
|
|
603
603
|
|
|
604
|
-
//# sourceMappingURL=scanner-
|
|
604
|
+
//# sourceMappingURL=scanner-BLo3ssiF.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scanner-CEiT8Tgx.mjs","names":[],"sources":["../src/core/ast-scanner/pre-filter.ts","../src/core/ast-scanner/index.ts","../src/core/scanner.ts"],"sourcesContent":["import type { ExtractedString } from './types.js'\n\n// ─── Pre-filter Result ───\n\nexport interface PreFilterResult {\n /** Strings that passed the pre-filter (candidates for agent) */\n candidates: ExtractedString[]\n /** Total number of strings removed by shouldSkip */\n skipped: number\n /** Total number of strings removed by low content score */\n lowConfidence: number\n /** Breakdown: skip reason → count */\n skipReasons: Record<string, number>\n}\n\n// ─── Value-based regexes ───\n\nconst PURE_NUMBER_RE = /^-?\\d+(\\.\\d+)?$/\nconst HEX_COLOR_RE = /^#[0-9a-f]{3,8}$/i\nconst FILE_EXT_RE = /\\.(png|jpg|jpeg|gif|svg|webp|ico|css|scss|less|js|ts|tsx|jsx|json|md|html|xml|yaml|yml|woff|woff2|ttf|eot|mp4|webm|mp3|wav|pdf)$/i\nconst SVG_PATH_DATA_RE = /^[Mm][\\d\\s.,LHVCSQTAZlhvcsqtazmMzZ-]+$/\nconst SVG_VIEWBOX_RE = /^\\d+(\\.\\d+)?\\s+\\d+(\\.\\d+)?\\s+\\d+(\\.\\d+)?\\s+\\d+(\\.\\d+)?$/\nconst I18N_KEY_RE = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$/\nconst TECHNICAL_IDENTIFIER_RE = /^[_a-z][a-z0-9_-]*$/\nconst ERROR_CODE_RE = /^[A-Z][A-Z0-9_]+$/\nconst PLACEHOLDER_RE = /^\\{\\d+\\}$|^\\.{2,}$/\nconst CAMEL_CASE_RE = /^[a-z]+[A-Z]/\nconst LOCALE_CODE_RE = /^[a-z]{2}[-_][A-Z]{2}$/\nconst DIMENSION_RE = /^\\d+[x×]\\d+$/\nconst REPEAT_CHAR_RE = /^(.)\\1{3,}$/\nconst MIME_TYPE_RE = /^(application|text|image|audio|video|multipart|font)\\/[\\w.+-]+$/\nconst PASCAL_CASE_RE = /^[A-Z][a-z]+[A-Z]/\n\nconst HTML_TARGETS = new Set(['_blank', '_self', '_parent', '_top'])\n\n// ─── URL / path detection (consolidated from legacy isNonContent) ───\n\nfunction isURLLike(str: string): boolean {\n if (/^(https?|ftp|file|mailto|data):/.test(str)) return true\n if (/^(\\.\\.?\\/|\\/|[A-Za-z]:\\\\)/.test(str)) return true\n if (/^['\"]?[@a-z][\\w-]*/.test(str.toLowerCase()) && !str.includes(' ') && (str.includes('/') || str.includes('.'))) {\n return true\n }\n return false\n}\n\n// ─── CSS / Tailwind detection ───\n\nconst TAILWIND_SEGMENT_RE = /^(?:bg-|text-|border-|flex|grid|p-|px-|py-|pt-|pb-|pl-|pr-|m-|mx-|my-|mt-|mb-|ml-|mr-|rounded|shadow|w-|h-|min-|max-|gap-|space-|items-|justify-|self-|overflow-|z-|opacity-|transition|duration-|ease-|animate-|font-|leading-|tracking-|decoration-|underline|line-through|uppercase|lowercase|capitalize|truncate|whitespace-|break-|sr-only|not-sr-only|hover:|focus:|active:|disabled:|dark:|sm:|md:|lg:|xl:|2xl:|group-|peer-|ring-|outline-|divide-|table-|col-|row-|aspect-|object-|inset-|top-|right-|bottom-|left-|translate-|rotate-|scale-|skew-|origin-|cursor-|select-|resize-|fill-|stroke-|block|inline|absolute|relative|fixed|sticky|static|float-|clear-|isolate|visible|invisible|grow|shrink|basis-|order-|place-)/\n\nfunction isCssClassList(value: string): boolean {\n const segments = value.trim().split(/\\s+/)\n if (segments.length < 2) return false\n let matched = 0\n for (const seg of segments) {\n if (TAILWIND_SEGMENT_RE.test(seg)) matched++\n }\n return matched / segments.length >= 0.5\n}\n\nfunction isSingleCssUtility(value: string): boolean {\n const trimmed = value.trim()\n if (trimmed.includes(' ')) return false\n return TAILWIND_SEGMENT_RE.test(trimmed)\n}\n\n// ─── SVG technical attributes ───\n\nconst SVG_TECHNICAL_ATTRIBUTES = new Set([\n 'd', 'viewBox', 'points', 'transform', 'pathLength',\n 'xmlns', 'preserveAspectRatio',\n 'stroke-linecap', 'stroke-linejoin', 'stroke-width',\n 'stroke-dasharray', 'stroke-dashoffset', 'stroke-miterlimit',\n 'fill-rule', 'clip-rule',\n])\n\nconst SVG_GRAPHIC_ELEMENTS = new Set([\n 'svg', 'path', 'circle', 'rect', 'line', 'polyline', 'polygon',\n 'ellipse', 'g', 'defs', 'use', 'symbol', 'clipPath', 'mask',\n 'pattern', 'linearGradient', 'radialGradient', 'stop',\n 'marker', 'animate', 'animateTransform', 'image',\n])\n\n// ─── Known function names ───\n\nconst I18N_FUNCTIONS = new Set([\n 't', '$t', 'i18n', 'translate', 'formatMessage', 'msg',\n])\n\nconst EMIT_FUNCTIONS = new Set([\n 'emit', '$emit',\n])\n\n// ─── Translatable attribute whitelist (i18next-cli compatible + extended) ───\n\nconst TRANSLATABLE_ATTRIBUTES = new Set([\n // Standard HTML content attributes\n 'title', 'alt', 'placeholder', 'label', 'summary', 'caption',\n 'abbr', 'accesskey', 'content', 'description',\n // ARIA content\n 'aria-label', 'aria-description', 'aria-placeholder',\n 'aria-roledescription', 'aria-valuetext',\n // React Native accessibility (equivalent to aria-label)\n 'accessibilityLabel', 'accessibilityHint', 'accessibilityValue',\n // Common component content props\n 'heading', 'subheading', 'message', 'hint', 'tooltip',\n 'helper-text', 'error-message', 'success-message',\n 'confirm-text', 'cancel-text', 'empty-text', 'loading-text',\n 'no-data-text', 'no-results-text',\n])\n\n// ─── Translatable object property whitelist (i18next-cli compatible) ───\n\nconst TRANSLATABLE_PROPERTIES = new Set([\n 'label', 'title', 'description', 'text', 'message', 'placeholder',\n 'caption', 'summary', 'heading', 'subheading', 'subtitle', 'tooltip',\n 'hint', 'helpText', 'errorMessage', 'successMessage', 'name',\n])\n\n// ─── shouldSkip: Binary non-content detection ───\n\n/**\n * Determines if a string is definitely NOT user-visible content.\n * Returns skip reason if it should be filtered, null if it should proceed to scoring.\n *\n * Conservative for template_text/jsx_text (tag-between text is almost always content).\n * Aggressive for everything else (technical tokens, config values, framework artifacts).\n */\nexport function shouldSkip(str: ExtractedString): string | null {\n // ── Context-based rules (AST-determined, 100% accurate) ──\n\n if (str.context === 'import_path') return 'import_path'\n if (str.context === 'type_annotation') return 'type_annotation'\n if (str.context === 'css_class') return 'css_class'\n if (str.context === 'css_utility_call') return 'css_utility_call'\n if (str.context === 'console_call') return 'console_call'\n if (str.context === 'test_assertion') return 'test_assertion'\n if (str.context === 'switch_case') return 'switch_case'\n\n const v = str.value\n\n // ── Value-based rules (structural patterns) ──\n\n if (v.length <= 1) return 'single_char'\n if (/^\\s+$/.test(v)) return 'whitespace'\n if (PURE_NUMBER_RE.test(v)) return 'pure_number'\n if (HEX_COLOR_RE.test(v)) return 'hex_color'\n if (FILE_EXT_RE.test(v)) return 'file_extension'\n if (v.startsWith('--')) return 'cli_flag'\n\n // ── i18n key paths (checked before URL — both contain dots, but i18n keys are more specific) ──\n\n if (I18N_KEY_RE.test(v)) return 'i18n_key'\n\n // ── MIME types (checked before URL — both contain slash, MIME is more specific) ──\n\n if (MIME_TYPE_RE.test(v)) return 'mime_type'\n\n // ── URL/path patterns ──\n\n if (isURLLike(v)) return 'url_path'\n\n // ── CSS patterns ──\n\n if (isCssClassList(v)) return 'css_class_list'\n if (isSingleCssUtility(v)) return 'css_utility_token'\n\n // ── SVG patterns ──\n\n if (v.length > 3 && SVG_PATH_DATA_RE.test(v)) return 'svg_path_data'\n if (SVG_VIEWBOX_RE.test(v)) return 'svg_viewbox'\n if (str.parentProperty !== undefined && SVG_TECHNICAL_ATTRIBUTES.has(str.parentProperty)) return 'svg_technical_attr'\n if (str.context === 'template_attribute' && SVG_GRAPHIC_ELEMENTS.has(str.parent)) return 'svg_element_attr'\n\n // ── Framework event patterns ──\n\n if (v.startsWith('update:')) return 'vue_emit_event'\n\n // ── Placeholder / interpolation ──\n\n if (PLACEHOLDER_RE.test(v)) return 'placeholder'\n\n // ── Structural value patterns (100% non-content) ──\n\n if (LOCALE_CODE_RE.test(v)) return 'locale_code'\n if (DIMENSION_RE.test(v)) return 'dimension'\n if (REPEAT_CHAR_RE.test(v)) return 'repeat_chars'\n if (HTML_TARGETS.has(v)) return 'html_target'\n\n // ── Known function argument detection ──\n\n if (str.context === 'function_argument' && I18N_FUNCTIONS.has(str.parent)) {\n // i18n function args: filter lowercase identifiers (namespace/key), keep sentences\n if (/^[a-z][a-z0-9_.-]*$/.test(v)) return 'i18n_function_arg'\n }\n\n if (str.context === 'function_argument' && EMIT_FUNCTIONS.has(str.parent)) {\n return 'emit_event_arg'\n }\n\n // ── CRITICAL: Technical identifier detection (i18next-cli proven pattern) ──\n // Single lowercase ASCII word/kebab-case/snake_case < 30 chars → technical token\n // EXEMPT: template_text and jsx_text (tag-between text IS content, even lowercase)\n\n if (str.context !== 'template_text' && str.context !== 'jsx_text') {\n if (TECHNICAL_IDENTIFIER_RE.test(v) && v.length < 30) {\n return 'technical_identifier'\n }\n }\n\n // ── Error codes (SCREAMING_SNAKE_CASE with underscores) ──\n\n if (ERROR_CODE_RE.test(v) && v.includes('_') && v.length > 3) {\n return 'error_code'\n }\n\n return null\n}\n\n// ─── calculateContentScore: 0-1 confidence scoring ───\n\n/**\n * Calculates a content confidence score (0-1) for a string that passed shouldSkip.\n * Uses AST context metadata (our advantage over offset-based tools) combined with\n * value-based signals proven by i18next-cli.\n *\n * Base score: 0.5. Boosted/penalized by context and value characteristics.\n */\nexport function calculateContentScore(str: ExtractedString): number {\n let score = 0.5\n\n // ── Context signals (AST metadata advantage) ──\n\n // Template/JSX text = almost certainly user-visible content\n if (str.context === 'template_text' || str.context === 'jsx_text') {\n score += 0.3\n }\n\n // Content-bearing attribute (title, alt, placeholder, aria-label, etc.)\n if (str.context === 'template_attribute' || str.context === 'jsx_attribute') {\n if (str.parentProperty && TRANSLATABLE_ATTRIBUTES.has(str.parentProperty)) {\n score += 0.2\n } else {\n score -= 0.2 // Unknown/technical attribute\n }\n }\n\n // Content-bearing object property (message, label, description, etc.)\n if (str.context === 'object_property') {\n if (str.parentProperty && TRANSLATABLE_PROPERTIES.has(str.parentProperty)) {\n score += 0.25\n }\n }\n\n // ── Value signals (i18next-cli proven heuristics) ──\n\n // Multi-word strings are more likely content\n const wordCount = str.value.split(/\\s+/).length\n if (wordCount >= 3) score += 0.2\n else if (wordCount === 2) score += 0.1\n\n // Terminal punctuation suggests a sentence\n if (/[.!?:;]$/.test(str.value)) score += 0.1\n\n // Non-ASCII characters (Turkish, Chinese, Arabic, etc.) → almost certainly content\n if (/[\\u0080-\\uFFFF]/.test(str.value)) score += 0.15\n\n // Capitalized first letter with lowercase body (Dashboard, Kaydet, Settings)\n if (/^[A-Z]/.test(str.value) && /[a-z]/.test(str.value)) score += 0.1\n\n // camelCase → probably a technical identifier\n if (CAMEL_CASE_RE.test(str.value)) score -= 0.3\n\n // PascalCase with internal uppercase (PhGameController, GameCard) → likely component/icon name\n // Does NOT match single-uppercase words (Dashboard, Karadeniz, Settings)\n if (PASCAL_CASE_RE.test(str.value) && !str.value.includes(' ')) score -= 0.25\n\n // Short ALL-CAPS (TRY, GET, USD) → likely code/abbreviation, not content\n // In template_text the +0.3 context boost keeps real labels like \"FAQ\" above threshold\n if (/^[A-Z]{2,5}$/.test(str.value)) score -= 0.15\n\n // Contains slash without spaces → path-like\n if (str.value.includes('/') && !str.value.includes(' ')) score -= 0.2\n\n return Math.max(0, Math.min(1, score))\n}\n\n// ─── Public API ───\n\n/**\n * Two-phase pre-filter:\n * 1. shouldSkip(): Binary removal of definite non-content\n * 2. calculateContentScore(): 0-1 confidence scoring for ambiguous strings\n *\n * Returns candidates that passed both phases, with content scores attached.\n */\nexport function applyPreFilter(\n strings: ExtractedString[],\n minScore: number = 0.4,\n): PreFilterResult {\n const candidates: ExtractedString[] = []\n const skipReasons: Record<string, number> = {}\n let skipped = 0\n let lowConfidence = 0\n\n for (const str of strings) {\n // Phase 1: Binary skip\n const skipReason = shouldSkip(str)\n if (skipReason) {\n skipped++\n skipReasons[skipReason] = (skipReasons[skipReason] ?? 0) + 1\n continue\n }\n\n // Phase 2: Content scoring\n const contentScore = calculateContentScore(str)\n if (contentScore < minScore) {\n lowConfidence++\n skipReasons['low_confidence'] = (skipReasons['low_confidence'] ?? 0) + 1\n continue\n }\n\n // Attach score to the extraction for downstream use\n ;(str as ExtractedString & { contentScore: number }).contentScore = contentScore\n candidates.push(str)\n }\n\n return { candidates, skipped, lowConfidence, skipReasons }\n}\n","import type { ExtractedString } from './types.js'\nimport { applyPreFilter } from './pre-filter.js'\nimport { parseTsx } from './tsx-parser.js'\n\n// ─── Extension sets ───\n\nconst TSX_EXTENSIONS = new Set(['.tsx', '.jsx', '.ts', '.js', '.mjs'])\nconst VUE_EXTENSIONS = new Set(['.vue'])\nconst SVELTE_EXTENSIONS = new Set(['.svelte'])\nconst ASTRO_EXTENSIONS = new Set(['.astro'])\n\n// ─── Lazy-loaded parsers ───\n\n/**\n * Lazily import vue-parser. Returns undefined if not available\n * (@vue/compiler-sfc is an optional dependency).\n */\nasync function loadVueParser(): Promise<((content: string, fileName: string) => Promise<ExtractedString[]> | ExtractedString[]) | undefined> {\n try {\n const mod = await import('./vue-parser.js')\n return mod.parseVue\n } catch {\n return undefined\n }\n}\n\n/**\n * Lazily import svelte-parser. Returns undefined if not available\n * (svelte is an optional dependency).\n */\nasync function loadSvelteParser(): Promise<((content: string, fileName: string) => Promise<ExtractedString[]>) | undefined> {\n try {\n const mod = await import('./svelte-parser.js')\n return mod.parseSvelte\n } catch {\n return undefined\n }\n}\n\n/**\n * Lazily import astro-parser. Returns undefined if not available\n * (@astrojs/compiler is an optional dependency).\n */\nasync function loadAstroParser(): Promise<((content: string, fileName: string) => Promise<ExtractedString[]>) | undefined> {\n try {\n const mod = await import('./astro-parser.js')\n return mod.parseAstro\n } catch {\n return undefined\n }\n}\n\n// ─── Regex fallback for unknown extensions ───\n\nconst SURROUNDING_MAX = 120\n\n/**\n * Minimal regex-based extractor for file types without AST parsers.\n * Extracts quoted strings and tag text — conservative, low accuracy.\n * Used as fallback when dedicated parsers are unavailable.\n */\nfunction extractWithRegex(content: string, _filePath: string): ExtractedString[] {\n const lines = content.split('\\n')\n const results: ExtractedString[] = []\n\n // Simple quoted string extraction\n const stringRe = /(['\"`])(?:(?!\\1|\\\\).|\\\\.)*?\\1/g\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!\n const trimmed = line.trim()\n\n // Skip comments, imports\n if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) continue\n if (/^\\s*(import|export)\\s/.test(line)) continue\n\n let match: RegExpExecArray | null\n stringRe.lastIndex = 0\n\n while ((match = stringRe.exec(line)) !== null) {\n // Remove surrounding quotes\n const raw = match[0]\n const value = raw.slice(1, -1)\n if (value.length === 0) continue\n\n const surrounding = buildSurrounding(lines, i)\n\n results.push({\n value,\n line: i + 1,\n column: match.index + 1,\n context: 'other',\n scope: 'script',\n parent: '',\n surrounding,\n })\n }\n\n // Tag text extraction: >text<\n const tagTextRe = />([^<>{]+)</g\n let tagMatch: RegExpExecArray | null\n tagTextRe.lastIndex = 0\n\n while ((tagMatch = tagTextRe.exec(line)) !== null) {\n const text = tagMatch[1]!.trim()\n if (text.length === 0) continue\n if (/^[\\s\\W]*$/.test(text) && !/[a-zA-Z]/.test(text)) continue\n\n results.push({\n value: text,\n line: i + 1,\n column: tagMatch.index + 1,\n context: 'template_text',\n scope: 'template',\n parent: '',\n surrounding: buildSurrounding(lines, i),\n })\n }\n }\n\n return results\n}\n\nfunction buildSurrounding(lines: string[], lineIdx: number): string {\n const start = Math.max(0, lineIdx - 1)\n const end = Math.min(lines.length - 1, lineIdx + 1)\n\n const parts: string[] = []\n for (let i = start; i <= end; i++) {\n const line = lines[i]\n if (line !== undefined) {\n parts.push(line)\n }\n }\n\n const joined = parts.join('\\n')\n if (joined.length > SURROUNDING_MAX) {\n return joined.slice(0, SURROUNDING_MAX)\n }\n return joined\n}\n\n// ─── Public API ───\n\n/**\n * Extract strings from a source file with structural context.\n *\n * Routes to the correct parser based on file extension:\n * - .tsx/.jsx/.ts/.js/.mjs -> tsx-parser (AST-based)\n * - .vue -> vue-parser (lazy-loaded, AST-based)\n * - .svelte -> svelte-parser (lazy-loaded, AST-based)\n * - .astro -> astro-parser (lazy-loaded, AST-based)\n * - unknown -> empty array\n *\n * Falls back to regex extraction when the dedicated parser's\n * optional dependency is not installed.\n *\n * Applies structural pre-filter to remove 100% non-content strings.\n * Returns only candidates that should be sent to the agent.\n */\nexport async function extractStrings(\n filePath: string,\n content: string,\n ext: string,\n): Promise<ExtractedString[]> {\n const normalizedExt = ext.startsWith('.') ? ext : `.${ext}`\n let rawStrings: ExtractedString[]\n\n if (TSX_EXTENSIONS.has(normalizedExt)) {\n rawStrings = parseTsx(content, filePath)\n } else if (VUE_EXTENSIONS.has(normalizedExt)) {\n const parseVue = await loadVueParser()\n if (parseVue) {\n rawStrings = await parseVue(content, filePath)\n } else {\n rawStrings = extractWithRegex(content, filePath)\n }\n } else if (SVELTE_EXTENSIONS.has(normalizedExt)) {\n const parseSvelte = await loadSvelteParser()\n if (parseSvelte) {\n try {\n rawStrings = await parseSvelte(content, filePath)\n } catch {\n // svelte/compiler not installed — fall back to regex\n rawStrings = extractWithRegex(content, filePath)\n }\n } else {\n rawStrings = extractWithRegex(content, filePath)\n }\n } else if (ASTRO_EXTENSIONS.has(normalizedExt)) {\n const parseAstro = await loadAstroParser()\n if (parseAstro) {\n try {\n rawStrings = await parseAstro(content, filePath)\n } catch {\n // @astrojs/compiler not installed — fall back to regex\n rawStrings = extractWithRegex(content, filePath)\n }\n } else {\n rawStrings = extractWithRegex(content, filePath)\n }\n } else {\n return []\n }\n\n // Apply structural pre-filter\n const { candidates } = applyPreFilter(rawStrings)\n return candidates\n}\n\n// Re-export types for convenience\nexport type { ExtractedString, StructuralContext, PreFilterRule } from './types.js'\nexport { applyPreFilter, shouldSkip, calculateContentScore } from './pre-filter.js'\nexport type { PreFilterResult } from './pre-filter.js'\n","import type {\n StringContext,\n ScanCandidate,\n DuplicateGroup,\n ScanCandidatesResult,\n ScanSummaryResult,\n} from '@contentrain/types'\nimport { join, extname } from 'node:path'\nimport { readText } from '../util/fs.js'\nimport {\n autoDetectSourceDirs,\n discoverFiles,\n} from './scan-config.js'\nimport { extractStrings } from './ast-scanner/index.js'\nimport { calculateContentScore } from './ast-scanner/pre-filter.js'\nimport type { StructuralContext } from './ast-scanner/types.js'\n\n// ─── Options ───\n\nexport interface ScanOptions {\n paths?: string[]\n include?: string[]\n exclude?: string[]\n limit?: number\n offset?: number\n min_length?: number\n max_length?: number\n /** Minimum content confidence score (0-1). Default: 0.4 */\n min_score?: number\n}\n\n// ─── Constants ───\n\nconst DEFAULT_LIMIT = 50\nconst DEFAULT_OFFSET = 0\nconst DEFAULT_MIN_LENGTH = 2\nconst DEFAULT_MAX_LENGTH = 500\nconst DEFAULT_MIN_SCORE = 0.4\nconst SUMMARY_SAMPLE_SIZE = 10\n\n// ─── Context mapping: StructuralContext → StringContext ───\n\nconst CONTEXT_MAP: Record<StructuralContext, StringContext> = {\n 'template_text': 'template_text',\n 'template_attribute': 'template_attribute',\n 'jsx_text': 'jsx_text',\n 'jsx_attribute': 'jsx_attribute',\n 'variable_assignment': 'variable_assignment',\n 'object_property': 'object_value',\n 'function_argument': 'function_argument',\n 'array_element': 'other',\n 'enum_value': 'other',\n 'template_literal': 'other',\n 'switch_case': 'other',\n 'other': 'other',\n // Pre-filtered contexts should not reach here, but map them just in case\n 'import_path': 'other',\n 'type_annotation': 'other',\n 'css_class': 'other',\n 'css_utility_call': 'other',\n 'console_call': 'other',\n 'test_assertion': 'other',\n}\n\n// ─── Main: scanCandidates ───\n\nexport async function scanCandidates(\n projectRoot: string,\n options?: ScanOptions,\n): Promise<ScanCandidatesResult> {\n const limit = options?.limit ?? DEFAULT_LIMIT\n const offset = options?.offset ?? DEFAULT_OFFSET\n const minLength = options?.min_length ?? DEFAULT_MIN_LENGTH\n const maxLength = options?.max_length ?? DEFAULT_MAX_LENGTH\n const minScore = options?.min_score ?? DEFAULT_MIN_SCORE\n\n const scanDirs = options?.paths ?? await autoDetectSourceDirs(projectRoot)\n const files = await discoverFiles(projectRoot, {\n paths: scanDirs,\n include: options?.include,\n exclude: options?.exclude,\n })\n\n // ─── Phase 1: Extract strings from all files (pre-filter applied inside extractStrings) ───\n\n const filePromises = files.map(async (relPath) => {\n const filePath = join(projectRoot, relPath)\n const content = await readText(filePath)\n if (!content) return { relPath, extractions: [] }\n\n const ext = extname(filePath)\n const extractions = await extractStrings(filePath, content, ext)\n return { relPath, extractions }\n })\n\n const fileResults = await Promise.all(filePromises)\n\n // ─── Phase 2: Score + length filter + deduplicate ───\n\n let rawStringsFound = 0\n let skippedCount = 0\n let lowConfidenceCount = 0\n const skipReasons: Record<string, number> = {}\n\n // Deduplication map: value → first candidate + all occurrences\n const uniqueMap = new Map<string, {\n candidate: ScanCandidate\n maxScore: number\n }>()\n\n // Also track duplicates for backward compatibility\n const dupeMap = new Map<string, Array<{ file: string; line: number }>>()\n\n for (const { relPath, extractions } of fileResults) {\n rawStringsFound += extractions.length\n\n for (const extraction of extractions) {\n // Length filter\n if (extraction.value.length < minLength || extraction.value.length > maxLength) {\n skippedCount++\n skipReasons['length_filter'] = (skipReasons['length_filter'] ?? 0) + 1\n continue\n }\n\n // shouldSkip was already applied inside extractStrings (via applyPreFilter).\n // But applyPreFilter uses default minScore. Here we apply the user-configured minScore\n // on the contentScore that was attached during pre-filtering.\n\n // Get the contentScore attached by applyPreFilter\n const contentScore: number = (extraction as unknown as { contentScore?: number }).contentScore ?? calculateContentScore(extraction)\n\n if (contentScore < minScore) {\n lowConfidenceCount++\n skipReasons['low_confidence'] = (skipReasons['low_confidence'] ?? 0) + 1\n continue\n }\n\n const mappedContext = CONTEXT_MAP[extraction.context]\n const loc = { file: relPath, line: extraction.line }\n\n // Track all occurrences for duplicates section\n if (!dupeMap.has(extraction.value)) {\n dupeMap.set(extraction.value, [])\n }\n dupeMap.get(extraction.value)!.push(loc)\n\n // Deduplication: keep first occurrence, accumulate locations\n if (!uniqueMap.has(extraction.value)) {\n uniqueMap.set(extraction.value, {\n candidate: {\n file: relPath,\n line: extraction.line,\n column: extraction.column,\n value: extraction.value,\n context: mappedContext,\n surrounding: extraction.surrounding,\n contentScore,\n occurrences: [loc],\n },\n maxScore: contentScore,\n })\n } else {\n const entry = uniqueMap.get(extraction.value)!\n entry.candidate.occurrences.push(loc)\n // Keep the highest score across occurrences\n if (contentScore > entry.maxScore) {\n entry.maxScore = contentScore\n entry.candidate.contentScore = contentScore\n entry.candidate.file = relPath\n entry.candidate.line = extraction.line\n entry.candidate.column = extraction.column\n entry.candidate.context = mappedContext\n entry.candidate.surrounding = extraction.surrounding\n }\n }\n }\n }\n\n // Build sorted unique candidates (highest score first)\n const allUniqueCandidates = [...uniqueMap.values()]\n .map(e => e.candidate)\n .toSorted((a, b) => b.contentScore - a.contentScore)\n\n // Build duplicate groups (only count >= 2), sorted by count descending\n const duplicates: DuplicateGroup[] = [...dupeMap.entries()]\n .filter(([, occurrences]) => occurrences.length >= 2)\n .map(([value, occurrences]) => ({ value, count: occurrences.length, occurrences }))\n .toSorted((a, b) => b.count - a.count)\n\n // Pagination on unique candidates\n const uniqueCount = allUniqueCandidates.length\n const paginated = allUniqueCandidates.slice(offset, offset + limit)\n const hasMore = uniqueCount > offset + limit\n\n return {\n candidates: paginated,\n duplicates,\n stats: {\n files_scanned: files.length,\n raw_strings_found: rawStringsFound,\n skipped: skippedCount,\n low_confidence: lowConfidenceCount,\n unique_candidates: uniqueCount,\n candidates_returned: paginated.length,\n has_more: hasMore,\n skip_reasons: skipReasons,\n },\n }\n}\n\n// ─── Main: scanSummary ───\n\nexport async function scanSummary(\n projectRoot: string,\n options?: ScanOptions,\n): Promise<ScanSummaryResult> {\n const minLength = options?.min_length ?? DEFAULT_MIN_LENGTH\n const maxLength = options?.max_length ?? DEFAULT_MAX_LENGTH\n const minScore = options?.min_score ?? DEFAULT_MIN_SCORE\n\n const scanDirs = options?.paths ?? await autoDetectSourceDirs(projectRoot)\n const files = await discoverFiles(projectRoot, {\n paths: scanDirs,\n include: options?.include,\n exclude: options?.exclude,\n })\n\n // Group files by directory\n const dirFiles = new Map<string, string[]>()\n const fileTypes: Record<string, number> = {}\n\n for (const relPath of files) {\n const parts = relPath.split('/')\n const dir = parts.slice(0, -1).join('/') || '.'\n const ext = extname(relPath)\n\n if (!dirFiles.has(dir)) dirFiles.set(dir, [])\n dirFiles.get(dir)!.push(relPath)\n\n fileTypes[ext] = (fileTypes[ext] ?? 0) + 1\n }\n\n // Sample files per directory and count candidates\n const byDirectory: Record<string, { files: number; candidates: number }> = {}\n const freqMap = new Map<string, number>()\n let totalCandidatesEstimate = 0\n\n for (const [dir, dirFileList] of dirFiles) {\n const totalInDir = dirFileList.length\n const sampleFiles = dirFileList.slice(0, SUMMARY_SAMPLE_SIZE)\n let sampleCandidates = 0\n\n const samplePromises = sampleFiles.map(async (relPath) => {\n const filePath = join(projectRoot, relPath)\n const content = await readText(filePath)\n if (!content) return []\n\n const ext = extname(filePath)\n return extractStrings(filePath, content, ext)\n })\n\n const sampleResults = await Promise.all(samplePromises)\n\n for (const extractions of sampleResults) {\n for (const extraction of extractions) {\n // Apply same filters as scanCandidates\n if (extraction.value.length < minLength || extraction.value.length > maxLength) continue\n\n const contentScore: number = (extraction as unknown as { contentScore?: number }).contentScore ?? calculateContentScore(extraction)\n if (contentScore < minScore) continue\n\n sampleCandidates++\n\n // Track frequencies for top_repeated\n const prev = freqMap.get(extraction.value) ?? 0\n freqMap.set(extraction.value, prev + 1)\n }\n }\n\n // Estimate for full directory\n const avgPerFile = sampleFiles.length > 0 ? sampleCandidates / sampleFiles.length : 0\n const estimatedCandidates = Math.round(avgPerFile * totalInDir)\n\n byDirectory[dir] = {\n files: totalInDir,\n candidates: estimatedCandidates,\n }\n\n totalCandidatesEstimate += estimatedCandidates\n }\n\n // Top repeated strings\n const topRepeated = [...freqMap.entries()]\n .filter(([, count]) => count >= 2)\n .toSorted((a, b) => b[1] - a[1])\n .slice(0, 20)\n .map(([value, count]) => ({ value, count }))\n\n return {\n total_files: files.length,\n total_candidates_estimate: totalCandidatesEstimate,\n by_directory: byDirectory,\n top_repeated: topRepeated,\n sampling_note: `Based on first ${SUMMARY_SAMPLE_SIZE} files per directory. Counts are from sampled subset, not project-wide.`,\n file_types: fileTypes,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scanner-BLo3ssiF.mjs","names":[],"sources":["../src/core/ast-scanner/pre-filter.ts","../src/core/ast-scanner/index.ts","../src/core/scanner.ts"],"sourcesContent":["import type { ExtractedString } from './types.js'\n\n// ─── Pre-filter Result ───\n\nexport interface PreFilterResult {\n /** Strings that passed the pre-filter (candidates for agent) */\n candidates: ExtractedString[]\n /** Total number of strings removed by shouldSkip */\n skipped: number\n /** Total number of strings removed by low content score */\n lowConfidence: number\n /** Breakdown: skip reason → count */\n skipReasons: Record<string, number>\n}\n\n// ─── Value-based regexes ───\n\nconst PURE_NUMBER_RE = /^-?\\d+(\\.\\d+)?$/\nconst HEX_COLOR_RE = /^#[0-9a-f]{3,8}$/i\nconst FILE_EXT_RE = /\\.(png|jpg|jpeg|gif|svg|webp|ico|css|scss|less|js|ts|tsx|jsx|json|md|html|xml|yaml|yml|woff|woff2|ttf|eot|mp4|webm|mp3|wav|pdf)$/i\nconst SVG_PATH_DATA_RE = /^[Mm][\\d\\s.,LHVCSQTAZlhvcsqtazmMzZ-]+$/\nconst SVG_VIEWBOX_RE = /^\\d+(\\.\\d+)?\\s+\\d+(\\.\\d+)?\\s+\\d+(\\.\\d+)?\\s+\\d+(\\.\\d+)?$/\nconst I18N_KEY_RE = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$/\nconst TECHNICAL_IDENTIFIER_RE = /^[_a-z][a-z0-9_-]*$/\nconst ERROR_CODE_RE = /^[A-Z][A-Z0-9_]+$/\nconst PLACEHOLDER_RE = /^\\{\\d+\\}$|^\\.{2,}$/\nconst CAMEL_CASE_RE = /^[a-z]+[A-Z]/\nconst LOCALE_CODE_RE = /^[a-z]{2}[-_][A-Z]{2}$/\nconst DIMENSION_RE = /^\\d+[x×]\\d+$/\nconst REPEAT_CHAR_RE = /^(.)\\1{3,}$/\nconst MIME_TYPE_RE = /^(application|text|image|audio|video|multipart|font)\\/[\\w.+-]+$/\nconst PASCAL_CASE_RE = /^[A-Z][a-z]+[A-Z]/\n\nconst HTML_TARGETS = new Set(['_blank', '_self', '_parent', '_top'])\n\n// ─── URL / path detection (consolidated from legacy isNonContent) ───\n\nfunction isURLLike(str: string): boolean {\n if (/^(https?|ftp|file|mailto|data):/.test(str)) return true\n if (/^(\\.\\.?\\/|\\/|[A-Za-z]:\\\\)/.test(str)) return true\n if (/^['\"]?[@a-z][\\w-]*/.test(str.toLowerCase()) && !str.includes(' ') && (str.includes('/') || str.includes('.'))) {\n return true\n }\n return false\n}\n\n// ─── CSS / Tailwind detection ───\n\nconst TAILWIND_SEGMENT_RE = /^(?:bg-|text-|border-|flex|grid|p-|px-|py-|pt-|pb-|pl-|pr-|m-|mx-|my-|mt-|mb-|ml-|mr-|rounded|shadow|w-|h-|min-|max-|gap-|space-|items-|justify-|self-|overflow-|z-|opacity-|transition|duration-|ease-|animate-|font-|leading-|tracking-|decoration-|underline|line-through|uppercase|lowercase|capitalize|truncate|whitespace-|break-|sr-only|not-sr-only|hover:|focus:|active:|disabled:|dark:|sm:|md:|lg:|xl:|2xl:|group-|peer-|ring-|outline-|divide-|table-|col-|row-|aspect-|object-|inset-|top-|right-|bottom-|left-|translate-|rotate-|scale-|skew-|origin-|cursor-|select-|resize-|fill-|stroke-|block|inline|absolute|relative|fixed|sticky|static|float-|clear-|isolate|visible|invisible|grow|shrink|basis-|order-|place-)/\n\nfunction isCssClassList(value: string): boolean {\n const segments = value.trim().split(/\\s+/)\n if (segments.length < 2) return false\n let matched = 0\n for (const seg of segments) {\n if (TAILWIND_SEGMENT_RE.test(seg)) matched++\n }\n return matched / segments.length >= 0.5\n}\n\nfunction isSingleCssUtility(value: string): boolean {\n const trimmed = value.trim()\n if (trimmed.includes(' ')) return false\n return TAILWIND_SEGMENT_RE.test(trimmed)\n}\n\n// ─── SVG technical attributes ───\n\nconst SVG_TECHNICAL_ATTRIBUTES = new Set([\n 'd', 'viewBox', 'points', 'transform', 'pathLength',\n 'xmlns', 'preserveAspectRatio',\n 'stroke-linecap', 'stroke-linejoin', 'stroke-width',\n 'stroke-dasharray', 'stroke-dashoffset', 'stroke-miterlimit',\n 'fill-rule', 'clip-rule',\n])\n\nconst SVG_GRAPHIC_ELEMENTS = new Set([\n 'svg', 'path', 'circle', 'rect', 'line', 'polyline', 'polygon',\n 'ellipse', 'g', 'defs', 'use', 'symbol', 'clipPath', 'mask',\n 'pattern', 'linearGradient', 'radialGradient', 'stop',\n 'marker', 'animate', 'animateTransform', 'image',\n])\n\n// ─── Known function names ───\n\nconst I18N_FUNCTIONS = new Set([\n 't', '$t', 'i18n', 'translate', 'formatMessage', 'msg',\n])\n\nconst EMIT_FUNCTIONS = new Set([\n 'emit', '$emit',\n])\n\n// ─── Translatable attribute whitelist (i18next-cli compatible + extended) ───\n\nconst TRANSLATABLE_ATTRIBUTES = new Set([\n // Standard HTML content attributes\n 'title', 'alt', 'placeholder', 'label', 'summary', 'caption',\n 'abbr', 'accesskey', 'content', 'description',\n // ARIA content\n 'aria-label', 'aria-description', 'aria-placeholder',\n 'aria-roledescription', 'aria-valuetext',\n // React Native accessibility (equivalent to aria-label)\n 'accessibilityLabel', 'accessibilityHint', 'accessibilityValue',\n // Common component content props\n 'heading', 'subheading', 'message', 'hint', 'tooltip',\n 'helper-text', 'error-message', 'success-message',\n 'confirm-text', 'cancel-text', 'empty-text', 'loading-text',\n 'no-data-text', 'no-results-text',\n])\n\n// ─── Translatable object property whitelist (i18next-cli compatible) ───\n\nconst TRANSLATABLE_PROPERTIES = new Set([\n 'label', 'title', 'description', 'text', 'message', 'placeholder',\n 'caption', 'summary', 'heading', 'subheading', 'subtitle', 'tooltip',\n 'hint', 'helpText', 'errorMessage', 'successMessage', 'name',\n])\n\n// ─── shouldSkip: Binary non-content detection ───\n\n/**\n * Determines if a string is definitely NOT user-visible content.\n * Returns skip reason if it should be filtered, null if it should proceed to scoring.\n *\n * Conservative for template_text/jsx_text (tag-between text is almost always content).\n * Aggressive for everything else (technical tokens, config values, framework artifacts).\n */\nexport function shouldSkip(str: ExtractedString): string | null {\n // ── Context-based rules (AST-determined, 100% accurate) ──\n\n if (str.context === 'import_path') return 'import_path'\n if (str.context === 'type_annotation') return 'type_annotation'\n if (str.context === 'css_class') return 'css_class'\n if (str.context === 'css_utility_call') return 'css_utility_call'\n if (str.context === 'console_call') return 'console_call'\n if (str.context === 'test_assertion') return 'test_assertion'\n if (str.context === 'switch_case') return 'switch_case'\n\n const v = str.value\n\n // ── Value-based rules (structural patterns) ──\n\n if (v.length <= 1) return 'single_char'\n if (/^\\s+$/.test(v)) return 'whitespace'\n if (PURE_NUMBER_RE.test(v)) return 'pure_number'\n if (HEX_COLOR_RE.test(v)) return 'hex_color'\n if (FILE_EXT_RE.test(v)) return 'file_extension'\n if (v.startsWith('--')) return 'cli_flag'\n\n // ── i18n key paths (checked before URL — both contain dots, but i18n keys are more specific) ──\n\n if (I18N_KEY_RE.test(v)) return 'i18n_key'\n\n // ── MIME types (checked before URL — both contain slash, MIME is more specific) ──\n\n if (MIME_TYPE_RE.test(v)) return 'mime_type'\n\n // ── URL/path patterns ──\n\n if (isURLLike(v)) return 'url_path'\n\n // ── CSS patterns ──\n\n if (isCssClassList(v)) return 'css_class_list'\n if (isSingleCssUtility(v)) return 'css_utility_token'\n\n // ── SVG patterns ──\n\n if (v.length > 3 && SVG_PATH_DATA_RE.test(v)) return 'svg_path_data'\n if (SVG_VIEWBOX_RE.test(v)) return 'svg_viewbox'\n if (str.parentProperty !== undefined && SVG_TECHNICAL_ATTRIBUTES.has(str.parentProperty)) return 'svg_technical_attr'\n if (str.context === 'template_attribute' && SVG_GRAPHIC_ELEMENTS.has(str.parent)) return 'svg_element_attr'\n\n // ── Framework event patterns ──\n\n if (v.startsWith('update:')) return 'vue_emit_event'\n\n // ── Placeholder / interpolation ──\n\n if (PLACEHOLDER_RE.test(v)) return 'placeholder'\n\n // ── Structural value patterns (100% non-content) ──\n\n if (LOCALE_CODE_RE.test(v)) return 'locale_code'\n if (DIMENSION_RE.test(v)) return 'dimension'\n if (REPEAT_CHAR_RE.test(v)) return 'repeat_chars'\n if (HTML_TARGETS.has(v)) return 'html_target'\n\n // ── Known function argument detection ──\n\n if (str.context === 'function_argument' && I18N_FUNCTIONS.has(str.parent)) {\n // i18n function args: filter lowercase identifiers (namespace/key), keep sentences\n if (/^[a-z][a-z0-9_.-]*$/.test(v)) return 'i18n_function_arg'\n }\n\n if (str.context === 'function_argument' && EMIT_FUNCTIONS.has(str.parent)) {\n return 'emit_event_arg'\n }\n\n // ── CRITICAL: Technical identifier detection (i18next-cli proven pattern) ──\n // Single lowercase ASCII word/kebab-case/snake_case < 30 chars → technical token\n // EXEMPT: template_text and jsx_text (tag-between text IS content, even lowercase)\n\n if (str.context !== 'template_text' && str.context !== 'jsx_text') {\n if (TECHNICAL_IDENTIFIER_RE.test(v) && v.length < 30) {\n return 'technical_identifier'\n }\n }\n\n // ── Error codes (SCREAMING_SNAKE_CASE with underscores) ──\n\n if (ERROR_CODE_RE.test(v) && v.includes('_') && v.length > 3) {\n return 'error_code'\n }\n\n return null\n}\n\n// ─── calculateContentScore: 0-1 confidence scoring ───\n\n/**\n * Calculates a content confidence score (0-1) for a string that passed shouldSkip.\n * Uses AST context metadata (our advantage over offset-based tools) combined with\n * value-based signals proven by i18next-cli.\n *\n * Base score: 0.5. Boosted/penalized by context and value characteristics.\n */\nexport function calculateContentScore(str: ExtractedString): number {\n let score = 0.5\n\n // ── Context signals (AST metadata advantage) ──\n\n // Template/JSX text = almost certainly user-visible content\n if (str.context === 'template_text' || str.context === 'jsx_text') {\n score += 0.3\n }\n\n // Content-bearing attribute (title, alt, placeholder, aria-label, etc.)\n if (str.context === 'template_attribute' || str.context === 'jsx_attribute') {\n if (str.parentProperty && TRANSLATABLE_ATTRIBUTES.has(str.parentProperty)) {\n score += 0.2\n } else {\n score -= 0.2 // Unknown/technical attribute\n }\n }\n\n // Content-bearing object property (message, label, description, etc.)\n if (str.context === 'object_property') {\n if (str.parentProperty && TRANSLATABLE_PROPERTIES.has(str.parentProperty)) {\n score += 0.25\n }\n }\n\n // ── Value signals (i18next-cli proven heuristics) ──\n\n // Multi-word strings are more likely content\n const wordCount = str.value.split(/\\s+/).length\n if (wordCount >= 3) score += 0.2\n else if (wordCount === 2) score += 0.1\n\n // Terminal punctuation suggests a sentence\n if (/[.!?:;]$/.test(str.value)) score += 0.1\n\n // Non-ASCII characters (Turkish, Chinese, Arabic, etc.) → almost certainly content\n if (/[\\u0080-\\uFFFF]/.test(str.value)) score += 0.15\n\n // Capitalized first letter with lowercase body (Dashboard, Kaydet, Settings)\n if (/^[A-Z]/.test(str.value) && /[a-z]/.test(str.value)) score += 0.1\n\n // camelCase → probably a technical identifier\n if (CAMEL_CASE_RE.test(str.value)) score -= 0.3\n\n // PascalCase with internal uppercase (PhGameController, GameCard) → likely component/icon name\n // Does NOT match single-uppercase words (Dashboard, Karadeniz, Settings)\n if (PASCAL_CASE_RE.test(str.value) && !str.value.includes(' ')) score -= 0.25\n\n // Short ALL-CAPS (TRY, GET, USD) → likely code/abbreviation, not content\n // In template_text the +0.3 context boost keeps real labels like \"FAQ\" above threshold\n if (/^[A-Z]{2,5}$/.test(str.value)) score -= 0.15\n\n // Contains slash without spaces → path-like\n if (str.value.includes('/') && !str.value.includes(' ')) score -= 0.2\n\n return Math.max(0, Math.min(1, score))\n}\n\n// ─── Public API ───\n\n/**\n * Two-phase pre-filter:\n * 1. shouldSkip(): Binary removal of definite non-content\n * 2. calculateContentScore(): 0-1 confidence scoring for ambiguous strings\n *\n * Returns candidates that passed both phases, with content scores attached.\n */\nexport function applyPreFilter(\n strings: ExtractedString[],\n minScore: number = 0.4,\n): PreFilterResult {\n const candidates: ExtractedString[] = []\n const skipReasons: Record<string, number> = {}\n let skipped = 0\n let lowConfidence = 0\n\n for (const str of strings) {\n // Phase 1: Binary skip\n const skipReason = shouldSkip(str)\n if (skipReason) {\n skipped++\n skipReasons[skipReason] = (skipReasons[skipReason] ?? 0) + 1\n continue\n }\n\n // Phase 2: Content scoring\n const contentScore = calculateContentScore(str)\n if (contentScore < minScore) {\n lowConfidence++\n skipReasons['low_confidence'] = (skipReasons['low_confidence'] ?? 0) + 1\n continue\n }\n\n // Attach score to the extraction for downstream use\n ;(str as ExtractedString & { contentScore: number }).contentScore = contentScore\n candidates.push(str)\n }\n\n return { candidates, skipped, lowConfidence, skipReasons }\n}\n","import type { ExtractedString } from './types.js'\nimport { applyPreFilter } from './pre-filter.js'\nimport { parseTsx } from './tsx-parser.js'\n\n// ─── Extension sets ───\n\nconst TSX_EXTENSIONS = new Set(['.tsx', '.jsx', '.ts', '.js', '.mjs'])\nconst VUE_EXTENSIONS = new Set(['.vue'])\nconst SVELTE_EXTENSIONS = new Set(['.svelte'])\nconst ASTRO_EXTENSIONS = new Set(['.astro'])\n\n// ─── Lazy-loaded parsers ───\n\n/**\n * Lazily import vue-parser. Returns undefined if not available\n * (@vue/compiler-sfc is an optional dependency).\n */\nasync function loadVueParser(): Promise<((content: string, fileName: string) => Promise<ExtractedString[]> | ExtractedString[]) | undefined> {\n try {\n const mod = await import('./vue-parser.js')\n return mod.parseVue\n } catch {\n return undefined\n }\n}\n\n/**\n * Lazily import svelte-parser. Returns undefined if not available\n * (svelte is an optional dependency).\n */\nasync function loadSvelteParser(): Promise<((content: string, fileName: string) => Promise<ExtractedString[]>) | undefined> {\n try {\n const mod = await import('./svelte-parser.js')\n return mod.parseSvelte\n } catch {\n return undefined\n }\n}\n\n/**\n * Lazily import astro-parser. Returns undefined if not available\n * (@astrojs/compiler is an optional dependency).\n */\nasync function loadAstroParser(): Promise<((content: string, fileName: string) => Promise<ExtractedString[]>) | undefined> {\n try {\n const mod = await import('./astro-parser.js')\n return mod.parseAstro\n } catch {\n return undefined\n }\n}\n\n// ─── Regex fallback for unknown extensions ───\n\nconst SURROUNDING_MAX = 120\n\n/**\n * Minimal regex-based extractor for file types without AST parsers.\n * Extracts quoted strings and tag text — conservative, low accuracy.\n * Used as fallback when dedicated parsers are unavailable.\n */\nfunction extractWithRegex(content: string, _filePath: string): ExtractedString[] {\n const lines = content.split('\\n')\n const results: ExtractedString[] = []\n\n // Simple quoted string extraction\n const stringRe = /(['\"`])(?:(?!\\1|\\\\).|\\\\.)*?\\1/g\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!\n const trimmed = line.trim()\n\n // Skip comments, imports\n if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) continue\n if (/^\\s*(import|export)\\s/.test(line)) continue\n\n let match: RegExpExecArray | null\n stringRe.lastIndex = 0\n\n while ((match = stringRe.exec(line)) !== null) {\n // Remove surrounding quotes\n const raw = match[0]\n const value = raw.slice(1, -1)\n if (value.length === 0) continue\n\n const surrounding = buildSurrounding(lines, i)\n\n results.push({\n value,\n line: i + 1,\n column: match.index + 1,\n context: 'other',\n scope: 'script',\n parent: '',\n surrounding,\n })\n }\n\n // Tag text extraction: >text<\n const tagTextRe = />([^<>{]+)</g\n let tagMatch: RegExpExecArray | null\n tagTextRe.lastIndex = 0\n\n while ((tagMatch = tagTextRe.exec(line)) !== null) {\n const text = tagMatch[1]!.trim()\n if (text.length === 0) continue\n if (/^[\\s\\W]*$/.test(text) && !/[a-zA-Z]/.test(text)) continue\n\n results.push({\n value: text,\n line: i + 1,\n column: tagMatch.index + 1,\n context: 'template_text',\n scope: 'template',\n parent: '',\n surrounding: buildSurrounding(lines, i),\n })\n }\n }\n\n return results\n}\n\nfunction buildSurrounding(lines: string[], lineIdx: number): string {\n const start = Math.max(0, lineIdx - 1)\n const end = Math.min(lines.length - 1, lineIdx + 1)\n\n const parts: string[] = []\n for (let i = start; i <= end; i++) {\n const line = lines[i]\n if (line !== undefined) {\n parts.push(line)\n }\n }\n\n const joined = parts.join('\\n')\n if (joined.length > SURROUNDING_MAX) {\n return joined.slice(0, SURROUNDING_MAX)\n }\n return joined\n}\n\n// ─── Public API ───\n\n/**\n * Extract strings from a source file with structural context.\n *\n * Routes to the correct parser based on file extension:\n * - .tsx/.jsx/.ts/.js/.mjs -> tsx-parser (AST-based)\n * - .vue -> vue-parser (lazy-loaded, AST-based)\n * - .svelte -> svelte-parser (lazy-loaded, AST-based)\n * - .astro -> astro-parser (lazy-loaded, AST-based)\n * - unknown -> empty array\n *\n * Falls back to regex extraction when the dedicated parser's\n * optional dependency is not installed.\n *\n * Applies structural pre-filter to remove 100% non-content strings.\n * Returns only candidates that should be sent to the agent.\n */\nexport async function extractStrings(\n filePath: string,\n content: string,\n ext: string,\n): Promise<ExtractedString[]> {\n const normalizedExt = ext.startsWith('.') ? ext : `.${ext}`\n let rawStrings: ExtractedString[]\n\n if (TSX_EXTENSIONS.has(normalizedExt)) {\n rawStrings = parseTsx(content, filePath)\n } else if (VUE_EXTENSIONS.has(normalizedExt)) {\n const parseVue = await loadVueParser()\n if (parseVue) {\n rawStrings = await parseVue(content, filePath)\n } else {\n rawStrings = extractWithRegex(content, filePath)\n }\n } else if (SVELTE_EXTENSIONS.has(normalizedExt)) {\n const parseSvelte = await loadSvelteParser()\n if (parseSvelte) {\n try {\n rawStrings = await parseSvelte(content, filePath)\n } catch {\n // svelte/compiler not installed — fall back to regex\n rawStrings = extractWithRegex(content, filePath)\n }\n } else {\n rawStrings = extractWithRegex(content, filePath)\n }\n } else if (ASTRO_EXTENSIONS.has(normalizedExt)) {\n const parseAstro = await loadAstroParser()\n if (parseAstro) {\n try {\n rawStrings = await parseAstro(content, filePath)\n } catch {\n // @astrojs/compiler not installed — fall back to regex\n rawStrings = extractWithRegex(content, filePath)\n }\n } else {\n rawStrings = extractWithRegex(content, filePath)\n }\n } else {\n return []\n }\n\n // Apply structural pre-filter\n const { candidates } = applyPreFilter(rawStrings)\n return candidates\n}\n\n// Re-export types for convenience\nexport type { ExtractedString, StructuralContext, PreFilterRule } from './types.js'\nexport { applyPreFilter, shouldSkip, calculateContentScore } from './pre-filter.js'\nexport type { PreFilterResult } from './pre-filter.js'\n","import type {\n StringContext,\n ScanCandidate,\n DuplicateGroup,\n ScanCandidatesResult,\n ScanSummaryResult,\n} from '@contentrain/types'\nimport { join, extname } from 'node:path'\nimport { readText } from '../util/fs.js'\nimport {\n autoDetectSourceDirs,\n discoverFiles,\n} from './scan-config.js'\nimport { extractStrings } from './ast-scanner/index.js'\nimport { calculateContentScore } from './ast-scanner/pre-filter.js'\nimport type { StructuralContext } from './ast-scanner/types.js'\n\n// ─── Options ───\n\nexport interface ScanOptions {\n paths?: string[]\n include?: string[]\n exclude?: string[]\n limit?: number\n offset?: number\n min_length?: number\n max_length?: number\n /** Minimum content confidence score (0-1). Default: 0.4 */\n min_score?: number\n}\n\n// ─── Constants ───\n\nconst DEFAULT_LIMIT = 50\nconst DEFAULT_OFFSET = 0\nconst DEFAULT_MIN_LENGTH = 2\nconst DEFAULT_MAX_LENGTH = 500\nconst DEFAULT_MIN_SCORE = 0.4\nconst SUMMARY_SAMPLE_SIZE = 10\n\n// ─── Context mapping: StructuralContext → StringContext ───\n\nconst CONTEXT_MAP: Record<StructuralContext, StringContext> = {\n 'template_text': 'template_text',\n 'template_attribute': 'template_attribute',\n 'jsx_text': 'jsx_text',\n 'jsx_attribute': 'jsx_attribute',\n 'variable_assignment': 'variable_assignment',\n 'object_property': 'object_value',\n 'function_argument': 'function_argument',\n 'array_element': 'other',\n 'enum_value': 'other',\n 'template_literal': 'other',\n 'switch_case': 'other',\n 'other': 'other',\n // Pre-filtered contexts should not reach here, but map them just in case\n 'import_path': 'other',\n 'type_annotation': 'other',\n 'css_class': 'other',\n 'css_utility_call': 'other',\n 'console_call': 'other',\n 'test_assertion': 'other',\n}\n\n// ─── Main: scanCandidates ───\n\nexport async function scanCandidates(\n projectRoot: string,\n options?: ScanOptions,\n): Promise<ScanCandidatesResult> {\n const limit = options?.limit ?? DEFAULT_LIMIT\n const offset = options?.offset ?? DEFAULT_OFFSET\n const minLength = options?.min_length ?? DEFAULT_MIN_LENGTH\n const maxLength = options?.max_length ?? DEFAULT_MAX_LENGTH\n const minScore = options?.min_score ?? DEFAULT_MIN_SCORE\n\n const scanDirs = options?.paths ?? await autoDetectSourceDirs(projectRoot)\n const files = await discoverFiles(projectRoot, {\n paths: scanDirs,\n include: options?.include,\n exclude: options?.exclude,\n })\n\n // ─── Phase 1: Extract strings from all files (pre-filter applied inside extractStrings) ───\n\n const filePromises = files.map(async (relPath) => {\n const filePath = join(projectRoot, relPath)\n const content = await readText(filePath)\n if (!content) return { relPath, extractions: [] }\n\n const ext = extname(filePath)\n const extractions = await extractStrings(filePath, content, ext)\n return { relPath, extractions }\n })\n\n const fileResults = await Promise.all(filePromises)\n\n // ─── Phase 2: Score + length filter + deduplicate ───\n\n let rawStringsFound = 0\n let skippedCount = 0\n let lowConfidenceCount = 0\n const skipReasons: Record<string, number> = {}\n\n // Deduplication map: value → first candidate + all occurrences\n const uniqueMap = new Map<string, {\n candidate: ScanCandidate\n maxScore: number\n }>()\n\n // Also track duplicates for backward compatibility\n const dupeMap = new Map<string, Array<{ file: string; line: number }>>()\n\n for (const { relPath, extractions } of fileResults) {\n rawStringsFound += extractions.length\n\n for (const extraction of extractions) {\n // Length filter\n if (extraction.value.length < minLength || extraction.value.length > maxLength) {\n skippedCount++\n skipReasons['length_filter'] = (skipReasons['length_filter'] ?? 0) + 1\n continue\n }\n\n // shouldSkip was already applied inside extractStrings (via applyPreFilter).\n // But applyPreFilter uses default minScore. Here we apply the user-configured minScore\n // on the contentScore that was attached during pre-filtering.\n\n // Get the contentScore attached by applyPreFilter\n const contentScore: number = (extraction as unknown as { contentScore?: number }).contentScore ?? calculateContentScore(extraction)\n\n if (contentScore < minScore) {\n lowConfidenceCount++\n skipReasons['low_confidence'] = (skipReasons['low_confidence'] ?? 0) + 1\n continue\n }\n\n const mappedContext = CONTEXT_MAP[extraction.context]\n const loc = { file: relPath, line: extraction.line }\n\n // Track all occurrences for duplicates section\n if (!dupeMap.has(extraction.value)) {\n dupeMap.set(extraction.value, [])\n }\n dupeMap.get(extraction.value)!.push(loc)\n\n // Deduplication: keep first occurrence, accumulate locations\n if (!uniqueMap.has(extraction.value)) {\n uniqueMap.set(extraction.value, {\n candidate: {\n file: relPath,\n line: extraction.line,\n column: extraction.column,\n value: extraction.value,\n context: mappedContext,\n surrounding: extraction.surrounding,\n contentScore,\n occurrences: [loc],\n },\n maxScore: contentScore,\n })\n } else {\n const entry = uniqueMap.get(extraction.value)!\n entry.candidate.occurrences.push(loc)\n // Keep the highest score across occurrences\n if (contentScore > entry.maxScore) {\n entry.maxScore = contentScore\n entry.candidate.contentScore = contentScore\n entry.candidate.file = relPath\n entry.candidate.line = extraction.line\n entry.candidate.column = extraction.column\n entry.candidate.context = mappedContext\n entry.candidate.surrounding = extraction.surrounding\n }\n }\n }\n }\n\n // Build sorted unique candidates (highest score first)\n const allUniqueCandidates = [...uniqueMap.values()]\n .map(e => e.candidate)\n .toSorted((a, b) => b.contentScore - a.contentScore)\n\n // Build duplicate groups (only count >= 2), sorted by count descending\n const duplicates: DuplicateGroup[] = [...dupeMap.entries()]\n .filter(([, occurrences]) => occurrences.length >= 2)\n .map(([value, occurrences]) => ({ value, count: occurrences.length, occurrences }))\n .toSorted((a, b) => b.count - a.count)\n\n // Pagination on unique candidates\n const uniqueCount = allUniqueCandidates.length\n const paginated = allUniqueCandidates.slice(offset, offset + limit)\n const hasMore = uniqueCount > offset + limit\n\n return {\n candidates: paginated,\n duplicates,\n stats: {\n files_scanned: files.length,\n raw_strings_found: rawStringsFound,\n skipped: skippedCount,\n low_confidence: lowConfidenceCount,\n unique_candidates: uniqueCount,\n candidates_returned: paginated.length,\n has_more: hasMore,\n skip_reasons: skipReasons,\n },\n }\n}\n\n// ─── Main: scanSummary ───\n\nexport async function scanSummary(\n projectRoot: string,\n options?: ScanOptions,\n): Promise<ScanSummaryResult> {\n const minLength = options?.min_length ?? DEFAULT_MIN_LENGTH\n const maxLength = options?.max_length ?? DEFAULT_MAX_LENGTH\n const minScore = options?.min_score ?? DEFAULT_MIN_SCORE\n\n const scanDirs = options?.paths ?? await autoDetectSourceDirs(projectRoot)\n const files = await discoverFiles(projectRoot, {\n paths: scanDirs,\n include: options?.include,\n exclude: options?.exclude,\n })\n\n // Group files by directory\n const dirFiles = new Map<string, string[]>()\n const fileTypes: Record<string, number> = {}\n\n for (const relPath of files) {\n const parts = relPath.split('/')\n const dir = parts.slice(0, -1).join('/') || '.'\n const ext = extname(relPath)\n\n if (!dirFiles.has(dir)) dirFiles.set(dir, [])\n dirFiles.get(dir)!.push(relPath)\n\n fileTypes[ext] = (fileTypes[ext] ?? 0) + 1\n }\n\n // Sample files per directory and count candidates\n const byDirectory: Record<string, { files: number; candidates: number }> = {}\n const freqMap = new Map<string, number>()\n let totalCandidatesEstimate = 0\n\n for (const [dir, dirFileList] of dirFiles) {\n const totalInDir = dirFileList.length\n const sampleFiles = dirFileList.slice(0, SUMMARY_SAMPLE_SIZE)\n let sampleCandidates = 0\n\n const samplePromises = sampleFiles.map(async (relPath) => {\n const filePath = join(projectRoot, relPath)\n const content = await readText(filePath)\n if (!content) return []\n\n const ext = extname(filePath)\n return extractStrings(filePath, content, ext)\n })\n\n const sampleResults = await Promise.all(samplePromises)\n\n for (const extractions of sampleResults) {\n for (const extraction of extractions) {\n // Apply same filters as scanCandidates\n if (extraction.value.length < minLength || extraction.value.length > maxLength) continue\n\n const contentScore: number = (extraction as unknown as { contentScore?: number }).contentScore ?? calculateContentScore(extraction)\n if (contentScore < minScore) continue\n\n sampleCandidates++\n\n // Track frequencies for top_repeated\n const prev = freqMap.get(extraction.value) ?? 0\n freqMap.set(extraction.value, prev + 1)\n }\n }\n\n // Estimate for full directory\n const avgPerFile = sampleFiles.length > 0 ? sampleCandidates / sampleFiles.length : 0\n const estimatedCandidates = Math.round(avgPerFile * totalInDir)\n\n byDirectory[dir] = {\n files: totalInDir,\n candidates: estimatedCandidates,\n }\n\n totalCandidatesEstimate += estimatedCandidates\n }\n\n // Top repeated strings\n const topRepeated = [...freqMap.entries()]\n .filter(([, count]) => count >= 2)\n .toSorted((a, b) => b[1] - a[1])\n .slice(0, 20)\n .map(([value, count]) => ({ value, count }))\n\n return {\n total_files: files.length,\n total_candidates_estimate: totalCandidatesEstimate,\n by_directory: byDirectory,\n top_repeated: topRepeated,\n sampling_note: `Based on first ${SUMMARY_SAMPLE_SIZE} files per directory. Counts are from sampled subset, not project-wide.`,\n file_types: fileTypes,\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
import "../../contracts-DfL0BfrD.mjs";
|
|
2
|
-
import "../../reconcile-
|
|
3
|
-
import "../../context-
|
|
4
|
-
import "../../model-manager-
|
|
5
|
-
import "../../ops-
|
|
6
|
-
import "../../fs-
|
|
7
|
-
import "../../meta-manager-
|
|
8
|
-
import "../../config-
|
|
9
|
-
import "../../validator-
|
|
2
|
+
import "../../reconcile-CPDGFQ8H.mjs";
|
|
3
|
+
import "../../context-fJCKH_qv.mjs";
|
|
4
|
+
import "../../model-manager-DPXulg7z.mjs";
|
|
5
|
+
import "../../ops-DxhCCs_Z.mjs";
|
|
6
|
+
import "../../fs-BFTuXbbw.mjs";
|
|
7
|
+
import "../../meta-manager-CSpKtjnP.mjs";
|
|
8
|
+
import "../../config-BLwZtwlI.mjs";
|
|
9
|
+
import "../../validator-C08p1OpM.mjs";
|
|
10
10
|
import "../../identity-CxAB9Qb8.mjs";
|
|
11
|
-
import "../../branch-lifecycle-
|
|
11
|
+
import "../../branch-lifecycle-f4gI_eGo.mjs";
|
|
12
12
|
import "../../id-CjGMPP1k.mjs";
|
|
13
|
-
import "../../transaction-
|
|
14
|
-
import "../../local-
|
|
15
|
-
import "../../detect-
|
|
13
|
+
import "../../transaction-BWJt1KQb.mjs";
|
|
14
|
+
import "../../local-DkDTrGt_.mjs";
|
|
15
|
+
import "../../detect-DUYFv3-O.mjs";
|
|
16
16
|
import "../../annotations-CXHPnS-M.mjs";
|
|
17
|
-
import { n as createServer } from "../../server-
|
|
17
|
+
import { n as createServer } from "../../server-Ioqkq_v-.mjs";
|
|
18
18
|
import "../../errors-BmLE-JSC.mjs";
|
|
19
|
-
import "../../reconcile-
|
|
20
|
-
import "../../scan-config-
|
|
21
|
-
import "../../graph-builder-
|
|
22
|
-
import "../../scanner-
|
|
19
|
+
import "../../reconcile-Cq60cldB.mjs";
|
|
20
|
+
import "../../scan-config-DkRKu_ON.mjs";
|
|
21
|
+
import "../../graph-builder-CNswWNtb.mjs";
|
|
22
|
+
import "../../scanner-BLo3ssiF.mjs";
|
|
23
23
|
import "../../tsx-parser-go5V9zoc.mjs";
|
|
24
|
-
import "../../apply-manager-
|
|
25
|
-
import "../../doctor-
|
|
24
|
+
import "../../apply-manager-llmSyK5K.mjs";
|
|
25
|
+
import "../../doctor-fIG4TtsO.mjs";
|
|
26
26
|
import { randomUUID } from "node:crypto";
|
|
27
27
|
import http from "node:http";
|
|
28
28
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { n as readContext } from "./context-
|
|
2
|
-
import { A as contentDirPath, C as resolveMdFilePath, O as writeContent, S as resolveLocaleStrategy, _ as listContent, a as countEntries, b as resolveContentDir, d as readModel, g as deleteContent, h as writeModel, i as collectFieldPaths, j as contentFilePath, m as validateModelDefinition, r as checkReferences, s as fieldDefZodSchema, u as listModels, x as resolveJsonFilePath } from "./model-manager-
|
|
3
|
-
import { a as planModelDelete, i as planModelSave, n as planVocabularySave, o as planContentSave, s as planContentDelete, t as planVocabularyDelete } from "./ops-
|
|
4
|
-
import { a as readJson, n as ensureDir, r as pathExists, s as writeJson, t as contentrainDir } from "./fs-
|
|
5
|
-
import { a as writeMeta, i as readMeta, o as writeMetaEntries, r as mergeEntryMeta, t as applyStatusChange } from "./meta-manager-
|
|
1
|
+
import { n as readContext } from "./context-fJCKH_qv.mjs";
|
|
2
|
+
import { A as contentDirPath, C as resolveMdFilePath, O as writeContent, S as resolveLocaleStrategy, _ as listContent, a as countEntries, b as resolveContentDir, d as readModel, g as deleteContent, h as writeModel, i as collectFieldPaths, j as contentFilePath, m as validateModelDefinition, r as checkReferences, s as fieldDefZodSchema, u as listModels, x as resolveJsonFilePath } from "./model-manager-DPXulg7z.mjs";
|
|
3
|
+
import { a as planModelDelete, i as planModelSave, n as planVocabularySave, o as planContentSave, s as planContentDelete, t as planVocabularyDelete } from "./ops-DxhCCs_Z.mjs";
|
|
4
|
+
import { a as readJson, n as ensureDir, r as pathExists, s as writeJson, t as contentrainDir } from "./fs-BFTuXbbw.mjs";
|
|
5
|
+
import { a as writeMeta, i as readMeta, o as writeMetaEntries, r as mergeEntryMeta, t as applyStatusChange } from "./meta-manager-CSpKtjnP.mjs";
|
|
6
6
|
import { t as OverlayReader } from "./overlay-reader-Bx1YxGXq.mjs";
|
|
7
|
-
import { n as readVocabulary, t as readConfig } from "./config-
|
|
8
|
-
import { t as validateProject } from "./validator-
|
|
7
|
+
import { n as readVocabulary, t as readConfig } from "./config-BLwZtwlI.mjs";
|
|
8
|
+
import { t as validateProject } from "./validator-C08p1OpM.mjs";
|
|
9
9
|
import { r as createGit } from "./identity-CxAB9Qb8.mjs";
|
|
10
|
-
import { a as contentBranchRelation, c as listRemoteCrBranches, i as cleanupMergedBranches, n as checkBranchHealth, o as deleteRemoteBranch, u as pruneMergedRemoteBranches } from "./branch-lifecycle-
|
|
11
|
-
import { i as ensureContentBranch, n as buildBranchName, o as mergeBranch, r as createTransaction } from "./transaction-
|
|
12
|
-
import { n as isMerged, t as LocalProvider } from "./local-
|
|
10
|
+
import { a as contentBranchRelation, c as listRemoteCrBranches, d as resolveBaseBranch, i as cleanupMergedBranches, n as checkBranchHealth, o as deleteRemoteBranch, u as pruneMergedRemoteBranches } from "./branch-lifecycle-f4gI_eGo.mjs";
|
|
11
|
+
import { i as ensureContentBranch, n as buildBranchName, o as mergeBranch, r as createTransaction } from "./transaction-BWJt1KQb.mjs";
|
|
12
|
+
import { n as isMerged, t as LocalProvider } from "./local-DkDTrGt_.mjs";
|
|
13
13
|
import { n as isToolAvailable, t as TOOL_REQUIREMENTS } from "./availability-DYHNoP0_.mjs";
|
|
14
|
-
import { t as detectStack } from "./detect-
|
|
14
|
+
import { t as detectStack } from "./detect-DUYFv3-O.mjs";
|
|
15
15
|
import { t as TOOL_ANNOTATIONS } from "./annotations-CXHPnS-M.mjs";
|
|
16
16
|
import { n as listTemplates, t as getTemplate } from "./templates-BZbohJc8.mjs";
|
|
17
17
|
import { t as normalizeOperationError } from "./errors-BmLE-JSC.mjs";
|
|
18
|
-
import { t as reconcileBranches } from "./reconcile-
|
|
19
|
-
import { t as buildGraph } from "./graph-builder-
|
|
20
|
-
import { n as scanSummary, t as scanCandidates } from "./scanner-
|
|
21
|
-
import { n as applyExtract, r as applyReuse } from "./apply-manager-
|
|
22
|
-
import { t as runDoctor } from "./doctor-
|
|
18
|
+
import { t as reconcileBranches } from "./reconcile-Cq60cldB.mjs";
|
|
19
|
+
import { t as buildGraph } from "./graph-builder-CNswWNtb.mjs";
|
|
20
|
+
import { n as scanSummary, t as scanCandidates } from "./scanner-BLo3ssiF.mjs";
|
|
21
|
+
import { n as applyExtract, r as applyReuse } from "./apply-manager-llmSyK5K.mjs";
|
|
22
|
+
import { t as runDoctor } from "./doctor-fIG4TtsO.mjs";
|
|
23
23
|
import { join } from "node:path";
|
|
24
24
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
25
|
-
import { CONTENTRAIN_BRANCH, MODEL_EXTENSION_KEYS, validateModelLocales } from "@contentrain/types";
|
|
26
25
|
import { appendFile, readFile, writeFile } from "node:fs/promises";
|
|
26
|
+
import { CONTENTRAIN_BRANCH, MODEL_EXTENSION_KEYS, validateModelLocales } from "@contentrain/types";
|
|
27
27
|
import { z } from "zod";
|
|
28
28
|
import "simple-git";
|
|
29
29
|
//#region src/tools/context.ts
|
|
@@ -95,7 +95,7 @@ function registerContextTools(server, provider, projectRoot) {
|
|
|
95
95
|
const git = createGit(projectRoot);
|
|
96
96
|
const branches = await git.branchLocal();
|
|
97
97
|
if (branches.all.includes(CONTENTRAIN_BRANCH)) {
|
|
98
|
-
const baseBranch =
|
|
98
|
+
const baseBranch = await resolveBaseBranch(git, config, { currentBranch: branches.current });
|
|
99
99
|
const relation = await contentBranchRelation(git, baseBranch);
|
|
100
100
|
result["content_branch"] = {
|
|
101
101
|
base: baseBranch,
|
|
@@ -1817,7 +1817,7 @@ function registerWorkflowTools(server, provider, projectRoot) {
|
|
|
1817
1817
|
};
|
|
1818
1818
|
}
|
|
1819
1819
|
try {
|
|
1820
|
-
const baseBranch =
|
|
1820
|
+
const baseBranch = await resolveBaseBranch(git, config);
|
|
1821
1821
|
const branchSummary = await git.branchLocal();
|
|
1822
1822
|
let unmergedRaw = "";
|
|
1823
1823
|
try {
|
|
@@ -3026,7 +3026,7 @@ function registerMediaTools(server, provider, _projectRoot) {
|
|
|
3026
3026
|
}
|
|
3027
3027
|
//#endregion
|
|
3028
3028
|
//#region package.json
|
|
3029
|
-
var version = "3.6.
|
|
3029
|
+
var version = "3.6.3";
|
|
3030
3030
|
//#endregion
|
|
3031
3031
|
//#region src/server.ts
|
|
3032
3032
|
/**
|
|
@@ -3101,4 +3101,4 @@ function resolveServerContext(input) {
|
|
|
3101
3101
|
//#endregion
|
|
3102
3102
|
export { createServer as n, DEFAULT_INSTRUCTIONS as t };
|
|
3103
3103
|
|
|
3104
|
-
//# sourceMappingURL=server-
|
|
3104
|
+
//# sourceMappingURL=server-Ioqkq_v-.mjs.map
|