@prasenjeet/shipli 1.0.0 → 1.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.
- package/README.md +120 -61
- package/package.json +6 -3
- package/src/defaults.js +16 -0
- package/src/index.js +1 -14
- package/src/mcp-server.js +194 -0
package/README.md
CHANGED
|
@@ -1,33 +1,36 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Shipli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@prasenjeet/shipli)
|
|
4
|
+
[](LICENSE)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Store rejections cost days of development time. **Shipli** is a CLI tool that audits your Flutter source code against **Apple App Store** and **Google Play** guidelines using AI.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
Catch missing permissions, policy violations, and compliance issues before you submit.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
* **Two Audit Modes:** `--mode store` for compliance, `--mode code` for quality, or both (default).
|
|
11
|
-
* **Live Guidelines:** Fetches the latest store policies at runtime and caches them locally.
|
|
12
|
-
* **Auto-Detection:** Detects project type (app/package) and platform (ios/android) automatically.
|
|
13
|
-
* **Multi-Provider:** Choose between **Google Gemini** or **Anthropic Claude** as your AI backend.
|
|
14
|
-
* **Zero-Setup Artifacts:** No `.ipa` or `.apk` needed — reads `lib/`, `pubspec.yaml`, `Info.plist`, and `AndroidManifest.xml` directly.
|
|
15
|
-
* **CI-Friendly:** Exit code `1` on FAIL, `0` on PASS/WARNING.
|
|
10
|
+
## Features
|
|
16
11
|
|
|
17
|
-
|
|
12
|
+
- **Dual Store Support** — Audit against Apple App Store and Google Play, together or individually.
|
|
13
|
+
- **Two Audit Modes** — Store compliance, code quality, or both in a single run.
|
|
14
|
+
- **AI-Powered** — Choose between Google Gemini or Anthropic Claude. Bring your own API key.
|
|
15
|
+
- **Up-to-Date Policies** — Ships with the latest Apple and Google Play store policies. Works offline.
|
|
16
|
+
- **Auto-Detection** — Automatically detects project type and target platform from your project structure.
|
|
17
|
+
- **Zero Setup** — No compiled artifacts needed. Point it at your Flutter project and run.
|
|
18
|
+
- **CI-Ready** — Designed for automation. Integrates with any CI/CD pipeline.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
18
21
|
|
|
19
22
|
```bash
|
|
20
|
-
npm install -g shipli
|
|
23
|
+
npm install -g @prasenjeet/shipli
|
|
21
24
|
```
|
|
22
25
|
|
|
23
|
-
Requires
|
|
26
|
+
Requires Node.js 18 or later.
|
|
24
27
|
|
|
25
|
-
##
|
|
28
|
+
## Usage
|
|
26
29
|
|
|
27
30
|
### Quick Start
|
|
28
31
|
|
|
29
32
|
```bash
|
|
30
|
-
# One-time setup
|
|
33
|
+
# One-time setup — select provider, model, and enter API key
|
|
31
34
|
shipli init
|
|
32
35
|
|
|
33
36
|
# Run full audit (auto-detects platform)
|
|
@@ -39,10 +42,18 @@ shipli --dir ./ --platform ios --mode store
|
|
|
39
42
|
# Google Play only
|
|
40
43
|
shipli --dir ./ --platform android --mode store
|
|
41
44
|
|
|
42
|
-
# Code quality only
|
|
45
|
+
# Code quality only
|
|
43
46
|
shipli --dir ./ --mode code
|
|
44
47
|
```
|
|
45
48
|
|
|
49
|
+
### Commands
|
|
50
|
+
|
|
51
|
+
| Command | Description |
|
|
52
|
+
|---------|-------------|
|
|
53
|
+
| `shipli init` | Create a `.shipli` config file interactively |
|
|
54
|
+
| `shipli config` | Update provider, model, or API key |
|
|
55
|
+
| `shipli --dir <path>` | Run the audit (default command) |
|
|
56
|
+
|
|
46
57
|
### Options
|
|
47
58
|
|
|
48
59
|
| Flag | Description | Default |
|
|
@@ -50,12 +61,12 @@ shipli --dir ./ --mode code
|
|
|
50
61
|
| `--dir <path>` | Path to Flutter project root | *required* |
|
|
51
62
|
| `--key <key>` | API key | `.shipli` / env var |
|
|
52
63
|
| `--provider <name>` | `gemini` or `claude` | `gemini` |
|
|
53
|
-
| `--model <model>` | Model override |
|
|
54
|
-
| `--type <type>` | `app` or `package` | auto-detected
|
|
64
|
+
| `--model <model>` | Model override | per provider |
|
|
65
|
+
| `--type <type>` | `app` or `package` | auto-detected |
|
|
55
66
|
| `--mode <mode>` | `store`, `code`, or `both` | `both` |
|
|
56
|
-
| `--platform <platform>` | `ios`, `android`, or `both` | auto-detected
|
|
67
|
+
| `--platform <platform>` | `ios`, `android`, or `both` | auto-detected |
|
|
57
68
|
|
|
58
|
-
### Configuration
|
|
69
|
+
### Configuration
|
|
59
70
|
|
|
60
71
|
Run `shipli init` to create a `.shipli` config file, or create one manually:
|
|
61
72
|
|
|
@@ -69,74 +80,122 @@ Run `shipli init` to create a `.shipli` config file, or create one manually:
|
|
|
69
80
|
```
|
|
70
81
|
|
|
71
82
|
The CLI looks for `.shipli` in two places:
|
|
83
|
+
|
|
72
84
|
1. **Project-level** — in the `--dir` path (highest priority)
|
|
73
85
|
2. **Global** — in your home directory `~/.shipli`
|
|
74
86
|
|
|
75
|
-
|
|
87
|
+
Resolution order: CLI flags > project `.shipli` > global `~/.shipli` > env vars > defaults.
|
|
76
88
|
|
|
77
|
-
>
|
|
89
|
+
> Add `.shipli` to your `.gitignore` — it contains your API key.
|
|
78
90
|
|
|
79
|
-
|
|
91
|
+
Use `shipli config` to update settings anytime.
|
|
80
92
|
|
|
81
|
-
|
|
82
|
-
2. **Extracts Evidence:** Scans `lib/` for Dart files, keeping only the architectural skeleton. For packages, also scans `example/`.
|
|
83
|
-
3. **Gathers Metadata:** Reads `Info.plist` (iOS) and/or `AndroidManifest.xml` (Android) permissions.
|
|
84
|
-
4. **Fetches Live Guidelines:** Downloads the latest Apple App Store Review Guidelines and/or Google Play Developer Policies (cached for 7 days).
|
|
85
|
-
5. **AI Audit:** Sends evidence + live guidelines to the LLM with a tailored system prompt.
|
|
86
|
-
6. **Actionable Report:** Outputs a Pass/Warning/Fail checklist with specific guideline citations.
|
|
93
|
+
### Supported Models
|
|
87
94
|
|
|
88
|
-
|
|
95
|
+
| Provider | Models |
|
|
96
|
+
|----------|--------|
|
|
97
|
+
| Claude | opus-4-6, sonnet-4-6, opus-4-5, sonnet-4-5, haiku-4-5, opus-4-1, opus-4, sonnet-4, sonnet-3-7, haiku-3-5, sonnet-3-5 |
|
|
98
|
+
| Gemini | 3.1-pro, 3.1-flash-lite, 3-flash, 2.5-flash, 2.5-pro, 2.5-flash-lite, 1.5-pro, 1.5-flash |
|
|
99
|
+
|
|
100
|
+
## Audit Categories
|
|
101
|
+
|
|
102
|
+
### App Store (iOS)
|
|
89
103
|
|
|
90
104
|
| Category | Examples |
|
|
91
105
|
|----------|---------|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
|
|
|
106
|
+
| Privacy & Permissions | Undeclared sensitive APIs, missing usage descriptions |
|
|
107
|
+
| Data Collection & Tracking | Tracking transparency, analytics disclosure |
|
|
108
|
+
| Content & Design | Minimum functionality, UI compliance |
|
|
109
|
+
| In-App Purchases | Payment method compliance |
|
|
110
|
+
| Legal & Compliance | Privacy policy, export compliance |
|
|
111
|
+
| Prohibited Behaviors | Dynamic code loading, hot-patching |
|
|
98
112
|
|
|
99
|
-
|
|
113
|
+
### Play Store (Android)
|
|
100
114
|
|
|
101
115
|
| Category | Examples |
|
|
102
116
|
|----------|---------|
|
|
103
|
-
|
|
|
104
|
-
|
|
|
105
|
-
|
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
117
|
+
| Permissions & Data Safety | Unnecessary permissions, data safety declarations |
|
|
118
|
+
| Data Collection & Privacy | Privacy policy, user data handling |
|
|
119
|
+
| Content & Behavior | Restricted content, deceptive behavior |
|
|
120
|
+
| Billing & Monetization | Payment method compliance |
|
|
121
|
+
| API Level & Compatibility | Target API requirements, version compatibility |
|
|
122
|
+
| Security & Abuse | Malware patterns, abusive behavior |
|
|
109
123
|
|
|
110
|
-
|
|
124
|
+
### Code Quality
|
|
111
125
|
|
|
112
126
|
| Category | Examples |
|
|
113
127
|
|----------|---------|
|
|
114
|
-
|
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
|
|
|
118
|
-
|
|
|
119
|
-
|
|
|
128
|
+
| Security | Hardcoded credentials, insecure connections |
|
|
129
|
+
| Architecture | State management, separation of concerns |
|
|
130
|
+
| Error Handling | Exception coverage, crash handling |
|
|
131
|
+
| Performance | Memory leaks, unnecessary rebuilds |
|
|
132
|
+
| Best Practices | Lifecycle handling, null safety |
|
|
133
|
+
| Dependencies | Outdated packages, version constraints |
|
|
120
134
|
|
|
121
|
-
|
|
135
|
+
### Package Auditing
|
|
122
136
|
|
|
123
137
|
| Category | Examples |
|
|
124
138
|
|----------|---------|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
|
|
|
139
|
+
| API Surface & Documentation | Export hygiene, entry points |
|
|
140
|
+
| Platform Declarations | Channel consistency, missing platforms |
|
|
141
|
+
| Consumer Guidance | Undocumented permission requirements |
|
|
142
|
+
| Dependency Hygiene | Constraint quality, misplaced dependencies |
|
|
143
|
+
| Example App Quality | Missing or incomplete examples |
|
|
144
|
+
|
|
145
|
+
## MCP Server
|
|
146
|
+
|
|
147
|
+
Shipli includes a built-in [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server, so AI coding assistants like Claude Code, Cursor, and Windsurf can run audits directly inside your editor.
|
|
148
|
+
|
|
149
|
+
### Setup
|
|
150
|
+
|
|
151
|
+
Add to your MCP config (e.g. `~/.claude/.mcp.json`):
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"mcpServers": {
|
|
156
|
+
"shipli": {
|
|
157
|
+
"command": "shipli-mcp",
|
|
158
|
+
"env": {
|
|
159
|
+
"SHIPLI_PROVIDER": "claude",
|
|
160
|
+
"SHIPLI_MODEL": "claude-haiku-4-5",
|
|
161
|
+
"ANTHROPIC_API_KEY": "your-api-key"
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
For Gemini, use `SHIPLI_PROVIDER: "gemini"` and set `GEMINI_API_KEY` instead.
|
|
169
|
+
|
|
170
|
+
### Available Tools
|
|
171
|
+
|
|
172
|
+
| Tool | Description | Parameters |
|
|
173
|
+
|------|-------------|------------|
|
|
174
|
+
| `shipli_store_audit` | Run store compliance audit against Apple App Store and/or Google Play guidelines | `projectDir` (required), `platform` (optional: `ios`, `android`, `both`) |
|
|
175
|
+
| `shipli_code_review` | Run code quality and security review | `projectDir` (required) |
|
|
176
|
+
|
|
177
|
+
Both tools return structured JSON with PASS/WARNING/FAIL scores and specific guideline citations.
|
|
178
|
+
|
|
179
|
+
### Usage
|
|
180
|
+
|
|
181
|
+
Once configured, ask your AI assistant:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
"Run a store audit on /path/to/my-flutter-app"
|
|
185
|
+
"Review the code quality of this Flutter project"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The assistant will call the appropriate Shipli MCP tool and return the results inline.
|
|
130
189
|
|
|
131
|
-
##
|
|
190
|
+
## CI Integration
|
|
132
191
|
|
|
133
192
|
```yaml
|
|
134
|
-
# GitHub Actions
|
|
193
|
+
# GitHub Actions
|
|
135
194
|
- name: Shipli Audit
|
|
136
|
-
run: npx shipli --dir ./ --
|
|
195
|
+
run: npx @prasenjeet/shipli --dir ./ --provider claude --key ${{ secrets.ANTHROPIC_API_KEY }}
|
|
137
196
|
```
|
|
138
197
|
|
|
139
|
-
The CLI exits with code `1`
|
|
198
|
+
The CLI exits with code `1` on failure, making it easy to gate deployments.
|
|
140
199
|
|
|
141
200
|
## License
|
|
142
201
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prasenjeet/shipli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "AI-powered App Store review audit for Flutter projects. Catch rejections before you submit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"shipli": "src/index.js"
|
|
7
|
+
"shipli": "src/index.js",
|
|
8
|
+
"shipli-mcp": "src/mcp-server.js"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"src",
|
|
@@ -32,11 +33,13 @@
|
|
|
32
33
|
"url": ""
|
|
33
34
|
},
|
|
34
35
|
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
35
37
|
"chalk": "^5.4.1",
|
|
36
38
|
"commander": "^13.1.0",
|
|
37
39
|
"fast-glob": "^3.3.3",
|
|
38
40
|
"js-yaml": "^4.1.0",
|
|
39
41
|
"ora": "^8.2.0",
|
|
40
|
-
"plist": "^3.1.0"
|
|
42
|
+
"plist": "^3.1.0",
|
|
43
|
+
"zod": "^4.3.6"
|
|
41
44
|
}
|
|
42
45
|
}
|
package/src/defaults.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const PROVIDER_DEFAULTS = {
|
|
5
|
+
gemini: { model: 'gemini-2.5-flash', envKey: 'GEMINI_API_KEY' },
|
|
6
|
+
claude: { model: 'claude-sonnet-4-6', envKey: 'ANTHROPIC_API_KEY' },
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function detectPlatform(projectDir) {
|
|
10
|
+
const hasIos = existsSync(join(projectDir, 'ios'));
|
|
11
|
+
const hasAndroid = existsSync(join(projectDir, 'android'));
|
|
12
|
+
if (hasIos && hasAndroid) return 'both';
|
|
13
|
+
if (hasIos) return 'ios';
|
|
14
|
+
if (hasAndroid) return 'android';
|
|
15
|
+
return 'both';
|
|
16
|
+
}
|
package/src/index.js
CHANGED
|
@@ -17,25 +17,12 @@ import { read as readPubspec } from './pubspec-reader.js';
|
|
|
17
17
|
import { audit } from './auditor.js';
|
|
18
18
|
import { fetchGuidelines } from './guidelines.js';
|
|
19
19
|
import { print as printReport } from './reporter.js';
|
|
20
|
+
import { PROVIDER_DEFAULTS as DEFAULTS, detectPlatform } from './defaults.js';
|
|
20
21
|
|
|
21
22
|
// Read package version
|
|
22
23
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
24
|
const pkg = JSON.parse(await readFile(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
24
25
|
|
|
25
|
-
const DEFAULTS = {
|
|
26
|
-
gemini: { model: 'gemini-2.5-flash', envKey: 'GEMINI_API_KEY' },
|
|
27
|
-
claude: { model: 'claude-sonnet-4-6', envKey: 'ANTHROPIC_API_KEY' },
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
function detectPlatform(projectDir) {
|
|
31
|
-
const hasIos = existsSync(join(projectDir, 'ios'));
|
|
32
|
-
const hasAndroid = existsSync(join(projectDir, 'android'));
|
|
33
|
-
if (hasIos && hasAndroid) return 'both';
|
|
34
|
-
if (hasIos) return 'ios';
|
|
35
|
-
if (hasAndroid) return 'android';
|
|
36
|
-
return 'both'; // default
|
|
37
|
-
}
|
|
38
|
-
|
|
39
26
|
const program = new Command();
|
|
40
27
|
|
|
41
28
|
program
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { readFile } from 'node:fs/promises';
|
|
8
|
+
import { resolve, join, dirname } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { scan } from './scanner.js';
|
|
11
|
+
import { read as readPubspec } from './pubspec-reader.js';
|
|
12
|
+
import { read as readPlist } from './plist-reader.js';
|
|
13
|
+
import { read as readManifest } from './manifest-reader.js';
|
|
14
|
+
import { fetchGuidelines } from './guidelines.js';
|
|
15
|
+
import { audit } from './auditor.js';
|
|
16
|
+
import { loadConfig } from './config.js';
|
|
17
|
+
import { PROVIDER_DEFAULTS, detectPlatform } from './defaults.js';
|
|
18
|
+
|
|
19
|
+
// ── Read package version ──
|
|
20
|
+
|
|
21
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const pkg = JSON.parse(await readFile(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
23
|
+
|
|
24
|
+
// ── Resolve provider config from env vars at startup ──
|
|
25
|
+
|
|
26
|
+
function resolveConfig(projectDir) {
|
|
27
|
+
const provider = (process.env.SHIPLI_PROVIDER || 'gemini').toLowerCase();
|
|
28
|
+
const defaults = PROVIDER_DEFAULTS[provider];
|
|
29
|
+
if (!defaults) {
|
|
30
|
+
throw new Error(`Unknown provider "${provider}". Set SHIPLI_PROVIDER to "gemini" or "claude".`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const model = process.env.SHIPLI_MODEL || defaults.model;
|
|
34
|
+
const apiKey = process.env[defaults.envKey];
|
|
35
|
+
|
|
36
|
+
if (!apiKey) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`No API key found. Set ${defaults.envKey} in your MCP server env config.`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { provider, model, apiKey };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Validate project directory ──
|
|
46
|
+
|
|
47
|
+
function validateProject(projectDir) {
|
|
48
|
+
const resolved = resolve(projectDir);
|
|
49
|
+
if (!existsSync(resolved)) {
|
|
50
|
+
throw new Error(`Directory not found: ${resolved}`);
|
|
51
|
+
}
|
|
52
|
+
if (!existsSync(join(resolved, 'lib'))) {
|
|
53
|
+
throw new Error(`No lib/ directory found in ${resolved}. Is this a Flutter project?`);
|
|
54
|
+
}
|
|
55
|
+
return resolved;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Create server ──
|
|
59
|
+
|
|
60
|
+
const server = new McpServer({
|
|
61
|
+
name: 'shipli',
|
|
62
|
+
version: pkg.version,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// ── Tool: shipli_store_audit ──
|
|
66
|
+
|
|
67
|
+
server.tool(
|
|
68
|
+
'shipli_store_audit',
|
|
69
|
+
'Run a store compliance audit on a Flutter project against Apple App Store and/or Google Play guidelines. Returns structured findings with PASS/WARNING/FAIL scores and specific guideline citations.',
|
|
70
|
+
{
|
|
71
|
+
projectDir: z.string().describe('Absolute path to the Flutter project root directory'),
|
|
72
|
+
platform: z.enum(['ios', 'android', 'both']).optional().describe('Target platform. Auto-detected from project structure if omitted.'),
|
|
73
|
+
},
|
|
74
|
+
async ({ projectDir, platform }) => {
|
|
75
|
+
try {
|
|
76
|
+
const dir = validateProject(projectDir);
|
|
77
|
+
const { provider, model, apiKey } = resolveConfig(dir);
|
|
78
|
+
|
|
79
|
+
const pubspec = await readPubspec(dir);
|
|
80
|
+
const projectType = pubspec.projectType;
|
|
81
|
+
const resolvedPlatform = platform || detectPlatform(dir);
|
|
82
|
+
|
|
83
|
+
const { files } = await scan(dir);
|
|
84
|
+
|
|
85
|
+
let exampleFiles = [];
|
|
86
|
+
if (projectType === 'package' && existsSync(join(dir, 'example', 'lib'))) {
|
|
87
|
+
const exampleResult = await scan(join(dir, 'example'));
|
|
88
|
+
exampleFiles = exampleResult.files;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let plistData = { found: false, permissions: {}, bundleId: null };
|
|
92
|
+
let manifestData = { found: false, permissions: [], packageName: null };
|
|
93
|
+
|
|
94
|
+
if (projectType === 'app' && (resolvedPlatform === 'ios' || resolvedPlatform === 'both')) {
|
|
95
|
+
plistData = await readPlist(dir);
|
|
96
|
+
}
|
|
97
|
+
if (projectType === 'app' && (resolvedPlatform === 'android' || resolvedPlatform === 'both')) {
|
|
98
|
+
manifestData = await readManifest(dir);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let appleGuidelines = null;
|
|
102
|
+
let googleGuidelines = null;
|
|
103
|
+
|
|
104
|
+
if (resolvedPlatform === 'ios' || resolvedPlatform === 'both') {
|
|
105
|
+
appleGuidelines = await fetchGuidelines('apple');
|
|
106
|
+
}
|
|
107
|
+
if (resolvedPlatform === 'android' || resolvedPlatform === 'both') {
|
|
108
|
+
googleGuidelines = await fetchGuidelines('google');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const result = await audit(
|
|
112
|
+
{
|
|
113
|
+
files,
|
|
114
|
+
exampleFiles,
|
|
115
|
+
permissions: plistData.permissions,
|
|
116
|
+
androidPermissions: manifestData.permissions,
|
|
117
|
+
pubspec,
|
|
118
|
+
plistFound: (resolvedPlatform === 'ios' || resolvedPlatform === 'both') ? plistData.found : undefined,
|
|
119
|
+
androidManifestFound: (resolvedPlatform === 'android' || resolvedPlatform === 'both') ? manifestData.found : undefined,
|
|
120
|
+
projectType,
|
|
121
|
+
appleGuidelines,
|
|
122
|
+
googleGuidelines,
|
|
123
|
+
},
|
|
124
|
+
{ apiKey, model, provider, mode: 'store', platform: resolvedPlatform },
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
129
|
+
};
|
|
130
|
+
} catch (err) {
|
|
131
|
+
return {
|
|
132
|
+
content: [{ type: 'text', text: `Error: ${err.message}` }],
|
|
133
|
+
isError: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// ── Tool: shipli_code_review ──
|
|
140
|
+
|
|
141
|
+
server.tool(
|
|
142
|
+
'shipli_code_review',
|
|
143
|
+
'Run a code quality and security review on a Flutter project. Checks architecture, error handling, performance, dependencies, and security vulnerabilities.',
|
|
144
|
+
{
|
|
145
|
+
projectDir: z.string().describe('Absolute path to the Flutter project root directory'),
|
|
146
|
+
},
|
|
147
|
+
async ({ projectDir }) => {
|
|
148
|
+
try {
|
|
149
|
+
const dir = validateProject(projectDir);
|
|
150
|
+
const { provider, model, apiKey } = resolveConfig(dir);
|
|
151
|
+
|
|
152
|
+
const pubspec = await readPubspec(dir);
|
|
153
|
+
const projectType = pubspec.projectType;
|
|
154
|
+
|
|
155
|
+
const { files } = await scan(dir);
|
|
156
|
+
|
|
157
|
+
let exampleFiles = [];
|
|
158
|
+
if (projectType === 'package' && existsSync(join(dir, 'example', 'lib'))) {
|
|
159
|
+
const exampleResult = await scan(join(dir, 'example'));
|
|
160
|
+
exampleFiles = exampleResult.files;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const result = await audit(
|
|
164
|
+
{
|
|
165
|
+
files,
|
|
166
|
+
exampleFiles,
|
|
167
|
+
permissions: {},
|
|
168
|
+
androidPermissions: [],
|
|
169
|
+
pubspec,
|
|
170
|
+
plistFound: undefined,
|
|
171
|
+
androidManifestFound: undefined,
|
|
172
|
+
projectType,
|
|
173
|
+
appleGuidelines: null,
|
|
174
|
+
googleGuidelines: null,
|
|
175
|
+
},
|
|
176
|
+
{ apiKey, model, provider, mode: 'code', platform: 'both' },
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
181
|
+
};
|
|
182
|
+
} catch (err) {
|
|
183
|
+
return {
|
|
184
|
+
content: [{ type: 'text', text: `Error: ${err.message}` }],
|
|
185
|
+
isError: true,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// ── Start server ──
|
|
192
|
+
|
|
193
|
+
const transport = new StdioServerTransport();
|
|
194
|
+
await server.connect(transport);
|