ios-agent-mcp 2.0.0 → 2.1.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,222 @@
1
+ import { realpathSync, statSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { analyzeConcurrency } from "./analyzers/concurrency.js";
4
+ import { analyzeArchitecture } from "./analyzers/architecture.js";
5
+ import { analyzeSwiftUI } from "./analyzers/swiftui.js";
6
+ import { analyzeAvailability } from "./analyzers/availability.js";
7
+ import { analyzeAppStore, analyzeProjectLevelAppStore } from "./analyzers/appstore.js";
8
+ import { analyzeMemory } from "./analyzers/memory.js";
9
+ import { analyzeSecurity } from "./analyzers/security.js";
10
+ import { analyzeTesting, analyzeTestCoverage } from "./analyzers/testing.js";
11
+ import { analyzePerformance } from "./analyzers/performance.js";
12
+ import { sortFindings } from "./analyzers/types.js";
13
+ import { readProjectContext, readSwiftFiles, resolveProjectRoot, summarizeProject, } from "./scan.js";
14
+ /**
15
+ * Resource-backed project state.
16
+ *
17
+ * Tools are verbs the model chooses to call. Resources are nouns a client can
18
+ * read without being asked — so `ios://project/info` lets a client attach the
19
+ * project's shape to context up front, instead of the model having to think to
20
+ * run an analysis first.
21
+ *
22
+ * WHY THERE IS A CONFIGURED ROOT
23
+ *
24
+ * Resources are addressed by a fixed URI with no arguments, so `ios://project/…`
25
+ * only means something if the server knows which project it is. MCP clients
26
+ * already configure servers per project — `.cursor/mcp.json` lives in the
27
+ * repository, `claude mcp add` is run from it — so a root passed at launch fits
28
+ * how the server is actually deployed.
29
+ *
30
+ * WHY THERE IS NO ios://project/build-status
31
+ *
32
+ * It would have to run `xcodebuild`. That needs macOS and Xcode, and it breaks
33
+ * the `filesystem: read, network: none` contract that lets this package install
34
+ * anywhere in ~26 KB. Build and simulator state belong in the separate package
35
+ * that already requires a full toolchain — see ROADMAP.md.
36
+ */
37
+ /**
38
+ * The directory `ios-agent` keeps its internal files in.
39
+ *
40
+ * Read-only here: this server never creates it. Its presence is used purely as
41
+ * a project-root marker, the same way git treats `.git` — which is what lets
42
+ * the CLI and this server agree on a root without either configuring the other.
43
+ */
44
+ export const INTERNAL_DIR = ".ios-agent";
45
+ /** How far up the tree to look. Guards against a symlink loop. */
46
+ const MAX_ASCENT = 64;
47
+ /**
48
+ * Walk up from `start` looking for a directory containing `.ios-agent/`.
49
+ *
50
+ * Returns `undefined` rather than a fallback, so the caller decides what an
51
+ * absent marker means. Guessing here is how a server ends up analyzing a user's
52
+ * home directory because it was launched from the wrong place.
53
+ */
54
+ export function findProjectRootUpwards(start) {
55
+ let current = resolve(start);
56
+ for (let step = 0; step < MAX_ASCENT; step += 1) {
57
+ try {
58
+ if (statSync(join(current, INTERNAL_DIR)).isDirectory())
59
+ return realpathSync(current);
60
+ }
61
+ catch {
62
+ // Not here; keep walking.
63
+ }
64
+ const parent = dirname(current);
65
+ if (parent === current)
66
+ return undefined;
67
+ current = parent;
68
+ }
69
+ return undefined;
70
+ }
71
+ /**
72
+ * Resolve the project root, and say how.
73
+ *
74
+ * Order: `--project`, then `IOS_AGENT_PROJECT`, then the nearest ancestor with
75
+ * a `.ios-agent/` marker, then cwd. The marker step is what makes the server
76
+ * work when a client spawns it from a nested directory — previously that
77
+ * silently analyzed whatever subtree it happened to land in.
78
+ *
79
+ * The `source` travels with the root because an implicit root is unfalsifiable:
80
+ * an empty result looks identical whether the project has no Swift or the
81
+ * server is pointed somewhere else entirely.
82
+ */
83
+ export function resolveRootFrom(argv, env) {
84
+ const flagIndex = argv.indexOf("--project");
85
+ if (flagIndex !== -1 && argv[flagIndex + 1]) {
86
+ return { root: resolve(argv[flagIndex + 1]), source: "flag" };
87
+ }
88
+ if (env.IOS_AGENT_PROJECT) {
89
+ return { root: resolve(env.IOS_AGENT_PROJECT), source: "environment" };
90
+ }
91
+ const cwd = resolve(env.PWD ?? process.cwd());
92
+ const marker = findProjectRootUpwards(cwd);
93
+ if (marker)
94
+ return { root: marker, source: "marker" };
95
+ return { root: cwd, source: "cwd" };
96
+ }
97
+ /** Resolve the project root from an explicit flag, the environment, a marker, or cwd. */
98
+ export function projectRootFrom(argv, env) {
99
+ return resolveRootFrom(argv, env).root;
100
+ }
101
+ /**
102
+ * Every resource states the root it used.
103
+ *
104
+ * The root is implicit — it comes from a flag, the environment, or the working
105
+ * directory the client happened to spawn the server in. A reader who cannot see
106
+ * which of those won has no way to tell an empty project from a wrong path.
107
+ */
108
+ function payload(uri, body) {
109
+ return {
110
+ uri,
111
+ mimeType: "application/json",
112
+ text: `${JSON.stringify(body, null, 2)}\n`,
113
+ };
114
+ }
115
+ function unavailable(uri, root, reason) {
116
+ return payload(uri, {
117
+ project_root: root,
118
+ available: false,
119
+ reason,
120
+ fix: "Launch the server with `--project /path/to/project`, set IOS_AGENT_PROJECT, or run it from a directory under one holding a `.ios-agent/` marker. MCP clients usually set this in the per-project config file.",
121
+ });
122
+ }
123
+ /** Scan once; every resource in a read is served from the same snapshot. */
124
+ async function snapshot(root) {
125
+ const resolved = await resolveProjectRoot(root);
126
+ const [files, context] = await Promise.all([
127
+ readSwiftFiles(resolved),
128
+ readProjectContext(resolved),
129
+ ]);
130
+ return { resolved, files, context };
131
+ }
132
+ export async function projectInfoResource(uri, root, source) {
133
+ try {
134
+ const { resolved, files } = await snapshot(root);
135
+ if (files.length === 0) {
136
+ return unavailable(uri, resolved, "No Swift files found under this path.");
137
+ }
138
+ const summary = await summarizeProject(resolved, files);
139
+ return payload(uri, {
140
+ project_root: resolved,
141
+ resolved_from: source ?? "cwd",
142
+ available: true,
143
+ swift_files: summary.swiftFileCount,
144
+ lines: summary.lineCount,
145
+ deployment_target: summary.deploymentTarget,
146
+ swift_tools_version: summary.swiftToolsVersion,
147
+ ui_framework: summary.uiFramework,
148
+ architecture: summary.architecture,
149
+ // The evidence ships with the verdict. "MVVM" alone is a guess presented
150
+ // as a fact; the reader must be able to check it.
151
+ architecture_evidence: summary.architectureEvidence,
152
+ uses_dependency_injection: summary.usesDependencyInjection,
153
+ has_tests: summary.hasTests,
154
+ has_package_swift: summary.hasPackageSwift,
155
+ has_xcode_project: summary.hasXcodeProject,
156
+ frameworks: summary.frameworks,
157
+ });
158
+ }
159
+ catch (error) {
160
+ return unavailable(uri, root, error instanceof Error ? error.message : String(error));
161
+ }
162
+ }
163
+ export async function projectDependenciesResource(uri, root) {
164
+ try {
165
+ const { resolved, files } = await snapshot(root);
166
+ const summary = await summarizeProject(resolved, files);
167
+ return payload(uri, {
168
+ project_root: resolved,
169
+ available: true,
170
+ third_party: summary.dependencies,
171
+ // Apple frameworks are not dependencies you manage, but they are the
172
+ // best available signal for what the app actually does.
173
+ apple_frameworks: summary.frameworks,
174
+ });
175
+ }
176
+ catch (error) {
177
+ return unavailable(uri, root, error instanceof Error ? error.message : String(error));
178
+ }
179
+ }
180
+ export async function projectIssuesResource(uri, root) {
181
+ try {
182
+ const { resolved, files, context } = await snapshot(root);
183
+ if (files.length === 0) {
184
+ return unavailable(uri, resolved, "No Swift files found under this path.");
185
+ }
186
+ const categories = {
187
+ concurrency: files.flatMap(analyzeConcurrency),
188
+ architecture: files.flatMap(analyzeArchitecture),
189
+ swiftui: files.flatMap(analyzeSwiftUI),
190
+ availability: files.flatMap(analyzeAvailability),
191
+ memory: files.flatMap(analyzeMemory),
192
+ security: files.flatMap(analyzeSecurity),
193
+ performance: files.flatMap(analyzePerformance),
194
+ testing: [...analyzeTestCoverage(files), ...files.flatMap(analyzeTesting)],
195
+ app_store: [
196
+ ...analyzeProjectLevelAppStore(context),
197
+ ...files.flatMap((file) => analyzeAppStore(file, context)),
198
+ ],
199
+ };
200
+ const all = sortFindings(Object.values(categories).flat());
201
+ return payload(uri, {
202
+ project_root: resolved,
203
+ available: true,
204
+ files_checked: files.length,
205
+ counts: {
206
+ blocker: all.filter((f) => f.severity === "blocker").length,
207
+ serious: all.filter((f) => f.severity === "serious").length,
208
+ minor: all.filter((f) => f.severity === "minor").length,
209
+ total: all.length,
210
+ },
211
+ by_category: Object.fromEntries(Object.entries(categories).map(([name, found]) => [name, found.length])),
212
+ // Capped: a resource is attached to context wholesale, so an unbounded
213
+ // list would crowd out the conversation it is meant to inform.
214
+ issues: all.slice(0, 100),
215
+ truncated: all.length > 100 ? all.length - 100 : 0,
216
+ });
217
+ }
218
+ catch (error) {
219
+ return unavailable(uri, root, error instanceof Error ? error.message : String(error));
220
+ }
221
+ }
222
+ //# sourceMappingURL=resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,203 @@
1
+ import { z } from "zod";
2
+ import { Finding } from "./analyzers/types.js";
3
+ /**
4
+ * A 0–100 defect-density score.
5
+ *
6
+ * penalty = 10·blockers + 3·serious + 1·minor
7
+ * capacity = filesChecked × 10
8
+ * score = clamp(0, 100, round(100 × (1 − penalty / capacity)))
9
+ *
10
+ * 100 means no findings. The denominator is file count, so the score is a
11
+ * *density*: adding clean files raises it, which is the intended behaviour for
12
+ * tracking one project over time.
13
+ *
14
+ * It is deliberately NOT comparable between projects — a UI-heavy target and a
15
+ * networking library have different rule surfaces. Use it as a direction of
16
+ * travel, and use the counts for anything that matters.
17
+ */
18
+ export declare function scoreFor(findings: Finding[], filesChecked: number): number;
19
+ export declare const issueSchema: z.ZodObject<{
20
+ file: z.ZodString;
21
+ line: z.ZodNumber;
22
+ severity: z.ZodEnum<["blocker", "serious", "minor"]>;
23
+ rule: z.ZodString;
24
+ message: z.ZodString;
25
+ consequence: z.ZodString;
26
+ fix: z.ZodString;
27
+ doc: z.ZodString;
28
+ excerpt: z.ZodString;
29
+ }, "strip", z.ZodTypeAny, {
30
+ severity: "blocker" | "serious" | "minor";
31
+ file: string;
32
+ line: number;
33
+ message: string;
34
+ rule: string;
35
+ consequence: string;
36
+ fix: string;
37
+ doc: string;
38
+ excerpt: string;
39
+ }, {
40
+ severity: "blocker" | "serious" | "minor";
41
+ file: string;
42
+ line: number;
43
+ message: string;
44
+ rule: string;
45
+ consequence: string;
46
+ fix: string;
47
+ doc: string;
48
+ excerpt: string;
49
+ }>;
50
+ /** The shape every review tool declares as its `outputSchema`. */
51
+ export declare const reviewOutputShape: {
52
+ summary: z.ZodString;
53
+ score: z.ZodNumber;
54
+ counts: z.ZodObject<{
55
+ blocker: z.ZodNumber;
56
+ serious: z.ZodNumber;
57
+ minor: z.ZodNumber;
58
+ total: z.ZodNumber;
59
+ }, "strip", z.ZodTypeAny, {
60
+ blocker: number;
61
+ serious: number;
62
+ minor: number;
63
+ total: number;
64
+ }, {
65
+ blocker: number;
66
+ serious: number;
67
+ minor: number;
68
+ total: number;
69
+ }>;
70
+ files_checked: z.ZodNumber;
71
+ issues: z.ZodArray<z.ZodObject<{
72
+ file: z.ZodString;
73
+ line: z.ZodNumber;
74
+ severity: z.ZodEnum<["blocker", "serious", "minor"]>;
75
+ rule: z.ZodString;
76
+ message: z.ZodString;
77
+ consequence: z.ZodString;
78
+ fix: z.ZodString;
79
+ doc: z.ZodString;
80
+ excerpt: z.ZodString;
81
+ }, "strip", z.ZodTypeAny, {
82
+ severity: "blocker" | "serious" | "minor";
83
+ file: string;
84
+ line: number;
85
+ message: string;
86
+ rule: string;
87
+ consequence: string;
88
+ fix: string;
89
+ doc: string;
90
+ excerpt: string;
91
+ }, {
92
+ severity: "blocker" | "serious" | "minor";
93
+ file: string;
94
+ line: number;
95
+ message: string;
96
+ rule: string;
97
+ consequence: string;
98
+ fix: string;
99
+ doc: string;
100
+ excerpt: string;
101
+ }>, "many">;
102
+ suggestions: z.ZodArray<z.ZodString, "many">;
103
+ };
104
+ export declare const reviewOutputSchema: z.ZodObject<{
105
+ summary: z.ZodString;
106
+ score: z.ZodNumber;
107
+ counts: z.ZodObject<{
108
+ blocker: z.ZodNumber;
109
+ serious: z.ZodNumber;
110
+ minor: z.ZodNumber;
111
+ total: z.ZodNumber;
112
+ }, "strip", z.ZodTypeAny, {
113
+ blocker: number;
114
+ serious: number;
115
+ minor: number;
116
+ total: number;
117
+ }, {
118
+ blocker: number;
119
+ serious: number;
120
+ minor: number;
121
+ total: number;
122
+ }>;
123
+ files_checked: z.ZodNumber;
124
+ issues: z.ZodArray<z.ZodObject<{
125
+ file: z.ZodString;
126
+ line: z.ZodNumber;
127
+ severity: z.ZodEnum<["blocker", "serious", "minor"]>;
128
+ rule: z.ZodString;
129
+ message: z.ZodString;
130
+ consequence: z.ZodString;
131
+ fix: z.ZodString;
132
+ doc: z.ZodString;
133
+ excerpt: z.ZodString;
134
+ }, "strip", z.ZodTypeAny, {
135
+ severity: "blocker" | "serious" | "minor";
136
+ file: string;
137
+ line: number;
138
+ message: string;
139
+ rule: string;
140
+ consequence: string;
141
+ fix: string;
142
+ doc: string;
143
+ excerpt: string;
144
+ }, {
145
+ severity: "blocker" | "serious" | "minor";
146
+ file: string;
147
+ line: number;
148
+ message: string;
149
+ rule: string;
150
+ consequence: string;
151
+ fix: string;
152
+ doc: string;
153
+ excerpt: string;
154
+ }>, "many">;
155
+ suggestions: z.ZodArray<z.ZodString, "many">;
156
+ }, "strip", z.ZodTypeAny, {
157
+ issues: {
158
+ severity: "blocker" | "serious" | "minor";
159
+ file: string;
160
+ line: number;
161
+ message: string;
162
+ rule: string;
163
+ consequence: string;
164
+ fix: string;
165
+ doc: string;
166
+ excerpt: string;
167
+ }[];
168
+ summary: string;
169
+ score: number;
170
+ counts: {
171
+ blocker: number;
172
+ serious: number;
173
+ minor: number;
174
+ total: number;
175
+ };
176
+ files_checked: number;
177
+ suggestions: string[];
178
+ }, {
179
+ issues: {
180
+ severity: "blocker" | "serious" | "minor";
181
+ file: string;
182
+ line: number;
183
+ message: string;
184
+ rule: string;
185
+ consequence: string;
186
+ fix: string;
187
+ doc: string;
188
+ excerpt: string;
189
+ }[];
190
+ summary: string;
191
+ score: number;
192
+ counts: {
193
+ blocker: number;
194
+ serious: number;
195
+ minor: number;
196
+ total: number;
197
+ };
198
+ files_checked: number;
199
+ suggestions: string[];
200
+ }>;
201
+ export type ReviewOutput = z.infer<typeof reviewOutputSchema>;
202
+ /** Build the structured half of a tool result. */
203
+ export declare function buildReviewOutput(title: string, findings: Finding[], filesChecked: number): ReviewOutput;
package/dist/result.js ADDED
@@ -0,0 +1,135 @@
1
+ import { z } from "zod";
2
+ import { sortFindings } from "./analyzers/types.js";
3
+ /**
4
+ * Structured tool output.
5
+ *
6
+ * Every review tool returns BOTH a markdown `content` block and this object as
7
+ * `structuredContent`. The markdown is for a human reading the transcript; the
8
+ * object is for a workflow that needs to branch on the result without parsing
9
+ * prose. Returning only JSON would make the transcript unreadable; returning
10
+ * only prose forces every consumer to regex it.
11
+ *
12
+ * Field names are snake_case because this is a wire contract, not internal API.
13
+ */
14
+ /** Weights behind `score`. Stated here because a score nobody can reproduce is a vibe. */
15
+ const SEVERITY_WEIGHT = {
16
+ blocker: 10,
17
+ serious: 3,
18
+ minor: 1,
19
+ };
20
+ /** One blocker per file scanned is the notional floor. */
21
+ const CAPACITY_PER_FILE = 10;
22
+ /**
23
+ * A 0–100 defect-density score.
24
+ *
25
+ * penalty = 10·blockers + 3·serious + 1·minor
26
+ * capacity = filesChecked × 10
27
+ * score = clamp(0, 100, round(100 × (1 − penalty / capacity)))
28
+ *
29
+ * 100 means no findings. The denominator is file count, so the score is a
30
+ * *density*: adding clean files raises it, which is the intended behaviour for
31
+ * tracking one project over time.
32
+ *
33
+ * It is deliberately NOT comparable between projects — a UI-heavy target and a
34
+ * networking library have different rule surfaces. Use it as a direction of
35
+ * travel, and use the counts for anything that matters.
36
+ */
37
+ export function scoreFor(findings, filesChecked) {
38
+ if (filesChecked <= 0)
39
+ return 100;
40
+ const penalty = findings.reduce((total, finding) => total + SEVERITY_WEIGHT[finding.severity], 0);
41
+ const capacity = filesChecked * CAPACITY_PER_FILE;
42
+ const score = Math.round(100 * (1 - penalty / capacity));
43
+ return Math.max(0, Math.min(100, score));
44
+ }
45
+ export const issueSchema = z.object({
46
+ file: z.string(),
47
+ line: z.number().int(),
48
+ severity: z.enum(["blocker", "serious", "minor"]),
49
+ rule: z.string(),
50
+ message: z.string(),
51
+ consequence: z.string(),
52
+ fix: z.string(),
53
+ doc: z.string(),
54
+ excerpt: z.string(),
55
+ });
56
+ /** The shape every review tool declares as its `outputSchema`. */
57
+ export const reviewOutputShape = {
58
+ summary: z.string().describe("One-line plain-language result."),
59
+ score: z
60
+ .number()
61
+ .int()
62
+ .describe("0-100 defect density. 100 = no findings. penalty = 10*blockers + 3*serious + 1*minor; capacity = files*10. Comparable across runs on ONE project, not between projects."),
63
+ counts: z.object({
64
+ blocker: z.number().int(),
65
+ serious: z.number().int(),
66
+ minor: z.number().int(),
67
+ total: z.number().int(),
68
+ }),
69
+ files_checked: z.number().int().describe("Swift files actually scanned."),
70
+ issues: z.array(issueSchema).describe("Every finding, most severe first."),
71
+ suggestions: z
72
+ .array(z.string())
73
+ .describe("Prioritized next actions, deduplicated by rule — not a restatement of every issue's fix."),
74
+ };
75
+ export const reviewOutputSchema = z.object(reviewOutputShape);
76
+ /**
77
+ * Collapse findings into a handful of actions.
78
+ *
79
+ * A `suggestions` array that simply repeats every issue's `fix` is noise — the
80
+ * issues already carry those. This groups by rule so a file with forty
81
+ * literal-spacing findings produces one instruction, ordered by total weight.
82
+ */
83
+ function suggestionsFor(findings) {
84
+ if (findings.length === 0)
85
+ return [];
86
+ const byRule = new Map();
87
+ for (const finding of findings) {
88
+ const entry = byRule.get(finding.rule) ?? {
89
+ count: 0,
90
+ weight: 0,
91
+ fix: finding.fix,
92
+ };
93
+ entry.count += 1;
94
+ entry.weight += SEVERITY_WEIGHT[finding.severity];
95
+ byRule.set(finding.rule, entry);
96
+ }
97
+ return [...byRule.entries()]
98
+ .sort((a, b) => b[1].weight - a[1].weight)
99
+ .slice(0, 5)
100
+ .map(([rule, entry]) => entry.count === 1
101
+ ? `${rule}: ${entry.fix}`
102
+ : `${rule} (${entry.count} occurrences): ${entry.fix}`);
103
+ }
104
+ function summarize(title, counts, files) {
105
+ if (counts.total === 0) {
106
+ return `${title}: no findings across ${files} file${files === 1 ? "" : "s"}.`;
107
+ }
108
+ const parts = [];
109
+ if (counts.blocker > 0)
110
+ parts.push(`${counts.blocker} blocker`);
111
+ if (counts.serious > 0)
112
+ parts.push(`${counts.serious} serious`);
113
+ if (counts.minor > 0)
114
+ parts.push(`${counts.minor} minor`);
115
+ return `${title}: ${parts.join(", ")} across ${files} file${files === 1 ? "" : "s"}.`;
116
+ }
117
+ /** Build the structured half of a tool result. */
118
+ export function buildReviewOutput(title, findings, filesChecked) {
119
+ const sorted = sortFindings(findings);
120
+ const counts = {
121
+ blocker: sorted.filter((f) => f.severity === "blocker").length,
122
+ serious: sorted.filter((f) => f.severity === "serious").length,
123
+ minor: sorted.filter((f) => f.severity === "minor").length,
124
+ total: sorted.length,
125
+ };
126
+ return {
127
+ summary: summarize(title, counts, filesChecked),
128
+ score: scoreFor(sorted, filesChecked),
129
+ counts,
130
+ files_checked: filesChecked,
131
+ issues: sorted,
132
+ suggestions: suggestionsFor(sorted),
133
+ };
134
+ }
135
+ //# sourceMappingURL=result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.js","sourceRoot":"","sources":["../src/result.ts"],"names":[],"mappings":"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"}
package/dist/scan.d.ts CHANGED
@@ -20,6 +20,15 @@ export interface ProjectSummary {
20
20
  hasTests: boolean;
21
21
  hasPackageSwift: boolean;
22
22
  hasXcodeProject: boolean;
23
+ /** SwiftUI, UIKit, both, or neither — inferred from imports. */
24
+ uiFramework: "SwiftUI" | "UIKit" | "SwiftUI + UIKit" | "unknown";
25
+ /** Best-effort architecture read. Evidence is reported alongside it. */
26
+ architecture: string;
27
+ architectureEvidence: string[];
28
+ /** Third-party packages, from Package.swift / Podfile / project.pbxproj. */
29
+ dependencies: string[];
30
+ /** True when at least one dependency crosses an injected protocol boundary. */
31
+ usesDependencyInjection: boolean;
23
32
  }
24
33
  /** A structural overview of the project, independent of rule violations. */
25
34
  export declare function summarizeProject(root: string, files: SourceFile[]): Promise<ProjectSummary>;