@mmerterden/dev-toolkit-mcp 2.24.1

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +708 -0
  2. package/LICENSE +21 -0
  3. package/README.md +387 -0
  4. package/index.js +1277 -0
  5. package/package.json +80 -0
  6. package/tools/design-check/content-cardinality.js +204 -0
  7. package/tools/design-check/geometry.js +140 -0
  8. package/tools/design-check/index.js +213 -0
  9. package/tools/design-check/mock-detect.js +213 -0
  10. package/tools/design-check/report.js +590 -0
  11. package/tools/design-check/scan.js +91 -0
  12. package/tools/design-check/scenario-inventory.js +598 -0
  13. package/tools/design-check/visual-compare.js +961 -0
  14. package/tools/ios-app-store-audit/context.js +180 -0
  15. package/tools/ios-app-store-audit/data/apple-required-sdks.json +32 -0
  16. package/tools/ios-app-store-audit/data/debug-tools-blocklist.json +133 -0
  17. package/tools/ios-app-store-audit/index.js +164 -0
  18. package/tools/ios-app-store-audit/models.js +47 -0
  19. package/tools/ios-app-store-audit/rules/asset-validation.js +72 -0
  20. package/tools/ios-app-store-audit/rules/binary-size.js +70 -0
  21. package/tools/ios-app-store-audit/rules/code-signing.js +95 -0
  22. package/tools/ios-app-store-audit/rules/dead-reference.js +131 -0
  23. package/tools/ios-app-store-audit/rules/debug-tool-leak.js +185 -0
  24. package/tools/ios-app-store-audit/rules/duplicate-resource.js +130 -0
  25. package/tools/ios-app-store-audit/rules/embedded-sdk.js +126 -0
  26. package/tools/ios-app-store-audit/rules/entitlement.js +105 -0
  27. package/tools/ios-app-store-audit/rules/extension-signing.js +105 -0
  28. package/tools/ios-app-store-audit/rules/info-plist.js +158 -0
  29. package/tools/ios-app-store-audit/rules/ipv6-compliance.js +101 -0
  30. package/tools/ios-app-store-audit/rules/privacy-manifest.js +121 -0
  31. package/tools/ios-app-store-audit/rules/production-hygiene.js +237 -0
  32. package/tools/ios-app-store-audit/rules/provisioning-profile.js +127 -0
  33. package/tools/ios-app-store-audit/rules/required-reason-api.js +123 -0
  34. package/tools/ios-app-store-audit/rules/sdk-floor.js +104 -0
  35. package/tools/ios-app-store-audit/rules/swift-abi.js +64 -0
  36. package/tools/ios-app-store-audit/rules/team-id.js +62 -0
  37. package/tools/ios-testflight/index.js +479 -0
  38. package/ui-tree-dumper.swift +122 -0
@@ -0,0 +1,104 @@
1
+ /**
2
+ * sdk-floor - Validates that the archive was built against a new enough SDK.
3
+ *
4
+ * App Store Connect rejects uploads built with an SDK below the current floor
5
+ * (validation code ITMS-90725). The iOS 26 / Xcode 26 floor has been in force
6
+ * since 2026-04-28. The relevant build receipts live in the app's Info.plist:
7
+ * DTSDKName e.g. "iphoneos26.4"
8
+ * DTPlatformVersion e.g. "26.4"
9
+ * DTXcode e.g. "2640" (Xcode 26.4 - four digits, MMmp)
10
+ *
11
+ * All three are written by Xcode at build time, so this is fully checkable from
12
+ * the archive without contacting Apple.
13
+ */
14
+
15
+ import { Severity, makeViolation } from "../models.js";
16
+
17
+ export const id = "sdk-floor";
18
+ export const name = "SDK Floor";
19
+ export const description =
20
+ "Checks the build SDK and Xcode version against the App Store minimum (ITMS-90725)";
21
+
22
+ // Bump these two together when Apple raises the floor.
23
+ const MIN_SDK_MAJOR = 26;
24
+ const MIN_DTXCODE = 2600;
25
+ const FLOOR_LABEL = "iOS 26 SDK / Xcode 26";
26
+ const IN_FORCE_SINCE = "2026-04-28";
27
+
28
+ function majorOf(version) {
29
+ if (typeof version !== "string") return null;
30
+ const m = version.match(/(\d+)/);
31
+ return m ? parseInt(m[1], 10) : null;
32
+ }
33
+
34
+ export function check(ctx) {
35
+ const violations = [];
36
+ const plist = ctx.infoPlist || {};
37
+
38
+ const sdkName = typeof plist.DTSDKName === "string" ? plist.DTSDKName : null;
39
+ const platformVersion =
40
+ typeof plist.DTPlatformVersion === "string" ? plist.DTPlatformVersion : null;
41
+ const rawXcode = plist.DTXcode;
42
+
43
+ // No build receipts at all: the archive did not come from a normal Xcode
44
+ // build, so report it rather than silently passing.
45
+ if (!sdkName && !platformVersion && rawXcode === undefined) {
46
+ violations.push(
47
+ makeViolation({
48
+ ruleID: id,
49
+ guidelineRef: "ITMS-90725",
50
+ severity: Severity.WARNING,
51
+ message:
52
+ "Info.plist carries no DTSDKName / DTPlatformVersion / DTXcode, so the build SDK cannot be verified",
53
+ file: `${ctx.appPath}/Info.plist`,
54
+ suggestion:
55
+ "Re-export the archive from Xcode. A missing build receipt usually means the bundle was assembled by hand or stripped.",
56
+ })
57
+ );
58
+ return violations;
59
+ }
60
+
61
+ const sdkMajor = majorOf(sdkName) ?? majorOf(platformVersion);
62
+ if (sdkMajor !== null && sdkMajor < MIN_SDK_MAJOR) {
63
+ violations.push(
64
+ makeViolation({
65
+ ruleID: id,
66
+ guidelineRef: "ITMS-90725",
67
+ severity: Severity.ERROR,
68
+ message: `Built against SDK ${sdkName || platformVersion} but the App Store requires ${FLOOR_LABEL} (in force since ${IN_FORCE_SINCE})`,
69
+ file: `${ctx.appPath}/Info.plist`,
70
+ suggestion: `Rebuild with Xcode ${MIN_SDK_MAJOR} or newer. App Store Connect rejects this upload with ITMS-90725.`,
71
+ })
72
+ );
73
+ }
74
+
75
+ const xcodeNum =
76
+ typeof rawXcode === "string" || typeof rawXcode === "number"
77
+ ? parseInt(String(rawXcode), 10)
78
+ : NaN;
79
+ if (Number.isFinite(xcodeNum) && xcodeNum < MIN_DTXCODE) {
80
+ violations.push(
81
+ makeViolation({
82
+ ruleID: id,
83
+ guidelineRef: "ITMS-90725",
84
+ severity: Severity.ERROR,
85
+ message: `DTXcode is ${rawXcode} (Xcode ${(xcodeNum / 100).toFixed(1)}), below the required Xcode ${MIN_SDK_MAJOR}`,
86
+ file: `${ctx.appPath}/Info.plist`,
87
+ suggestion: `Rebuild with Xcode ${MIN_SDK_MAJOR} or newer before uploading.`,
88
+ })
89
+ );
90
+ }
91
+
92
+ if (violations.length === 0) {
93
+ violations.push(
94
+ makeViolation({
95
+ ruleID: id,
96
+ severity: Severity.INFO,
97
+ message: `Build SDK ${sdkName || platformVersion || "unknown"}${Number.isFinite(xcodeNum) ? ` (DTXcode ${rawXcode})` : ""} satisfies the ${FLOOR_LABEL} floor`,
98
+ file: `${ctx.appPath}/Info.plist`,
99
+ })
100
+ );
101
+ }
102
+
103
+ return violations;
104
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * swift-abi - Validates Swift runtime compatibility for the deployment target.
3
+ * Direct port of SwiftABIRule.swift.
4
+ *
5
+ * Apps targeting iOS ≥12.2 don't need to embed `libswift*.dylib` (Swift is
6
+ * ABI-stable since iOS 12.2 - runtime ships with the OS). Embedding them
7
+ * still works but inflates the bundle and may produce ITMS-90426 warnings.
8
+ */
9
+
10
+ import { existsSync, readdirSync } from "fs";
11
+ import { join } from "path";
12
+ import { Severity, makeViolation } from "../models.js";
13
+
14
+ export const id = "swift-abi";
15
+ export const name = "Swift ABI Compatibility";
16
+ export const description = "Checks for Swift runtime libraries and ABI stability issues";
17
+
18
+ export function check(ctx) {
19
+ const violations = [];
20
+
21
+ const swiftSupportPath = join(ctx.archivePath, "SwiftSupport");
22
+ const hasSwiftSupport = existsSync(swiftSupportPath);
23
+
24
+ const frameworksPath = join(ctx.appPath, "Frameworks");
25
+ let embeddedSwiftLibs = [];
26
+ if (existsSync(frameworksPath)) {
27
+ try {
28
+ embeddedSwiftLibs = readdirSync(frameworksPath).filter(
29
+ (n) => n.startsWith("libswift") && n.endsWith(".dylib")
30
+ );
31
+ } catch {
32
+ embeddedSwiftLibs = [];
33
+ }
34
+ }
35
+
36
+ const minOS = typeof ctx.infoPlist?.MinimumOSVersion === "string" ? ctx.infoPlist.MinimumOSVersion : null;
37
+ if (!minOS) return violations;
38
+ const parts = minOS.split(".").map((p) => parseInt(p, 10));
39
+ const major = Number.isFinite(parts[0]) ? parts[0] : 0;
40
+ const minor = Number.isFinite(parts[1]) ? parts[1] : 0;
41
+
42
+ const isAbiStable = major > 12 || (major === 12 && minor >= 2);
43
+
44
+ if (isAbiStable) {
45
+ if (embeddedSwiftLibs.length > 0) {
46
+ violations.push(makeViolation({
47
+ ruleID: id, guidelineRef: "ITMS-90426", severity: Severity.WARNING,
48
+ message: `App embeds ${embeddedSwiftLibs.length} Swift runtime libraries but targets iOS ${minOS} (ABI stable)`,
49
+ file: frameworksPath,
50
+ suggestion: "Remove embedded Swift libraries - they're included in the OS since iOS 12.2",
51
+ }));
52
+ }
53
+ } else {
54
+ if (!hasSwiftSupport && embeddedSwiftLibs.length > 0) {
55
+ violations.push(makeViolation({
56
+ ruleID: id, guidelineRef: "ITMS-90426", severity: Severity.ERROR,
57
+ message: `Archive missing SwiftSupport directory for pre-ABI-stability target (iOS ${minOS})`,
58
+ suggestion: "Archive must include SwiftSupport when targeting iOS < 12.2",
59
+ }));
60
+ }
61
+ }
62
+
63
+ return violations;
64
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * team-id - Validates all targets (main app + embedded extensions) share
3
+ * the same Team ID. ITMS-90046 reject if mismatched.
4
+ * Direct port of TeamIDConsistencyRule.swift.
5
+ */
6
+
7
+ import { join } from "path";
8
+ import { Severity, makeViolation } from "../models.js";
9
+ import { parsePlist, extractEntitlements } from "../context.js";
10
+
11
+ export const id = "team-id-consistency";
12
+ export const name = "Team ID Consistency";
13
+ export const description = "Checks all targets use the same development team identifier";
14
+
15
+ function extractTeamID(applicationIdentifier) {
16
+ if (typeof applicationIdentifier !== "string") return null;
17
+ const first = applicationIdentifier.split(".")[0];
18
+ return first && first.length >= 8 ? first : null;
19
+ }
20
+
21
+ export function check(ctx) {
22
+ const violations = [];
23
+ const teamIDs = [];
24
+
25
+ // Main app
26
+ const ent = ctx.entitlements;
27
+ if (ent) {
28
+ const appID = ent["application-identifier"] ?? ent["com.apple.application-identifier"];
29
+ const teamID = extractTeamID(appID);
30
+ if (teamID) teamIDs.push({ target: "Main App", teamID });
31
+ }
32
+
33
+ // Embedded extensions (.appex bundles)
34
+ for (const pluginPath of ctx.embeddedPlugins ?? []) {
35
+ const plugInfo = parsePlist(join(pluginPath, "Info.plist"));
36
+ if (!plugInfo) continue;
37
+ const execName = plugInfo.CFBundleExecutable;
38
+ if (typeof execName !== "string") continue;
39
+ const execPath = join(pluginPath, execName);
40
+ const plugEnt = extractEntitlements(execPath);
41
+ if (!plugEnt) continue;
42
+ const appID = plugEnt["application-identifier"] ?? plugEnt["com.apple.application-identifier"];
43
+ const teamID = extractTeamID(appID);
44
+ if (teamID) {
45
+ const name = pluginPath.split("/").pop();
46
+ teamIDs.push({ target: name, teamID });
47
+ }
48
+ }
49
+
50
+ // Consistency check
51
+ const uniq = new Set(teamIDs.map((t) => t.teamID));
52
+ if (uniq.size > 1) {
53
+ const details = teamIDs.map((t) => `${t.target}: ${t.teamID}`).join(", ");
54
+ violations.push(makeViolation({
55
+ ruleID: id, guidelineRef: "ITMS-90046", severity: Severity.ERROR,
56
+ message: `Multiple Team IDs detected across targets: ${details}`,
57
+ suggestion: "All targets must use the same development team. Check signing settings in Xcode",
58
+ }));
59
+ }
60
+
61
+ return violations;
62
+ }