Skip to content

Commit 589c923

Browse files
authored
Merge pull request #123 from mihaelamj/feat/81-swiftsyntax-ast-indexing
Summary - AST Indexing: SwiftSyntax-based extraction of symbols, property wrappers, conformances from sample code - Unified Search: Single search across all 8 sources with hierarchical result numbering (1.1, 1.2, 2.1...) - Doctor Command Enhanced: Package diagnostics with orphan detection - Code Quality: Consolidated constants, fixed race conditions in tests, SwiftLint compliance Changes Added - ASTIndexer package with SwiftSourceExtractor for semantic code analysis - StringFormatterTests - 34 unit tests for display formatting - Hierarchical result numbering and source counts in search output Fixed - Display formatting bugs (double spaces, title-casing) - Race condition in PriorityPackagesCatalog tests - SwiftLint violations (line length, identifier names) - Package-docs fetch now reads user selections (#107) Changed - 698 tests across 73 suites (up from 93/7) - Renamed md → output in formatters for clarity Test plan - All 698 tests pass - swift build succeeds - cupertino search "swiftui navigation" produces correct output - SwiftLint passes on modified files Related Issues Closes #81, #107
2 parents 72e5994 + ad56c8a commit 589c923

File tree

68 files changed

+8659
-1770
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+8659
-1770
lines changed

CHANGELOG.md

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,58 @@
1+
## 0.8.0 (2025-12-20)
2+
3+
### Added
4+
- **Doctor Command Enhanced** - Package diagnostics (#81)
5+
- Shows user selections file status and package count
6+
- Shows downloaded README count
7+
- Warns about orphaned READMEs (packages no longer selected)
8+
- Displays priority package breakdown (Apple vs ecosystem)
9+
- **String Formatter Tests** - 34 unit tests for display formatting (#81)
10+
- `StringFormatterTests.swift` covers truncation, markdown escaping, camelCase splitting
11+
12+
### Changed
13+
- **Code Quality Improvements** (#81)
14+
- Consolidated magic numbers into `Shared.Constants` (timeouts, delays, limits, intervals)
15+
- Added `Timeout`, `Delay`, `Limit`, `Interval` namespaces for better organization
16+
- Replaced hardcoded values across WKWebCrawler, HIGCrawler, and other modules
17+
- **PriorityPackagesCatalog** - Made fields optional for TUI compatibility
18+
- `appleOfficial` tier now optional (TUI only saves ecosystem tier)
19+
- Stats fields `totalCriticalApplePackages` and `totalEcosystemPackages` now optional
20+
- **Search Result Formatting** (#81)
21+
- Hierarchical result numbering (1.1, 1.2, 2.1, etc.)
22+
- Source counts in headers: `## 1. Apple Documentation (20) 📚`
23+
- Renamed `md` variable to `output` in formatters for clarity
24+
25+
### Fixed
26+
- **Package-docs fetch now reads user selections** (#107)
27+
- `cupertino fetch --type package-docs` now loads from `~/.cupertino/selected-packages.json`
28+
- Falls back to bundled `priority-packages.json` if user file doesn't exist
29+
- TUI package selections are now respected by fetch command
30+
- **Display Formatting Bugs** (#81)
31+
- Double space artifacts ("Tab bars" → "Tab bars")
32+
- Smart title-casing (only lowercase first letters get uppercased)
33+
- SwiftLint violations (line length, identifier names)
34+
35+
### Related Issues
36+
- Closes #81, #107
37+
38+
---
39+
40+
## 0.7.0 (2025-12-15)
41+
42+
### Added
43+
- **Unified Search with Source Parameter**
44+
- New `--source` parameter: `apple-docs`, `samples`, `hig`, `apple-archive`, `swift-evolution`, `swift-org`, `swift-book`, `packages`, `all`
45+
- Teasers show results from alternate sources in every search response
46+
- Source-aware messaging tells AI exactly what was searched
47+
- **Documentation Database Expanded** - 302,424 docs across 307 frameworks (up from 234k/287)
48+
49+
### Changed
50+
- Consolidated multiple search tools into one unified search tool
51+
- Shared formatters between MCP and CLI for consistent output
52+
- Shared TeaserFormatter and constants eliminate hardcoding
53+
54+
---
55+
156
## 0.6.0 (2025-12-12)
257

358
### Added
@@ -30,17 +85,6 @@
3085

3186
---
3287

33-
## 0.7.0 (2025-12-15)
34-
35-
### Added
36-
-
37-
38-
### Changed
39-
-
40-
41-
### Fixed
42-
-
43-
4488
## 0.5.0 (2025-12-11)
4589

4690
**Why minor bump?** The `cupertino release` command was removed from the public CLI. Users who had scripts calling `cupertino release` will need to update them. This is a breaking change for maintainer workflows.

Packages/Package.resolved

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Packages/Package.swift

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ let macOSOnlyProducts: [Product] = [
2525
.singleTargetLibrary("Services"),
2626
.singleTargetLibrary("Resources"),
2727
.singleTargetLibrary("Availability"),
28+
.singleTargetLibrary("ASTIndexer"),
2829
.singleTargetLibrary("MCPSupport"),
2930
.singleTargetLibrary("SearchToolProvider"),
3031
.singleTargetLibrary("MCPClient"),
@@ -49,6 +50,8 @@ let allProducts = baseProducts + macOSOnlyProducts
4950
let deps: [Package.Dependency] = [
5051
// Swift Argument Parser (cross-platform CLI tool)
5152
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
53+
// SwiftSyntax for AST parsing (#81)
54+
.package(url: "https://github.com/swiftlang/swift-syntax", from: "600.0.0"),
5255
]
5356

5457
// -------------------------------------------------------------
@@ -118,7 +121,7 @@ let targets: [Target] = {
118121

119122
let searchTarget = Target.target(
120123
name: "Search",
121-
dependencies: ["Shared", "Logging", "Core"]
124+
dependencies: ["Shared", "Logging", "Core", "ASTIndexer"]
122125
)
123126
let searchTestsTarget = Target.testTarget(
124127
name: "SearchTests",
@@ -127,7 +130,7 @@ let targets: [Target] = {
127130

128131
let sampleIndexTarget = Target.target(
129132
name: "SampleIndex",
130-
dependencies: ["Shared", "Logging"]
133+
dependencies: ["Shared", "Logging", "ASTIndexer"]
131134
)
132135
let sampleIndexTestsTarget = Target.testTarget(
133136
name: "SampleIndexTests",
@@ -188,6 +191,20 @@ let targets: [Target] = {
188191
dependencies: ["Availability", "TestSupport"]
189192
)
190193

194+
let astIndexerTarget = Target.target(
195+
name: "ASTIndexer",
196+
dependencies: [
197+
"Shared",
198+
"Logging",
199+
.product(name: "SwiftSyntax", package: "swift-syntax"),
200+
.product(name: "SwiftParser", package: "swift-syntax"),
201+
]
202+
)
203+
let astIndexerTestsTarget = Target.testTarget(
204+
name: "ASTIndexerTests",
205+
dependencies: ["ASTIndexer", "Search", "SampleIndex", "TestSupport"]
206+
)
207+
191208
let cliTarget = Target.executableTarget(
192209
name: "CLI",
193210
dependencies: [
@@ -309,6 +326,8 @@ let targets: [Target] = {
309326
remoteSyncTestsTarget,
310327
availabilityTarget,
311328
availabilityTestsTarget,
329+
astIndexerTarget,
330+
astIndexerTestsTarget,
312331
testSupportTarget,
313332
cliTarget,
314333
tuiTarget,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// ASTIndexer.swift
2+
// SwiftSyntax-based AST parsing for semantic code indexing (#81)
3+
4+
import Foundation
5+
6+
/// Namespace for AST indexing types
7+
public enum ASTIndexer {
8+
/// Current schema version for AST index tables
9+
public static let schemaVersion: Int = 1
10+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// ExtractedSymbol.swift
2+
// Data models for extracted AST symbols (#81)
3+
4+
import Foundation
5+
6+
extension ASTIndexer {
7+
/// Kind of Swift symbol
8+
public enum SymbolKind: String, Codable, Sendable, CaseIterable {
9+
case `class`
10+
case `struct`
11+
case `enum`
12+
case `actor`
13+
case `protocol`
14+
case `extension`
15+
case function
16+
case method
17+
case initializer
18+
case property
19+
case `subscript`
20+
case `typealias`
21+
case `associatedtype`
22+
case `case` // enum case
23+
case `operator`
24+
case macro
25+
}
26+
27+
/// A symbol extracted from Swift source code
28+
public struct ExtractedSymbol: Codable, Sendable, Hashable {
29+
/// Symbol name (e.g., "AppState", "fetchItems", "count")
30+
public let name: String
31+
32+
/// Kind of symbol
33+
public let kind: SymbolKind
34+
35+
/// Line number in source (1-indexed)
36+
public let line: Int
37+
38+
/// Column number in source (1-indexed)
39+
public let column: Int
40+
41+
/// Full signature for functions/methods (e.g., "func fetchItems() async throws -> [Item]")
42+
public let signature: String?
43+
44+
/// Whether the symbol is async
45+
public let isAsync: Bool
46+
47+
/// Whether the symbol throws
48+
public let isThrows: Bool
49+
50+
/// Whether the symbol is public
51+
public let isPublic: Bool
52+
53+
/// Whether the symbol is static
54+
public let isStatic: Bool
55+
56+
/// Attributes applied to the symbol (e.g., ["@MainActor", "@Observable"])
57+
public let attributes: [String]
58+
59+
/// Protocol conformances (for types)
60+
public let conformances: [String]
61+
62+
/// Generic parameters (e.g., ["T", "U: Hashable"])
63+
public let genericParameters: [String]
64+
65+
public init(
66+
name: String,
67+
kind: SymbolKind,
68+
line: Int,
69+
column: Int,
70+
signature: String? = nil,
71+
isAsync: Bool = false,
72+
isThrows: Bool = false,
73+
isPublic: Bool = false,
74+
isStatic: Bool = false,
75+
attributes: [String] = [],
76+
conformances: [String] = [],
77+
genericParameters: [String] = []
78+
) {
79+
self.name = name
80+
self.kind = kind
81+
self.line = line
82+
self.column = column
83+
self.signature = signature
84+
self.isAsync = isAsync
85+
self.isThrows = isThrows
86+
self.isPublic = isPublic
87+
self.isStatic = isStatic
88+
self.attributes = attributes
89+
self.conformances = conformances
90+
self.genericParameters = genericParameters
91+
}
92+
}
93+
94+
/// An import statement extracted from Swift source
95+
public struct ExtractedImport: Codable, Sendable, Hashable {
96+
/// Module name (e.g., "SwiftUI", "Foundation")
97+
public let moduleName: String
98+
99+
/// Line number in source
100+
public let line: Int
101+
102+
/// Whether this is an @_exported import
103+
public let isExported: Bool
104+
105+
public init(moduleName: String, line: Int, isExported: Bool = false) {
106+
self.moduleName = moduleName
107+
self.line = line
108+
self.isExported = isExported
109+
}
110+
}
111+
112+
/// Result of parsing a Swift source file
113+
public struct ExtractionResult: Codable, Sendable {
114+
/// All symbols found in the source
115+
public let symbols: [ExtractedSymbol]
116+
117+
/// All imports found in the source
118+
public let imports: [ExtractedImport]
119+
120+
/// Whether the source had syntax errors
121+
public let hasErrors: Bool
122+
123+
/// Error message if parsing failed completely
124+
public let errorMessage: String?
125+
126+
public init(
127+
symbols: [ExtractedSymbol],
128+
imports: [ExtractedImport],
129+
hasErrors: Bool = false,
130+
errorMessage: String? = nil
131+
) {
132+
self.symbols = symbols
133+
self.imports = imports
134+
self.hasErrors = hasErrors
135+
self.errorMessage = errorMessage
136+
}
137+
138+
/// Empty result (for failed parsing)
139+
public static let empty = ExtractionResult(symbols: [], imports: [], hasErrors: true)
140+
}
141+
}

0 commit comments

Comments
 (0)