@contentrain/mcp 1.1.2 → 1.2.0

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.
Files changed (34) hide show
  1. package/README.md +29 -20
  2. package/dist/{apply-manager-Cf5OWOJ7.mjs → apply-manager-BkPMfpsN.mjs} +8 -3
  3. package/dist/apply-manager-BkPMfpsN.mjs.map +1 -0
  4. package/dist/{astro-parser-RofKmKQi.mjs → astro-parser-BJ-54JGe.mjs} +3 -3
  5. package/dist/{astro-parser-RofKmKQi.mjs.map → astro-parser-BJ-54JGe.mjs.map} +1 -1
  6. package/dist/core/apply-manager.d.mts.map +1 -1
  7. package/dist/core/apply-manager.mjs +2 -2
  8. package/dist/core/scanner.d.mts +3 -9
  9. package/dist/core/scanner.d.mts.map +1 -1
  10. package/dist/core/scanner.mjs +3 -3
  11. package/dist/git/transaction.d.mts +6 -1
  12. package/dist/git/transaction.d.mts.map +1 -1
  13. package/dist/git/transaction.mjs +2 -2
  14. package/dist/index.mjs +5 -5
  15. package/dist/{scanner-DPrbU-oK.mjs → scanner-CQOxARUU.mjs} +275 -308
  16. package/dist/scanner-CQOxARUU.mjs.map +1 -0
  17. package/dist/{server-CjK63H6X.mjs → server-DfWRpzdP.mjs} +172 -20
  18. package/dist/server-DfWRpzdP.mjs.map +1 -0
  19. package/dist/server.mjs +5 -5
  20. package/dist/{svelte-parser-COTNG92w.mjs → svelte-parser-1bBcMx3T.mjs} +16 -6
  21. package/dist/{svelte-parser-COTNG92w.mjs.map → svelte-parser-1bBcMx3T.mjs.map} +1 -1
  22. package/dist/{transaction-B0V0_SE2.mjs → transaction-C_Pvux78.mjs} +102 -2
  23. package/dist/{transaction-B0V0_SE2.mjs.map → transaction-C_Pvux78.mjs.map} +1 -1
  24. package/dist/tsx-parser-BFLv292Y.mjs +2 -0
  25. package/dist/{tsx-parser-CYVyBWjn.mjs → tsx-parser-BWiMGAIR.mjs} +3 -2
  26. package/dist/tsx-parser-BWiMGAIR.mjs.map +1 -0
  27. package/dist/{vue-parser-CjS4YAoQ.mjs → vue-parser-DTPTN3Yb.mjs} +16 -5
  28. package/dist/{vue-parser-CjS4YAoQ.mjs.map → vue-parser-DTPTN3Yb.mjs.map} +1 -1
  29. package/package.json +2 -2
  30. package/dist/apply-manager-Cf5OWOJ7.mjs.map +0 -1
  31. package/dist/scanner-DPrbU-oK.mjs.map +0 -1
  32. package/dist/server-CjK63H6X.mjs.map +0 -1
  33. package/dist/tsx-parser-Bf8Firrq.mjs +0 -2
  34. package/dist/tsx-parser-CYVyBWjn.mjs.map +0 -1
package/README.md CHANGED
@@ -69,29 +69,38 @@ All write operations are designed around git-backed safety:
69
69
 
70
70
  ## 🧰 Tool Surface
71
71
 
72
- Current MCP tools exposed by the server:
73
-
74
- | Tool | Purpose |
75
- | --- | --- |
76
- | `contentrain_status` | Project status, config, models, branch health, context |
77
- | `contentrain_describe` | Full schema and sample data for a model |
78
- | `contentrain_describe_format` | File-format and storage contract reference |
79
- | `contentrain_init` | Create `.contentrain/` structure and base config |
80
- | `contentrain_scaffold` | Apply a starter template such as blog, docs, landing, saas |
81
- | `contentrain_model_save` | Create or update a model definition |
82
- | `contentrain_model_delete` | Delete a model definition |
83
- | `contentrain_content_save` | Save content entries for any model kind |
84
- | `contentrain_content_delete` | Delete content entries |
85
- | `contentrain_content_list` | Read content entries |
86
- | `contentrain_validate` | Validate project content, optionally auto-fix structural issues |
87
- | `contentrain_submit` | Push `contentrain/*` branches to remote |
88
- | `contentrain_scan` | Graph- and candidate-based hardcoded string scan |
89
- | `contentrain_apply` | Normalize extract/reuse execution with dry-run support |
90
- | `contentrain_bulk` | Bulk locale copy, status updates, and deletes |
72
+ 16 MCP tools with [annotations](https://spec.modelcontextprotocol.io/specification/2025-03-26/server/tools/#annotations) (`readOnlyHint`, `destructiveHint`, `idempotentHint`) for client safety hints:
73
+
74
+ | Tool | Purpose | Read-only | Destructive |
75
+ | --- | --- | --- | --- |
76
+ | `contentrain_status` | Project status, config, models, branch health, context | Yes | — |
77
+ | `contentrain_describe` | Full schema and sample data for a model | Yes | — |
78
+ | `contentrain_describe_format` | File-format and storage contract reference | Yes | — |
79
+ | `contentrain_init` | Create `.contentrain/` structure and base config | — | — |
80
+ | `contentrain_scaffold` | Apply a starter template such as blog, docs, landing, saas | — | — |
81
+ | `contentrain_model_save` | Create or update a model definition | — | — |
82
+ | `contentrain_model_delete` | Delete a model definition | — | **Yes** |
83
+ | `contentrain_content_save` | Save content entries for any model kind | — | — |
84
+ | `contentrain_content_delete` | Delete content entries | — | **Yes** |
85
+ | `contentrain_content_list` | Read content entries | Yes | — |
86
+ | `contentrain_validate` | Validate project content, optionally auto-fix structural issues | — | — |
87
+ | `contentrain_submit` | Push `contentrain/*` branches to remote | — | — |
88
+ | `contentrain_merge` | Merge a review-mode branch into contentrain locally | — | — |
89
+ | `contentrain_scan` | Graph- and candidate-based hardcoded string scan | Yes | — |
90
+ | `contentrain_apply` | Normalize extract/reuse execution with dry-run support | — | — |
91
+ | `contentrain_bulk` | Bulk locale copy, status updates, and deletes | — | — |
91
92
 
92
93
  ## 🚀 Quick Start
93
94
 
94
- ### Run as an MCP server
95
+ ### Configure via CLI (recommended)
96
+
97
+ ```bash
98
+ npx contentrain setup claude-code # or: cursor, vscode, windsurf, copilot
99
+ ```
100
+
101
+ This auto-creates the correct MCP config file for your IDE. See [CLI docs](https://ai.contentrain.io/packages/cli) for details.
102
+
103
+ ### Run as a standalone MCP server
95
104
 
96
105
  ```bash
97
106
  CONTENTRAIN_PROJECT_ROOT=/path/to/project npx contentrain-mcp
@@ -3,7 +3,7 @@ import { t as readConfig } from "./config-B5kc2oeV.mjs";
3
3
  import { S as writeContent, _ as resolveMdFilePath, c as validateModelDefinition, h as resolveJsonFilePath, l as writeModel, m as resolveContentDir, o as listModels, s as readModel } from "./model-manager-DW_fOGRM.mjs";
4
4
  import { n as writeContext } from "./context-DIDt_TxX.mjs";
5
5
  import { t as checkBranchHealth } from "./branch-lifecycle-wGJg25LT.mjs";
6
- import { n as createTransaction, t as buildBranchName } from "./transaction-B0V0_SE2.mjs";
6
+ import { n as createTransaction, t as buildBranchName } from "./transaction-C_Pvux78.mjs";
7
7
  import { extname, join } from "node:path";
8
8
  //#region src/core/apply-manager.ts
9
9
  const MAX_PATCHES = 100;
@@ -396,6 +396,9 @@ async function applyExtract(projectRoot, input) {
396
396
  next_steps: [
397
397
  "Run contentrain_validate to check the extracted content",
398
398
  "Run contentrain_submit to push the branch for review",
399
+ "For browser-based review: ensure `contentrain serve` is running, direct user to http://localhost:3333/normalize",
400
+ "For terminal workflow: use contentrain_merge to merge the branch locally",
401
+ "After merge, run `npx contentrain generate` to update SDK client",
399
402
  "After review, proceed with mode:reuse to patch source files"
400
403
  ]
401
404
  };
@@ -614,7 +617,9 @@ async function applyReuse(projectRoot, input) {
614
617
  patchesSkipped.length > 0 ? `${patchesSkipped.length} patches were skipped — review and retry if needed` : "",
615
618
  syntaxErrors.length > 0 ? `WARNING: ${syntaxErrors.length} file(s) may have syntax errors after patching — review manually` : "",
616
619
  scopeWarnings.length > 0 ? `NOTE: ${scopeWarnings.length} patch file(s) not in extract source map` : "",
617
- "Run contentrain_submit to push the branch for review"
620
+ "Run contentrain_submit to push the branch for review",
621
+ "For review: direct user to http://localhost:3333/branches or use contentrain_merge",
622
+ "After all reuse phases complete, run `npx contentrain generate` to update SDK types"
618
623
  ].filter(Boolean)
619
624
  };
620
625
  } catch (error) {
@@ -748,4 +753,4 @@ function addImportsToLines(lines, imports) {
748
753
  //#endregion
749
754
  export { detectFileFramework as a, validatePatchPath as c, checkSyntax as i, applyExtract as n, replaceInLine as o, applyReuse as r, validateFrameworkExpression as s, PATCHABLE_EXTENSIONS as t };
750
755
 
751
- //# sourceMappingURL=apply-manager-Cf5OWOJ7.mjs.map
756
+ //# sourceMappingURL=apply-manager-BkPMfpsN.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-manager-BkPMfpsN.mjs","names":[],"sources":["../src/core/apply-manager.ts"],"sourcesContent":["import type { ModelDefinition, FieldDef, FileFramework } from '@contentrain/types'\nimport { join, extname } from 'node:path'\nimport { readText, writeText, pathExists } from '../util/fs.js'\nimport { readModel, writeModel, listModels, validateModelDefinition } from './model-manager.js'\nimport { writeContent, resolveContentDir, resolveJsonFilePath, resolveMdFilePath, type ContentEntry } from './content-manager.js'\nimport { readConfig } from './config.js'\nimport { writeContext } from './context.js'\nimport { createTransaction, buildBranchName } from '../git/transaction.js'\nimport { checkBranchHealth } from '../git/branch-lifecycle.js'\n\n// ─── Types ───\n\nexport interface ExtractionEntry {\n model: string\n kind: 'singleton' | 'collection' | 'dictionary' | 'document'\n domain: string\n i18n?: boolean\n fields?: Record<string, FieldDef>\n entries: Array<{\n locale?: string\n slug?: string\n data: Record<string, unknown>\n source?: { file: string; line: number; value: string }\n sources?: Array<{ file: string; line: number; key: string; value: string }>\n }>\n}\n\nexport interface ExtractionInput {\n extractions: ExtractionEntry[]\n dry_run?: boolean\n}\n\nexport interface ExtractionPreview {\n models_to_create: string[]\n models_to_update: string[]\n total_entries: number\n content_files: string[]\n}\n\nexport interface ExtractionResult {\n dry_run: boolean\n preview?: ExtractionPreview\n error?: string\n validation_errors?: string[]\n results?: {\n models_created: string[]\n models_updated: string[]\n entries_written: number\n source_map: Array<{ model: string; locale: string; value: string; file: string; line: number }>\n }\n git?: { branch: string; action: string; commit: string }\n context_updated?: boolean\n next_steps: string[]\n}\n\nexport interface PatchEntry {\n file: string\n line: number\n old_value: string\n new_expression: string\n import_statement?: string\n}\n\nexport interface ReuseInput {\n scope: { model?: string; domain?: string }\n patches: PatchEntry[]\n dry_run?: boolean\n}\n\nexport interface SyntaxError {\n file: string\n error: string\n}\n\nexport interface ReuseResult {\n dry_run: boolean\n error?: string\n scope_warnings?: string[]\n preview?: {\n files_to_modify: string[]\n patches_count: number\n imports_to_add: number\n }\n results?: {\n files_modified: string[]\n patches_applied: number\n patches_skipped: Array<{ file: string; line: number; reason: string }>\n imports_added: number\n framework_warnings?: Array<{ file: string; warning: string }>\n syntax_errors?: SyntaxError[]\n }\n git?: { branch: string; action: string; commit: string }\n next_steps: string[]\n}\n\n// ─── Constants ───\n\nconst MAX_PATCHES = 100\n\n/** File extensions allowed for patching — scannable source files only */\nexport const PATCHABLE_EXTENSIONS = new Set([\n '.vue', '.tsx', '.jsx', '.ts', '.js', '.mjs', '.astro', '.svelte',\n])\n\n/** Directories that must never be patched */\nconst FORBIDDEN_PATH_SEGMENTS = new Set([\n '.contentrain', 'node_modules', '.git', 'dist', 'build', '.next', '.nuxt',\n])\n\n// ─── Framework Detection (Guardrail #2) ───\n\nexport type { FileFramework } from '@contentrain/types'\n\nexport function detectFileFramework(filePath: string): FileFramework {\n const ext = extname(filePath).toLowerCase()\n switch (ext) {\n case '.vue': return 'vue'\n case '.svelte': return 'svelte'\n case '.tsx':\n case '.jsx': return 'jsx'\n case '.astro': return 'astro'\n case '.ts':\n case '.js':\n case '.mjs': return 'script'\n default: return 'script'\n }\n}\n\n/**\n * Validate that a replacement expression uses the correct template syntax\n * for the target file's framework. Returns a warning string or null.\n */\nexport function validateFrameworkExpression(\n filePath: string,\n newExpression: string,\n context: 'tag_text' | 'other',\n): string | null {\n if (context !== 'tag_text') return null\n\n const framework = detectFileFramework(filePath)\n\n switch (framework) {\n case 'vue':\n if (!newExpression.includes('{{')) {\n return `Vue file \"${filePath}\": tag text expression \"${newExpression}\" does not contain \"{{\" — expected Vue template syntax like {{ $t('key') }}`\n }\n break\n case 'jsx':\n if (!newExpression.includes('{')) {\n return `JSX file \"${filePath}\": tag text expression \"${newExpression}\" does not contain \"{\" — expected JSX syntax like {t('key')}`\n }\n break\n case 'svelte':\n if (!newExpression.includes('{')) {\n return `Svelte file \"${filePath}\": tag text expression \"${newExpression}\" does not contain \"{\" — expected Svelte syntax like {$t('key')}`\n }\n break\n case 'astro':\n if (!newExpression.includes('{')) {\n return `Astro file \"${filePath}\": tag text expression \"${newExpression}\" does not contain \"{\" — expected Astro syntax like {t('key')}`\n }\n break\n case 'script':\n // Script files don't have template interpolation — warn if attempting tag text replacement\n return `Script file \"${filePath}\": tag text replacement not applicable for .ts/.js files`\n }\n\n return null\n}\n\n// ─── Scope Validation (Guardrail #1) ───\n\n/**\n * Validate that a patch file path is safe and within allowed scope.\n * Returns an error message or null if valid.\n */\nexport function validatePatchPath(filePath: string): string | null {\n const normalizedPath = filePath.replace(/\\\\/g, '/')\n\n // Reject path traversal\n if (normalizedPath.includes('..')) {\n return `Path traversal detected: \"${filePath}\"`\n }\n\n // Reject absolute paths\n if (normalizedPath.startsWith('/')) {\n return `Absolute path not allowed: \"${filePath}\"`\n }\n\n // Reject forbidden directories\n const segments = normalizedPath.split('/')\n for (const seg of segments) {\n if (FORBIDDEN_PATH_SEGMENTS.has(seg)) {\n return `Patching files inside \"${seg}/\" is not allowed: \"${filePath}\"`\n }\n }\n\n // Reject non-scannable extensions\n const ext = extname(filePath).toLowerCase()\n if (!PATCHABLE_EXTENSIONS.has(ext)) {\n return `File extension \"${ext}\" is not patchable. Allowed: ${[...PATCHABLE_EXTENSIONS].join(', ')}. Path: \"${filePath}\"`\n }\n\n return null\n}\n\n// ─── Syntax Check (Guardrail #5) ───\n\n/**\n * Perform a basic syntax check on a patched file.\n * Returns an error message or null if syntax appears valid.\n */\nexport function checkSyntax(filePath: string, content: string): string | null {\n const ext = extname(filePath).toLowerCase()\n\n switch (ext) {\n case '.ts':\n case '.tsx':\n case '.js':\n case '.jsx':\n case '.mjs':\n return checkJsSyntax(content)\n case '.vue':\n return checkVueSyntax(content)\n case '.svelte':\n case '.astro':\n return checkTagBalance(content)\n default:\n return null\n }\n}\n\n/**\n * Basic JS/TS syntax check: bracket/paren/brace balance + string literal closure.\n * This is intentionally conservative — it catches obvious breakage without\n * requiring a full parser.\n */\nfunction checkJsSyntax(content: string): string | null {\n const stack: string[] = []\n const pairs: Record<string, string> = { ')': '(', ']': '[', '}': '{' }\n let inString: string | null = null\n let escaped = false\n let inLineComment = false\n let inBlockComment = false\n\n for (let i = 0; i < content.length; i++) {\n const ch = content[i]!\n const next = content[i + 1]\n\n // Handle escape sequences inside strings\n if (escaped) {\n escaped = false\n continue\n }\n\n if (ch === '\\\\' && inString !== null) {\n escaped = true\n continue\n }\n\n // Line comment\n if (!inString && !inBlockComment && ch === '/' && next === '/') {\n inLineComment = true\n continue\n }\n if (inLineComment) {\n if (ch === '\\n') inLineComment = false\n continue\n }\n\n // Block comment\n if (!inString && !inBlockComment && ch === '/' && next === '*') {\n inBlockComment = true\n i++ // skip *\n continue\n }\n if (inBlockComment) {\n if (ch === '*' && next === '/') {\n inBlockComment = false\n i++ // skip /\n }\n continue\n }\n\n // String handling\n if (inString !== null) {\n if (ch === inString) {\n // Template literal allows multi-line, others don't\n inString = null\n } else if (inString !== '`' && ch === '\\n') {\n return `Unterminated string literal near offset ${i}`\n }\n continue\n }\n\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n inString = ch\n continue\n }\n\n // Bracket matching\n if (ch === '(' || ch === '[' || ch === '{') {\n stack.push(ch)\n } else if (ch === ')' || ch === ']' || ch === '}') {\n const expected = pairs[ch]!\n if (stack.length === 0) {\n return `Unmatched closing \"${ch}\" near offset ${i}`\n }\n const top = stack.pop()!\n if (top !== expected) {\n return `Mismatched bracket: expected closing for \"${top}\" but found \"${ch}\" near offset ${i}`\n }\n }\n }\n\n if (inString !== null) {\n return `Unterminated string literal (opened with ${inString})`\n }\n\n if (stack.length > 0) {\n return `Unclosed bracket \"${stack[stack.length - 1]}\" — ${stack.length} unclosed bracket(s)`\n }\n\n return null\n}\n\n/**\n * Vue SFC syntax check: ensure <template>, <script>, <style> tags are balanced.\n */\nfunction checkVueSyntax(content: string): string | null {\n const tagBalance = checkTagBalance(content)\n if (tagBalance) return tagBalance\n\n // Vue-specific: check that SFC root tags are present and balanced\n for (const tag of ['template', 'script']) {\n const openRe = new RegExp(`<${tag}[\\\\s>]`, 'g')\n const closeRe = new RegExp(`</${tag}>`, 'g')\n const opens = content.match(openRe)?.length ?? 0\n const closes = content.match(closeRe)?.length ?? 0\n if (opens !== closes) {\n return `Unbalanced <${tag}> tag: ${opens} opening vs ${closes} closing`\n }\n }\n\n return null\n}\n\n/**\n * Basic tag balance check for HTML-like files.\n * Checks that self-closing tags are handled and major structural tags are balanced.\n */\nfunction checkTagBalance(content: string): string | null {\n // Check for common structural tags balance\n const structuralTags = ['div', 'section', 'main', 'header', 'footer', 'nav', 'article', 'aside', 'ul', 'ol', 'table']\n\n for (const tag of structuralTags) {\n const openRe = new RegExp(`<${tag}[\\\\s>]`, 'g')\n const closeRe = new RegExp(`</${tag}>`, 'g')\n const opens = content.match(openRe)?.length ?? 0\n const closes = content.match(closeRe)?.length ?? 0\n if (opens !== closes) {\n return `Unbalanced <${tag}> tag: ${opens} opening vs ${closes} closing`\n }\n }\n\n return null\n}\n\n// ─── Extract Mode ───\n\nexport async function applyExtract(\n projectRoot: string,\n input: ExtractionInput,\n): Promise<ExtractionResult> {\n const config = await readConfig(projectRoot)\n if (!config) throw new Error('Project not initialized. Run contentrain_init first.')\n\n const { extractions, dry_run } = input\n\n // Analyze what will happen\n const existingModels = await listModels(projectRoot)\n const existingIds = new Set(existingModels.map(m => m.id))\n\n const modelsToCreate: string[] = []\n const modelsToUpdate: string[] = []\n const contentFiles: string[] = []\n let totalEntries = 0\n\n const validationErrors: string[] = []\n\n for (const ext of extractions) {\n // Validate model definition with same rules as model_save\n const modelErrors = validateModelDefinition({\n id: ext.model,\n kind: ext.kind,\n fields: ext.fields as Record<string, unknown> | undefined,\n })\n if (modelErrors.length > 0) {\n validationErrors.push(...modelErrors.map(e => `[${ext.model}] ${e}`))\n }\n\n for (const entry of ext.entries) {\n if (ext.kind === 'dictionary') {\n if (entry.data['id'] !== undefined || entry.data['slug'] !== undefined) {\n validationErrors.push(`[${ext.model}] Dictionary entries should not have id or slug`)\n }\n for (const [key, val] of Object.entries(entry.data)) {\n if (typeof val !== 'string') {\n validationErrors.push(`[${ext.model}] Dictionary entry value for key \"${key}\" must be a string, got ${typeof val}`)\n }\n }\n } else if (ext.kind === 'document') {\n if (!entry.slug && !entry.data['slug']) {\n validationErrors.push(`[${ext.model}] Document entries must have a slug`)\n }\n } else if (ext.kind === 'collection') {\n if (entry.slug !== undefined || entry.data['slug'] !== undefined) {\n validationErrors.push(`[${ext.model}] Collection entries should not have slug`)\n }\n } else if (ext.kind === 'singleton') {\n if (entry.data['id'] !== undefined || entry.slug !== undefined || entry.data['slug'] !== undefined) {\n validationErrors.push(`[${ext.model}] Singleton entries should not have id or slug`)\n }\n }\n }\n\n if (existingIds.has(ext.model)) {\n modelsToUpdate.push(ext.model)\n } else {\n modelsToCreate.push(ext.model)\n }\n totalEntries += ext.entries.length\n\n // Guardrail #3: Preview-Execute Parity — use real model metadata if it exists\n let previewModel: ModelDefinition\n if (existingIds.has(ext.model)) {\n const real = await readModel(projectRoot, ext.model)\n if (real) {\n previewModel = real\n } else {\n previewModel = { id: ext.model, kind: ext.kind, domain: ext.domain, i18n: ext.i18n ?? true } as ModelDefinition\n }\n } else {\n previewModel = { id: ext.model, kind: ext.kind, domain: ext.domain, i18n: ext.i18n ?? true } as ModelDefinition\n }\n\n const cDir = resolveContentDir(projectRoot, previewModel)\n for (const entry of ext.entries) {\n const locale = entry.locale ?? config.locales.default\n if (ext.kind === 'document' && entry.slug) {\n contentFiles.push(resolveMdFilePath(cDir, previewModel, locale, entry.slug))\n } else {\n contentFiles.push(resolveJsonFilePath(cDir, previewModel, locale))\n }\n }\n }\n\n const preview: ExtractionPreview = {\n models_to_create: modelsToCreate,\n models_to_update: modelsToUpdate,\n total_entries: totalEntries,\n content_files: [...new Set(contentFiles)],\n }\n\n // Dry run — return preview only (include validation errors if any)\n if (dry_run !== false) {\n return {\n dry_run: true,\n preview,\n ...(validationErrors.length > 0 ? { validation_errors: validationErrors } : {}),\n next_steps: [\n ...(validationErrors.length > 0\n ? [`WARNING: ${validationErrors.length} validation error(s) found — fix before executing`]\n : []),\n 'Review the preview above',\n 'Call contentrain_apply with mode:extract and dry_run:false to execute',\n ],\n }\n }\n\n // Block execute if validation errors exist\n if (validationErrors.length > 0) {\n return {\n dry_run: false,\n error: 'Model validation failed — cannot execute extract with invalid model definitions',\n validation_errors: validationErrors,\n next_steps: ['Fix the validation errors and retry'],\n }\n }\n\n // Branch health gate\n const health = await checkBranchHealth(projectRoot)\n if (health.blocked) {\n return {\n error: health.message,\n action: 'blocked' as const,\n hint: 'Merge or delete old contentrain/* branches before executing normalize.',\n } as unknown as ExtractionResult\n }\n\n // Execute — git transaction (always review mode for normalize)\n const branchName = buildBranchName('normalize', 'extract')\n const tx = await createTransaction(projectRoot, branchName, { workflowOverride: 'review' })\n const sourceMap: Array<{ model: string; locale: string; value: string; file: string; line: number }> = []\n const modelsCreated: string[] = []\n const modelsUpdated: string[] = []\n let entriesWritten = 0\n\n try {\n await tx.write(async (wt) => {\n for (const ext of extractions) {\n // Create or merge model\n const existing = await readModel(wt, ext.model)\n if (existing) {\n // Merge fields: add new, keep existing\n if (ext.fields) {\n const merged = { ...existing.fields, ...ext.fields }\n // Only overwrite if new fields were actually added\n const newFieldNames = Object.keys(ext.fields).filter(k => !(k in (existing.fields ?? {})))\n if (newFieldNames.length > 0) {\n existing.fields = merged\n await writeModel(wt, existing)\n modelsUpdated.push(ext.model)\n }\n }\n } else {\n // Create new model\n const newModel: ModelDefinition = {\n id: ext.model,\n name: ext.model.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),\n kind: ext.kind,\n domain: ext.domain,\n i18n: ext.i18n ?? true,\n fields: ext.fields,\n }\n await writeModel(wt, newModel)\n modelsCreated.push(ext.model)\n }\n\n // Write content entries\n const model = (await readModel(wt, ext.model))!\n const entries: ContentEntry[] = ext.entries.map(e => ({\n locale: e.locale,\n slug: e.slug,\n data: e.data,\n }))\n\n const wtConfig = await readConfig(wt) ?? config\n await writeContent(wt, model, entries, wtConfig)\n entriesWritten += entries.length\n\n // Track source map\n for (const entry of ext.entries) {\n // For dictionary entries with per-key source tracking\n if (ext.kind === 'dictionary' && entry.sources) {\n for (const s of entry.sources) {\n sourceMap.push({\n model: ext.model,\n locale: entry.locale ?? config.locales.default,\n value: s.value,\n file: s.file,\n line: s.line,\n })\n }\n } else if (entry.source) {\n sourceMap.push({\n model: ext.model,\n locale: entry.locale ?? config.locales.default,\n value: entry.source.value,\n file: entry.source.file,\n line: entry.source.line,\n })\n }\n }\n }\n\n // Write source map for reuse scope enforcement\n if (sourceMap.length > 0) {\n const sourcesByModel: Record<string, { source_files: string[]; entry_count: number }> = {}\n for (const s of sourceMap) {\n if (!sourcesByModel[s.model]) {\n sourcesByModel[s.model] = { source_files: [], entry_count: 0 }\n }\n const modelEntry = sourcesByModel[s.model]!\n if (!modelEntry.source_files.includes(s.file)) {\n modelEntry.source_files.push(s.file)\n }\n modelEntry.entry_count++\n }\n const sourcesJson = JSON.stringify({\n version: 1,\n created_at: new Date().toISOString(),\n models: sourcesByModel,\n }, null, 2) + '\\n'\n // Write to worktree only — merge brings it to main\n // Phase 2 (reuse) must wait for extract branch to be merged first\n await writeText(join(wt, '.contentrain', 'normalize-sources.json'), sourcesJson)\n }\n\n // Update context\n await writeContext(wt, {\n tool: 'contentrain_apply',\n model: extractions.map(e => e.model).join(','),\n locale: config.locales.default,\n entries: extractions.flatMap(e => e.entries.map(en => en.slug ?? 'entry')),\n })\n })\n\n const commitMsg = `[contentrain] normalize: extract ${entriesWritten} entries to ${extractions.length} models`\n await tx.commit(commitMsg)\n\n const gitResult = { branch: branchName, action: 'pending-review', commit: '' }\n try {\n const completed = await tx.complete()\n gitResult.action = completed.action\n gitResult.commit = completed.commit\n } catch {\n gitResult.action = 'pending-review'\n } finally {\n await tx.cleanup()\n }\n\n return {\n dry_run: false,\n results: {\n models_created: modelsCreated,\n models_updated: modelsUpdated,\n entries_written: entriesWritten,\n source_map: sourceMap,\n },\n git: gitResult,\n context_updated: true,\n next_steps: [\n 'Run contentrain_validate to check the extracted content',\n 'Run contentrain_submit to push the branch for review',\n 'For browser-based review: ensure `contentrain serve` is running, direct user to http://localhost:3333/normalize',\n 'For terminal workflow: use contentrain_merge to merge the branch locally',\n 'After merge, run `npx contentrain generate` to update SDK client',\n 'After review, proceed with mode:reuse to patch source files',\n ],\n }\n } catch (error) {\n await tx.cleanup()\n throw error\n }\n}\n\n// ─── Reuse Mode ───\n\nexport async function applyReuse(\n projectRoot: string,\n input: ReuseInput,\n): Promise<ReuseResult> {\n const config = await readConfig(projectRoot)\n if (!config) throw new Error('Project not initialized. Run contentrain_init first.')\n\n const { scope, patches, dry_run } = input\n\n // Validate scope\n if (!scope.model && !scope.domain) {\n throw new Error('Scope required: provide model or domain. Whole-project patching is not allowed.')\n }\n\n // Validate patch count\n if (patches.length > MAX_PATCHES) {\n throw new Error(`Too many patches (${patches.length}). Maximum ${MAX_PATCHES} per operation. Split into multiple calls.`)\n }\n\n // Check content exists for scope (soft warning)\n if (scope.model) {\n const model = await readModel(projectRoot, scope.model)\n if (!model) {\n throw new Error(`Model \"${scope.model}\" not found. Run extract phase first.`)\n }\n }\n\n const scopeWarnings: string[] = []\n\n // Guardrail #1: Scope Real Enforcement\n // Step 1: Path safety — every patch must target a valid, patchable source file\n for (const patch of patches) {\n const pathError = validatePatchPath(patch.file)\n if (pathError) {\n throw new Error(`Invalid patch path: ${pathError}`)\n }\n }\n\n // Step 2: Semantic scope — verify scope model/domain exists and cross-check patch files\n if (scope.model || scope.domain) {\n const models = await listModels(projectRoot)\n const scopeModels = scope.model\n ? models.filter(m => m.id === scope.model)\n : scope.domain\n ? models.filter(m => m.domain === scope.domain)\n : models\n\n if (scopeModels.length === 0) {\n throw new Error(`No models found for scope ${scope.model ? `model=\"${scope.model}\"` : `domain=\"${scope.domain}\"`}`)\n }\n\n // Step 3: Verify patch files are source files (not content/config/meta files)\n // and belong to detectable source directories (not random locations)\n const { autoDetectSourceDirs } = await import('./scan-config.js')\n const sourceDirs = await autoDetectSourceDirs(projectRoot)\n\n // Build allowed file prefixes from source dirs\n const allowedPrefixes = sourceDirs.map(d => d === '.' ? '' : d + '/')\n\n for (const patch of patches) {\n const normalizedPath = patch.file.replace(/\\\\/g, '/')\n\n // Reject patches targeting .contentrain/ directory (content files should never be patched by reuse)\n if (normalizedPath.startsWith('.contentrain/') || normalizedPath.includes('/.contentrain/')) {\n throw new Error(`Cannot patch content/config files directly: \"${patch.file}\". Reuse patches source files only.`)\n }\n\n // If source dirs were detected (not just \".\"), verify patch files are within them\n if (sourceDirs.length > 0 && sourceDirs[0] !== '.') {\n const inSourceDir = allowedPrefixes.some(prefix =>\n prefix === '' || normalizedPath.startsWith(prefix),\n )\n if (!inSourceDir) {\n throw new Error(\n `Patch file \"${patch.file}\" is outside detected source directories (${sourceDirs.join(', ')}). ` +\n `Reuse patches must target source files within the project's source tree.`,\n )\n }\n }\n }\n\n // Step 4: Semantic source→model cross-check via normalize-sources.json\n // Source map is written by extract into the review branch worktree.\n // It only exists on base after extract branch is merged.\n // If missing: dry_run proceeds with warning, execute is blocked.\n if (scope.model || scope.domain) {\n const sourcesPath = join(projectRoot, '.contentrain', 'normalize-sources.json')\n const sourcesRaw = await readText(sourcesPath)\n\n if (!sourcesRaw) {\n // Source map not found — check if scoped model is a dictionary\n // Dictionaries store all keys in one entry, so per-file source tracking\n // is not available. Allow reuse with a warning instead of blocking.\n const scopedModel = scopeModels.find(m => m.id === scope.model)\n const isDictionary = scopedModel?.kind === 'dictionary'\n\n if (isDictionary) {\n scopeWarnings.push(\n 'normalize-sources.json not found. Dictionary models do not generate per-file source maps. ' +\n 'Scope enforcement is based on source-tree locality only.',\n )\n } else if (dry_run !== false) {\n scopeWarnings.push(\n 'normalize-sources.json not found. Semantic scope enforcement is unavailable. ' +\n 'Merge the extract branch first, then reuse will have full scope protection.',\n )\n } else {\n throw new Error(\n 'Cannot execute reuse: normalize-sources.json not found on base branch. ' +\n 'The extract branch must be merged before reuse can execute. ' +\n 'This ensures semantic scope enforcement protects against out-of-scope patching.',\n )\n }\n } else {\n const sourcesData = JSON.parse(sourcesRaw) as {\n models?: Record<string, { source_files?: string[] }>\n }\n\n // Collect all allowed source files for the scope\n let allowedSourceFiles: string[] = []\n let scopeLabel = ''\n\n if (scope.model) {\n const modelSources = sourcesData.models?.[scope.model]?.source_files\n if (modelSources) allowedSourceFiles = modelSources\n scopeLabel = `model \"${scope.model}\"`\n } else if (scope.domain) {\n const domainModelIds = scopeModels.map(m => m.id)\n for (const modelId of domainModelIds) {\n const modelSources = sourcesData.models?.[modelId]?.source_files\n if (modelSources) allowedSourceFiles.push(...modelSources)\n }\n allowedSourceFiles = [...new Set(allowedSourceFiles)]\n scopeLabel = `domain \"${scope.domain}\"`\n }\n\n if (allowedSourceFiles.length > 0) {\n const outOfScopePatches: string[] = []\n for (const patch of patches) {\n const normalizedPath = patch.file.replace(/\\\\/g, '/')\n if (!allowedSourceFiles.includes(normalizedPath)) {\n outOfScopePatches.push(patch.file)\n }\n }\n if (outOfScopePatches.length > 0) {\n throw new Error(\n `Scope enforcement: ${outOfScopePatches.length} patch file(s) are not associated with ${scopeLabel}. ` +\n `Out-of-scope files: ${outOfScopePatches.join(', ')}. ` +\n `Known source files: ${allowedSourceFiles.join(', ')}.`,\n )\n }\n }\n }\n }\n }\n\n // Group patches by file\n const patchesByFile = new Map<string, PatchEntry[]>()\n for (const patch of patches) {\n if (!patchesByFile.has(patch.file)) {\n patchesByFile.set(patch.file, [])\n }\n patchesByFile.get(patch.file)!.push(patch)\n }\n\n const filesToModify = [...patchesByFile.keys()]\n const importsToAdd = patches.filter(p => p.import_statement).length\n\n // Dry run — return preview only\n if (dry_run !== false) {\n return {\n dry_run: true,\n preview: {\n files_to_modify: filesToModify,\n patches_count: patches.length,\n imports_to_add: importsToAdd,\n },\n ...(scopeWarnings.length > 0 ? { scope_warnings: scopeWarnings } : {}),\n next_steps: [\n ...(scopeWarnings.length > 0 ? [`WARNING: ${scopeWarnings.length} patch file(s) not in extract source map — verify intent`] : []),\n 'Review the files and patches above',\n 'Call contentrain_apply with mode:reuse and dry_run:false to execute',\n ],\n }\n }\n\n // Branch health gate\n const reuseHealth = await checkBranchHealth(projectRoot)\n if (reuseHealth.blocked) {\n return {\n dry_run: false,\n error: `Branch blocked: ${reuseHealth.message}`,\n next_steps: ['Merge or delete old contentrain/* branches before executing reuse.'],\n }\n }\n\n // Execute — git transaction\n const scopeTarget = scope.model ?? scope.domain!\n const branchName = buildBranchName('normalize/reuse', scopeTarget)\n const tx = await createTransaction(projectRoot, branchName, { workflowOverride: 'review' })\n\n const filesModified: string[] = []\n let patchesApplied = 0\n let importsAdded = 0\n const patchesSkipped: Array<{ file: string; line: number; reason: string }> = []\n const frameworkWarnings: Array<{ file: string; warning: string }> = []\n const syntaxErrors: SyntaxError[] = []\n\n try {\n await tx.write(async (wt) => {\n for (const [relFile, filePatches] of patchesByFile) {\n const absPath = join(wt, relFile)\n\n if (!(await pathExists(absPath))) {\n for (const p of filePatches) {\n patchesSkipped.push({ file: relFile, line: p.line, reason: 'file not found' })\n }\n continue\n }\n\n const content = await readText(absPath)\n if (content === null) {\n for (const p of filePatches) {\n patchesSkipped.push({ file: relFile, line: p.line, reason: 'file unreadable' })\n }\n continue\n }\n\n // Sort patches by line DESC (bottom-up to avoid line shifts)\n const sorted = [...filePatches].toSorted((a, b) => b.line - a.line)\n\n const lines = content.split('\\n')\n let fileModified = false\n\n for (const patch of sorted) {\n // Determine replacement context before applying\n const targetLine = lines[patch.line - 1] ?? ''\n const isTagTextContext = targetLine.includes(`>${patch.old_value}<`)\n const replacementContext = isTagTextContext ? 'tag_text' as const : 'other' as const\n\n // Guardrail #2: Framework-aware validation — only warn for tag text replacements\n if (isTagTextContext) {\n const fwWarning = validateFrameworkExpression(relFile, patch.new_expression, replacementContext)\n if (fwWarning) {\n frameworkWarnings.push({ file: relFile, warning: fwWarning })\n }\n }\n\n const applied = applyPatchToLines(lines, patch)\n if (applied) {\n patchesApplied++\n fileModified = true\n } else {\n patchesSkipped.push({ file: relFile, line: patch.line, reason: 'old_value not found at or near specified line' })\n }\n }\n\n // Add imports (deduplicate)\n const importStatements = new Set(\n filePatches\n .filter(p => p.import_statement)\n .map(p => p.import_statement!),\n )\n\n if (importStatements.size > 0) {\n const added = addImportsToLines(lines, importStatements)\n importsAdded += added\n if (added > 0) fileModified = true\n }\n\n if (fileModified) {\n const newContent = lines.join('\\n')\n await writeText(absPath, newContent)\n filesModified.push(relFile)\n\n // Guardrail #5: Syntax check after patching\n const syntaxError = checkSyntax(relFile, newContent)\n if (syntaxError) {\n syntaxErrors.push({ file: relFile, error: syntaxError })\n }\n }\n }\n\n // Update context\n await writeContext(wt, {\n tool: 'contentrain_apply',\n model: scopeTarget,\n locale: config.locales.default,\n })\n })\n\n if (filesModified.length === 0) {\n await tx.cleanup()\n return {\n dry_run: false,\n results: {\n files_modified: [],\n patches_applied: 0,\n patches_skipped: patchesSkipped,\n imports_added: 0,\n framework_warnings: frameworkWarnings.length > 0 ? frameworkWarnings : undefined,\n },\n next_steps: ['No files were modified. Check patch definitions and try again.'],\n }\n }\n\n const commitMsg = `[contentrain] normalize: reuse ${scopeTarget} — patch ${filesModified.length} files (${patchesApplied} replacements)`\n await tx.commit(commitMsg)\n\n const gitResult = { branch: branchName, action: 'pending-review', commit: '' }\n try {\n const completed = await tx.complete()\n gitResult.action = completed.action\n gitResult.commit = completed.commit\n } catch {\n gitResult.action = 'pending-review'\n } finally {\n await tx.cleanup()\n }\n\n return {\n dry_run: false,\n results: {\n files_modified: filesModified,\n patches_applied: patchesApplied,\n patches_skipped: patchesSkipped,\n imports_added: importsAdded,\n framework_warnings: frameworkWarnings.length > 0 ? frameworkWarnings : undefined,\n syntax_errors: syntaxErrors.length > 0 ? syntaxErrors : undefined,\n },\n ...(scopeWarnings.length > 0 ? { scope_warnings: scopeWarnings } : {}),\n git: gitResult,\n next_steps: [\n 'Run contentrain_validate to verify the patched files',\n patchesSkipped.length > 0 ? `${patchesSkipped.length} patches were skipped — review and retry if needed` : '',\n syntaxErrors.length > 0 ? `WARNING: ${syntaxErrors.length} file(s) may have syntax errors after patching — review manually` : '',\n scopeWarnings.length > 0 ? `NOTE: ${scopeWarnings.length} patch file(s) not in extract source map` : '',\n 'Run contentrain_submit to push the branch for review',\n 'For review: direct user to http://localhost:3333/branches or use contentrain_merge',\n 'After all reuse phases complete, run `npx contentrain generate` to update SDK types',\n ].filter(Boolean),\n }\n } catch (error) {\n await tx.cleanup()\n throw error\n }\n}\n\n// ─── Patch Helpers ───\n\n/**\n * Apply a single patch to a lines array. Mutates lines in place.\n * Uses line hint for proximity matching — searches ±10 lines from hint.\n */\nfunction applyPatchToLines(lines: string[], patch: PatchEntry): boolean {\n const { line, old_value, new_expression } = patch\n const lineIdx = line - 1 // 0-based\n\n // Search range: ±10 lines from hint\n const searchStart = Math.max(0, lineIdx - 10)\n const searchEnd = Math.min(lines.length, lineIdx + 11)\n\n // First pass: exact line match\n if (lineIdx >= 0 && lineIdx < lines.length) {\n const replaced = replaceInLine(lines[lineIdx]!, old_value, new_expression)\n if (replaced !== null) {\n lines[lineIdx] = replaced\n return true\n }\n }\n\n // Second pass: proximity search\n for (let i = searchStart; i < searchEnd; i++) {\n if (i === lineIdx) continue // already tried\n const replaced = replaceInLine(lines[i]!, old_value, new_expression)\n if (replaced !== null) {\n lines[i] = replaced\n return true\n }\n }\n\n return false\n}\n\n/**\n * Replace old_value with new_expression in a single line.\n * Matches the string literal (quoted or unquoted tag text).\n * Returns the modified line, or null if not found.\n *\n * Guardrail #4: Safer patch matching — word boundary awareness and\n * ambiguity rejection for plain text fallback.\n */\nexport function replaceInLine(line: string, oldValue: string, newExpression: string): string | null {\n // Try exact match of the value in quoted strings\n // Match: \"old_value\", 'old_value', `old_value`\n for (const quote of ['\"', \"'\", '`']) {\n const quoted = `${quote}${oldValue}${quote}`\n if (line.includes(quoted)) {\n return line.replace(quoted, newExpression)\n }\n }\n\n // Try unquoted tag text match: >old_value<\n // The agent provides the complete new_expression with correct framework syntax:\n // JSX: {t('key')}\n // Vue: {{ $t('key') }}\n // Svelte: {$t('key')}\n // So we insert the expression as-is between > and <, without wrapping in braces.\n if (line.includes(`>${oldValue}<`)) {\n return line.replace(`>${oldValue}<`, `>${newExpression}<`)\n }\n\n // Guardrail #4: Safer plain text fallback\n // Only match if old_value appears at a word boundary and is unambiguous\n if (line.includes(oldValue)) {\n // Count occurrences — if multiple, it's ambiguous\n const occurrences = countOccurrences(line, oldValue)\n if (occurrences > 1) {\n return null // ambiguous — let proximity search handle it\n }\n\n // Word boundary check: don't replace \"Submit\" inside \"SubmitButton\"\n // Reject if the old_value is a substring of a larger word — check if\n // adjacent characters are word characters that extend the token.\n const idx = line.indexOf(oldValue)\n const charBefore = idx > 0 ? line[idx - 1]! : ''\n const charAfter = idx + oldValue.length < line.length ? line[idx + oldValue.length]! : ''\n\n const oldStartsWithWord = oldValue.length > 0 && isWordChar(oldValue[0]!)\n const oldEndsWithWord = oldValue.length > 0 && isWordChar(oldValue[oldValue.length - 1]!)\n\n // If old_value starts with a word char and char before is also word char, it's a partial match\n if (oldStartsWithWord && isWordChar(charBefore)) {\n return null\n }\n // If old_value ends with a word char and char after is also word char, it's a partial match\n if (oldEndsWithWord && isWordChar(charAfter)) {\n return null\n }\n\n return line.replace(oldValue, newExpression)\n }\n\n return null\n}\n\n/** Count non-overlapping occurrences of a substring */\nfunction countOccurrences(str: string, sub: string): number {\n let count = 0\n let pos = 0\n while (pos <= str.length - sub.length) {\n const idx = str.indexOf(sub, pos)\n if (idx === -1) break\n count++\n pos = idx + sub.length\n }\n return count\n}\n\n/** Check if a character is a word character (letter, digit, underscore) */\nfunction isWordChar(ch: string): boolean {\n return /\\w/.test(ch)\n}\n\n/**\n * Add import statements to the top of a file (after existing imports).\n * Deduplicates — won't add if the import already exists.\n * Returns number of imports actually added.\n */\nfunction addImportsToLines(lines: string[], imports: Set<string>): number {\n let added = 0\n const existingContent = lines.join('\\n')\n\n // Find the last import line position (handles multi-line imports)\n let lastImportIdx = -1\n let inMultiLineImport = false\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i]!.trim()\n if (inMultiLineImport) {\n lastImportIdx = i\n if (trimmed.includes('}')) inMultiLineImport = false\n continue\n }\n if (trimmed.startsWith('import ') || trimmed.startsWith('import{')) {\n lastImportIdx = i\n // Check if this is a multi-line import (has { but no closing })\n if (trimmed.includes('{') && !trimmed.includes('}')) {\n inMultiLineImport = true\n }\n continue\n }\n // Stop searching after a non-import, non-empty, non-comment line following imports\n if (lastImportIdx >= 0 && trimmed.length > 0 && !trimmed.startsWith('//') && !trimmed.startsWith('/*') && !trimmed.startsWith('*')) {\n break\n }\n }\n\n let insertAt: number\n if (lastImportIdx >= 0) {\n insertAt = lastImportIdx + 1\n } else {\n // No existing imports found — insert after shebang and directive lines\n insertAt = 0\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i]!.trim()\n if (i === 0 && trimmed.startsWith('#!')) {\n insertAt = i + 1\n continue\n }\n if (trimmed === \"'use client'\" || trimmed === '\"use client\"'\n || trimmed === \"'use server'\" || trimmed === '\"use server\"'\n || trimmed === \"'use client';\" || trimmed === '\"use client\";'\n || trimmed === \"'use server';\" || trimmed === '\"use server\";') {\n insertAt = i + 1\n continue\n }\n if (insertAt > 0 && trimmed.length > 0) break\n if (insertAt === 0 && trimmed.length > 0) break\n }\n }\n const toInsert: string[] = []\n\n for (const imp of imports) {\n // Check if this import already exists (by checking the from clause)\n if (!existingContent.includes(imp)) {\n toInsert.push(imp)\n added++\n }\n }\n\n if (toInsert.length > 0) {\n lines.splice(insertAt, 0, ...toInsert)\n }\n\n return added\n}\n"],"mappings":"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"}
@@ -13,7 +13,7 @@ let _tsxParser = null;
13
13
  async function loadTsxParser() {
14
14
  if (_tsxParser) return _tsxParser;
15
15
  try {
16
- _tsxParser = (await import("./tsx-parser-Bf8Firrq.mjs")).parseTsx;
16
+ _tsxParser = (await import("./tsx-parser-BFLv292Y.mjs")).parseTsx;
17
17
  return _tsxParser;
18
18
  } catch {
19
19
  return null;
@@ -149,7 +149,7 @@ async function parseAstro(content, fileName) {
149
149
  if (tsxParser) {
150
150
  const frontmatterContent = frontmatter.value;
151
151
  const contentStartLine = (frontmatter.position?.start.line ?? 1) + 1;
152
- const scriptResults = tsxParser(frontmatterContent, fileName);
152
+ const scriptResults = tsxParser(frontmatterContent, fileName.replace(/\.astro$/, ".ts"));
153
153
  for (const r of scriptResults) results.push({
154
154
  ...r,
155
155
  line: r.line + contentStartLine - 1,
@@ -163,4 +163,4 @@ async function parseAstro(content, fileName) {
163
163
  //#endregion
164
164
  export { parseAstro };
165
165
 
166
- //# sourceMappingURL=astro-parser-RofKmKQi.mjs.map
166
+ //# sourceMappingURL=astro-parser-BJ-54JGe.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"astro-parser-RofKmKQi.mjs","names":[],"sources":["../src/core/ast-scanner/astro-parser.ts"],"sourcesContent":["// ─── Astro Parser for Scanner v2 ───\n// Parses .astro files using @astrojs/compiler.\n// Extracts ALL strings with structural context metadata.\n// Scanner does NOT classify — agent does. When in doubt, INCLUDE.\n\nimport type { ExtractedString } from './types.js'\n\n// ─── Lazy-loaded @astrojs/compiler ───\n\n// Astro AST node types from @astrojs/compiler\ninterface AstroBaseNode {\n type: string\n position?: {\n start: { line: number; column: number; offset: number }\n end?: { line: number; column: number; offset: number }\n }\n}\n\ninterface AstroRoot extends AstroBaseNode {\n type: 'root'\n children: AstroNode[]\n}\n\ninterface AstroElement extends AstroBaseNode {\n type: 'element'\n name: string\n attributes: AstroAttribute[]\n children: AstroNode[]\n}\n\ninterface AstroComponent extends AstroBaseNode {\n type: 'component'\n name: string\n attributes: AstroAttribute[]\n children: AstroNode[]\n}\n\ninterface AstroCustomElement extends AstroBaseNode {\n type: 'custom-element'\n name: string\n attributes: AstroAttribute[]\n children: AstroNode[]\n}\n\ninterface AstroFragment extends AstroBaseNode {\n type: 'fragment'\n children: AstroNode[]\n}\n\ninterface AstroText extends AstroBaseNode {\n type: 'text'\n value: string\n}\n\ninterface AstroExpression extends AstroBaseNode {\n type: 'expression'\n children: AstroNode[]\n}\n\ninterface AstroFrontmatter extends AstroBaseNode {\n type: 'frontmatter'\n value: string\n}\n\ninterface AstroComment extends AstroBaseNode {\n type: 'comment'\n value: string\n}\n\ninterface AstroDoctype extends AstroBaseNode {\n type: 'doctype'\n}\n\ninterface AstroAttribute {\n name: string\n type: 'attribute' | 'expression' | 'spread' | 'shorthand' | 'template-literal'\n kind: string\n value: string\n raw?: string\n position?: {\n start: { line: number; column: number; offset: number }\n end?: { line: number; column: number; offset: number }\n }\n}\n\ntype AstroNode =\n | AstroRoot\n | AstroElement\n | AstroComponent\n | AstroCustomElement\n | AstroFragment\n | AstroText\n | AstroExpression\n | AstroFrontmatter\n | AstroComment\n | AstroDoctype\n | AstroBaseNode\n\ninterface AstroParseResult {\n ast: AstroRoot\n}\n\ninterface AstroCompiler {\n parse: (source: string, options?: { position?: boolean }) => Promise<AstroParseResult>\n}\n\nlet _compiler: AstroCompiler | null = null\n\nasync function loadCompiler(): Promise<AstroCompiler> {\n if (_compiler) return _compiler\n try {\n const mod = await import('@astrojs/compiler')\n _compiler = mod as unknown as AstroCompiler\n return _compiler\n } catch {\n throw new Error(\n '@astrojs/compiler is required to parse .astro files. '\n + 'Install it with: pnpm add -D @astrojs/compiler',\n )\n }\n}\n\n// ─── tsx-parser delegation ───\n\ntype TsxParserFn = (content: string, fileName: string) => ExtractedString[]\n\nlet _tsxParser: TsxParserFn | null = null\n\nasync function loadTsxParser(): Promise<TsxParserFn | null> {\n if (_tsxParser) return _tsxParser\n try {\n const mod = await import('./tsx-parser.js')\n _tsxParser = mod.parseTsx\n return _tsxParser\n } catch {\n return null\n }\n}\n\n// ─── Constants ───\n\nconst SURROUNDING_MAX = 120\n\n/** Attributes whose values are CSS, not content */\nconst CSS_ATTRIBUTES = new Set(['class', 'style', 'className'])\n\n/** Event/code attribute prefixes — skip these */\nconst CODE_ATTRIBUTE_PREFIXES = ['on', 'set:', 'define:', 'is:']\n\n// ─── Helpers ───\n\nfunction getSurroundingByLine(content: string, line: number): string {\n const lines = content.split('\\n')\n const idx = line - 1\n const start = Math.max(0, idx - 1)\n const end = Math.min(lines.length - 1, idx + 1)\n\n const parts: string[] = []\n for (let i = start; i <= end; i++) {\n const l = lines[i]\n if (l !== undefined) {\n parts.push(l)\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\nfunction _getLineAndColumn(content: string, offset: number): { line: number; column: number } {\n let line = 1\n let lastNewline = -1\n\n for (let i = 0; i < offset && i < content.length; i++) {\n if (content[i] === '\\n') {\n line++\n lastNewline = i\n }\n }\n\n return { line, column: offset - lastNewline }\n}\n\n// ─── Template AST Walker ───\n\nfunction walkAstroTemplate(\n node: AstroNode,\n content: string,\n results: ExtractedString[],\n parentTag: string = '',\n): void {\n const nodeType = node.type\n\n switch (nodeType) {\n case 'root': {\n const root = node as AstroRoot\n for (const child of root.children) {\n walkAstroTemplate(child, content, results, parentTag)\n }\n break\n }\n\n case 'text': {\n const textNode = node as AstroText\n const trimmed = textNode.value.trim()\n if (trimmed.length > 0 && /\\S/.test(trimmed)) {\n const line = textNode.position?.start.line ?? 1\n const column = textNode.position?.start.column ?? 1\n results.push({\n value: trimmed,\n line,\n column,\n context: 'template_text',\n scope: 'template',\n parent: parentTag,\n surrounding: getSurroundingByLine(content, line),\n })\n }\n break\n }\n\n case 'element':\n case 'component':\n case 'custom-element': {\n const el = node as AstroElement | AstroComponent | AstroCustomElement\n const tag = el.name\n\n // Process attributes\n for (const attr of el.attributes) {\n processAttribute(attr, tag, content, results)\n }\n\n // Recurse into children\n for (const child of el.children) {\n walkAstroTemplate(child, content, results, tag)\n }\n break\n }\n\n case 'fragment': {\n const fragment = node as AstroFragment\n for (const child of fragment.children) {\n walkAstroTemplate(child, content, results, parentTag)\n }\n break\n }\n\n case 'expression': {\n // JSX expressions like {variable} — code, skip\n // (The agent decides if embedded strings in expressions matter)\n break\n }\n\n case 'frontmatter':\n case 'comment':\n case 'doctype': {\n // Frontmatter is handled separately via tsx-parser delegation\n // Comments and doctype are skipped\n break\n }\n\n default: {\n // For unknown node types, try to walk children\n const unknownNode = node as AstroBaseNode & { children?: AstroNode[] }\n if (unknownNode.children) {\n for (const child of unknownNode.children) {\n walkAstroTemplate(child, content, results, parentTag)\n }\n }\n break\n }\n }\n}\n\nfunction processAttribute(\n attr: AstroAttribute,\n parentTag: string,\n content: string,\n results: ExtractedString[],\n): void {\n const attrName = attr.name\n\n // Skip expression attributes (dynamic bindings) and spread attributes\n if (attr.type === 'expression' || attr.type === 'spread' || attr.type === 'shorthand') return\n\n // Skip code-related attributes (event handlers, directives)\n for (const prefix of CODE_ATTRIBUTE_PREFIXES) {\n if (attrName.startsWith(prefix)) return\n }\n\n // Skip boolean attributes (no value)\n const attrValue = attr.value\n if (!attrValue || (typeof attrValue === 'string' && attrValue.trim().length === 0)) return\n\n const line = attr.position?.start.line ?? 1\n const column = attr.position?.start.column ?? 1\n\n // CSS attributes get css_class context\n if (CSS_ATTRIBUTES.has(attrName)) {\n results.push({\n value: attrValue,\n line,\n column,\n context: 'css_class',\n scope: 'template',\n parent: parentTag,\n parentProperty: attrName,\n surrounding: getSurroundingByLine(content, line),\n })\n return\n }\n\n results.push({\n value: attrValue,\n line,\n column,\n context: 'template_attribute',\n scope: 'template',\n parent: parentTag,\n parentProperty: attrName,\n surrounding: getSurroundingByLine(content, line),\n })\n}\n\n// ─── Frontmatter Parsing ───\n\nfunction findFrontmatter(ast: AstroRoot): AstroFrontmatter | null {\n for (const child of ast.children) {\n if (child.type === 'frontmatter') {\n return child as AstroFrontmatter\n }\n }\n return null\n}\n\n// ─── Main Export ───\n\nexport async function parseAstro(content: string, fileName: string): Promise<ExtractedString[]> {\n const compiler = await loadCompiler()\n const results: ExtractedString[] = []\n\n let parseResult: AstroParseResult\n try {\n parseResult = await compiler.parse(content, { position: true })\n } catch {\n // If Astro parsing fails, return empty — malformed files shouldn't block scanning\n return []\n }\n\n const ast = parseResult.ast\n\n // ─── Frontmatter → tsx-parser ───\n const frontmatter = findFrontmatter(ast)\n if (frontmatter && frontmatter.value.trim().length > 0) {\n const tsxParser = await loadTsxParser()\n if (tsxParser) {\n const frontmatterContent = frontmatter.value\n // Frontmatter starts after the opening ---\n const frontmatterLine = frontmatter.position?.start.line ?? 1\n // The content starts on the line after ---\n const contentStartLine = frontmatterLine + 1\n\n const scriptResults = tsxParser(frontmatterContent, fileName)\n for (const r of scriptResults) {\n results.push({\n ...r,\n line: r.line + contentStartLine - 1,\n scope: 'script',\n })\n }\n }\n }\n\n // ─── Template (everything outside frontmatter) ───\n walkAstroTemplate(ast, content, results)\n\n return results\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"astro-parser-BJ-54JGe.mjs","names":[],"sources":["../src/core/ast-scanner/astro-parser.ts"],"sourcesContent":["// ─── Astro Parser for Scanner v2 ───\n// Parses .astro files using @astrojs/compiler.\n// Extracts ALL strings with structural context metadata.\n// Scanner does NOT classify — agent does. When in doubt, INCLUDE.\n\nimport type { ExtractedString } from './types.js'\n\n// ─── Lazy-loaded @astrojs/compiler ───\n\n// Astro AST node types from @astrojs/compiler\ninterface AstroBaseNode {\n type: string\n position?: {\n start: { line: number; column: number; offset: number }\n end?: { line: number; column: number; offset: number }\n }\n}\n\ninterface AstroRoot extends AstroBaseNode {\n type: 'root'\n children: AstroNode[]\n}\n\ninterface AstroElement extends AstroBaseNode {\n type: 'element'\n name: string\n attributes: AstroAttribute[]\n children: AstroNode[]\n}\n\ninterface AstroComponent extends AstroBaseNode {\n type: 'component'\n name: string\n attributes: AstroAttribute[]\n children: AstroNode[]\n}\n\ninterface AstroCustomElement extends AstroBaseNode {\n type: 'custom-element'\n name: string\n attributes: AstroAttribute[]\n children: AstroNode[]\n}\n\ninterface AstroFragment extends AstroBaseNode {\n type: 'fragment'\n children: AstroNode[]\n}\n\ninterface AstroText extends AstroBaseNode {\n type: 'text'\n value: string\n}\n\ninterface AstroExpression extends AstroBaseNode {\n type: 'expression'\n children: AstroNode[]\n}\n\ninterface AstroFrontmatter extends AstroBaseNode {\n type: 'frontmatter'\n value: string\n}\n\ninterface AstroComment extends AstroBaseNode {\n type: 'comment'\n value: string\n}\n\ninterface AstroDoctype extends AstroBaseNode {\n type: 'doctype'\n}\n\ninterface AstroAttribute {\n name: string\n type: 'attribute' | 'expression' | 'spread' | 'shorthand' | 'template-literal'\n kind: string\n value: string\n raw?: string\n position?: {\n start: { line: number; column: number; offset: number }\n end?: { line: number; column: number; offset: number }\n }\n}\n\ntype AstroNode =\n | AstroRoot\n | AstroElement\n | AstroComponent\n | AstroCustomElement\n | AstroFragment\n | AstroText\n | AstroExpression\n | AstroFrontmatter\n | AstroComment\n | AstroDoctype\n | AstroBaseNode\n\ninterface AstroParseResult {\n ast: AstroRoot\n}\n\ninterface AstroCompiler {\n parse: (source: string, options?: { position?: boolean }) => Promise<AstroParseResult>\n}\n\nlet _compiler: AstroCompiler | null = null\n\nasync function loadCompiler(): Promise<AstroCompiler> {\n if (_compiler) return _compiler\n try {\n const mod = await import('@astrojs/compiler')\n _compiler = mod as unknown as AstroCompiler\n return _compiler\n } catch {\n throw new Error(\n '@astrojs/compiler is required to parse .astro files. '\n + 'Install it with: pnpm add -D @astrojs/compiler',\n )\n }\n}\n\n// ─── tsx-parser delegation ───\n\ntype TsxParserFn = (content: string, fileName: string) => ExtractedString[]\n\nlet _tsxParser: TsxParserFn | null = null\n\nasync function loadTsxParser(): Promise<TsxParserFn | null> {\n if (_tsxParser) return _tsxParser\n try {\n const mod = await import('./tsx-parser.js')\n _tsxParser = mod.parseTsx\n return _tsxParser\n } catch {\n return null\n }\n}\n\n// ─── Constants ───\n\nconst SURROUNDING_MAX = 120\n\n/** Attributes whose values are CSS, not content */\nconst CSS_ATTRIBUTES = new Set(['class', 'style', 'className'])\n\n/** Event/code attribute prefixes — skip these */\nconst CODE_ATTRIBUTE_PREFIXES = ['on', 'set:', 'define:', 'is:']\n\n// ─── Helpers ───\n\nfunction getSurroundingByLine(content: string, line: number): string {\n const lines = content.split('\\n')\n const idx = line - 1\n const start = Math.max(0, idx - 1)\n const end = Math.min(lines.length - 1, idx + 1)\n\n const parts: string[] = []\n for (let i = start; i <= end; i++) {\n const l = lines[i]\n if (l !== undefined) {\n parts.push(l)\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\nfunction _getLineAndColumn(content: string, offset: number): { line: number; column: number } {\n let line = 1\n let lastNewline = -1\n\n for (let i = 0; i < offset && i < content.length; i++) {\n if (content[i] === '\\n') {\n line++\n lastNewline = i\n }\n }\n\n return { line, column: offset - lastNewline }\n}\n\n// ─── Template AST Walker ───\n\nfunction walkAstroTemplate(\n node: AstroNode,\n content: string,\n results: ExtractedString[],\n parentTag: string = '',\n): void {\n const nodeType = node.type\n\n switch (nodeType) {\n case 'root': {\n const root = node as AstroRoot\n for (const child of root.children) {\n walkAstroTemplate(child, content, results, parentTag)\n }\n break\n }\n\n case 'text': {\n const textNode = node as AstroText\n const trimmed = textNode.value.trim()\n if (trimmed.length > 0 && /\\S/.test(trimmed)) {\n const line = textNode.position?.start.line ?? 1\n const column = textNode.position?.start.column ?? 1\n results.push({\n value: trimmed,\n line,\n column,\n context: 'template_text',\n scope: 'template',\n parent: parentTag,\n surrounding: getSurroundingByLine(content, line),\n })\n }\n break\n }\n\n case 'element':\n case 'component':\n case 'custom-element': {\n const el = node as AstroElement | AstroComponent | AstroCustomElement\n const tag = el.name\n\n // Process attributes\n for (const attr of el.attributes) {\n processAttribute(attr, tag, content, results)\n }\n\n // Recurse into children\n for (const child of el.children) {\n walkAstroTemplate(child, content, results, tag)\n }\n break\n }\n\n case 'fragment': {\n const fragment = node as AstroFragment\n for (const child of fragment.children) {\n walkAstroTemplate(child, content, results, parentTag)\n }\n break\n }\n\n case 'expression': {\n // JSX expressions like {variable} — code, skip\n // (The agent decides if embedded strings in expressions matter)\n break\n }\n\n case 'frontmatter':\n case 'comment':\n case 'doctype': {\n // Frontmatter is handled separately via tsx-parser delegation\n // Comments and doctype are skipped\n break\n }\n\n default: {\n // For unknown node types, try to walk children\n const unknownNode = node as AstroBaseNode & { children?: AstroNode[] }\n if (unknownNode.children) {\n for (const child of unknownNode.children) {\n walkAstroTemplate(child, content, results, parentTag)\n }\n }\n break\n }\n }\n}\n\nfunction processAttribute(\n attr: AstroAttribute,\n parentTag: string,\n content: string,\n results: ExtractedString[],\n): void {\n const attrName = attr.name\n\n // Skip expression attributes (dynamic bindings) and spread attributes\n if (attr.type === 'expression' || attr.type === 'spread' || attr.type === 'shorthand') return\n\n // Skip code-related attributes (event handlers, directives)\n for (const prefix of CODE_ATTRIBUTE_PREFIXES) {\n if (attrName.startsWith(prefix)) return\n }\n\n // Skip boolean attributes (no value)\n const attrValue = attr.value\n if (!attrValue || (typeof attrValue === 'string' && attrValue.trim().length === 0)) return\n\n const line = attr.position?.start.line ?? 1\n const column = attr.position?.start.column ?? 1\n\n // CSS attributes get css_class context\n if (CSS_ATTRIBUTES.has(attrName)) {\n results.push({\n value: attrValue,\n line,\n column,\n context: 'css_class',\n scope: 'template',\n parent: parentTag,\n parentProperty: attrName,\n surrounding: getSurroundingByLine(content, line),\n })\n return\n }\n\n results.push({\n value: attrValue,\n line,\n column,\n context: 'template_attribute',\n scope: 'template',\n parent: parentTag,\n parentProperty: attrName,\n surrounding: getSurroundingByLine(content, line),\n })\n}\n\n// ─── Frontmatter Parsing ───\n\nfunction findFrontmatter(ast: AstroRoot): AstroFrontmatter | null {\n for (const child of ast.children) {\n if (child.type === 'frontmatter') {\n return child as AstroFrontmatter\n }\n }\n return null\n}\n\n// ─── Main Export ───\n\nexport async function parseAstro(content: string, fileName: string): Promise<ExtractedString[]> {\n const compiler = await loadCompiler()\n const results: ExtractedString[] = []\n\n let parseResult: AstroParseResult\n try {\n parseResult = await compiler.parse(content, { position: true })\n } catch {\n // If Astro parsing fails, return empty — malformed files shouldn't block scanning\n return []\n }\n\n const ast = parseResult.ast\n\n // ─── Frontmatter → tsx-parser ───\n const frontmatter = findFrontmatter(ast)\n if (frontmatter && frontmatter.value.trim().length > 0) {\n const tsxParser = await loadTsxParser()\n if (tsxParser) {\n const frontmatterContent = frontmatter.value\n // Frontmatter starts after the opening ---\n const frontmatterLine = frontmatter.position?.start.line ?? 1\n // The content starts on the line after ---\n const contentStartLine = frontmatterLine + 1\n\n // Astro frontmatter is always TypeScript — resolve filename accordingly\n const resolvedFileName = fileName.replace(/\\.astro$/, '.ts')\n const scriptResults = tsxParser(frontmatterContent, resolvedFileName)\n for (const r of scriptResults) {\n results.push({\n ...r,\n line: r.line + contentStartLine - 1,\n scope: 'script',\n })\n }\n }\n }\n\n // ─── Template (everything outside frontmatter) ───\n walkAstroTemplate(ast, content, results)\n\n return results\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"apply-manager.d.mts","names":[],"sources":["../../src/core/apply-manager.ts"],"mappings":";;;UAYiB,eAAA;EACf,KAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;EACA,MAAA,GAAS,MAAA,SAAe,QAAA;EACxB,OAAA,EAAS,KAAA;IACP,MAAA;IACA,IAAA;IACA,IAAA,EAAM,MAAA;IACN,MAAA;MAAW,IAAA;MAAc,IAAA;MAAc,KAAA;IAAA;IACvC,OAAA,GAAU,KAAA;MAAQ,IAAA;MAAc,IAAA;MAAc,GAAA;MAAa,KAAA;IAAA;EAAA;AAAA;AAAA,UAI9C,eAAA;EACf,WAAA,EAAa,eAAA;EACb,OAAA;AAAA;AAAA,UAGe,iBAAA;EACf,gBAAA;EACA,gBAAA;EACA,aAAA;EACA,aAAA;AAAA;AAAA,UAGe,gBAAA;EACf,OAAA;EACA,OAAA,GAAU,iBAAA;EACV,KAAA;EACA,iBAAA;EACA,OAAA;IACE,cAAA;IACA,cAAA;IACA,eAAA;IACA,UAAA,EAAY,KAAA;MAAQ,KAAA;MAAe,MAAA;MAAgB,KAAA;MAAe,IAAA;MAAc,IAAA;IAAA;EAAA;EAElF,GAAA;IAAQ,MAAA;IAAgB,MAAA;IAAgB,MAAA;EAAA;EACxC,eAAA;EACA,UAAA;AAAA;AAAA,UAGe,UAAA;EACf,IAAA;EACA,IAAA;EACA,SAAA;EACA,cAAA;EACA,gBAAA;AAAA;AAAA,UAGe,UAAA;EACf,KAAA;IAAS,KAAA;IAAgB,MAAA;EAAA;EACzB,OAAA,EAAS,UAAA;EACT,OAAA;AAAA;AAAA,UAGe,WAAA;EACf,IAAA;EACA,KAAA;AAAA;AAAA,UAGe,WAAA;EACf,OAAA;EACA,KAAA;EACA,cAAA;EACA,OAAA;IACE,eAAA;IACA,aAAA;IACA,cAAA;EAAA;EAEF,OAAA;IACE,cAAA;IACA,eAAA;IACA,eAAA,EAAiB,KAAA;MAAQ,IAAA;MAAc,IAAA;MAAc,MAAA;IAAA;IACrD,aAAA;IACA,kBAAA,GAAqB,KAAA;MAAQ,IAAA;MAAc,OAAA;IAAA;IAC3C,aAAA,GAAgB,WAAA;EAAA;EAElB,GAAA;IAAQ,MAAA;IAAgB,MAAA;IAAgB,MAAA;EAAA;EACxC,UAAA;AAAA;;cAQW,oBAAA,EAAoB,GAAA;AAAA,iBAajB,mBAAA,CAAoB,QAAA,WAAmB,eAAA;AAvCvD;;;;AAAA,iBA0DgB,2BAAA,CACd,QAAA,UACA,aAAA,UACA,OAAA;;;;;iBAyCc,iBAAA,CAAkB,QAAA;;;;;iBAoClB,WAAA,CAAY,QAAA,UAAkB,OAAA;AAAA,iBA8JxB,YAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,GACN,OAAA,CAAQ,gBAAA;AAAA,iBAiRW,UAAA,CACpB,WAAA,UACA,KAAA,EAAO,UAAA,GACN,OAAA,CAAQ,WAAA;;;;;;;;;iBAmYK,aAAA,CAAc,IAAA,UAAc,QAAA,UAAkB,aAAA"}
1
+ {"version":3,"file":"apply-manager.d.mts","names":[],"sources":["../../src/core/apply-manager.ts"],"mappings":";;;UAYiB,eAAA;EACf,KAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;EACA,MAAA,GAAS,MAAA,SAAe,QAAA;EACxB,OAAA,EAAS,KAAA;IACP,MAAA;IACA,IAAA;IACA,IAAA,EAAM,MAAA;IACN,MAAA;MAAW,IAAA;MAAc,IAAA;MAAc,KAAA;IAAA;IACvC,OAAA,GAAU,KAAA;MAAQ,IAAA;MAAc,IAAA;MAAc,GAAA;MAAa,KAAA;IAAA;EAAA;AAAA;AAAA,UAI9C,eAAA;EACf,WAAA,EAAa,eAAA;EACb,OAAA;AAAA;AAAA,UAGe,iBAAA;EACf,gBAAA;EACA,gBAAA;EACA,aAAA;EACA,aAAA;AAAA;AAAA,UAGe,gBAAA;EACf,OAAA;EACA,OAAA,GAAU,iBAAA;EACV,KAAA;EACA,iBAAA;EACA,OAAA;IACE,cAAA;IACA,cAAA;IACA,eAAA;IACA,UAAA,EAAY,KAAA;MAAQ,KAAA;MAAe,MAAA;MAAgB,KAAA;MAAe,IAAA;MAAc,IAAA;IAAA;EAAA;EAElF,GAAA;IAAQ,MAAA;IAAgB,MAAA;IAAgB,MAAA;EAAA;EACxC,eAAA;EACA,UAAA;AAAA;AAAA,UAGe,UAAA;EACf,IAAA;EACA,IAAA;EACA,SAAA;EACA,cAAA;EACA,gBAAA;AAAA;AAAA,UAGe,UAAA;EACf,KAAA;IAAS,KAAA;IAAgB,MAAA;EAAA;EACzB,OAAA,EAAS,UAAA;EACT,OAAA;AAAA;AAAA,UAGe,WAAA;EACf,IAAA;EACA,KAAA;AAAA;AAAA,UAGe,WAAA;EACf,OAAA;EACA,KAAA;EACA,cAAA;EACA,OAAA;IACE,eAAA;IACA,aAAA;IACA,cAAA;EAAA;EAEF,OAAA;IACE,cAAA;IACA,eAAA;IACA,eAAA,EAAiB,KAAA;MAAQ,IAAA;MAAc,IAAA;MAAc,MAAA;IAAA;IACrD,aAAA;IACA,kBAAA,GAAqB,KAAA;MAAQ,IAAA;MAAc,OAAA;IAAA;IAC3C,aAAA,GAAgB,WAAA;EAAA;EAElB,GAAA;IAAQ,MAAA;IAAgB,MAAA;IAAgB,MAAA;EAAA;EACxC,UAAA;AAAA;;cAQW,oBAAA,EAAoB,GAAA;AAAA,iBAajB,mBAAA,CAAoB,QAAA,WAAmB,eAAA;AAvCvD;;;;AAAA,iBA0DgB,2BAAA,CACd,QAAA,UACA,aAAA,UACA,OAAA;;;;;iBAyCc,iBAAA,CAAkB,QAAA;;;;;iBAoClB,WAAA,CAAY,QAAA,UAAkB,OAAA;AAAA,iBA8JxB,YAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,GACN,OAAA,CAAQ,gBAAA;AAAA,iBAoRW,UAAA,CACpB,WAAA,UACA,KAAA,EAAO,UAAA,GACN,OAAA,CAAQ,WAAA;;;;;;;;;iBAqYK,aAAA,CAAc,IAAA,UAAc,QAAA,UAAkB,aAAA"}
@@ -6,6 +6,6 @@ import "../model-manager-DW_fOGRM.mjs";
6
6
  import "../context-DIDt_TxX.mjs";
7
7
  import "../branch-lifecycle-wGJg25LT.mjs";
8
8
  import "../id-DV_T9Ic8.mjs";
9
- import "../transaction-B0V0_SE2.mjs";
10
- import { a as detectFileFramework, c as validatePatchPath, i as checkSyntax, n as applyExtract, o as replaceInLine, r as applyReuse, s as validateFrameworkExpression, t as PATCHABLE_EXTENSIONS } from "../apply-manager-Cf5OWOJ7.mjs";
9
+ import "../transaction-C_Pvux78.mjs";
10
+ import { a as detectFileFramework, c as validatePatchPath, i as checkSyntax, n as applyExtract, o as replaceInLine, r as applyReuse, s as validateFrameworkExpression, t as PATCHABLE_EXTENSIONS } from "../apply-manager-BkPMfpsN.mjs";
11
11
  export { PATCHABLE_EXTENSIONS, applyExtract, applyReuse, checkSyntax, detectFileFramework, replaceInLine, validateFrameworkExpression, validatePatchPath };
@@ -9,17 +9,11 @@ interface ScanOptions {
9
9
  offset?: number;
10
10
  min_length?: number;
11
11
  max_length?: number;
12
+ /** Minimum content confidence score (0-1). Default: 0.4 */
13
+ min_score?: number;
12
14
  }
13
- /**
14
- * Determines if a string is non-content (technical) and should be filtered out.
15
- * Pure function — no side effects, no semantic decisions.
16
- * Conservative: if in doubt, returns false (keeps the string).
17
- *
18
- * @deprecated Kept for backward compatibility. Scanner v2 uses structural pre-filter instead.
19
- */
20
- declare function isNonContent(value: string, lineContext: string, minLength?: number, maxLength?: number): boolean;
21
15
  declare function scanCandidates(projectRoot: string, options?: ScanOptions): Promise<ScanCandidatesResult>;
22
16
  declare function scanSummary(projectRoot: string, options?: ScanOptions): Promise<ScanSummaryResult>;
23
17
  //#endregion
24
- export { ScanOptions, isNonContent, scanCandidates, scanSummary };
18
+ export { ScanOptions, scanCandidates, scanSummary };
25
19
  //# sourceMappingURL=scanner.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scanner.d.mts","names":[],"sources":["../../src/core/scanner.ts"],"mappings":";;;UAkBiB,WAAA;EACf,KAAA;EACA,OAAA;EACA,OAAA;EACA,KAAA;EACA,MAAA;EACA,UAAA;EACA,UAAA;AAAA;;;;;;;AAmEF;iBAAgB,YAAA,CAAa,KAAA,UAAe,WAAA,UAAqB,SAAA,WAAwC,SAAA;AAAA,iBA0GnF,cAAA,CACpB,WAAA,UACA,OAAA,GAAU,WAAA,GACT,OAAA,CAAQ,oBAAA;AAAA,iBAmFW,WAAA,CACpB,WAAA,UACA,OAAA,GAAU,WAAA,GACT,OAAA,CAAQ,iBAAA"}
1
+ {"version":3,"file":"scanner.d.mts","names":[],"sources":["../../src/core/scanner.ts"],"mappings":";;;UAmBiB,WAAA;EACf,KAAA;EACA,OAAA;EACA,OAAA;EACA,KAAA;EACA,MAAA;EACA,UAAA;EACA,UAAA;EAJA;EAMA,SAAA;AAAA;AAAA,iBAsCoB,cAAA,CACpB,WAAA,UACA,OAAA,GAAU,WAAA,GACT,OAAA,CAAQ,oBAAA;AAAA,iBA+IW,WAAA,CACpB,WAAA,UACA,OAAA,GAAU,WAAA,GACT,OAAA,CAAQ,iBAAA"}
@@ -1,6 +1,6 @@
1
1
  import "../serializer-CnHTtmc1.mjs";
2
2
  import "../fs-BnIQ4dKj.mjs";
3
3
  import "../scan-config-QgnJ1-52.mjs";
4
- import { n as scanCandidates, r as scanSummary, t as isNonContent } from "../scanner-DPrbU-oK.mjs";
5
- import "../tsx-parser-CYVyBWjn.mjs";
6
- export { isNonContent, scanCandidates, scanSummary };
4
+ import { n as scanSummary, t as scanCandidates } from "../scanner-CQOxARUU.mjs";
5
+ import "../tsx-parser-BWiMGAIR.mjs";
6
+ export { scanCandidates, scanSummary };
@@ -24,7 +24,12 @@ declare function ensureContentBranch(projectRoot: string): Promise<void>;
24
24
  declare function createTransaction(projectRoot: string, branchName: string, options?: {
25
25
  workflowOverride?: 'review' | 'auto-merge';
26
26
  }): Promise<GitTransaction>;
27
+ declare function mergeBranch(projectRoot: string, branchName: string): Promise<{
28
+ action: 'merged';
29
+ commit: string;
30
+ sync: SyncResult;
31
+ }>;
27
32
  declare function buildBranchName(scope: string, target: string, locale?: string): string;
28
33
  //#endregion
29
- export { ContextUpdate, GitTransaction, buildBranchName, createTransaction, ensureContentBranch };
34
+ export { ContextUpdate, GitTransaction, buildBranchName, createTransaction, ensureContentBranch, mergeBranch };
30
35
  //# sourceMappingURL=transaction.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transaction.d.mts","names":[],"sources":["../../src/git/transaction.ts"],"mappings":";;;UAWiB,aAAA;EACf,IAAA;EACA,KAAA;EACA,MAAA;EACA,OAAA;AAAA;AAAA,UAGe,cAAA;EACf,QAAA;EACA,MAAA;EACA,KAAA,CAAM,QAAA,GAAW,YAAA,aAAyB,OAAA,SAAgB,OAAA;EAC1D,MAAA,CAAO,OAAA,UAAiB,aAAA,GAAgB,aAAA,GAAgB,OAAA;EACxD,QAAA,IAAY,OAAA;IAAU,MAAA;IAA0C,MAAA;IAAgB,IAAA,GAAO,UAAA;IAAY,OAAA;EAAA;EACnG,OAAA,IAAW,OAAA;AAAA;AAAA,iBAGS,mBAAA,CAAoB,WAAA,WAAsB,OAAA;AAAA,iBA2I1C,iBAAA,CACpB,WAAA,UACA,UAAA,UACA,OAAA;EAAY,gBAAA;AAAA,IACX,OAAA,CAAQ,cAAA;AAAA,iBA0NK,eAAA,CAAgB,KAAA,UAAe,MAAA,UAAgB,MAAA"}
1
+ {"version":3,"file":"transaction.d.mts","names":[],"sources":["../../src/git/transaction.ts"],"mappings":";;;UAWiB,aAAA;EACf,IAAA;EACA,KAAA;EACA,MAAA;EACA,OAAA;AAAA;AAAA,UAGe,cAAA;EACf,QAAA;EACA,MAAA;EACA,KAAA,CAAM,QAAA,GAAW,YAAA,aAAyB,OAAA,SAAgB,OAAA;EAC1D,MAAA,CAAO,OAAA,UAAiB,aAAA,GAAgB,aAAA,GAAgB,OAAA;EACxD,QAAA,IAAY,OAAA;IAAU,MAAA;IAA0C,MAAA;IAAgB,IAAA,GAAO,UAAA;IAAY,OAAA;EAAA;EACnG,OAAA,IAAW,OAAA;AAAA;AAAA,iBAGS,mBAAA,CAAoB,WAAA,WAAsB,OAAA;AAAA,iBA2I1C,iBAAA,CACpB,WAAA,UACA,UAAA,UACA,OAAA;EAAY,gBAAA;AAAA,IACX,OAAA,CAAQ,cAAA;AAAA,iBA0NW,WAAA,CACpB,WAAA,UACA,UAAA,WACC,OAAA;EAAU,MAAA;EAAkB,MAAA;EAAgB,IAAA,EAAM,UAAA;AAAA;AAAA,iBA0HrC,eAAA,CAAgB,KAAA,UAAe,MAAA,UAAgB,MAAA"}
@@ -5,5 +5,5 @@ import "../meta-manager--0IwAPCl.mjs";
5
5
  import "../model-manager-DW_fOGRM.mjs";
6
6
  import "../context-DIDt_TxX.mjs";
7
7
  import "../id-DV_T9Ic8.mjs";
8
- import { n as createTransaction, r as ensureContentBranch, t as buildBranchName } from "../transaction-B0V0_SE2.mjs";
9
- export { buildBranchName, createTransaction, ensureContentBranch };
8
+ import { i as mergeBranch, n as createTransaction, r as ensureContentBranch, t as buildBranchName } from "../transaction-C_Pvux78.mjs";
9
+ export { buildBranchName, createTransaction, ensureContentBranch, mergeBranch };
package/dist/index.mjs CHANGED
@@ -7,15 +7,15 @@ import "./model-manager-DW_fOGRM.mjs";
7
7
  import "./context-DIDt_TxX.mjs";
8
8
  import "./detect-DinOQAmK.mjs";
9
9
  import "./branch-lifecycle-wGJg25LT.mjs";
10
- import { t as createServer } from "./server-CjK63H6X.mjs";
10
+ import { t as createServer } from "./server-DfWRpzdP.mjs";
11
11
  import "./id-DV_T9Ic8.mjs";
12
- import "./transaction-B0V0_SE2.mjs";
12
+ import "./transaction-C_Pvux78.mjs";
13
13
  import "./validator-SQYUV2JP.mjs";
14
14
  import "./scan-config-QgnJ1-52.mjs";
15
15
  import "./graph-builder-tM7zj-SX.mjs";
16
- import "./scanner-DPrbU-oK.mjs";
17
- import "./tsx-parser-CYVyBWjn.mjs";
18
- import "./apply-manager-Cf5OWOJ7.mjs";
16
+ import "./scanner-CQOxARUU.mjs";
17
+ import "./tsx-parser-BWiMGAIR.mjs";
18
+ import "./apply-manager-BkPMfpsN.mjs";
19
19
  import { resolve } from "node:path";
20
20
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
21
21
  //#region src/index.ts