ios-agent-mcp 2.0.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.
@@ -0,0 +1,67 @@
1
+ import { eachLine, isSupportFile } from "./types.js";
2
+ const DESIGN_DOC = "docs/design/design-tokens.md";
3
+ const STATE_DOC = "docs/swiftui/state-and-data-flow.md";
4
+ export function analyzeSwiftUI(file) {
5
+ const findings = [];
6
+ if (isSupportFile(file.path))
7
+ return findings;
8
+ if (!/import\s+SwiftUI/.test(file.content))
9
+ return findings;
10
+ const push = (line, excerpt, rule, severity, message, consequence, fix, doc) => findings.push({
11
+ file: file.path,
12
+ line,
13
+ severity,
14
+ rule,
15
+ message,
16
+ consequence,
17
+ fix,
18
+ doc,
19
+ excerpt: excerpt.trim(),
20
+ });
21
+ eachLine(file, (line, number) => {
22
+ // Fixed font size breaks Dynamic Type.
23
+ if (/\.font\(\s*\.system\(\s*size:\s*\d+/.test(line) && !/relativeTo:/.test(line)) {
24
+ push(number, line, "fixed-font-size", "serious", "Fixed font size ignores Dynamic Type.", "Text stays the same size at every accessibility setting, making the app unusable for low-vision users.", "Use a semantic style (`.body`, `.headline`) or `.custom(_:size:relativeTo:)`.", DESIGN_DOC);
25
+ }
26
+ // Fixed height on a text container clips at large sizes.
27
+ if (/\.frame\(\s*height:\s*\d+/.test(line)) {
28
+ push(number, line, "fixed-height", "minor", "Fixed height clips content at large Dynamic Type sizes.", "Text is cut off at accessibility text sizes rather than growing.", "Use `minHeight:` so the container can grow.", DESIGN_DOC);
29
+ }
30
+ // AnyView defeats structural diffing.
31
+ if (/\bAnyView\s*\(/.test(line)) {
32
+ push(number, line, "any-view", "minor", "AnyView erases the view type.", "SwiftUI loses structural identity, so it cannot diff efficiently and animations may break.", "Restructure with `@ViewBuilder` or `some View`.", "docs/swiftui/views-and-controls.md");
33
+ }
34
+ // Deprecated modifier.
35
+ if (/\.cornerRadius\(/.test(line)) {
36
+ push(number, line, "deprecated-corner-radius", "minor", ".cornerRadius is deprecated.", "Clips without antialiasing control and does not compose with shape-based backgrounds.", "Use `.clipShape(.rect(cornerRadius:))` or the `in:` parameter of `.background`.", DESIGN_DOC);
37
+ }
38
+ // Literal design values at a call site.
39
+ const padding = /\.padding\(\s*(\d+)\s*\)/.exec(line);
40
+ if (padding) {
41
+ push(number, line, "literal-spacing", "minor", `Literal spacing value (${padding[1]}) at a call site.`, "Changing the spacing rhythm means finding every literal by hand, and values drift apart over time.", "Use a spacing token (`Space.contentInset`).", DESIGN_DOC);
42
+ }
43
+ // Material with nothing behind it renders as flat gray.
44
+ if (/\.background\(\s*\.(ultraThin|thin|regular|thick)Material\s*\)/.test(line)) {
45
+ push(number, line, "material-possibly-on-solid", "minor", "Material effect — verify there is content behind it.", "Applied over a solid background a material renders as flat gray mud, which is the most common way a SwiftUI screen looks unfinished.", "Use materials only over scrolling or layered content. Otherwise use a solid surface token.", DESIGN_DOC);
46
+ }
47
+ // Transient UI state on an observable model.
48
+ if (/^\s*var\s+(show|is)\w*(Sheet|Alert|Presented|Expanded)\b/.test(line)) {
49
+ if (/@Observable/.test(file.content) && !/struct\s+\w+\s*:\s*View/.test(file.content)) {
50
+ push(number, line, "view-state-on-model", "minor", "Transient presentation state lives on an observable model.", "Sheet flags and draft text are view-local; storing them on a model couples unrelated screens and survives longer than the view.", "Move it to `@State` on the view.", STATE_DOC);
51
+ }
52
+ }
53
+ // Legacy observation in new code.
54
+ if (/:\s*ObservableObject\b/.test(line)) {
55
+ push(number, line, "legacy-observableobject", "minor", "ObservableObject is legacy on iOS 17+.", "Coarser invalidation — any published change re-renders every observing view — and requires @Published on every property.", "Use `@MainActor @Observable final class` (Observation framework).", STATE_DOC);
56
+ }
57
+ if (/@EnvironmentObject\b/.test(line)) {
58
+ push(number, line, "environmentobject", "serious", "@EnvironmentObject crashes at runtime when the object is missing.", "A missing injection is a crash in production rather than a compile error.", "Use `@Environment(Type.self)` with an `@Observable` type.", STATE_DOC);
59
+ }
60
+ // try! is a crash waiting to happen.
61
+ if (/\btry!\s/.test(line)) {
62
+ push(number, line, "force-try", "serious", "try! crashes the app on any thrown error.", "Any failure — a malformed response, a full disk — terminates the process instead of surfacing.", "Use `try` with `do`/`catch`, or `try?` where a nil result is genuinely correct.", "patterns/error-handling.md");
63
+ }
64
+ });
65
+ return findings;
66
+ }
67
+ //# sourceMappingURL=swiftui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"swiftui.js","sourceRoot":"","sources":["../../src/analyzers/swiftui.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /** Severity of a finding, ordered most-serious first when reported. */
2
+ export type Severity = "blocker" | "serious" | "minor";
3
+ /** A single rule violation, anchored to a location with a concrete fix. */
4
+ export interface Finding {
5
+ /** Repo-relative path. */
6
+ file: string;
7
+ /** 1-indexed line. */
8
+ line: number;
9
+ severity: Severity;
10
+ /** Short kebab-case rule id, e.g. "observable-without-mainactor". */
11
+ rule: string;
12
+ /** One sentence stating the defect. */
13
+ message: string;
14
+ /** What goes wrong at runtime, concretely. */
15
+ consequence: string;
16
+ /** The specific change to make. */
17
+ fix: string;
18
+ /** Path into this repo's docs that explains the rule in full. */
19
+ doc: string;
20
+ /** The offending source line, trimmed. */
21
+ excerpt: string;
22
+ }
23
+ /** A Swift source file to analyze. */
24
+ export interface SourceFile {
25
+ /** Repo-relative path. */
26
+ path: string;
27
+ content: string;
28
+ }
29
+ export type Analyzer = (file: SourceFile) => Finding[];
30
+ /** Most severe first, then by file and line, so output is stable. */
31
+ export declare function sortFindings(findings: Finding[]): Finding[];
32
+ /**
33
+ * Files that legitimately do things app code must not — test doubles construct
34
+ * live-looking types, previews hardcode values. Excluding them is what keeps
35
+ * the signal-to-noise ratio high enough that the output is worth reading.
36
+ */
37
+ export declare function isSupportFile(path: string): boolean;
38
+ /** Strip `//` line comments so commented-out code is not reported. */
39
+ export declare function stripComment(line: string): string;
40
+ /** Walk lines with 1-indexed numbers, skipping comment-only content. */
41
+ export declare function eachLine(file: SourceFile, visit: (line: string, number: number, raw: string) => void): void;
@@ -0,0 +1,47 @@
1
+ const SEVERITY_ORDER = {
2
+ blocker: 0,
3
+ serious: 1,
4
+ minor: 2,
5
+ };
6
+ /** Most severe first, then by file and line, so output is stable. */
7
+ export function sortFindings(findings) {
8
+ return [...findings].sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity] ||
9
+ a.file.localeCompare(b.file) ||
10
+ a.line - b.line);
11
+ }
12
+ /**
13
+ * Files that legitimately do things app code must not — test doubles construct
14
+ * live-looking types, previews hardcode values. Excluding them is what keeps
15
+ * the signal-to-noise ratio high enough that the output is worth reading.
16
+ */
17
+ export function isSupportFile(path) {
18
+ return /(Tests?|Mock\w*|Stub\w*|Fake\w*|Preview\w*|\w*Preview|Fixtures?)\.swift$/i.test(path);
19
+ }
20
+ /** Strip `//` line comments so commented-out code is not reported. */
21
+ export function stripComment(line) {
22
+ const index = line.indexOf("//");
23
+ return index === -1 ? line : line.slice(0, index);
24
+ }
25
+ /** Walk lines with 1-indexed numbers, skipping comment-only content. */
26
+ export function eachLine(file, visit) {
27
+ const lines = file.content.split("\n");
28
+ let inBlockComment = false;
29
+ for (let index = 0; index < lines.length; index += 1) {
30
+ const raw = lines[index];
31
+ if (inBlockComment) {
32
+ if (raw.includes("*/"))
33
+ inBlockComment = false;
34
+ continue;
35
+ }
36
+ if (raw.trimStart().startsWith("/*")) {
37
+ if (!raw.includes("*/"))
38
+ inBlockComment = true;
39
+ continue;
40
+ }
41
+ const code = stripComment(raw);
42
+ if (code.trim() === "")
43
+ continue;
44
+ visit(code, index + 1, raw);
45
+ }
46
+ }
47
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/analyzers/types.ts"],"names":[],"mappings":"AAiCA,MAAM,cAAc,GAA6B;IAC/C,OAAO,EAAE,CAAC;IACV,OAAO,EAAE,CAAC;IACV,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,qEAAqE;AACrE,MAAM,UAAU,YAAY,CAAC,QAAmB;IAC9C,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,CACvB,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC;QACvD,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5B,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAClB,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,2EAA2E,CAAC,IAAI,CACrF,IAAI,CACL,CAAC;AACJ,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACpD,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,QAAQ,CACtB,IAAgB,EAChB,KAA0D;IAE1D,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,cAAc,GAAG,KAAK,CAAC;IAE3B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACrD,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QAEzB,IAAI,cAAc,EAAE,CAAC;YACnB,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,cAAc,GAAG,KAAK,CAAC;YAC/C,SAAS;QACX,CAAC;QACD,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,cAAc,GAAG,IAAI,CAAC;YAC/C,SAAS;QACX,CAAC;QAED,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,SAAS;QACjC,KAAK,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { analyzeConcurrency } from "./analyzers/concurrency.js";
6
+ import { analyzeArchitecture } from "./analyzers/architecture.js";
7
+ import { analyzeSwiftUI } from "./analyzers/swiftui.js";
8
+ import { analyzeAvailability } from "./analyzers/availability.js";
9
+ import { analyzeAppStore, analyzeProjectLevelAppStore } from "./analyzers/appstore.js";
10
+ import { readProjectContext, readSwiftFiles, resolveProjectRoot, summarizeProject, } from "./scan.js";
11
+ import { renderFindings } from "./report.js";
12
+ const VERSION = "1.0.0";
13
+ /**
14
+ * Handle CLI flags before starting the stdio transport.
15
+ *
16
+ * Without this, `ios-agent-mcp --help` starts the server and blocks on stdin
17
+ * forever — indistinguishable from a hang, and the first thing someone tries
18
+ * after installing. MCP clients spawn the binary with no arguments, so this
19
+ * never interferes with normal operation.
20
+ */
21
+ function handleCLIFlags(argv) {
22
+ if (argv.includes("--version") || argv.includes("-v")) {
23
+ process.stdout.write(`${VERSION}\n`);
24
+ return true;
25
+ }
26
+ if (argv.includes("--help") || argv.includes("-h")) {
27
+ process.stdout.write([
28
+ `ios-agent-mcp ${VERSION}`,
29
+ "",
30
+ "An MCP server that reviews Swift projects for iOS-specific defects.",
31
+ "",
32
+ "This is a Model Context Protocol server. It speaks JSON-RPC over stdio",
33
+ "and is meant to be launched by an MCP client, not run directly.",
34
+ "",
35
+ "USAGE",
36
+ " ios-agent-mcp Start the server (stdio transport)",
37
+ " ios-agent-mcp --help Show this message",
38
+ " ios-agent-mcp --version Print the version",
39
+ "",
40
+ "SETUP",
41
+ " Claude Code claude mcp add ios-agent -- npx -y ios-agent-mcp",
42
+ " Claude Desktop add to claude_desktop_config.json",
43
+ " Cursor add to .cursor/mcp.json",
44
+ "",
45
+ "TOOLS",
46
+ " analyze_swift_project Overview plus findings per category",
47
+ " review_swift_concurrency Actor isolation and Swift 6 concurrency",
48
+ " review_swift_architecture Layer boundaries and testability",
49
+ " review_swiftui Views, state, Dynamic Type, tokens",
50
+ " check_availability_guards Missing and over-restrictive @available",
51
+ " audit_app_store_readiness Purpose strings, privacy, accessibility",
52
+ "",
53
+ "Each tool takes one argument: an absolute path to a Swift project root.",
54
+ "",
55
+ "DOCS https://github.com/Nagarjuna2997/ios-agent-skill/tree/main/docs/mcp",
56
+ "",
57
+ ].join("\n"));
58
+ return true;
59
+ }
60
+ return false;
61
+ }
62
+ if (handleCLIFlags(process.argv.slice(2))) {
63
+ process.exit(0);
64
+ }
65
+ const server = new McpServer({
66
+ name: "ios-agent-mcp",
67
+ version: VERSION,
68
+ });
69
+ const pathInput = {
70
+ path: z
71
+ .string()
72
+ .describe("Absolute path to the Swift project root (the folder containing Package.swift or the .xcodeproj)."),
73
+ };
74
+ /** Shared plumbing: resolve, scan, analyze, render — with real errors on bad input. */
75
+ async function scanAndRender(path, title, analyze) {
76
+ try {
77
+ const root = await resolveProjectRoot(path);
78
+ const files = await readSwiftFiles(root);
79
+ if (files.length === 0) {
80
+ return {
81
+ content: [
82
+ {
83
+ type: "text",
84
+ text: `No Swift files found under \`${root}\`.\n\nCheck the path points at the project root. Build directories (\`.build\`, \`DerivedData\`, \`Pods\`) are skipped deliberately.`,
85
+ },
86
+ ],
87
+ };
88
+ }
89
+ const findings = await analyze(files);
90
+ return {
91
+ content: [{ type: "text", text: renderFindings(title, findings, files.length) }],
92
+ };
93
+ }
94
+ catch (error) {
95
+ return {
96
+ content: [
97
+ {
98
+ type: "text",
99
+ text: `Scan failed: ${error instanceof Error ? error.message : String(error)}`,
100
+ },
101
+ ],
102
+ isError: true,
103
+ };
104
+ }
105
+ }
106
+ server.registerTool("review_swift_concurrency", {
107
+ title: "Review Swift concurrency and actor isolation",
108
+ description: "Check a Swift project for Swift 6 concurrency and isolation defects: @Observable types missing @MainActor, Task.detached, DispatchQueue.main.async in async code, @unchecked Sendable, nonisolated(unsafe), unstructured Tasks in onAppear, empty catch blocks, and types named Task that shadow _Concurrency.Task. Use when reviewing Swift code, migrating to Swift 6, or diagnosing a data race.",
109
+ inputSchema: pathInput,
110
+ }, async ({ path }) => scanAndRender(path, "Swift Concurrency Review", (files) => files.flatMap(analyzeConcurrency)));
111
+ server.registerTool("review_swift_architecture", {
112
+ title: "Review iOS architecture boundaries",
113
+ description: "Check a Swift project for architecture and testability defects: dependencies defaulting to live implementations, the presentation layer naming concrete data-layer types (URLSession, APIClient, ModelContext), singletons resolved inside view models, the domain layer importing UI frameworks, nested NavigationStacks, and deprecated NavigationView. Use when reviewing MVVM or Clean Architecture code, or when a screen cannot be previewed without a network.",
114
+ inputSchema: pathInput,
115
+ }, async ({ path }) => scanAndRender(path, "Architecture Review", (files) => files.flatMap(analyzeArchitecture)));
116
+ server.registerTool("review_swiftui", {
117
+ title: "Review SwiftUI views and state",
118
+ description: "Check SwiftUI code for view and state defects: fixed font sizes and heights that break Dynamic Type, AnyView, deprecated .cornerRadius, literal spacing values instead of design tokens, materials applied over solid backgrounds, transient presentation state stored on models, legacy ObservableObject and @EnvironmentObject, and try!. Use when reviewing or building SwiftUI screens.",
119
+ inputSchema: pathInput,
120
+ }, async ({ path }) => scanAndRender(path, "SwiftUI Review", (files) => files.flatMap(analyzeSwiftUI)));
121
+ server.registerTool("check_availability_guards", {
122
+ title: "Check iOS availability guards",
123
+ description: "Verify that version-gated Apple APIs are guarded on the version where they were INTRODUCED rather than the newest SDK. Catches both missing guards and over-restrictive ones — for example Liquid Glass (iOS 26) guarded at iOS 27, which silently drops every iOS 26 device to the fallback. Also flags Foundation Models used without a runtime SystemLanguageModel.availability check. Use before shipping, or after bumping an SDK.",
124
+ inputSchema: pathInput,
125
+ }, async ({ path }) => scanAndRender(path, "Availability Guard Check", (files) => files.flatMap(analyzeAvailability)));
126
+ server.registerTool("audit_app_store_readiness", {
127
+ title: "Audit App Store submission readiness",
128
+ description: "Check a Swift project for issues that cause App Store rejection or runtime termination: permission-gated frameworks used without an Info.plist purpose string, a missing PrivacyInfo.xcprivacy manifest, unlocalized user-facing strings, icon-only buttons with no accessibility label, and print() used for logging. Use before submitting to App Review.",
129
+ inputSchema: pathInput,
130
+ }, async ({ path }) => {
131
+ try {
132
+ const root = await resolveProjectRoot(path);
133
+ const [files, context] = await Promise.all([
134
+ readSwiftFiles(root),
135
+ readProjectContext(root),
136
+ ]);
137
+ const findings = [
138
+ ...analyzeProjectLevelAppStore(context),
139
+ ...files.flatMap((file) => analyzeAppStore(file, context)),
140
+ ];
141
+ return {
142
+ content: [
143
+ {
144
+ type: "text",
145
+ text: renderFindings("App Store Readiness Audit", findings, files.length),
146
+ },
147
+ ],
148
+ };
149
+ }
150
+ catch (error) {
151
+ return {
152
+ content: [
153
+ {
154
+ type: "text",
155
+ text: `Audit failed: ${error instanceof Error ? error.message : String(error)}`,
156
+ },
157
+ ],
158
+ isError: true,
159
+ };
160
+ }
161
+ });
162
+ server.registerTool("analyze_swift_project", {
163
+ title: "Analyze a Swift project",
164
+ description: "Produce an overview of a Swift project — file and line counts, deployment target, Swift tools version, frameworks in use, whether tests exist — together with a summary count of findings across every rule category (concurrency, architecture, SwiftUI, availability, App Store). Use this first when you are unfamiliar with a codebase, then run the focused tools on whichever category has findings.",
165
+ inputSchema: pathInput,
166
+ }, async ({ path }) => {
167
+ try {
168
+ const root = await resolveProjectRoot(path);
169
+ const [files, context] = await Promise.all([
170
+ readSwiftFiles(root),
171
+ readProjectContext(root),
172
+ ]);
173
+ const summary = await summarizeProject(root, files);
174
+ const categories = {
175
+ Concurrency: files.flatMap(analyzeConcurrency),
176
+ Architecture: files.flatMap(analyzeArchitecture),
177
+ SwiftUI: files.flatMap(analyzeSwiftUI),
178
+ Availability: files.flatMap(analyzeAvailability),
179
+ "App Store": [
180
+ ...analyzeProjectLevelAppStore(context),
181
+ ...files.flatMap((file) => analyzeAppStore(file, context)),
182
+ ],
183
+ };
184
+ const lines = [
185
+ "# Swift Project Analysis",
186
+ "",
187
+ "## Structure",
188
+ "",
189
+ `- **Swift files:** ${summary.swiftFileCount}`,
190
+ `- **Lines:** ${summary.lineCount.toLocaleString()}`,
191
+ `- **Deployment target:** ${summary.deploymentTarget ? `iOS ${summary.deploymentTarget}` : "not detected"}`,
192
+ `- **Swift tools version:** ${summary.swiftToolsVersion ?? "not detected"}`,
193
+ `- **Package.swift:** ${summary.hasPackageSwift ? "yes" : "no"}`,
194
+ `- **Xcode project:** ${summary.hasXcodeProject ? "yes" : "no"}`,
195
+ `- **Test files:** ${summary.hasTests ? "found" : "**none found**"}`,
196
+ "",
197
+ `- **Frameworks:** ${summary.frameworks.slice(0, 25).join(", ") || "none detected"}`,
198
+ "",
199
+ "## Findings by category",
200
+ "",
201
+ "| Category | 🔴 Blocker | 🟠 Serious | 🟡 Minor | Tool |",
202
+ "|---|---:|---:|---:|---|",
203
+ ];
204
+ const toolFor = {
205
+ Concurrency: "review_swift_concurrency",
206
+ Architecture: "review_swift_architecture",
207
+ SwiftUI: "review_swiftui",
208
+ Availability: "check_availability_guards",
209
+ "App Store": "audit_app_store_readiness",
210
+ };
211
+ let total = 0;
212
+ for (const [name, findings] of Object.entries(categories)) {
213
+ const blocker = findings.filter((f) => f.severity === "blocker").length;
214
+ const serious = findings.filter((f) => f.severity === "serious").length;
215
+ const minor = findings.filter((f) => f.severity === "minor").length;
216
+ total += findings.length;
217
+ lines.push(`| ${name} | ${blocker} | ${serious} | ${minor} | \`${toolFor[name]}\` |`);
218
+ }
219
+ lines.push("", total === 0
220
+ ? "**No findings in any category.**"
221
+ : `**${total} findings total.** Run the tool in the right-hand column for details on any category.`, "", "---", "", "_Static analysis only. It cannot prove the app builds or behaves correctly — run `swift build` and `swift test` for that._");
222
+ return { content: [{ type: "text", text: lines.join("\n") }] };
223
+ }
224
+ catch (error) {
225
+ return {
226
+ content: [
227
+ {
228
+ type: "text",
229
+ text: `Analysis failed: ${error instanceof Error ? error.message : String(error)}`,
230
+ },
231
+ ],
232
+ isError: true,
233
+ };
234
+ }
235
+ });
236
+ const transport = new StdioServerTransport();
237
+ await server.connect(transport);
238
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACvF,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,OAAO,GAAG,OAAO,CAAC;AAExB;;;;;;;GAOG;AACH,SAAS,cAAc,CAAC,IAAc;IACpC,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB;YACE,iBAAiB,OAAO,EAAE;YAC1B,EAAE;YACF,qEAAqE;YACrE,EAAE;YACF,wEAAwE;YACxE,iEAAiE;YACjE,EAAE;YACF,OAAO;YACP,iEAAiE;YACjE,gDAAgD;YAChD,gDAAgD;YAChD,EAAE;YACF,OAAO;YACP,oEAAoE;YACpE,qDAAqD;YACrD,2CAA2C;YAC3C,EAAE;YACF,OAAO;YACP,oEAAoE;YACpE,wEAAwE;YACxE,iEAAiE;YACjE,mEAAmE;YACnE,wEAAwE;YACxE,wEAAwE;YACxE,EAAE;YACF,yEAAyE;YACzE,EAAE;YACF,2EAA2E;YAC3E,EAAE;SACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,IAAI,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,eAAe;IACrB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG;IAChB,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,QAAQ,CAAC,kGAAkG,CAAC;CAChH,CAAC;AAEF,uFAAuF;AACvF,KAAK,UAAU,aAAa,CAC1B,IAAY,EACZ,KAAa,EACb,OAA8F;IAE9F,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;QAEzC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,gCAAgC,IAAI,uIAAuI;qBAClL;iBACF;aACF,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC;QACtC,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;SACjF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,gBAAgB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;iBAC/E;aACF;YACD,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,YAAY,CACjB,0BAA0B,EAC1B;IACE,KAAK,EAAE,8CAA8C;IACrD,WAAW,EACT,qYAAqY;IACvY,WAAW,EAAE,SAAS;CACvB,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CACjB,aAAa,CAAC,IAAI,EAAE,0BAA0B,EAAE,CAAC,KAAK,EAAE,EAAE,CACxD,KAAK,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAClC,CACJ,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,2BAA2B,EAC3B;IACE,KAAK,EAAE,oCAAoC;IAC3C,WAAW,EACT,ucAAuc;IACzc,WAAW,EAAE,SAAS;CACvB,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CACjB,aAAa,CAAC,IAAI,EAAE,qBAAqB,EAAE,CAAC,KAAK,EAAE,EAAE,CACnD,KAAK,CAAC,OAAO,CAAC,mBAAmB,CAAC,CACnC,CACJ,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,gBAAgB,EAChB;IACE,KAAK,EAAE,gCAAgC;IACvC,WAAW,EACT,6XAA6X;IAC/X,WAAW,EAAE,SAAS;CACvB,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CACjB,aAAa,CAAC,IAAI,EAAE,gBAAgB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAClF,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,2BAA2B,EAC3B;IACE,KAAK,EAAE,+BAA+B;IACtC,WAAW,EACT,yaAAya;IAC3a,WAAW,EAAE,SAAS;CACvB,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CACjB,aAAa,CAAC,IAAI,EAAE,0BAA0B,EAAE,CAAC,KAAK,EAAE,EAAE,CACxD,KAAK,CAAC,OAAO,CAAC,mBAAmB,CAAC,CACnC,CACJ,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,2BAA2B,EAC3B;IACE,KAAK,EAAE,sCAAsC;IAC7C,WAAW,EACT,6VAA6V;IAC/V,WAAW,EAAE,SAAS;CACvB,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACzC,cAAc,CAAC,IAAI,CAAC;YACpB,kBAAkB,CAAC,IAAI,CAAC;SACzB,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG;YACf,GAAG,2BAA2B,CAAC,OAAO,CAAC;YACvC,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;SAC3D,CAAC;QACF,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,cAAc,CAAC,2BAA2B,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC;iBAC1E;aACF;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,iBAAiB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;iBAChF;aACF;YACD,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,uBAAuB,EACvB;IACE,KAAK,EAAE,yBAAyB;IAChC,WAAW,EACT,4YAA4Y;IAC9Y,WAAW,EAAE,SAAS;CACvB,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACzC,cAAc,CAAC,IAAI,CAAC;YACpB,kBAAkB,CAAC,IAAI,CAAC;SACzB,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAEpD,MAAM,UAAU,GAAG;YACjB,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,kBAAkB,CAAC;YAC9C,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,mBAAmB,CAAC;YAChD,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC;YACtC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,mBAAmB,CAAC;YAChD,WAAW,EAAE;gBACX,GAAG,2BAA2B,CAAC,OAAO,CAAC;gBACvC,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;aAC3D;SACF,CAAC;QAEF,MAAM,KAAK,GAAa;YACtB,0BAA0B;YAC1B,EAAE;YACF,cAAc;YACd,EAAE;YACF,sBAAsB,OAAO,CAAC,cAAc,EAAE;YAC9C,gBAAgB,OAAO,CAAC,SAAS,CAAC,cAAc,EAAE,EAAE;YACpD,4BAA4B,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE;YAC3G,8BAA8B,OAAO,CAAC,iBAAiB,IAAI,cAAc,EAAE;YAC3E,wBAAwB,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;YAChE,wBAAwB,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;YAChE,qBAAqB,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,EAAE;YACpE,EAAE;YACF,qBAAqB,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,eAAe,EAAE;YACpF,EAAE;YACF,yBAAyB;YACzB,EAAE;YACF,0DAA0D;YAC1D,0BAA0B;SAC3B,CAAC;QAEF,MAAM,OAAO,GAA2B;YACtC,WAAW,EAAE,0BAA0B;YACvC,YAAY,EAAE,2BAA2B;YACzC,OAAO,EAAE,gBAAgB;YACzB,YAAY,EAAE,2BAA2B;YACzC,WAAW,EAAE,2BAA2B;SACzC,CAAC;QAEF,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YAC1D,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;YACxE,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;YACxE,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM,CAAC;YACpE,KAAK,IAAI,QAAQ,CAAC,MAAM,CAAC;YACzB,KAAK,CAAC,IAAI,CACR,KAAK,IAAI,MAAM,OAAO,MAAM,OAAO,MAAM,KAAK,QAAQ,OAAO,CAAC,IAAI,CAAC,MAAM,CAC1E,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,IAAI,CACR,EAAE,EACF,KAAK,KAAK,CAAC;YACT,CAAC,CAAC,kCAAkC;YACpC,CAAC,CAAC,KAAK,KAAK,uFAAuF,EACrG,EAAE,EACF,KAAK,EACL,EAAE,EACF,4HAA4H,CAC7H,CAAC;QAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IAC1E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,oBAAoB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;iBACnF;aACF;YACD,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { Finding } from "./analyzers/types.js";
2
+ /**
3
+ * Render findings as readable markdown.
4
+ *
5
+ * Deliberately reports counts and the empty case explicitly: a report that says
6
+ * nothing is indistinguishable from a check that never ran. See
7
+ * docs/orchestration/verification.md.
8
+ */
9
+ export declare function renderFindings(title: string, findings: Finding[], scanned: number, options?: {
10
+ limit?: number;
11
+ }): string;
package/dist/report.js ADDED
@@ -0,0 +1,41 @@
1
+ import { sortFindings } from "./analyzers/types.js";
2
+ const ICON = {
3
+ blocker: "🔴",
4
+ serious: "🟠",
5
+ minor: "🟡",
6
+ };
7
+ /**
8
+ * Render findings as readable markdown.
9
+ *
10
+ * Deliberately reports counts and the empty case explicitly: a report that says
11
+ * nothing is indistinguishable from a check that never ran. See
12
+ * docs/orchestration/verification.md.
13
+ */
14
+ export function renderFindings(title, findings, scanned, options = {}) {
15
+ const limit = options.limit ?? 50;
16
+ const sorted = sortFindings(findings);
17
+ const lines = [`# ${title}`, ""];
18
+ lines.push(`Scanned **${scanned}** Swift file${scanned === 1 ? "" : "s"}.`, "");
19
+ if (sorted.length === 0) {
20
+ lines.push("**No findings.** Every rule in this category passed.", "", "_This is a static check. It cannot verify behavior — run the build and the test suite for that._");
21
+ return lines.join("\n");
22
+ }
23
+ const counts = {
24
+ blocker: sorted.filter((f) => f.severity === "blocker").length,
25
+ serious: sorted.filter((f) => f.severity === "serious").length,
26
+ minor: sorted.filter((f) => f.severity === "minor").length,
27
+ };
28
+ lines.push(`**${sorted.length} finding${sorted.length === 1 ? "" : "s"}** — ` +
29
+ `🔴 ${counts.blocker} blocker · 🟠 ${counts.serious} serious · 🟡 ${counts.minor} minor`, "");
30
+ const shown = sorted.slice(0, limit);
31
+ for (const finding of shown) {
32
+ lines.push(`### ${ICON[finding.severity]} ${finding.file}:${finding.line} — ${finding.message}`, "", "```swift", finding.excerpt, "```", "", `**Why it matters:** ${finding.consequence}`, "", `**Fix:** ${finding.fix}`, "", `_Rule \`${finding.rule}\` · see \`${finding.doc}\`_`, "");
33
+ }
34
+ if (sorted.length > shown.length) {
35
+ lines.push(`_… and ${sorted.length - shown.length} more. Narrow the scan with \`path\` to see them._`, "");
36
+ }
37
+ lines.push("---", "", "_Static analysis only. It cannot prove the app builds or behaves correctly — " +
38
+ "run `swift build` and `swift test` for that._");
39
+ return lines.join("\n");
40
+ }
41
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,IAAI,GAAwC;IAChD,OAAO,EAAE,IAAI;IACb,OAAO,EAAE,IAAI;IACb,KAAK,EAAE,IAAI;CACZ,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAa,EACb,QAAmB,EACnB,OAAe,EACf,UAA8B,EAAE;IAEhC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,KAAK,GAAa,CAAC,KAAK,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IAE3C,KAAK,CAAC,IAAI,CACR,aAAa,OAAO,gBAAgB,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,EAC/D,EAAE,CACH,CAAC;IAEF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CACR,sDAAsD,EACtD,EAAE,EACF,kGAAkG,CACnG,CAAC;QACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,MAAM,MAAM,GAAG;QACb,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM;QAC9D,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM;QAC9D,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM;KAC3D,CAAC;IAEF,KAAK,CAAC,IAAI,CACR,KAAK,MAAM,CAAC,MAAM,WAAW,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO;QAChE,MAAM,MAAM,CAAC,OAAO,iBAAiB,MAAM,CAAC,OAAO,iBAAiB,MAAM,CAAC,KAAK,QAAQ,EAC1F,EAAE,CACH,CAAC;IAEF,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACrC,KAAK,MAAM,OAAO,IAAI,KAAK,EAAE,CAAC;QAC5B,KAAK,CAAC,IAAI,CACR,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,MAAM,OAAO,CAAC,OAAO,EAAE,EACpF,EAAE,EACF,UAAU,EACV,OAAO,CAAC,OAAO,EACf,KAAK,EACL,EAAE,EACF,uBAAuB,OAAO,CAAC,WAAW,EAAE,EAC5C,EAAE,EACF,YAAY,OAAO,CAAC,GAAG,EAAE,EACzB,EAAE,EACF,WAAW,OAAO,CAAC,IAAI,cAAc,OAAO,CAAC,GAAG,KAAK,EACrD,EAAE,CACH,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QACjC,KAAK,CAAC,IAAI,CACR,UAAU,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,oDAAoD,EAC1F,EAAE,CACH,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,IAAI,CACR,KAAK,EACL,EAAE,EACF,+EAA+E;QAC7E,+CAA+C,CAClD,CAAC;IAEF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
package/dist/scan.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { SourceFile } from "./analyzers/types.js";
2
+ import { ProjectContext } from "./analyzers/appstore.js";
3
+ /**
4
+ * Resolve and validate a project root.
5
+ *
6
+ * Rejects paths that do not exist or are not directories, so a typo produces a
7
+ * clear error rather than an empty, falsely-clean report.
8
+ */
9
+ export declare function resolveProjectRoot(path: string): Promise<string>;
10
+ /** Read every Swift source file under `root`, capped for safety. */
11
+ export declare function readSwiftFiles(root: string): Promise<SourceFile[]>;
12
+ /** Gather the project-level context the App Store checks need. */
13
+ export declare function readProjectContext(root: string): Promise<ProjectContext>;
14
+ export interface ProjectSummary {
15
+ swiftFileCount: number;
16
+ lineCount: number;
17
+ deploymentTarget: string | null;
18
+ swiftToolsVersion: string | null;
19
+ frameworks: string[];
20
+ hasTests: boolean;
21
+ hasPackageSwift: boolean;
22
+ hasXcodeProject: boolean;
23
+ }
24
+ /** A structural overview of the project, independent of rule violations. */
25
+ export declare function summarizeProject(root: string, files: SourceFile[]): Promise<ProjectSummary>;