Skip to content
Merged
244 changes: 244 additions & 0 deletions .omd/project-memory.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
{
"version": "1.0.0",
"lastScanned": 1772269310504,
"projectRoot": "/Users/hubo/.superset/worktrees/screentranslate/featadd-translate-engine",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

硬编码的本地路径泄露了开发者环境信息。

projectRoot 包含完整的本地用户目录路径 /Users/hubo/.superset/worktrees/...,这属于敏感的开发环境信息,不应出现在公开仓库中。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.omd/project-memory.json at line 4, The projectMemory file currently
contains a hardcoded local absolute path in the projectRoot key which exposes a
developer's local filesystem; update the .omd/project-memory.json to remove or
neutralize that sensitive value by replacing the absolute path stored in the
"projectRoot" key with a non-sensitive relative path or a placeholder (e.g.
"<PROJECT_ROOT>") and ensure this file is not committed for local-specific
values (add .omd/project-memory.json to .gitignore or switch to
templated/configured generation at runtime); also scrub the repository history
or create a new commit that removes the leaked path so the sensitive data is not
retained in the repo.

"techStack": {
"languages": [],
"frameworks": [],
"packageManager": null,
"runtime": null
},
"build": {
"buildCommand": null,
"testCommand": null,
"lintCommand": null,
"devCommand": null,
"scripts": {}
},
"conventions": {
"namingStyle": null,
"importStyle": null,
"testPattern": null,
"fileOrganization": null
},
"structure": {
"isMonorepo": false,
"workspaces": [],
"mainDirectories": [
"docs"
],
"gitBranches": {
"defaultBranch": "main",
"branchingStrategy": null
}
},
"customNotes": [],
"directoryMap": {
"Build": {
"path": "Build",
"purpose": "Build output",
"fileCount": 1,
"lastAccessed": 1772269310491,
"keyFiles": []
},
"ScreenTranslate": {
"path": "ScreenTranslate",
"purpose": null,
"fileCount": 0,
"lastAccessed": 1772269310492,
"keyFiles": []
},
"ScreenTranslate.xcodeproj": {
"path": "ScreenTranslate.xcodeproj",
"purpose": null,
"fileCount": 1,
"lastAccessed": 1772269310492,
"keyFiles": [
"project.pbxproj"
]
},
"ScreenTranslateTests": {
"path": "ScreenTranslateTests",
"purpose": null,
"fileCount": 5,
"lastAccessed": 1772269310493,
"keyFiles": [
"KeyboardShortcutTests.swift",
"README.md",
"ScreenTranslateErrorTests.swift",
"ShortcutRecordingTypeTests.swift",
"TextTranslationErrorTests.swift"
]
},
"docs": {
"path": "docs",
"purpose": "Documentation",
"fileCount": 6,
"lastAccessed": 1772269310493,
"keyFiles": [
"README.md",
"api-reference.md",
"architecture.md",
"components.md",
"developer-guide.md"
]
},
"skills": {
"path": "skills",
"purpose": null,
"fileCount": 0,
"lastAccessed": 1772269310494,
"keyFiles": []
},
"tasks": {
"path": "tasks",
"purpose": null,
"fileCount": 6,
"lastAccessed": 1772269310494,
"keyFiles": [
"prd-.md",
"prd-macos-screentranslate.md",
"prd-screencoder-kiss-translator.md",
"prd-screencoder.md",
"prd-text-translation.json"
]
},
"ScreenTranslate/App": {
"path": "ScreenTranslate/App",
"purpose": "Application code",
"fileCount": 2,
"lastAccessed": 1772269310495,
"keyFiles": [
"AppDelegate.swift",
"ScreenTranslateApp.swift"
]
},
"ScreenTranslate/Models": {
"path": "ScreenTranslate/Models",
"purpose": "Data models",
"fileCount": 23,
"lastAccessed": 1772269310495,
"keyFiles": [
"Annotation.swift",
"AppLanguage.swift",
"AppSettings.swift"
]
},
"ScreenTranslate/Services": {
"path": "ScreenTranslate/Services",
"purpose": "Business logic services",
"fileCount": 26,
"lastAccessed": 1772269310495,
"keyFiles": [
"AccessibilityPermissionChecker.swift",
"AppleTranslationProvider.swift",
"ClaudeVLMProvider.swift"
]
}
},
"hotPaths": [
{
"path": "ScreenTranslate/Services/PaddleOCREngine.swift",
"accessCount": 17,
"lastAccessed": 1772277198204,
"type": "file"
},
{
"path": "ScreenTranslate/Services/Security/KeychainService.swift",
"accessCount": 14,
"lastAccessed": 1772277575721,
"type": "file"
},
{
"path": "ScreenTranslate/Models/AppSettings.swift",
"accessCount": 13,
"lastAccessed": 1772277135092,
"type": "directory"
},
{
"path": "ScreenTranslate/Resources/en.lproj/Localizable.strings",
"accessCount": 6,
"lastAccessed": 1772277251555,
"type": "file"
},
{
"path": "ScreenTranslate/Services/PaddleOCRVLMProvider.swift",
"accessCount": 6,
"lastAccessed": 1772277354512,
"type": "directory"
},
{
"path": "ScreenTranslate/Features/Settings/SettingsViewModel.swift",
"accessCount": 5,
"lastAccessed": 1772277092578,
"type": "directory"
},
Comment on lines +153 to +175
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

hotPathstype 字段存在数据不一致。

部分文件路径被标记为 "type": "directory",例如:

  • 第 157 行:AppSettings.swift"type": "directory"
  • 第 169 行:PaddleOCRVLMProvider.swift"type": "directory"
  • 第 175 行:SettingsViewModel.swift"type": "directory"

这是 .omd 工具的数据错误,进一步说明此文件不应被版本控制——它包含不可靠的自动生成数据。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.omd/project-memory.json around lines 153 - 175, The .omd project-memory
hotPaths contains incorrect "type" values (entries where "path" points to
specific Swift source files like AppSettings.swift, PaddleOCRVLMProvider.swift,
SettingsViewModel.swift are marked "directory" instead of "file"); update those
JSON entries under the "hotPaths" array to set "type":"file" (or regenerate the
project-memory with the .omd tool so types are correct), then remove the
generated project-memory from version control and add the
.omd/project-memory.json pattern to .gitignore so this unreliable auto-generated
data is not committed again.

{
"path": "ScreenTranslate/Resources",
"accessCount": 4,
"lastAccessed": 1772271181502,
"type": "directory"
},
{
"path": "ScreenTranslate/Features/Settings/EngineSettingsTab.swift",
"accessCount": 4,
"lastAccessed": 1772274724758,
"type": "directory"
},
{
"path": "ScreenTranslate/Resources/zh-Hans.lproj/Localizable.strings",
"accessCount": 4,
"lastAccessed": 1772277283269,
"type": "file"
},
{
"path": "ScreenTranslate/Features/TranslationFlow/TranslationFlowController.swift",
"accessCount": 3,
"lastAccessed": 1772270086752,
"type": "file"
},
{
"path": "ScreenTranslate",
"accessCount": 3,
"lastAccessed": 1772271133277,
"type": "directory"
},
{
"path": "ScreenTranslate/Features/Capture/ScreenDetector.swift",
"accessCount": 3,
"lastAccessed": 1772277067534,
"type": "directory"
},
{
"path": "ScreenTranslate/Features/Capture/CaptureManager.swift",
"accessCount": 2,
"lastAccessed": 1772277030873,
"type": "directory"
},
{
"path": "",
"accessCount": 1,
"lastAccessed": 1772269949125,
"type": "directory"
},
{
"path": "ScreenTranslate/Errors/ScreenTranslateError.swift",
"accessCount": 1,
"lastAccessed": 1772269960584,
"type": "file"
},
{
"path": "ScreenTranslate/Models/VLMProviderType.swift",
"accessCount": 1,
"lastAccessed": 1772269960585,
"type": "file"
},
{
"path": "ScreenTranslate/Services/ScreenCoderEngine.swift",
"accessCount": 1,
"lastAccessed": 1772270035460,
"type": "file"
}
],
"userDirectives": []
}
Comment on lines +1 to +244
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if .gitignore exists and contains .omd rules
echo "=== Checking .gitignore ==="
if [ -f .gitignore ]; then
  echo ".gitignore exists. Checking for .omd rules:"
  grep -n "\.omd\|omd" .gitignore || echo "No .omd rules found"
else
  echo ".gitignore does not exist"
fi

echo -e "\n=== Checking if .omd directory exists ==="
if [ -d .omd ]; then
  echo ".omd directory exists. Contents:"
  find .omd -type f | head -20
else
  echo ".omd directory does not exist"
fi

echo -e "\n=== Checking file details ==="
if [ -f .omd/project-memory.json ]; then
  echo ".omd/project-memory.json exists"
  wc -l .omd/project-memory.json
  echo -e "\nFirst 10 lines:"
  head -10 .omd/project-memory.json
fi

Repository: hubo1989/ScreenTranslate

Length of output: 687


🏁 Script executed:

#!/bin/bash
# List all top-level files to see what's actually in the repo
echo "=== Top-level files/directories ==="
ls -la | head -20

echo -e "\n=== Check git status if in a repo ==="
if [ -d .git ]; then
  echo "Repository found. Checking git status:"
  git status --short | head -20
fi

Repository: hubo1989/ScreenTranslate

Length of output: 1355


🏁 Script executed:

#!/bin/bash
# Check if .omd files are staged or tracked in git
echo "=== Check if .omd files are tracked/staged in git ==="
git ls-files .omd 2>/dev/null || echo "No .omd files tracked in git"

echo -e "\n=== Check git status for .omd directory ==="
git status .omd 2>/dev/null || echo "Not in git tracking"

echo -e "\n=== Check .gitignore content ==="
cat .gitignore

Repository: hubo1989/ScreenTranslate

Length of output: 1409


不应将 .omd/ 目录提交到版本控制。

.omd/project-memory.json 和相关会话文件是开发工具自动生成的元数据,包含硬编码的开发者本地路径 /Users/hubo/.superset/worktrees/screentranslate/featadd-translate-engine、机器特定的访问时间戳和访问计数等开发环境信息。这类文件当前已被追踪在 git 中,会导致:

  1. 泄露开发者本地环境信息
  2. 每位开发者的访问模式不同,频繁产生合并冲突
  3. 对生产代码无任何价值,增加仓库噪音

建议将 .omd/ 添加到 .gitignore(与现有的 .claude/.specify/.ralph-tui/ 等配置一致)并从此 PR 中移除该文件:

# Development tools (Claude Code specific)
.claude/
.specify/
+ .omd/
.ralph-tui/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.omd/project-memory.json around lines 1 - 244, The .omd/project-memory.json
file is generated metadata and must not be tracked; remove .omd/ from the PR and
stop tracking these files by adding the .omd/ directory to .gitignore and
removing the tracked file from the index, then commit the change; reference the
tracked file ".omd/project-memory.json" and ensure .gitignore contains an entry
for ".omd/" (consistent with other ignored dirs like .claude/ .specify/
.ralph-tui/) so future autogenerated .omd files are not committed.

8 changes: 8 additions & 0 deletions .omd/sessions/0c203f54-c10d-4417-8115-005c18e9036b.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"session_id": "0c203f54-c10d-4417-8115-005c18e9036b",
"ended_at": "2026-02-28T09:01:44.054Z",
"reason": "other",
"agents_spawned": 0,
"agents_completed": 0,
"modes_used": []
}
25 changes: 18 additions & 7 deletions ScreenTranslate/Features/Capture/ScreenDetector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,27 @@ actor ScreenDetector {
}

/// Checks if the app has screen recording permission.
/// Uses CGPreflightScreenCaptureAccess() which does NOT trigger system dialog.
/// This API is deprecated in macOS 15 but still works correctly.
/// Uses SCShareableContent to actually verify permission works (not just cached status).
/// - Parameter silent: If true, suppresses logging (default: true)
/// - Returns: True if permission is granted
func hasPermission(silent: Bool = true) async -> Bool {
// Use CGPreflightScreenCaptureAccess - does NOT trigger dialog
let granted = CGPreflightScreenCaptureAccess()
cachedPermissionStatus = granted
if !silent { print("[ScreenDetector] Permission check: \(granted ? "granted" : "denied")") }
return granted
// Quick check first using CGPreflightScreenCaptureAccess
guard CGPreflightScreenCaptureAccess() else {
cachedPermissionStatus = false
if !silent { print("[ScreenDetector] Permission check: denied (CGPreflight)") }
return false
}
// Actually verify by trying to get shareable content
do {
_ = try await SCShareableContent.current
cachedPermissionStatus = true
if !silent { print("[ScreenDetector] Permission check: granted") }
return true
} catch {
cachedPermissionStatus = false
if !silent { print("[ScreenDetector] Permission check: denied (SCShareableContent)") }
return false
}
}

/// Forces a fresh permission check (clears cache)
Expand Down
90 changes: 90 additions & 0 deletions ScreenTranslate/Features/Settings/EngineSettingsTab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,90 @@ struct PaddleOCRStatusSection: View {
.frame(maxWidth: 300)
}
}

// MLX-VLM settings (only show when mode is precise and not using cloud)
if viewModel.paddleOCRMode == .precise && !viewModel.paddleOCRUseCloud {
Divider()
.gridCellUnsizedAxes(.horizontal)

GridRow {
Text(localized("settings.paddleocr.useMLXVLM"))
.foregroundStyle(.secondary)
.gridColumnAlignment(.trailing)
Toggle("", isOn: $viewModel.paddleOCRUseMLXVLM)
.toggleStyle(.checkbox)
.onChange(of: viewModel.paddleOCRUseMLXVLM) { _, newValue in
if newValue {
viewModel.checkMLXVLMServerStatus()
}
}
}

if viewModel.paddleOCRUseMLXVLM {
// MLX-VLM server status
GridRow {
Text(localized("settings.paddleocr.mlxVLMStatus"))
.foregroundStyle(.secondary)
.gridColumnAlignment(.trailing)
HStack {
if viewModel.isCheckingMLXVLMServer {
ProgressView()
.controlSize(.small)
Text(localized("settings.paddleocr.mlxVLMChecking"))
.foregroundStyle(.secondary)
} else {
Image(systemName: viewModel.isMLXVLMServerRunning ? "checkmark.circle.fill" : "xmark.circle.fill")
.foregroundStyle(viewModel.isMLXVLMServerRunning ? .green : .red)
Text(viewModel.isMLXVLMServerRunning
? localized("settings.paddleocr.mlxVLMRunning")
: localized("settings.paddleocr.mlxVLMNotRunning"))
.foregroundStyle(.secondary)
}

Button {
viewModel.checkMLXVLMServerStatus()
} label: {
Image(systemName: "arrow.clockwise")
}
.buttonStyle(.borderless)
.controlSize(.small)
}
}

GridRow {
Text(localized("settings.paddleocr.mlxVLMServerURL"))
.foregroundStyle(.secondary)
.gridColumnAlignment(.trailing)
TextField("", text: $viewModel.paddleOCRMLXVLMServerURL)
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 300)
}

GridRow {
Text(localized("settings.paddleocr.mlxVLMModelName"))
.foregroundStyle(.secondary)
.gridColumnAlignment(.trailing)
TextField("", text: $viewModel.paddleOCRMLXVLMModelName)
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 300)
}
} else {
// Local model directory for native backend (when not using MLX-VLM)
GridRow {
Text(localized("settings.paddleocr.localVLModelDir"))
.foregroundStyle(.secondary)
.gridColumnAlignment(.trailing)
VStack(alignment: .leading, spacing: 4) {
TextField("", text: $viewModel.paddleOCRLocalVLModelDir)
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 300)
Text(localized("settings.paddleocr.localVLModelDir.hint"))
.font(.caption)
.foregroundStyle(.tertiary)
}
}
}
}
}

// Description
Expand Down Expand Up @@ -263,5 +347,11 @@ struct PaddleOCRStatusSection: View {
}
}
.padding(.top, 8)
.onAppear {
// Auto-check MLX-VLM server status when section appears
if viewModel.paddleOCRUseMLXVLM {
viewModel.checkMLXVLMServerStatus()
}
}
}
}
Loading