Skip to content

Commit 8d63b7c

Browse files
kochj23claude
andcommitted
feat: Add GitHub, code analysis, user memories, deploy pipeline — v6.0.0
New features: - GitHub integration: issues, PRs, branches, credential scanning - Code analysis tool: metrics, dependencies, lint, symbols - User memories system: persistent coding standards injected into prompts - Full Xcode deploy pipeline: version bump, build, archive, DMG, install - Context analysis service for project structure inspection - Project dashboard, GitHub panel, code analysis panel, build panel views - 14 tools (up from 11) Security: user-specific data (names, paths) injected at runtime from AppSettings — never hardcoded in source code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 910b6b8 commit 8d63b7c

23 files changed

Lines changed: 5336 additions & 113 deletions

MLX Code.xcodeproj/project.pbxproj

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,18 @@
8787
EE5F6A7B8C9D0E1F2A3B4C5D /* ToolApprovalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6A7B8C9D0E1F2A3B4C5D6E /* ToolApprovalView.swift */; };
8888
117B8C9D0E1F2A3B4C5D6E7F /* ContextBudget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228C9D0E1F2A3B4C5D6E7F81 /* ContextBudget.swift */; };
8989
339D0E1F2A3B4C5D6E7F8192 /* ContextManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005E0D461771E49D839C575 /* ContextManager.swift */; };
90+
C1D2E3F4A5B6071829304C5D /* ContextAnalysisService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1E2F3A4B5C60718293A4B5C /* ContextAnalysisService.swift */; };
91+
E2F3A4B5C6D7081929304C5D /* UserMemories.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2A3B4C5D6E7081929304C5D /* UserMemories.swift */; };
92+
B1C2D3E4F5A60718293B4C5D /* GitHubService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60718293A4B5C /* GitHubService.swift */; };
93+
B2C3D4E5F6A70829304C5D6E /* GitHubTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2B3C4D5E6F70829304B5C6D /* GitHubTool.swift */; };
94+
B3C4D5E6F7A8093A415D6E7F /* CodeAnalysisTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B4C5D6E7F8093A415C6D7E /* CodeAnalysisTool.swift */; };
95+
B4C5D6E7F8A90A4B526E7F80 /* ProjectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B5C6D7E8F90A4B526D7E8F /* ProjectViewModel.swift */; };
96+
B5C6D7E8F9AA1B5C637F8091 /* GitHubViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B6C7D8E9FA1B5C637E8F90 /* GitHubViewModel.swift */; };
97+
B6C7D8E9FAAB2C6D74809102 /* CodeAnalysisViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6B7C8D9EAFB2C6D748F9001 /* CodeAnalysisViewModel.swift */; };
98+
B7C8D9EAFBAC3D7E859102A3 /* ProjectDashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B8C9DAEBFC3D7E859001A2 /* ProjectDashboardView.swift */; };
99+
B8C9DAEBFBAD4E8F960213B4 /* BuildPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8B9CADBECED4E8F960112B3 /* BuildPanelView.swift */; };
100+
B9CADBECEBBE5F900A1324C5 /* GitHubPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BACBDCEDFE5F900A1223C4 /* GitHubPanelView.swift */; };
101+
BACBDCEDDCBF60011B2435D6 /* CodeAnalysisPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABBCCDDEEAF60011B2334D5 /* CodeAnalysisPanelView.swift */; };
90102
/* End PBXBuildFile section */
91103

92104
/* Begin PBXContainerItemProxy section */
@@ -115,6 +127,8 @@
115127

116128
/* Begin PBXFileReference section */
117129
0095C89C8F1E4539863AE74D96B702F4 /* MLXService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MLXService.swift; sourceTree = "<group>"; };
130+
D1E2F3A4B5C60718293A4B5C /* ContextAnalysisService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextAnalysisService.swift; sourceTree = "<group>"; };
131+
F2A3B4C5D6E7081929304C5D /* UserMemories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMemories.swift; sourceTree = "<group>"; };
118132
01A056387C5D6883F0932FA4 /* GitService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GitService.swift; path = "MLX Code/Services/GitService.swift"; sourceTree = "<group>"; };
119133
0674F2D9E84C4AC3AC8ED170619E99EA /* FileService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileService.swift; sourceTree = "<group>"; };
120134
07F3CD889AF1A1B378575B71 /* PathsSettingsView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PathsSettingsView.swift; sourceTree = "<group>"; };
@@ -202,6 +216,16 @@
202216
DD4E5F6A7B8C9D0E1F2A3B4C /* ToolApproval.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ToolApproval.swift; path = "MLX Code/Models/ToolApproval.swift"; sourceTree = SOURCE_ROOT; };
203217
FF6A7B8C9D0E1F2A3B4C5D6E /* ToolApprovalView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ToolApprovalView.swift; path = "MLX Code/Views/ToolApprovalView.swift"; sourceTree = SOURCE_ROOT; };
204218
228C9D0E1F2A3B4C5D6E7F81 /* ContextBudget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ContextBudget.swift; path = "MLX Code/Services/ContextBudget.swift"; sourceTree = SOURCE_ROOT; };
219+
A1B2C3D4E5F60718293A4B5C /* GitHubService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubService.swift; sourceTree = "<group>"; };
220+
A2B3C4D5E6F70829304B5C6D /* GitHubTool.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GitHubTool.swift; path = Tools/GitHubTool.swift; sourceTree = "<group>"; };
221+
A3B4C5D6E7F8093A415C6D7E /* CodeAnalysisTool.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CodeAnalysisTool.swift; path = Tools/CodeAnalysisTool.swift; sourceTree = "<group>"; };
222+
A4B5C6D7E8F90A4B526D7E8F /* ProjectViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ProjectViewModel.swift; sourceTree = "<group>"; };
223+
A5B6C7D8E9FA1B5C637E8F90 /* GitHubViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GitHubViewModel.swift; sourceTree = "<group>"; };
224+
A6B7C8D9EAFB2C6D748F9001 /* CodeAnalysisViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CodeAnalysisViewModel.swift; sourceTree = "<group>"; };
225+
A7B8C9DAEBFC3D7E859001A2 /* ProjectDashboardView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ProjectDashboardView.swift; sourceTree = "<group>"; };
226+
A8B9CADBECED4E8F960112B3 /* BuildPanelView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BuildPanelView.swift; sourceTree = "<group>"; };
227+
A9BACBDCEDFE5F900A1223C4 /* GitHubPanelView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GitHubPanelView.swift; sourceTree = "<group>"; };
228+
AABBCCDDEEAF60011B2334D5 /* CodeAnalysisPanelView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CodeAnalysisPanelView.swift; sourceTree = "<group>"; };
205229
/* End PBXFileReference section */
206230

207231
/* Begin PBXFrameworksBuildPhase section */
@@ -293,6 +317,8 @@
293317
64071C1709105BE6194D0570 /* DiffPreviewTool.swift */,
294318
B87A92AF8834D4EAB5ED2FED /* HelpTool.swift */,
295319
BB2C3D4E5F6A7B8C9D0E1F2A /* ToolTier.swift */,
320+
A2B3C4D5E6F70829304B5C6D /* GitHubTool.swift */,
321+
A3B4C5D6E7F8093A415C6D7E /* CodeAnalysisTool.swift */,
296322
);
297323
name = Tools;
298324
sourceTree = "<group>";
@@ -328,6 +354,10 @@
328354
D18CD3C400C3AD1177178192 /* ThinkingIndicatorView.swift */,
329355
B9DCFE1EB35E1587BE810984 /* CollapsibleToolResultView.swift */,
330356
3BA6541CD08A3BD34802C064 /* HelpView.swift */,
357+
A7B8C9DAEBFC3D7E859001A2 /* ProjectDashboardView.swift */,
358+
A8B9CADBECED4E8F960112B3 /* BuildPanelView.swift */,
359+
A9BACBDCEDFE5F900A1223C4 /* GitHubPanelView.swift */,
360+
AABBCCDDEEAF60011B2334D5 /* CodeAnalysisPanelView.swift */,
331361
);
332362
path = Views;
333363
sourceTree = "<group>";
@@ -340,6 +370,9 @@
340370
0674F2D9E84C4AC3AC8ED170619E99EA /* FileService.swift */,
341371
D3FFCCE81A9942AD88F69691662852EC /* PythonService.swift */,
342372
2484B300512B2B737C691AF5 /* WidgetDataSync.swift */,
373+
A1B2C3D4E5F60718293A4B5C /* GitHubService.swift */,
374+
D1E2F3A4B5C60718293A4B5C /* ContextAnalysisService.swift */,
375+
F2A3B4C5D6E7081929304C5D /* UserMemories.swift */,
343376
);
344377
path = Services;
345378
sourceTree = "<group>";
@@ -357,6 +390,9 @@
357390
children = (
358391
D8C77339FE1D4E7FAA34113946220883 /* ChatViewModel.swift */,
359392
DEE14123640793BDCED74DE1 /* ChatViewModel+Tools.swift */,
393+
A4B5C6D7E8F90A4B526D7E8F /* ProjectViewModel.swift */,
394+
A5B6C7D8E9FA1B5C637E8F90 /* GitHubViewModel.swift */,
395+
A6B7C8D9EAFB2C6D748F9001 /* CodeAnalysisViewModel.swift */,
360396
);
361397
path = ViewModels;
362398
sourceTree = "<group>";
@@ -651,6 +687,18 @@
651687
EE5F6A7B8C9D0E1F2A3B4C5D /* ToolApprovalView.swift in Sources */,
652688
117B8C9D0E1F2A3B4C5D6E7F /* ContextBudget.swift in Sources */,
653689
339D0E1F2A3B4C5D6E7F8192 /* ContextManager.swift in Sources */,
690+
C1D2E3F4A5B6071829304C5D /* ContextAnalysisService.swift in Sources */,
691+
E2F3A4B5C6D7081929304C5D /* UserMemories.swift in Sources */,
692+
B1C2D3E4F5A60718293B4C5D /* GitHubService.swift in Sources */,
693+
B2C3D4E5F6A70829304C5D6E /* GitHubTool.swift in Sources */,
694+
B3C4D5E6F7A8093A415D6E7F /* CodeAnalysisTool.swift in Sources */,
695+
B4C5D6E7F8A90A4B526E7F80 /* ProjectViewModel.swift in Sources */,
696+
B5C6D7E8F9AA1B5C637F8091 /* GitHubViewModel.swift in Sources */,
697+
B6C7D8E9FAAB2C6D74809102 /* CodeAnalysisViewModel.swift in Sources */,
698+
B7C8D9EAFBAC3D7E859102A3 /* ProjectDashboardView.swift in Sources */,
699+
B8C9DAEBFBAD4E8F960213B4 /* BuildPanelView.swift in Sources */,
700+
B9CADBECEBBE5F900A1324C5 /* GitHubPanelView.swift in Sources */,
701+
BACBDCEDDCBF60011B2435D6 /* CodeAnalysisPanelView.swift in Sources */,
654702
);
655703
runOnlyForDeploymentPostprocessing = 0;
656704
};
@@ -690,7 +738,7 @@
690738
"@executable_path/../Frameworks",
691739
);
692740
MACOSX_DEPLOYMENT_TARGET = 14.0;
693-
MARKETING_VERSION = 5.0.0;
741+
MARKETING_VERSION = 6.0.0;
694742
PRODUCT_BUNDLE_IDENTIFIER = com.local.mlxcode;
695743
PRODUCT_NAME = "$(TARGET_NAME)";
696744
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -769,7 +817,7 @@
769817
"@executable_path/../../../../Frameworks",
770818
);
771819
MACOSX_DEPLOYMENT_TARGET = 14.0;
772-
MARKETING_VERSION = 5.0.0;
820+
MARKETING_VERSION = 6.0.0;
773821
PRODUCT_BUNDLE_IDENTIFIER = com.local.mlxcode.widget;
774822
PRODUCT_NAME = "$(TARGET_NAME)";
775823
SDKROOT = macosx;
@@ -857,7 +905,7 @@
857905
"@executable_path/../../../../Frameworks",
858906
);
859907
MACOSX_DEPLOYMENT_TARGET = 14.0;
860-
MARKETING_VERSION = 5.0.0;
908+
MARKETING_VERSION = 6.0.0;
861909
PRODUCT_BUNDLE_IDENTIFIER = com.local.mlxcode.widget;
862910
PRODUCT_NAME = "$(TARGET_NAME)";
863911
SDKROOT = macosx;
@@ -890,7 +938,7 @@
890938
"@executable_path/../Frameworks",
891939
);
892940
MACOSX_DEPLOYMENT_TARGET = 14.0;
893-
MARKETING_VERSION = 5.0.0;
941+
MARKETING_VERSION = 6.0.0;
894942
PRODUCT_BUNDLE_IDENTIFIER = com.local.mlxcode;
895943
PRODUCT_NAME = "$(TARGET_NAME)";
896944
SWIFT_EMIT_LOC_STRINGS = YES;

MLX Code/MLXCodeApp.swift

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ struct MLXCodeApp: App {
3131
/// Chat view model for managing conversations
3232
@StateObject private var chatViewModel = ChatViewModel()
3333

34+
/// Project view model for build operations
35+
@StateObject private var projectViewModel = ProjectViewModel.shared
36+
37+
/// GitHub view model
38+
@StateObject private var githubViewModel = GitHubViewModel.shared
39+
40+
/// Code analysis view model
41+
@StateObject private var codeAnalysisViewModel = CodeAnalysisViewModel.shared
42+
3443
/// Show prerequisites window
3544
@State private var showingPrerequisites = false
3645

@@ -40,6 +49,19 @@ struct MLXCodeApp: App {
4049
/// Show GitHub panel
4150
@State private var showingGitHub = false
4251

52+
/// Refreshes user memories snapshot for system prompts
53+
@MainActor
54+
private func refreshMemories() async {
55+
let memories = await UserMemories.shared.getPromptMemories(
56+
userName: settings.authorName,
57+
binariesPath: settings.binariesPath,
58+
nasPath: settings.nasBinariesPath,
59+
projectsPath: settings.xcodeProjectsPath
60+
)
61+
SystemPrompts.memoriesSnapshot = memories
62+
print("Loaded \(memories.isEmpty ? "0" : "user") memories into system prompt")
63+
}
64+
4365
/// Discovers models on disk and updates the model list with correct paths
4466
@MainActor
4567
private func discoverAndRefreshModels() async {
@@ -73,11 +95,17 @@ struct MLXCodeApp: App {
7395
ChatView()
7496
.environmentObject(settings)
7597
.environmentObject(chatViewModel)
98+
.environmentObject(projectViewModel)
99+
.environmentObject(githubViewModel)
100+
.environmentObject(codeAnalysisViewModel)
76101
.frame(minWidth: 900, minHeight: 600)
77102
.onAppear {
78-
// Discover actual models on disk at startup
79103
Task {
104+
// Discover actual models on disk at startup
80105
await discoverAndRefreshModels()
106+
107+
// Load user memories into system prompt cache
108+
await refreshMemories()
81109
}
82110
}
83111
}

MLX Code/Models/AppSettings.swift

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,39 @@ class AppSettings: ObservableObject {
7575
/// Conversation export directory
7676
@Published var conversationsExportPath: String = "~/Documents"
7777

78+
// MARK: - Xcode Settings
79+
80+
/// Default build configuration
81+
@Published var defaultBuildConfiguration: String = "Debug"
82+
83+
/// Default build scheme (auto-detected if empty)
84+
@Published var defaultBuildScheme: String = ""
85+
86+
/// Local binaries archive path
87+
@Published var binariesPath: String = "/Volumes/Data/xcode/binaries"
88+
89+
/// NAS binaries archive path
90+
@Published var nasBinariesPath: String = "/Volumes/NAS/binaries"
91+
92+
/// Auto-analyze after build
93+
@Published var autoAnalyzeOnBuild: Bool = false
94+
95+
// MARK: - GitHub Settings
96+
97+
/// Default GitHub branch
98+
@Published var defaultGitBranch: String = "main"
99+
100+
/// Run credential scan before push
101+
@Published var credentialScanOnPush: Bool = true
102+
103+
// MARK: - Memory Settings
104+
105+
/// Author name for documentation and release notes (injected into LLM memories at runtime)
106+
@Published var authorName: String = ""
107+
108+
/// Enable user memories in LLM system prompt
109+
@Published var enableMemories: Bool = true
110+
78111
// MARK: - Private Properties
79112

80113
private let userDefaults = UserDefaults.standard
@@ -99,6 +132,15 @@ class AppSettings: ObservableObject {
99132
static let modelsPath = "modelsPath"
100133
static let templatesPath = "templatesPath"
101134
static let conversationsExportPath = "conversationsExportPath"
135+
static let defaultBuildConfiguration = "defaultBuildConfiguration"
136+
static let defaultBuildScheme = "defaultBuildScheme"
137+
static let binariesPath = "binariesPath"
138+
static let nasBinariesPath = "nasBinariesPath"
139+
static let autoAnalyzeOnBuild = "autoAnalyzeOnBuild"
140+
static let defaultGitBranch = "defaultGitBranch"
141+
static let credentialScanOnPush = "credentialScanOnPush"
142+
static let authorName = "authorName"
143+
static let enableMemories = "enableMemories"
102144
}
103145

104146
// MARK: - Initialization
@@ -253,6 +295,35 @@ class AppSettings: ObservableObject {
253295
conversationsExportPath = path
254296
}
255297

298+
// Load Xcode/GitHub settings
299+
if let config = userDefaults.string(forKey: Keys.defaultBuildConfiguration), !config.isEmpty {
300+
defaultBuildConfiguration = config
301+
}
302+
if let scheme = userDefaults.string(forKey: Keys.defaultBuildScheme) {
303+
defaultBuildScheme = scheme
304+
}
305+
if let path = userDefaults.string(forKey: Keys.binariesPath), !path.isEmpty {
306+
binariesPath = path
307+
}
308+
if let path = userDefaults.string(forKey: Keys.nasBinariesPath), !path.isEmpty {
309+
nasBinariesPath = path
310+
}
311+
if userDefaults.object(forKey: Keys.autoAnalyzeOnBuild) != nil {
312+
autoAnalyzeOnBuild = userDefaults.bool(forKey: Keys.autoAnalyzeOnBuild)
313+
}
314+
if let branch = userDefaults.string(forKey: Keys.defaultGitBranch), !branch.isEmpty {
315+
defaultGitBranch = branch
316+
}
317+
if userDefaults.object(forKey: Keys.credentialScanOnPush) != nil {
318+
credentialScanOnPush = userDefaults.bool(forKey: Keys.credentialScanOnPush)
319+
}
320+
if let name = userDefaults.string(forKey: Keys.authorName) {
321+
authorName = name
322+
}
323+
if userDefaults.object(forKey: Keys.enableMemories) != nil {
324+
enableMemories = userDefaults.bool(forKey: Keys.enableMemories)
325+
}
326+
256327
// Load available models
257328
if let modelsData = userDefaults.data(forKey: Keys.availableModels),
258329
let models = try? JSONDecoder().decode([MLXModel].self, from: modelsData) {
@@ -290,6 +361,17 @@ class AppSettings: ObservableObject {
290361
userDefaults.set(templatesPath, forKey: Keys.templatesPath)
291362
userDefaults.set(conversationsExportPath, forKey: Keys.conversationsExportPath)
292363

364+
// Save Xcode/GitHub settings
365+
userDefaults.set(defaultBuildConfiguration, forKey: Keys.defaultBuildConfiguration)
366+
userDefaults.set(defaultBuildScheme, forKey: Keys.defaultBuildScheme)
367+
userDefaults.set(binariesPath, forKey: Keys.binariesPath)
368+
userDefaults.set(nasBinariesPath, forKey: Keys.nasBinariesPath)
369+
userDefaults.set(autoAnalyzeOnBuild, forKey: Keys.autoAnalyzeOnBuild)
370+
userDefaults.set(defaultGitBranch, forKey: Keys.defaultGitBranch)
371+
userDefaults.set(credentialScanOnPush, forKey: Keys.credentialScanOnPush)
372+
userDefaults.set(authorName, forKey: Keys.authorName)
373+
userDefaults.set(enableMemories, forKey: Keys.enableMemories)
374+
293375
// Save selected model ID
294376
if let modelId = selectedModel?.id.uuidString {
295377
userDefaults.set(modelId, forKey: Keys.selectedModelId)

0 commit comments

Comments
 (0)