@aarwitz/tapp 0.15.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.
Files changed (77) hide show
  1. package/AGENTS.md +123 -0
  2. package/Harness/OCQAHarness/AppDelegate.swift +21 -0
  3. package/Harness/OCQAHarness/Info.plist +26 -0
  4. package/Harness/OCQAHarness.xcodeproj/project.pbxproj +199 -0
  5. package/Harness/OCQAHarness.xcodeproj/xcshareddata/xcschemes/OCQAHarnessUITests.xcscheme +22 -0
  6. package/Harness/OCQAHarnessUITests/ExplorerTests.swift +4526 -0
  7. package/Harness/OCQAHarnessUITests/Info.plist +22 -0
  8. package/Harness/generate-harness-xcodeproj.rb +254 -0
  9. package/LICENSE +21 -0
  10. package/README.md +374 -0
  11. package/bin/tapp.js +1382 -0
  12. package/browser/app.css +227 -0
  13. package/browser/app.js +675 -0
  14. package/browser/index.html +195 -0
  15. package/browser/product-contract.js +25 -0
  16. package/browser/view-model.js +16 -0
  17. package/docs/BROWSER-PRODUCT.md +72 -0
  18. package/docs/PRODUCT-ENGINE.md +102 -0
  19. package/docs/application-model.md +276 -0
  20. package/docs/scenarios.md +95 -0
  21. package/mcp-server/src/android-driver.js +287 -0
  22. package/mcp-server/src/android-explorer.js +197 -0
  23. package/mcp-server/src/android-flow.js +89 -0
  24. package/mcp-server/src/application-model.js +1597 -0
  25. package/mcp-server/src/browser-product.js +659 -0
  26. package/mcp-server/src/browser-workspaces.js +234 -0
  27. package/mcp-server/src/ci-report.js +557 -0
  28. package/mcp-server/src/ci-setup.js +359 -0
  29. package/mcp-server/src/contract-authoring.js +10 -0
  30. package/mcp-server/src/enrich.js +57 -0
  31. package/mcp-server/src/flow-runtime.js +127 -0
  32. package/mcp-server/src/html-report.js +124 -0
  33. package/mcp-server/src/index.js +3775 -0
  34. package/mcp-server/src/maintenance-proposal.js +178 -0
  35. package/mcp-server/src/managed-operation.js +61 -0
  36. package/mcp-server/src/pr-selection.js +841 -0
  37. package/mcp-server/src/product-execution.js +155 -0
  38. package/mcp-server/src/product-operations.js +526 -0
  39. package/mcp-server/src/project-config.js +101 -0
  40. package/mcp-server/src/release-contract.d.ts +81 -0
  41. package/mcp-server/src/release-contract.js +226 -0
  42. package/mcp-server/src/report.js +363 -0
  43. package/mcp-server/src/scenario-runtime.js +139 -0
  44. package/mcp-server/src/static-server.js +44 -0
  45. package/mcp-server/src/task-runtime.js +266 -0
  46. package/mcp-server/src/ui-map.js +661 -0
  47. package/mcp-server/src/web-explorer.js +493 -0
  48. package/mcp-server/src/web-flow.js +238 -0
  49. package/package.json +82 -0
  50. package/scripts/android-corpus-e2e.sh +30 -0
  51. package/scripts/ci-gate.sh +323 -0
  52. package/scripts/cleanup-xcode.sh +157 -0
  53. package/scripts/compile-contract.js +27 -0
  54. package/scripts/compile-flow.js +18 -0
  55. package/scripts/corpus-apps.txt +9 -0
  56. package/scripts/corpus-sweep.sh +121 -0
  57. package/scripts/coverage-eval.sh +92 -0
  58. package/scripts/coverage_eval_parse.py +95 -0
  59. package/scripts/deploy-and-build.sh +99 -0
  60. package/scripts/flow-platform.js +18 -0
  61. package/scripts/flow_ai_judge.py +102 -0
  62. package/scripts/flow_lib.py +154 -0
  63. package/scripts/mutation-recall-desktop.sh +186 -0
  64. package/scripts/mutation-recall.sh +121 -0
  65. package/scripts/mutation_lib.py +128 -0
  66. package/scripts/mutation_operators.py +144 -0
  67. package/scripts/platform-gate.js +186 -0
  68. package/scripts/pr-plan.js +68 -0
  69. package/scripts/quick-capture.sh +419 -0
  70. package/scripts/run-android-flow.js +27 -0
  71. package/scripts/run-flow.sh +90 -0
  72. package/scripts/run-web-flow.js +28 -0
  73. package/scripts/run-web-scenario.js +23 -0
  74. package/scripts/validation-matrix.sh +146 -0
  75. package/scripts/vision-fp-eval.sh +206 -0
  76. package/scripts/vision_escalation_responder.py +147 -0
  77. package/scripts/vision_fp_probe.py +221 -0
@@ -0,0 +1,22 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CFBundleDevelopmentRegion</key>
6
+ <string>en</string>
7
+ <key>CFBundleExecutable</key>
8
+ <string>$(EXECUTABLE_NAME)</string>
9
+ <key>CFBundleIdentifier</key>
10
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11
+ <key>CFBundleInfoDictionaryVersion</key>
12
+ <string>6.0</string>
13
+ <key>CFBundleName</key>
14
+ <string>$(PRODUCT_NAME)</string>
15
+ <key>CFBundlePackageType</key>
16
+ <string>BNDL</string>
17
+ <key>CFBundleShortVersionString</key>
18
+ <string>1.0</string>
19
+ <key>CFBundleVersion</key>
20
+ <string>1</string>
21
+ </dict>
22
+ </plist>
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env ruby
2
+ # generate-harness-xcodeproj.rb — Generates OCQAHarness.xcodeproj
3
+ # Run from the Harness/ directory
4
+
5
+ require 'fileutils'
6
+ require 'digest'
7
+
8
+ harness_dir = File.dirname(File.expand_path(__FILE__))
9
+ xcodeproj_dir = "#{harness_dir}/OCQAHarness.xcodeproj"
10
+ FileUtils.mkdir_p(xcodeproj_dir)
11
+
12
+ def uuid(seed)
13
+ Digest::MD5.hexdigest("ocqa-harness-#{seed}").upcase[0, 24]
14
+ end
15
+
16
+ pbxproj = <<~PBX
17
+ // !$*UTF8*$!
18
+ {
19
+ archiveVersion = 1;
20
+ classes = {};
21
+ objectVersion = 56;
22
+ objects = {
23
+ #{uuid('project')} /* Project object */ = {
24
+ isa = PBXProject;
25
+ buildConfigurationList = #{uuid('project-configs')};
26
+ compatibilityVersion = "Xcode 14.0";
27
+ developmentRegion = en;
28
+ hasScannedForEncodings = 0;
29
+ knownRegions = (en, Base);
30
+ mainGroup = #{uuid('main-group')};
31
+ productRefGroup = #{uuid('products-group')};
32
+ projectDirPath = "";
33
+ projectRoot = "";
34
+ targets = (
35
+ #{uuid('app-target')},
36
+ #{uuid('test-target')},
37
+ );
38
+ };
39
+
40
+ /* Main Group */
41
+ #{uuid('main-group')} = {
42
+ isa = PBXGroup;
43
+ children = (
44
+ #{uuid('app-group')},
45
+ #{uuid('test-group')},
46
+ #{uuid('products-group')},
47
+ );
48
+ sourceTree = "<group>";
49
+ };
50
+ #{uuid('products-group')} = {
51
+ isa = PBXGroup;
52
+ children = (
53
+ #{uuid('app-product')},
54
+ #{uuid('test-product')},
55
+ );
56
+ name = Products;
57
+ sourceTree = "<group>";
58
+ };
59
+ #{uuid('app-group')} = {
60
+ isa = PBXGroup;
61
+ children = (
62
+ #{uuid('appdelegate-ref')},
63
+ #{uuid('app-info-ref')},
64
+ );
65
+ path = OCQAHarness;
66
+ sourceTree = "<group>";
67
+ };
68
+ #{uuid('test-group')} = {
69
+ isa = PBXGroup;
70
+ children = (
71
+ #{uuid('explorer-ref')},
72
+ #{uuid('test-info-ref')},
73
+ );
74
+ path = OCQAHarnessUITests;
75
+ sourceTree = "<group>";
76
+ };
77
+
78
+ /* File References */
79
+ #{uuid('appdelegate-ref')} = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>";};
80
+ #{uuid('app-info-ref')} = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>";};
81
+ #{uuid('explorer-ref')} = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExplorerTests.swift; sourceTree = "<group>";};
82
+ #{uuid('test-info-ref')} = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>";};
83
+ #{uuid('app-product')} = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OCQAHarness.app; sourceTree = BUILT_PRODUCTS_DIR;};
84
+ #{uuid('test-product')} = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OCQAHarnessUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR;};
85
+
86
+ /* Build Files */
87
+ #{uuid('appdelegate-build')} = {isa = PBXBuildFile; fileRef = #{uuid('appdelegate-ref')};};
88
+ #{uuid('explorer-build')} = {isa = PBXBuildFile; fileRef = #{uuid('explorer-ref')};};
89
+
90
+ /* Source Build Phases */
91
+ #{uuid('app-sources')} = {
92
+ isa = PBXSourcesBuildPhase;
93
+ buildActionMask = 2147483647;
94
+ files = (#{uuid('appdelegate-build')},);
95
+ runOnlyForDeploymentPostprocessing = 0;
96
+ };
97
+ #{uuid('test-sources')} = {
98
+ isa = PBXSourcesBuildPhase;
99
+ buildActionMask = 2147483647;
100
+ files = (#{uuid('explorer-build')},);
101
+ runOnlyForDeploymentPostprocessing = 0;
102
+ };
103
+
104
+ /* Frameworks Build Phases */
105
+ #{uuid('app-frameworks')} = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0;};
106
+ #{uuid('test-frameworks')} = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0;};
107
+
108
+ /* App Target */
109
+ #{uuid('app-target')} = {
110
+ isa = PBXNativeTarget;
111
+ buildConfigurationList = #{uuid('app-configs')};
112
+ buildPhases = (#{uuid('app-sources')}, #{uuid('app-frameworks')},);
113
+ buildRules = ();
114
+ dependencies = ();
115
+ name = OCQAHarness;
116
+ productName = OCQAHarness;
117
+ productReference = #{uuid('app-product')};
118
+ productType = "com.apple.product-type.application";
119
+ };
120
+
121
+ /* Test Target */
122
+ #{uuid('test-target')} = {
123
+ isa = PBXNativeTarget;
124
+ buildConfigurationList = #{uuid('test-configs')};
125
+ buildPhases = (#{uuid('test-sources')}, #{uuid('test-frameworks')},);
126
+ buildRules = ();
127
+ dependencies = (#{uuid('test-dep')},);
128
+ name = OCQAHarnessUITests;
129
+ productName = OCQAHarnessUITests;
130
+ productReference = #{uuid('test-product')};
131
+ productType = "com.apple.product-type.bundle.ui-testing";
132
+ };
133
+
134
+ /* Target Dependency */
135
+ #{uuid('test-dep')} = {
136
+ isa = PBXTargetDependency;
137
+ target = #{uuid('app-target')};
138
+ targetProxy = #{uuid('test-proxy')};
139
+ };
140
+ #{uuid('test-proxy')} = {
141
+ isa = PBXContainerItemProxy;
142
+ containerPortal = #{uuid('project')};
143
+ proxyType = 1;
144
+ remoteGlobalIDString = #{uuid('app-target')};
145
+ remoteInfo = OCQAHarness;
146
+ };
147
+
148
+ /* Build Configurations */
149
+ #{uuid('project-configs')} = {
150
+ isa = XCConfigurationList;
151
+ buildConfigurations = (#{uuid('project-debug')},);
152
+ defaultConfigurationIsVisible = 0;
153
+ defaultConfigurationName = Debug;
154
+ };
155
+ #{uuid('project-debug')} = {
156
+ isa = XCBuildConfiguration;
157
+ buildSettings = {
158
+ ALWAYS_SEARCH_USER_PATHS = NO;
159
+ CLANG_ENABLE_MODULES = YES;
160
+ CODE_SIGN_IDENTITY = "-";
161
+ CODE_SIGN_STYLE = Automatic;
162
+ DEVELOPMENT_TEAM = "";
163
+ ENABLE_TESTABILITY = YES;
164
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
165
+ SDKROOT = iphoneos;
166
+ SWIFT_VERSION = 5.0;
167
+ TARGETED_DEVICE_FAMILY = "1,2";
168
+ };
169
+ name = Debug;
170
+ };
171
+ #{uuid('app-configs')} = {
172
+ isa = XCConfigurationList;
173
+ buildConfigurations = (#{uuid('app-debug')},);
174
+ defaultConfigurationIsVisible = 0;
175
+ defaultConfigurationName = Debug;
176
+ };
177
+ #{uuid('app-debug')} = {
178
+ isa = XCBuildConfiguration;
179
+ buildSettings = {
180
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
181
+ CODE_SIGN_IDENTITY = "-";
182
+ CODE_SIGN_STYLE = Automatic;
183
+ DEVELOPMENT_TEAM = "";
184
+ INFOPLIST_FILE = OCQAHarness/Info.plist;
185
+ PRODUCT_BUNDLE_IDENTIFIER = ai.autotap.harness;
186
+ PRODUCT_NAME = "$(TARGET_NAME)";
187
+ SWIFT_VERSION = 5.0;
188
+ TARGETED_DEVICE_FAMILY = "1,2";
189
+ };
190
+ name = Debug;
191
+ };
192
+ #{uuid('test-configs')} = {
193
+ isa = XCConfigurationList;
194
+ buildConfigurations = (#{uuid('test-debug')},);
195
+ defaultConfigurationIsVisible = 0;
196
+ defaultConfigurationName = Debug;
197
+ };
198
+ #{uuid('test-debug')} = {
199
+ isa = XCBuildConfiguration;
200
+ buildSettings = {
201
+ CODE_SIGN_IDENTITY = "-";
202
+ CODE_SIGN_STYLE = Automatic;
203
+ DEVELOPMENT_TEAM = "";
204
+ INFOPLIST_FILE = OCQAHarnessUITests/Info.plist;
205
+ PRODUCT_BUNDLE_IDENTIFIER = ai.autotap.harness.uitests;
206
+ PRODUCT_NAME = "$(TARGET_NAME)";
207
+ SWIFT_VERSION = 5.0;
208
+ TARGETED_DEVICE_FAMILY = "1,2";
209
+ TEST_TARGET_NAME = OCQAHarness;
210
+ };
211
+ name = Debug;
212
+ };
213
+ };
214
+ rootObject = #{uuid('project')};
215
+ }
216
+ PBX
217
+
218
+ File.write("#{xcodeproj_dir}/project.pbxproj", pbxproj)
219
+
220
+ # Create scheme
221
+ scheme_dir = "#{xcodeproj_dir}/xcshareddata/xcschemes"
222
+ FileUtils.mkdir_p(scheme_dir)
223
+
224
+ scheme = <<~SCHEME
225
+ <?xml version="1.0" encoding="UTF-8"?>
226
+ <Scheme LastUpgradeVersion = "1620" version = "1.7">
227
+ <BuildAction parallelizeBuildables = "YES" buildImplicitDependencies = "YES">
228
+ <BuildActionEntries>
229
+ <BuildActionEntry buildForTesting = "YES" buildForRunning = "YES" buildForProfiling = "YES" buildForArchiving = "YES" buildForAnalyzing = "YES">
230
+ <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "#{uuid('test-target')}" BuildableName = "OCQAHarnessUITests.xctest" BlueprintName = "OCQAHarnessUITests" ReferencedContainer = "container:OCQAHarness.xcodeproj"/>
231
+ </BuildActionEntry>
232
+ </BuildActionEntries>
233
+ </BuildAction>
234
+ <TestAction buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES">
235
+ <Testables>
236
+ <TestableReference skipped = "NO">
237
+ <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "#{uuid('test-target')}" BuildableName = "OCQAHarnessUITests.xctest" BlueprintName = "OCQAHarnessUITests" ReferencedContainer = "container:OCQAHarness.xcodeproj"/>
238
+ </TestableReference>
239
+ </Testables>
240
+ </TestAction>
241
+ <LaunchAction buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES">
242
+ <BuildableProductRunnable runnableDebuggingMode = "0">
243
+ <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "#{uuid('app-target')}" BuildableName = "OCQAHarness.app" BlueprintName = "OCQAHarness" ReferencedContainer = "container:OCQAHarness.xcodeproj"/>
244
+ </BuildableProductRunnable>
245
+ </LaunchAction>
246
+ </Scheme>
247
+ SCHEME
248
+
249
+ File.write("#{scheme_dir}/OCQAHarnessUITests.xcscheme", scheme)
250
+
251
+ puts "✅ Generated OCQAHarness.xcodeproj"
252
+ puts " Host app: ai.autotap.harness"
253
+ puts " Test target: OCQAHarnessUITests"
254
+ puts " Platform: iOS 16.0+"
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aaron Horowitz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,374 @@
1
+ # tapp — ship with proof
2
+
3
+ [![CI](https://github.com/aarwitz/tapp/actions/workflows/ci.yml/badge.svg)](https://github.com/aarwitz/tapp/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/%40aarwitz%2Ftapp?color=cb3837&label=npm)](https://www.npmjs.com/package/@aarwitz/tapp)
5
+ [![npm downloads](https://img.shields.io/npm/dw/%40aarwitz%2Ftapp?label=downloads)](https://www.npmjs.com/package/@aarwitz/tapp)
6
+ [![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
7
+ [![Install in Cursor](https://img.shields.io/badge/Cursor-Install_MCP-000000)](cursor://anysphere.cursor-deeplink/mcp/install?name=tapp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBhYXJ3aXR6L3RhcHAiLCJtY3AiXX0=)
8
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP-0098FF)](https://insiders.vscode.dev/redirect/mcp/install?name=tapp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40aarwitz%2Ftapp%22%2C%22mcp%22%5D%7D)
9
+
10
+ **Tapp is the release-contract and evidence layer for teams shipping agent-authored applications.**
11
+ It turns a repository and real product into an observed UI Map, a compact reviewed deterministic
12
+ suite, and an inspectable merge decision.
13
+
14
+ Coding agents can write the code, and (with Playwright & friends) they can even drive the app.
15
+ What nobody gives them is **judgment**: did it actually work? tapp explores your app like a user —
16
+ no test code, no app changes — detects what's broken, and commits to a verdict your merge queue
17
+ can trust: `ready`, `caution`, or `blocked`, with evidence.
18
+
19
+ Three platforms, one judgment layer:
20
+
21
+ - **iOS** — the missing Playwright for iOS. tapp is hands *and* judgment: a generic XCUITest
22
+ harness drives any app on the simulator via the accessibility surface. Native — no Appium,
23
+ no WebDriverAgent.
24
+ - **Android** — black-box native driving through ADB + UIAutomator. Install an APK, target its
25
+ application id, and run the same QA, committed Flows, evidence, and regression gate. The app
26
+ does not link a Tapp SDK.
27
+ - **Web (beta)** — built *on* Playwright. Your agent already has browser hands; tapp adds the
28
+ autonomous exploration, the deterministic detectors (uncaught exceptions, failed requests,
29
+ dead buttons, broken links, error pages), and the same verdict.
30
+
31
+ ```
32
+ you: "Add a logout button to the settings screen"
33
+ agent: *writes the Swift*
34
+ agent: *tapp: builds, opens the app, navigates to Settings, screenshots it*
35
+ agent: "Done — and here it is working on the simulator: [screenshot]"
36
+ ```
37
+
38
+ ## Quickstart
39
+
40
+ Requirements: **Node ≥ 18**. iOS needs **macOS + Xcode**; Android needs `adb` plus a connected
41
+ emulator/device; web needs Playwright + Chromium.
42
+
43
+ **Launch experience — browser Release Studio.** Open the source chooser from anywhere:
44
+
45
+ ```bash
46
+ npx -y @aarwitz/tapp app
47
+ ```
48
+
49
+ Drag and drop a local repository folder or connect through your authenticated GitHub CLI and select
50
+ an authorized repository. To work directly in an existing writable checkout, use `tapp app .`.
51
+ The local, loopback-only workspace detects iOS, Android, and web targets. A single configured target
52
+ builds and explores automatically; Tapp asks only when selection is ambiguous or configuration is
53
+ genuinely missing. It renders the UI Map,
54
+ supports release-contract review and keyless validation, records live semantic actions as committed
55
+ Flows, and produces target-scoped gate, baseline, evidence, and CI artifacts. CLI, MCP, VS Code, and
56
+ the GitHub Action use the same product/gate operations; the retained managed-runner prototype is
57
+ being replaced by the future isolated SaaS worker boundary.
58
+
59
+ **Zero config — get a verdict right now.** From your app's repo, one command. No server, no
60
+ config file, no test code — you don't even need to know your bundle id:
61
+
62
+ ```bash
63
+ cd YourApp
64
+ npx -y @aarwitz/tapp qa # finds your Xcode project → builds → installs on the simulator → explores → verdict
65
+ ```
66
+
67
+ The product, executable, and package leaf are all Tapp: npm distributes it as
68
+ `@aarwitz/tapp`, while the installed command remains `tapp`. Existing `npx runtapp ...`
69
+ and `npx tapp-mcp ...` configurations remain supported as deprecated compatibility paths.
70
+
71
+ To bootstrap maintained release infrastructure, preview the repository model and grounded plan
72
+ before Tapp writes anything:
73
+
74
+ ```bash
75
+ npx -y @aarwitz/tapp init . --dry-run --json-out /tmp/tapp-init.json
76
+ # Build/start the detected web target, ground the first UI Map, then stop it.
77
+ npx -y @aarwitz/tapp init . --explore --platform web
78
+ # Or build/install the detected Xcode target, ground the map, and persist the validated scheme.
79
+ npx -y @aarwitz/tapp init . --explore --platform ios --target .
80
+ # Or connect to an already-running owned URL:
81
+ npx -y @aarwitz/tapp init . --explore --platform web --url http://127.0.0.1:4173
82
+ # If the app has roles/accounts, bind names once; values stay in local/CI secrets.
83
+ npx -y @aarwitz/tapp actor set alice . --role member --session isolated \
84
+ --credential email=ALICE_EMAIL --credential password=ALICE_PASSWORD
85
+ # Review-only path: tapp init . → tapp plan show → tapp plan review --approve ...
86
+
87
+ # After approved drafts replay and are promoted, establish the selected target's baseline
88
+ # through the ordinary full gate, then generate the reviewable GitHub workflow.
89
+ npx -y @aarwitz/tapp baseline create . --platform web
90
+ npx -y @aarwitz/tapp ci install .
91
+ ```
92
+
93
+ The baseline command writes only after autonomous QA and every selected deterministic suite pass
94
+ conclusively. It stores `.autotap/baselines/<platform>/<target-id>.json`; the generated workflow
95
+ uses that exact target identity so two apps on the same platform never share a baseline. `ci
96
+ install` writes `.github/workflows/tapp.yml` plus `.autotap/ci.json`, refuses unresolved build
97
+ configuration and existing-file collisions, and never commits, pushes, enables branch protection,
98
+ or creates GitHub resources. Review and pin the generated Tapp release reference to its immutable
99
+ commit SHA before production.
100
+
101
+ Every verb takes whatever you have: nothing (auto-detects the repo you're in, or the app
102
+ already on the simulator), a repo directory, a `path/to/App.app`, or a bundle id:
103
+
104
+ ```bash
105
+ npx -y @aarwitz/tapp open [target] # launch the app → screen summary + screenshot file
106
+ npx -y @aarwitz/tapp tree [target] # accessibility tree of the current screen
107
+ npx -y @aarwitz/tapp shot # screenshot the booted simulator
108
+ npx -y @aarwitz/tapp apps # what's installed on the simulator (names + bundle ids)
109
+ npx -y @aarwitz/tapp build [dir] # just build + install (scheme auto-detected)
110
+ ```
111
+
112
+ Web (beta): `npx -y @aarwitz/tapp qa http://localhost:3000` *(one-time setup:
113
+ `npm i -g playwright && npx playwright install chromium`)*
114
+
115
+ Android:
116
+
117
+ ```bash
118
+ npx -y @aarwitz/tapp qa path/to/app-debug.apk --platform android --app-id com.acme.app
119
+ npx -y @aarwitz/tapp open com.acme.app --platform android
120
+ ```
121
+
122
+ Optional but recommended (prebuilds the test harness so the first run is fast):
123
+ ```bash
124
+ npx -y @aarwitz/tapp install # ~2 min, one time
125
+ npx -y @aarwitz/tapp doctor # verify Xcode / simulators / toolchain
126
+ ```
127
+
128
+ ### MCP hookup (optional)
129
+
130
+ The MCP server adds the two things a CLI can't do: **screenshots inline in your agent's
131
+ context** (the model literally sees the screen) and the **interactive session loop**
132
+ (tap → read tree → type, with the app staying open between actions).
133
+
134
+ **Claude Code:**
135
+ ```bash
136
+ claude mcp add tapp -- npx -y @aarwitz/tapp mcp
137
+ ```
138
+
139
+ **Cursor / VS Code (Copilot)** — add to `~/.cursor/mcp.json` (Cursor) or `.vscode/mcp.json` (VS Code):
140
+ ```json
141
+ {
142
+ "servers": {
143
+ "tapp": { "type": "stdio", "command": "npx", "args": ["-y", "@aarwitz/tapp", "mcp"] }
144
+ }
145
+ }
146
+ ```
147
+
148
+ **Codex CLI** — add to `~/.codex/config.toml`:
149
+ ```toml
150
+ [mcp_servers.tapp]
151
+ command = "npx"
152
+ args = ["-y", "@aarwitz/tapp", "mcp"]
153
+ ```
154
+
155
+ **Any other MCP client:** stdio command `npx -y @aarwitz/tapp mcp`.
156
+
157
+ Then ask your agent:
158
+ > "Run tapp qa on my app — is it ship-ready?"
159
+ > "Open com.mycompany.app on the simulator and screenshot the home screen."
160
+ > "Log in with test@example.com, drive to checkout, and record it as a replayable test."
161
+
162
+ ## What the agent gets
163
+
164
+ | | Tool | What it does |
165
+ |---|---|---|
166
+ | 👁 | `tapp_open_app` | **See a screen** — launch the app, return screenshot + accessibility tree. Seconds. |
167
+ | 📸 | `tapp_screenshot` | Whatever's on the sim right now, as an inline image. |
168
+ | 🌳 | `tapp_ui_tree` | The accessibility tree of the current screen (ids, labels, hittability). |
169
+ | 🕹 | `tapp_session_start/act/end` | **Interactive driving** — the Playwright loop. App launches once; each act (tap/type/swipe/back/wait) returns the fresh tree. |
170
+ | 🧪 | `tapp_run_qa` | **Autonomous QA** — explores with no authored test, returns `{verdict, releaseScore, findings[]}`. Takes `appBundleId` (iOS), `androidAppId` (Android), or `url` (web). |
171
+ | 🧭 | `tapp_init` | **Repository import** — detect targets; optionally explore a real surface; persist the shared UI Map; construct the evidence-classified model and grounded release plan. |
172
+ | 👤 | `tapp_actor_config` | **Actor/session setup** — store roles, isolation/provisioning, and environment-variable names without accepting or persisting credential values. |
173
+ | ✅ | `tapp_release_plan` | **Release-plan lifecycle** — inspect, approve/reject/defer, generate, real-target validate, and explicitly promote proposed guarantees without silent test edits. |
174
+ | 🚦 | `tapp_ci_setup` | **Baseline and CI setup** — import a conclusive target baseline or render/install the same reviewable target-aware workflow as the CLI. |
175
+ | 🗺️ | `tapp_ui_map` | **Persistent UI Map** — build, inspect, merge, and diff observed states, controls, transitions, provenance, and coverage. |
176
+ | 🧩 | `tapp_task` | **Reusable deterministic Tasks** — validate and compile shared actions such as `signIn` against the UI Map; replay stays keyless. |
177
+ | 📜 | `tapp_release_contract` | **Business-level release contracts** — validate, compile, or run typed guarantees composed from Tasks and named actors. |
178
+ | 📋 | `tapp_pr_plan` | **PR-aware evolution** — select reviewed contracts, schedule bounded changed-surface exploration, and explicitly adopt observed coverage proposals without silent rewrites. |
179
+ | 🔁 | `tapp_flow_run` / `flow_save` / `flow_generate` | **Deterministic E2E execution (Flows)** — raw steps or reusable Task calls replay with exact assertions. |
180
+ | 👥 | `tapp_scenario_run` | **Multi-actor system tests** — isolated named browser sessions verify cross-account state with deterministic assertions. No AI at replay time. |
181
+ | 📱 | `tapp_list_simulators` / `boot_simulator` / `install_app` | Simulator + app management. |
182
+ | 🩺 | `tapp_health`, `tapp_capture*`, `tapp_parse_markers` | Diagnostics and capture history. |
183
+
184
+ Full agent playbook: [AGENTS.md](./AGENTS.md) — ships inside the package so agents can read it too.
185
+ Application-model and import contract: [`docs/application-model.md`](docs/application-model.md).
186
+ The desktop Coverage view reads the same `.autotap/application-model.json`,
187
+ `.autotap/release-plan.json`, and `.autotap/ui-map.json`, including explicit proposal review; it
188
+ does not maintain a separate product model. Map nodes identify both the real launch entry and the
189
+ deterministic per-platform navigation root used for bounded changed-surface replay.
190
+
191
+ ## The verdict you can trust
192
+
193
+ **Adaptive exploration, deterministic judgment.** Exploration is adaptive — two runs may
194
+ traverse different paths through your app. Judgment is deterministic: the same evidence
195
+ trace always produces the same findings, the same score, and the same verdict — no LLM variability
196
+ in the decision loop. PR gating keys on the **regression diff**
197
+ (stable finding signatures vs. a baseline), so it reacts to what *changed*, not to
198
+ run-to-run path variance. For critical user journeys, committed **Tasks and Flows** provide the stable CI
199
+ suite: reusable semantic actions, exact assertions, condition-based waits, fresh launch state, bounded timeouts,
200
+ and evidence on failure. We call this *flake-resistant*, not magically flake-free—backend outages,
201
+ unstable test data, and poorly identified controls can still make any E2E test fail.
202
+
203
+ **A release score, not "confidence."** The 0–100 number is a heuristic quality score from
204
+ fixed, documented deductions — we don't call it confidence because it isn't calibrated
205
+ probability. Calibrating it against seeded-fault benchmarks is ongoing work; until then it
206
+ ranks runs, it doesn't promise odds.
207
+
208
+ `tapp_run_qa` explores like a user — accessibility surfaces on iOS/Android and a real browser on web —
209
+ and detects crashes, failed sign-ins, dead buttons, stuck loading screens, error surfaces,
210
+ navigation loops, and dead ends (plus, on web: uncaught JS exceptions, failed/5xx requests,
211
+ broken links and assets). The verdict is **deterministic** (no LLM in the run loop) and **honest**:
212
+
213
+ - `blocked` — a release-blocking issue was found.
214
+ - `caution` — issues to review, or the run couldn't see enough.
215
+ - `ready` — genuinely explored with no blockers. **A shallow run is never `ready`** — if the
216
+ app crashed on launch or a login wall blocked exploration, you get `inconclusive: true`,
217
+ not a false pass. Absence of findings is not a pass.
218
+
219
+ Apps behind a login? Pass `testEmail`/`testPassword` (typed into the login form automatically),
220
+ `appLaunchArgs` (e.g. `["--uitesting"]` if your app supports a bypass), or explicit `loginSteps`
221
+ for custom login UIs.
222
+
223
+ ## CI gate
224
+
225
+ The same engine runs as a merge gate — explore on every PR, replay committed release contracts,
226
+ Flows, and multi-actor Scenarios, diff findings
227
+ against the last conclusive default-branch run, fail on regressions, post a sticky PR comment, and
228
+ upload screenshots, the recording, and machine-readable JSON:
229
+
230
+ ```yaml
231
+ # .github/workflows/tapp.yml
232
+ name: Tapp release gate
233
+ on:
234
+ pull_request:
235
+ push:
236
+ branches: [main] # refreshes the automatic baseline after merges
237
+
238
+ permissions:
239
+ actions: read
240
+ contents: read
241
+ pull-requests: write
242
+
243
+ concurrency:
244
+ group: tapp-${{ github.ref }}
245
+ cancel-in-progress: true
246
+
247
+ jobs:
248
+ tapp:
249
+ runs-on: macos-15
250
+ timeout-minutes: 45
251
+ steps:
252
+ - uses: actions/checkout@v4
253
+ - uses: aarwitz/tapp@main # pin to the newest release tag for production
254
+ with:
255
+ project: MyApp.xcodeproj # or MyApp.xcworkspace
256
+ scheme: MyApp
257
+ ```
258
+
259
+ On pull requests, the Action automatically reads the complete changed-file set from GitHub,
260
+ retaining old and new paths for renames. It always runs critical/`policy.always` contracts, adds
261
+ contracts related through reviewed source ownership, the UI Map, and transitive Task composition,
262
+ and records skipped contracts and coverage gaps in `tapp-pr-plan.json`. A selected contract that
263
+ does not execute blocks the merge. Set `pr-selection: "false"` only when intentionally running the
264
+ full contract set.
265
+
266
+ Changed weakly covered surfaces are not limited to direct web URLs. Reviewed Task source ownership
267
+ can compile one bounded native target (or up to five web targets) through observed UI Map edges,
268
+ with condition waits and stable target evidence. Missing or failed targets make the run
269
+ inconclusive; Tapp does not guess a path from a screen name.
270
+
271
+ The first successful, conclusive run on `main` seeds a repository-scoped Actions cache and a
272
+ 90-day baseline artifact. Both are keyed by platform and stable application-model target id. Pull requests automatically restore it and fail only on **new**
273
+ high/critical findings or broken Flows—not pre-existing debt. No baseline commit or PAT is required.
274
+ If you prefer a reviewed, durable baseline, run `tapp baseline create` and commit the generated
275
+ `.autotap/baselines/<platform>/<target-id>.json`; `tapp ci install` wires its explicit path into the
276
+ corresponding job. The legacy `.autotap/baseline.json` is still recognized. Automatic baseline restore and the PR comment need `actions: read` and
277
+ `pull-requests: write` as shown above. Secrets are unavailable to workflows from forks, so
278
+ auth-gated apps should either use a non-secret UI-testing launch argument or skip the gate for
279
+ untrusted forks.
280
+
281
+ Already build the simulator app in another job, or use another CI provider? The portable command
282
+ accepts that `.app`, detects its bundle id, writes report artifacts, and exits non-zero when the
283
+ gate fails:
284
+
285
+ ```bash
286
+ npx -y @aarwitz/tapp ci --app path/to/MyApp.app \
287
+ --project-dir . --pr-base origin/main --pr-head HEAD \
288
+ --target-key target_ios_myapp \
289
+ --pr-plan-out tapp-pr-plan.json \
290
+ --baseline path/to/last-main-report.json \
291
+ --json-out tapp-report.json --md-out tapp-report.md
292
+ ```
293
+
294
+ See the self-test at
295
+ [.github/workflows/autotap-gate-example.yml](.github/workflows/autotap-gate-example.yml) for
296
+ Flows, auth inputs, and other controls. GitHub-hosted iOS runs require a macOS runner; the first
297
+ run also builds the XCUITest harness, so budget roughly 5–10 minutes depending on app size.
298
+
299
+ Android CI runs on Linux with an emulator/device already connected. The Action can build the APK
300
+ or accept a prebuilt one:
301
+
302
+ ```yaml
303
+ - uses: aarwitz/tapp@main
304
+ with:
305
+ platform: android
306
+ android-app-id: com.acme.app
307
+ android-project: android
308
+ android-task: :app:assembleDebug
309
+ flows: android/.autotap/flows/*.yml
310
+ ```
311
+
312
+ For web, pass `platform: web` plus `web-target:` and Tapp uses the application model to run its
313
+ lockfile-backed install/build, start a detected package script or read-only static server, wait for
314
+ readiness, gate it, and stop it even on failure. Pass `url:` instead for an already-running owned
315
+ environment. Add
316
+ `scenarios: .autotap/scenarios/*.yml` to gate isolated cross-account journeys; see
317
+ [`docs/scenarios.md`](docs/scenarios.md). Automatic
318
+ baselines are isolated by platform and target, so two same-platform apps are never compared.
319
+
320
+ **The hosted service at `app.runtapp.com` is under development and is not currently offered for
321
+ customer repositories.** Do not upload private code or credentials to an old preview. The retained
322
+ cloud prototype is not the production SaaS boundary. Use the local Release Studio and the portable
323
+ GitHub Action in infrastructure you control until the new account, tenant authorization, private
324
+ evidence, and isolated-worker boundary passes security review.
325
+
326
+ ## Make your repo agent-verified
327
+
328
+ Drop this into your repo's `AGENTS.md` (read by Codex, Cursor, Copilot, Devin, Zed, …) so
329
+ your agent proves its UI work instead of claiming it:
330
+
331
+ ```markdown
332
+ ## Verifying UI changes
333
+ This repo uses tapp (https://github.com/aarwitz/tapp) to verify UI work on a real app surface
334
+ (iOS simulator, Android emulator/device, or a browser for web). After any UI change, run `npx -y @aarwitz/tapp open` from the
335
+ repo root (it finds and builds the Xcode project itself) and look at the screenshot it saves as
336
+ proof. Before declaring a feature done, run `npx -y @aarwitz/tapp qa` (or `qa <url>` for web) and
337
+ report the ship/no-ship verdict. A change is not "done" until it has been seen working.
338
+ (If the tapp MCP server is connected, the tapp_* tools do the same with inline screenshots —
339
+ tapp_build builds + installs the app and returns the bundle id for tapp_run_qa.)
340
+ ```
341
+
342
+ ## How it works
343
+
344
+ Every driver speaks one protocol: structured `OCQA_*` markers (state, actions, issues,
345
+ transitions) that the judgment layer parses into trees, screenshots, findings, and the verdict.
346
+ On **iOS**, a generic **XCUITest harness** attaches to any app by bundle id — no SDK or app code
347
+ changes — and acts through the accessibility tree. On **Android**, ADB + UIAutomator provide the
348
+ same black-box driver contract. On **web** (beta), a deterministic **Playwright crawler** does the
349
+ same in a real browser. Same detectors' spirit,
350
+ same dedup, same regression gate, same honest verdict. Core exploration, evidence collection, and
351
+ verdict calculation run entirely locally — no telemetry, nothing phones home. Optional AI
352
+ features are explicit: finding enrichment requires `TAPP_ENABLE_REMOTE_AI=1` (an ambient
353
+ API key alone never changes data handling), and AI flow generation / `assert_ai` only run
354
+ when you invoke them; these send selected metadata (screen names, finding titles) to your
355
+ configured model provider. Env vars: `TAPP_*` preferred; `AUTOTAP_*` accepted as deprecated
356
+ aliases.
357
+
358
+ Committed Flow replay, recording a driven session, autonomous exploration, exact assertions,
359
+ regression comparison, and CI gating require **no API key and no coding agent at runtime**. AI is
360
+ only an optional authoring/enrichment layer (`tapp_flow_generate`, `assert_ai`, finding enrichment).
361
+
362
+ The first tool call builds the harness once (~2 min, cached in `~/.tapp`; rebuilt automatically
363
+ if you switch simulators). All captures land in `~/.tapp/captures/`.
364
+
365
+ ## Desktop status
366
+
367
+ The macOS cockpit is frozen as a supported native interface and parity floor; it still reads the
368
+ canonical Application Model, release plan, and UI Map in Coverage. Its older import/build path is
369
+ not yet a thin client of the shared product-operation layer, so new product work is converging in
370
+ the browser without deleting or reducing the desktop experience.
371
+
372
+ ## License
373
+
374
+ [MIT](./LICENSE)