@aarwitz/tapp 0.17.0-rc.12 → 0.17.0-rc.14
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/.claude-plugin/plugin.json +4 -3
- package/AGENTS.md +9 -9
- package/README.md +34 -34
- package/bin/tapp.js +12 -12
- package/docs/BROWSER-PRODUCT.md +1 -1
- package/mcp-server/src/application-model.js +6 -6
- package/mcp-server/src/ci-report.js +2 -2
- package/mcp-server/src/ci-setup.js +4 -4
- package/mcp-server/src/index.js +8 -8
- package/mcp-server/src/pr-selection.js +2 -2
- package/mcp-server/src/product-operations.js +1 -1
- package/package.json +1 -1
- package/skills/tapp/SKILL.md +6 -5
- package/skills/tapp/references/commands.md +15 -15
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tapp",
|
|
3
3
|
"description": "Test real iOS, Android, and web apps with Tapp. Includes the Tapp agent skill plus MCP hands, eyes, exploration, replayable Flows, and deterministic release evidence.",
|
|
4
|
-
"version": "0.17.0-rc.
|
|
4
|
+
"version": "0.17.0-rc.14",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Aaron Horowitz",
|
|
7
7
|
"url": "https://github.com/aarwitz"
|
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
],
|
|
22
22
|
"mcpServers": {
|
|
23
23
|
"tapp": {
|
|
24
|
-
"command": "
|
|
24
|
+
"command": "npx",
|
|
25
25
|
"args": [
|
|
26
|
-
"
|
|
26
|
+
"-y",
|
|
27
|
+
"@aarwitz/tapp@0.17.0-rc.14",
|
|
27
28
|
"mcp"
|
|
28
29
|
],
|
|
29
30
|
"cwd": "${CLAUDE_PROJECT_DIR}"
|
package/AGENTS.md
CHANGED
|
@@ -11,15 +11,15 @@ the app already on the simulator); it also accepts a repo dir, a `path/to/App.ap
|
|
|
11
11
|
bundle id, or (web) an http(s) URL. You never need to know a bundle id up front.
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npx -y @aarwitz/tapp explore [target] # autonomous exploration → findings + evidence (observation, not a gate; ≈ tapp_explore)
|
|
15
|
-
npx -y @aarwitz/tapp explore https://your-app.example --watch # web: visibly follow the same exploration
|
|
16
|
-
npx -y @aarwitz/tapp open [target] # launch + screen summary + screenshot saved to a file (≈ tapp_open_app)
|
|
17
|
-
npx -y @aarwitz/tapp tree [target] # accessibility tree, --json for every element (≈ tapp_ui_tree)
|
|
18
|
-
npx -y @aarwitz/tapp shot # screenshot the booted sim → file path (≈ tapp_screenshot)
|
|
19
|
-
npx -y @aarwitz/tapp apps # what's installed on the simulator, with bundle ids
|
|
20
|
-
npx -y @aarwitz/tapp build [dir] # build the app in an Xcode repo + install it (≈ tapp_build)
|
|
21
|
-
npx -y @aarwitz/tapp explore app.apk --platform android --app-id com.acme.app
|
|
22
|
-
npx -y @aarwitz/tapp flow run .tapp/flows/smoke.yml # committed, keyless E2E replay
|
|
14
|
+
npx -y @aarwitz/tapp@latest explore [target] # autonomous exploration → findings + evidence (observation, not a gate; ≈ tapp_explore)
|
|
15
|
+
npx -y @aarwitz/tapp@latest explore https://your-app.example --watch # web: visibly follow the same exploration
|
|
16
|
+
npx -y @aarwitz/tapp@latest open [target] # launch + screen summary + screenshot saved to a file (≈ tapp_open_app)
|
|
17
|
+
npx -y @aarwitz/tapp@latest tree [target] # accessibility tree, --json for every element (≈ tapp_ui_tree)
|
|
18
|
+
npx -y @aarwitz/tapp@latest shot # screenshot the booted sim → file path (≈ tapp_screenshot)
|
|
19
|
+
npx -y @aarwitz/tapp@latest apps # what's installed on the simulator, with bundle ids
|
|
20
|
+
npx -y @aarwitz/tapp@latest build [dir] # build the app in an Xcode repo + install it (≈ tapp_build)
|
|
21
|
+
npx -y @aarwitz/tapp@latest explore app.apk --platform android --app-id com.acme.app
|
|
22
|
+
npx -y @aarwitz/tapp@latest flow run .tapp/flows/smoke.yml # committed, keyless E2E replay
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
If repository onboarding detects multiple application targets, target detection is deterministic but
|
package/README.md
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
[](https://skills.sh/aarwitz/tapp)
|
|
8
8
|
[](https://marketplace.visualstudio.com/items?itemName=lidi-solutions.tapp)
|
|
9
|
-
[](cursor://anysphere.cursor-deeplink/mcp/install?name=tapp&config=
|
|
10
|
-
[](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
|
+
[](cursor://anysphere.cursor-deeplink/mcp/install?name=tapp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBhYXJ3aXR6L3RhcHBAbGF0ZXN0IiwibWNwIl19)
|
|
10
|
+
[](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%40latest%22%2C%22mcp%22%5D%7D)
|
|
11
11
|
|
|
12
12
|
**Tapp is the release-contract and evidence layer for teams shipping agent-authored applications.**
|
|
13
13
|
It turns a repository and real product into an observed UI Map, a compact reviewed deterministic
|
|
@@ -51,8 +51,8 @@ claude plugin install tapp@tapp
|
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
Restart Claude Code after installation, open the application repository, and use the short prompt
|
|
54
|
-
above. The plugin
|
|
55
|
-
prompt block or bundle id is required.
|
|
54
|
+
above. The plugin installs the `tapp` Agent Skill and launches the matching version of Tapp's stdio
|
|
55
|
+
MCP server through npm; no additional prompt block, MCP configuration, or bundle id is required.
|
|
56
56
|
|
|
57
57
|
**Claude, Codex, Cursor, Copilot, and other Agent Skills clients — skill only:**
|
|
58
58
|
|
|
@@ -67,7 +67,7 @@ or `--agent claude-code`, `--agent codex`, and similar selectors to constrain th
|
|
|
67
67
|
**No agent integration:** run the npm package directly from an app repository:
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
|
-
npx -y @aarwitz/tapp init . --explore
|
|
70
|
+
npx -y @aarwitz/tapp@latest init . --explore
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
**VS Code:** install [Tapp from the Marketplace](https://marketplace.visualstudio.com/items?itemName=lidi-solutions.tapp).
|
|
@@ -89,8 +89,8 @@ emulator/device; web needs Playwright + Chromium.
|
|
|
89
89
|
From the app repository, let the agent see the current screen and then explore it:
|
|
90
90
|
|
|
91
91
|
```bash
|
|
92
|
-
npx -y @aarwitz/tapp open # builds/launches as needed; prints a screenshot path + screen summary
|
|
93
|
-
npx -y @aarwitz/tapp explore # explores the real app; prints findings + evidence (an observation, not a gate)
|
|
92
|
+
npx -y @aarwitz/tapp@latest open # builds/launches as needed; prints a screenshot path + screen summary
|
|
93
|
+
npx -y @aarwitz/tapp@latest explore # explores the real app; prints findings + evidence (an observation, not a gate)
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
Claude Code can read the saved image with its file-reading tool; Codex can open it with
|
|
@@ -105,22 +105,22 @@ To bootstrap maintained release infrastructure, preview the repository model and
|
|
|
105
105
|
before Tapp writes anything:
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
|
-
npx -y @aarwitz/tapp init . --dry-run --json-out /tmp/tapp-init.json
|
|
108
|
+
npx -y @aarwitz/tapp@latest init . --dry-run --json-out /tmp/tapp-init.json
|
|
109
109
|
# Build/start the detected web target, ground the first UI Map, then stop it.
|
|
110
|
-
npx -y @aarwitz/tapp init . --explore --platform web
|
|
110
|
+
npx -y @aarwitz/tapp@latest init . --explore --platform web
|
|
111
111
|
# Or build/install the detected Xcode target, ground the map, and persist the validated scheme.
|
|
112
|
-
npx -y @aarwitz/tapp init . --explore --platform ios --target .
|
|
112
|
+
npx -y @aarwitz/tapp@latest init . --explore --platform ios --target .
|
|
113
113
|
# Or connect to an already-running owned URL:
|
|
114
|
-
npx -y @aarwitz/tapp init . --explore --platform web --url http://127.0.0.1:4173
|
|
114
|
+
npx -y @aarwitz/tapp@latest init . --explore --platform web --url http://127.0.0.1:4173
|
|
115
115
|
# If the app has roles/accounts, bind names once; values stay in local/CI secrets.
|
|
116
|
-
npx -y @aarwitz/tapp actor set alice . --role member --session isolated \
|
|
116
|
+
npx -y @aarwitz/tapp@latest actor set alice . --role member --session isolated \
|
|
117
117
|
--credential email=ALICE_EMAIL --credential password=ALICE_PASSWORD
|
|
118
118
|
# Review-only path: tapp init . → tapp plan show → tapp plan review --approve ...
|
|
119
119
|
|
|
120
120
|
# After approved drafts replay and are promoted, establish the selected target's baseline
|
|
121
121
|
# through the ordinary full gate, then generate the reviewable GitHub workflow.
|
|
122
|
-
npx -y @aarwitz/tapp baseline create . --platform web
|
|
123
|
-
npx -y @aarwitz/tapp ci install .
|
|
122
|
+
npx -y @aarwitz/tapp@latest baseline create . --platform web
|
|
123
|
+
npx -y @aarwitz/tapp@latest ci install .
|
|
124
124
|
```
|
|
125
125
|
|
|
126
126
|
In a repository containing multiple apps (for example, iOS plus web),
|
|
@@ -144,14 +144,14 @@ Every verb takes whatever you have: nothing (auto-detects the repo you're in, or
|
|
|
144
144
|
already on the simulator), a repo directory, a `path/to/App.app`, or a bundle id:
|
|
145
145
|
|
|
146
146
|
```bash
|
|
147
|
-
npx -y @aarwitz/tapp open [target] # launch the app → screen summary + screenshot file
|
|
148
|
-
npx -y @aarwitz/tapp tree [target] # accessibility tree of the current screen
|
|
149
|
-
npx -y @aarwitz/tapp shot # screenshot the booted simulator
|
|
150
|
-
npx -y @aarwitz/tapp apps # what's installed on the simulator (names + bundle ids)
|
|
151
|
-
npx -y @aarwitz/tapp build [dir] # just build + install (scheme auto-detected)
|
|
147
|
+
npx -y @aarwitz/tapp@latest open [target] # launch the app → screen summary + screenshot file
|
|
148
|
+
npx -y @aarwitz/tapp@latest tree [target] # accessibility tree of the current screen
|
|
149
|
+
npx -y @aarwitz/tapp@latest shot # screenshot the booted simulator
|
|
150
|
+
npx -y @aarwitz/tapp@latest apps # what's installed on the simulator (names + bundle ids)
|
|
151
|
+
npx -y @aarwitz/tapp@latest build [dir] # just build + install (scheme auto-detected)
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
-
Web (beta): `npx -y @aarwitz/tapp explore http://localhost:3000` *(one-time setup:
|
|
154
|
+
Web (beta): `npx -y @aarwitz/tapp@latest explore http://localhost:3000` *(one-time setup:
|
|
155
155
|
`npm i -g playwright && npx playwright install chromium`)*. Add `--watch` to open Tapp's controlled,
|
|
156
156
|
isolated Chromium window and follow its clicks with an on-page pointer/action label. Tapp hides that
|
|
157
157
|
watch UI from saved evidence screenshots and does not automate your personal/default browser profile.
|
|
@@ -160,26 +160,26 @@ Focused web inspection waits briefly for loading states to settle. If a consent
|
|
|
160
160
|
blocks the screen, dismiss it and wait for the content you care about in the same package-only call:
|
|
161
161
|
|
|
162
162
|
```bash
|
|
163
|
-
npx -y @aarwitz/tapp open https://example.com --tap "Not now" --wait-for "Dashboard"
|
|
164
|
-
npx -y @aarwitz/tapp tree https://example.com --tap "Not now" --wait-for "Dashboard" --json
|
|
163
|
+
npx -y @aarwitz/tapp@latest open https://example.com --tap "Not now" --wait-for "Dashboard"
|
|
164
|
+
npx -y @aarwitz/tapp@latest tree https://example.com --tap "Not now" --wait-for "Dashboard" --json
|
|
165
165
|
```
|
|
166
166
|
|
|
167
167
|
Android:
|
|
168
168
|
|
|
169
169
|
```bash
|
|
170
|
-
npx -y @aarwitz/tapp explore path/to/app-debug.apk --platform android --app-id com.acme.app
|
|
171
|
-
npx -y @aarwitz/tapp open com.acme.app --platform android
|
|
170
|
+
npx -y @aarwitz/tapp@latest explore path/to/app-debug.apk --platform android --app-id com.acme.app
|
|
171
|
+
npx -y @aarwitz/tapp@latest open com.acme.app --platform android
|
|
172
172
|
```
|
|
173
173
|
|
|
174
174
|
Optional but recommended (prebuilds the test harness so the first run is fast):
|
|
175
175
|
```bash
|
|
176
|
-
npx -y @aarwitz/tapp install # ~2 min, one time
|
|
177
|
-
npx -y @aarwitz/tapp doctor # verify Xcode / simulators / toolchain
|
|
176
|
+
npx -y @aarwitz/tapp@latest install # ~2 min, one time
|
|
177
|
+
npx -y @aarwitz/tapp@latest doctor # verify Xcode / simulators / toolchain
|
|
178
178
|
```
|
|
179
179
|
|
|
180
180
|
### Optional browser workspace
|
|
181
181
|
|
|
182
|
-
`npx -y @aarwitz/tapp app .` opens a local Release Studio for people who want visual repository
|
|
182
|
+
`npx -y @aarwitz/tapp@latest app .` opens a local Release Studio for people who want visual repository
|
|
183
183
|
onboarding, release-plan review, and CI preparation. It is not required for the coding-agent
|
|
184
184
|
`open`/`explore` workflow.
|
|
185
185
|
|
|
@@ -191,14 +191,14 @@ context** (the model literally sees the screen) and the **interactive session lo
|
|
|
191
191
|
|
|
192
192
|
**Claude Code:**
|
|
193
193
|
```bash
|
|
194
|
-
claude mcp add tapp -- npx -y @aarwitz/tapp mcp
|
|
194
|
+
claude mcp add tapp -- npx -y @aarwitz/tapp@latest mcp
|
|
195
195
|
```
|
|
196
196
|
|
|
197
197
|
**Cursor / VS Code (Copilot)** — add to `~/.cursor/mcp.json` (Cursor) or `.vscode/mcp.json` (VS Code):
|
|
198
198
|
```json
|
|
199
199
|
{
|
|
200
200
|
"servers": {
|
|
201
|
-
"tapp": { "type": "stdio", "command": "npx", "args": ["-y", "@aarwitz/tapp", "mcp"] }
|
|
201
|
+
"tapp": { "type": "stdio", "command": "npx", "args": ["-y", "@aarwitz/tapp@latest", "mcp"] }
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
```
|
|
@@ -207,10 +207,10 @@ claude mcp add tapp -- npx -y @aarwitz/tapp mcp
|
|
|
207
207
|
```toml
|
|
208
208
|
[mcp_servers.tapp]
|
|
209
209
|
command = "npx"
|
|
210
|
-
args = ["-y", "@aarwitz/tapp", "mcp"]
|
|
210
|
+
args = ["-y", "@aarwitz/tapp@latest", "mcp"]
|
|
211
211
|
```
|
|
212
212
|
|
|
213
|
-
**Any other MCP client:** stdio command `npx -y @aarwitz/tapp mcp`.
|
|
213
|
+
**Any other MCP client:** stdio command `npx -y @aarwitz/tapp@latest mcp`.
|
|
214
214
|
|
|
215
215
|
Then ask your agent:
|
|
216
216
|
> "Explore my app and show me what breaks."
|
|
@@ -353,7 +353,7 @@ accepts that `.app`, detects its bundle id, writes report artifacts, and exits n
|
|
|
353
353
|
gate fails:
|
|
354
354
|
|
|
355
355
|
```bash
|
|
356
|
-
npx -y @aarwitz/tapp ci --app path/to/MyApp.app \
|
|
356
|
+
npx -y @aarwitz/tapp@latest ci --app path/to/MyApp.app \
|
|
357
357
|
--project-dir . --pr-base origin/main --pr-head HEAD \
|
|
358
358
|
--target-key target_ios_myapp \
|
|
359
359
|
--pr-plan-out tapp-pr-plan.json \
|
|
@@ -401,9 +401,9 @@ your agent proves its UI work instead of claiming it:
|
|
|
401
401
|
```markdown
|
|
402
402
|
## Verifying UI changes
|
|
403
403
|
This repo uses tapp (https://github.com/aarwitz/tapp) to verify UI work on a real app surface
|
|
404
|
-
(iOS simulator, Android emulator/device, or a browser for web). After any UI change, run `npx -y @aarwitz/tapp open` from the
|
|
404
|
+
(iOS simulator, Android emulator/device, or a browser for web). After any UI change, run `npx -y @aarwitz/tapp@latest open` from the
|
|
405
405
|
repo root (it finds and builds the Xcode project itself) and look at the screenshot it saves as
|
|
406
|
-
proof. Before declaring a feature done, run `npx -y @aarwitz/tapp explore` (or `explore <url>` for web)
|
|
406
|
+
proof. Before declaring a feature done, run `npx -y @aarwitz/tapp@latest explore` (or `explore <url>` for web)
|
|
407
407
|
and report the findings + evidence. A change is not "done" until it has been seen working.
|
|
408
408
|
(If the tapp MCP server is connected, the tapp_* tools do the same with inline screenshots —
|
|
409
409
|
tapp_build builds + installs the app and returns the bundle id for tapp_explore.)
|
package/bin/tapp.js
CHANGED
|
@@ -155,7 +155,7 @@ const engineImport = () => import(path.join(packageRoot, "mcp-server", "src", "i
|
|
|
155
155
|
|
|
156
156
|
function requireMacFor(what) {
|
|
157
157
|
if (process.platform === "darwin") return;
|
|
158
|
-
console.error(`❌ ${what} requires macOS (Xcode + iOS simulator). The web beta runs anywhere: tapp explore https://localhost:3000`);
|
|
158
|
+
console.error(`❌ ${what} requires macOS (Xcode + iOS simulator). The web beta runs anywhere: npx -y @aarwitz/tapp@latest explore https://localhost:3000`);
|
|
159
159
|
process.exit(1);
|
|
160
160
|
}
|
|
161
161
|
|
|
@@ -840,7 +840,7 @@ switch (command) {
|
|
|
840
840
|
process.exit(1);
|
|
841
841
|
}
|
|
842
842
|
console.log(`🔨 Built ${path.basename(built.appPath)} (scheme ${built.scheme}) — installed as ${inst.bundleId}`);
|
|
843
|
-
console.log(`\nNext: tapp explore ${inst.bundleId}`);
|
|
843
|
+
console.log(`\nNext: npx -y @aarwitz/tapp@latest explore ${inst.bundleId}`);
|
|
844
844
|
break;
|
|
845
845
|
}
|
|
846
846
|
|
|
@@ -1054,7 +1054,7 @@ switch (command) {
|
|
|
1054
1054
|
try {
|
|
1055
1055
|
const adopted = adoptPrCoverageProposal({ projectDir, prPlanPath, item: flags.item, releasePlanPath: typeof flags["release-plan"] === "string" ? flags["release-plan"] : undefined });
|
|
1056
1056
|
console.log(`📥 ${adopted.mode === "reconciled-existing" ? "Reconciled PR evidence into" : "Adopted"} ${adopted.item.name}${adopted.mode === "reconciled-existing" ? ` while preserving decision '${adopted.item.decision}'` : " as a pending release-plan item"}; no Task or contract was generated or trusted`);
|
|
1057
|
-
console.log(` plan: ${adopted.path}\n next: tapp plan review ${adopted.path} --approve ${adopted.item.id}`);
|
|
1057
|
+
console.log(` plan: ${adopted.path}\n next: npx -y @aarwitz/tapp@latest plan review ${adopted.path} --approve ${adopted.item.id}`);
|
|
1058
1058
|
} catch (error) { console.error(`❌ Could not adopt PR coverage proposal: ${error.message || String(error)}`); process.exit(2); }
|
|
1059
1059
|
break;
|
|
1060
1060
|
}
|
|
@@ -1223,7 +1223,7 @@ switch (command) {
|
|
|
1223
1223
|
const booted = bootedSims();
|
|
1224
1224
|
ok("iOS", `${ver || "Xcode"}; ${booted.length ? `${booted[0].name} booted` : "no simulator booted yet"}`);
|
|
1225
1225
|
const xctestrun = harnessXctestrun();
|
|
1226
|
-
xctestrun ? ok("iOS harness cache", xctestrun) : console.log(" ⬜ iOS harness cache — builds on first use (or: tapp install)");
|
|
1226
|
+
xctestrun ? ok("iOS harness cache", xctestrun) : console.log(" ⬜ iOS harness cache — builds on first use (or: npx -y @aarwitz/tapp@latest install)");
|
|
1227
1227
|
} else {
|
|
1228
1228
|
console.log(" ⬜ iOS — unavailable (install Xcode + simulator runtime)");
|
|
1229
1229
|
}
|
|
@@ -1256,8 +1256,8 @@ switch (command) {
|
|
|
1256
1256
|
|
|
1257
1257
|
console.log(`\n Home: ${tappHome}`);
|
|
1258
1258
|
console.log(healthy
|
|
1259
|
-
? "\nReady. Start with:\n npx -y @aarwitz/tapp open [target]\n npx -y @aarwitz/tapp explore [target]"
|
|
1260
|
-
: "\nFix the ❌ items above, then re-run: tapp doctor");
|
|
1259
|
+
? "\nReady. Start with:\n npx -y @aarwitz/tapp@latest open [target]\n npx -y @aarwitz/tapp@latest explore [target]"
|
|
1260
|
+
: "\nFix the ❌ items above, then re-run: npx -y @aarwitz/tapp@latest doctor");
|
|
1261
1261
|
process.exit(healthy ? 0 : 1);
|
|
1262
1262
|
}
|
|
1263
1263
|
|
|
@@ -1332,7 +1332,7 @@ switch (command) {
|
|
|
1332
1332
|
console.log(`✅ Actor '${name}' configured — ${result.actor.session} session · ${result.actor.provisioning} provisioning`);
|
|
1333
1333
|
console.log(` ${result.path}`);
|
|
1334
1334
|
console.log(` bindings: ${Object.entries(result.actor.credentials).map(([key, binding]) => `${key}=$${binding.env}`).join(", ") || "none"}`);
|
|
1335
|
-
console.log(" No credential values were accepted or written. Rerun tapp init --refresh to update the application model.");
|
|
1335
|
+
console.log(" No credential values were accepted or written. Rerun npx -y @aarwitz/tapp@latest init --refresh to update the application model.");
|
|
1336
1336
|
} catch (error) { console.error(`❌ Actor not configured: ${error.message || String(error)}`); process.exit(2); }
|
|
1337
1337
|
break;
|
|
1338
1338
|
}
|
|
@@ -1347,7 +1347,7 @@ switch (command) {
|
|
|
1347
1347
|
const projectDir = fs.realpathSync(path.resolve(positionals[1] || (typeof flags["project-dir"] === "string" ? flags["project-dir"] : process.cwd())));
|
|
1348
1348
|
const modelPath = typeof flags.model === "string" ? path.resolve(projectDir, flags.model) : existingProjectArtifactPath(projectDir, "application-model.json");
|
|
1349
1349
|
if (!modelPath.startsWith(projectDir + path.sep) || !fs.existsSync(modelPath)) {
|
|
1350
|
-
console.error(`❌ Application model not found inside the repository: ${modelPath}\n Run tapp init --explore, review/generate/validate/promote the plan, then create the baseline.`);
|
|
1350
|
+
console.error(`❌ Application model not found inside the repository: ${modelPath}\n Run npx -y @aarwitz/tapp@latest init --explore, review/generate/validate/promote the plan, then create the baseline.`);
|
|
1351
1351
|
process.exit(2);
|
|
1352
1352
|
}
|
|
1353
1353
|
let model;
|
|
@@ -1454,7 +1454,7 @@ switch (command) {
|
|
|
1454
1454
|
catch { console.error(`❌ Repository directory not found: ${positionals[0] || flags["project-dir"] || process.cwd()}`); process.exit(2); }
|
|
1455
1455
|
const modelPath = typeof flags.model === "string" ? path.resolve(projectDir, flags.model) : existingProjectArtifactPath(projectDir, "application-model.json");
|
|
1456
1456
|
if (!modelPath.startsWith(projectDir + path.sep) || !fs.existsSync(modelPath)) {
|
|
1457
|
-
console.error(`❌ Application model not found inside the repository: ${modelPath}\n Run tapp init --explore first.`);
|
|
1457
|
+
console.error(`❌ Application model not found inside the repository: ${modelPath}\n Run npx -y @aarwitz/tapp@latest init --explore first.`);
|
|
1458
1458
|
process.exit(2);
|
|
1459
1459
|
}
|
|
1460
1460
|
const actionRef = typeof flags["action-ref"] === "string" ? flags["action-ref"] : `aarwitz/tapp@v${pkg.version}`;
|
|
@@ -1575,7 +1575,7 @@ switch (command) {
|
|
|
1575
1575
|
|
|
1576
1576
|
Core — explore, prove, gate (agents and humans can just run these — no server, no setup):
|
|
1577
1577
|
tapp explore [target] Autonomous exploration → findings + evidence (an observation, NOT a
|
|
1578
|
-
release decision — run 'tapp ci' to gate a merge)
|
|
1578
|
+
release decision — run 'npx -y @aarwitz/tapp@latest ci' to gate a merge)
|
|
1579
1579
|
(web: --watch · all: --platform ios|android|web · --actions N)
|
|
1580
1580
|
tapp contract run FILE Replay a business-level release contract — the guarantees that must hold
|
|
1581
1581
|
tapp ci ... Merge-blocking release gate — explore + suites + baseline → pass/fail/inconclusive
|
|
@@ -1642,9 +1642,9 @@ Agent Skill (optional — so a short “Use Tapp to test this app” prompt is e
|
|
|
1642
1642
|
claude plugin install tapp@tapp
|
|
1643
1643
|
|
|
1644
1644
|
MCP hookup (optional — for inline screenshots and the tap/type/inspect session loop):
|
|
1645
|
-
Claude Code: claude mcp add tapp -- npx -y @aarwitz/tapp mcp
|
|
1645
|
+
Claude Code: claude mcp add tapp -- npx -y @aarwitz/tapp@latest mcp
|
|
1646
1646
|
Cursor/VS Code (mcp.json):
|
|
1647
|
-
{ "servers": { "tapp": { "type": "stdio", "command": "npx", "args": ["-y", "@aarwitz/tapp", "mcp"] } } }
|
|
1647
|
+
{ "servers": { "tapp": { "type": "stdio", "command": "npx", "args": ["-y", "@aarwitz/tapp@latest", "mcp"] } } }
|
|
1648
1648
|
|
|
1649
1649
|
Then ask your agent things like:
|
|
1650
1650
|
"Explore com.mycompany.app and show me what breaks"
|
package/docs/BROWSER-PRODUCT.md
CHANGED
|
@@ -541,10 +541,10 @@ export async function inspectApplicationRepository({ projectDir, ownedUrl = "",
|
|
|
541
541
|
const requirements = [];
|
|
542
542
|
if (!targets.length) requirements.push({ id: "target", severity: "blocking", status: "missing", message: "No iOS application project, Android application module, or browser application target was detected.", remediation: "Pass the representative target explicitly or add its unavoidable build/runtime configuration." });
|
|
543
543
|
for (const target of targets) {
|
|
544
|
-
if (target.platform === "ios" && target.status !== "configured") requirements.push({ id: `${target.id}:scheme`, severity: "blocking", status: "needs-confirmation", message: `Confirm a shared build scheme for ${target.name}.`, remediation: `Run tapp build ${target.sourcePath} or provide the scheme during init/build.` });
|
|
544
|
+
if (target.platform === "ios" && target.status !== "configured") requirements.push({ id: `${target.id}:scheme`, severity: "blocking", status: "needs-confirmation", message: `Confirm a shared build scheme for ${target.name}.`, remediation: `Run npx -y @aarwitz/tapp@latest build ${target.sourcePath} or provide the scheme during init/build.` });
|
|
545
545
|
if (target.platform === "android" && !target.runtime.applicationId) requirements.push({ id: `${target.id}:application-id`, severity: "blocking", status: "missing", message: `Android application id was not statically detected for ${target.name}.`, remediation: "Provide --app-id or expose applicationId in the application module." });
|
|
546
|
-
if (target.platform === "web" && !target.runtime.ownedUrl && target.runtime.management !== "tapp-managed") requirements.push({ id: `${target.id}:owned-url`, severity: "blocking", status: "missing", message: `No safe managed runtime or owned URL is available for ${target.name}.`, remediation: "Add a deterministic start/static target or start the app and rerun tapp init with --url http://127.0.0.1:<port>." });
|
|
547
|
-
if (target.platform === "web" && target.build.dependencyStatus === "missing-lockfile") requirements.push({ id: `${target.id}:dependency-lock`, severity: "blocking", status: "missing", message: `${target.name} declares browser dependencies without an observed dependency lockfile.`, remediation: "Commit the package-manager lockfile so Tapp can install dependencies reproducibly, then rerun tapp init." });
|
|
546
|
+
if (target.platform === "web" && !target.runtime.ownedUrl && target.runtime.management !== "tapp-managed") requirements.push({ id: `${target.id}:owned-url`, severity: "blocking", status: "missing", message: `No safe managed runtime or owned URL is available for ${target.name}.`, remediation: "Add a deterministic start/static target or start the app and rerun npx -y @aarwitz/tapp@latest init with --url http://127.0.0.1:<port>." });
|
|
547
|
+
if (target.platform === "web" && target.build.dependencyStatus === "missing-lockfile") requirements.push({ id: `${target.id}:dependency-lock`, severity: "blocking", status: "missing", message: `${target.name} declares browser dependencies without an observed dependency lockfile.`, remediation: "Commit the package-manager lockfile so Tapp can install dependencies reproducibly, then rerun npx -y @aarwitz/tapp@latest init." });
|
|
548
548
|
}
|
|
549
549
|
const incompleteMaps = uiMaps.filter((record) => record.summary.status !== "observed");
|
|
550
550
|
for (const record of incompleteMaps.length ? incompleteMaps : (!targets.length && uiMap.status !== "observed" ? [{ summary: uiMap }] : [])) {
|
|
@@ -553,7 +553,7 @@ export async function inspectApplicationRepository({ projectDir, ownedUrl = "",
|
|
|
553
553
|
requirements.push({
|
|
554
554
|
id: targets.length <= 1 ? "ui-map" : `${target.id}:ui-map`, severity: "blocking", status: summary.status === "inconclusive" ? "inconclusive" : "missing",
|
|
555
555
|
message: target ? (summary.status === "inconclusive" ? `The UI Map for ${target.name} is inconclusive.` : `No grounded UI Map exists for ${target.name}.`) : "No repository UI Map has been grounded in a real run.",
|
|
556
|
-
remediation: target ? `Build/launch ${target.name}, explore the real target, and retain its map at ${summary.expectedPath || summary.path}.` : "Build/launch the target and run tapp init --explore so real exploration evidence is merged into .tapp/ui-map.json.",
|
|
556
|
+
remediation: target ? `Build/launch ${target.name}, explore the real target, and retain its map at ${summary.expectedPath || summary.path}.` : "Build/launch the target and run npx -y @aarwitz/tapp@latest init --explore so real exploration evidence is merged into .tapp/ui-map.json.",
|
|
557
557
|
});
|
|
558
558
|
}
|
|
559
559
|
if (!contracts.length) requirements.push({ id: "contracts", severity: "warning", status: "missing", message: "No reviewed release contracts exist yet.", remediation: "Review the proposed release plan, then generate and validate a compact set of contracts." });
|
|
@@ -561,7 +561,7 @@ export async function inspectApplicationRepository({ projectDir, ownedUrl = "",
|
|
|
561
561
|
for (const error of projectConfiguration.errors) requirements.push({ id: stableId("project-config-error", error), severity: "blocking", status: "invalid", message: error, remediation: `Fix ${projectConfiguration.relativePath}; actor configuration must contain environment-variable bindings, never credential values.` });
|
|
562
562
|
for (const actor of actors) {
|
|
563
563
|
const missingBindings = actor.credentialRequirements.filter((credential) => !actor.credentialBindings[credential]);
|
|
564
|
-
if (missingBindings.length) requirements.push({ id: `actor:${actor.name}:credential-bindings`, severity: "blocking", status: "missing", message: `Actor '${actor.name}' has unbound credential requirements: ${missingBindings.join(", ")}.`, remediation: `Run tapp actor set ${actor.name} --credential <name>=<ENV_NAME> for each credential, then replace any literal contract credentials with $ENV_NAME placeholders.` });
|
|
564
|
+
if (missingBindings.length) requirements.push({ id: `actor:${actor.name}:credential-bindings`, severity: "blocking", status: "missing", message: `Actor '${actor.name}' has unbound credential requirements: ${missingBindings.join(", ")}.`, remediation: `Run npx -y @aarwitz/tapp@latest actor set ${actor.name} --credential <name>=<ENV_NAME> for each credential, then replace any literal contract credentials with $ENV_NAME placeholders.` });
|
|
565
565
|
if (actor.bindingConflicts.length) requirements.push({ id: `actor:${actor.name}:credential-conflicts`, severity: "blocking", status: "conflict", message: `Actor '${actor.name}' has conflicting credential environment bindings.`, remediation: `Align ${projectConfiguration.relativePath} and reviewed contracts; Tapp will not guess which secret binding is correct.` });
|
|
566
566
|
}
|
|
567
567
|
for (const error of taskErrors) requirements.push({ id: stableId("task-error", error.path), severity: "blocking", status: "invalid", message: error.error, remediation: `Fix ${error.path} before generation.` });
|
|
@@ -997,7 +997,7 @@ function edgeSupportsPlatform(edge, nodes, platform) {
|
|
|
997
997
|
function shortestObservedPath(map, platform, targetId, { startNodeId } = {}) {
|
|
998
998
|
const nodes = new Map((map.nodes || []).map((node) => [node.id, node]));
|
|
999
999
|
const entryId = startNodeId || map.app?.navigationRoots?.[platform] || map.app?.entryNodes?.[platform];
|
|
1000
|
-
if (!entryId || !nodes.has(entryId)) throw new Error(`UI Map has no observed ${platform} navigation root; rerun tapp init --refresh --explore before generating Tasks`);
|
|
1000
|
+
if (!entryId || !nodes.has(entryId)) throw new Error(`UI Map has no observed ${platform} navigation root; rerun npx -y @aarwitz/tapp@latest init --refresh --explore before generating Tasks`);
|
|
1001
1001
|
if (!nodes.has(targetId)) throw new Error(`UI Map proposal target '${targetId}' is no longer present`);
|
|
1002
1002
|
if (entryId === targetId) return [];
|
|
1003
1003
|
const outgoing = new Map();
|
|
@@ -422,10 +422,10 @@ function renderMarkdown(report, regression, flows, scenarios, contracts, prPlan,
|
|
|
422
422
|
? ` at \`${target.navigation.route}\``
|
|
423
423
|
: target.navigation?.mode === "ui-map-path" ? ` through ${(target.navigation.steps || []).length} observed map edge(s)` : "";
|
|
424
424
|
lines.push(`- ${icon} PR exploration **${target.node?.name || target.id}** — ${target.execution?.status || target.status}${navigation}`);
|
|
425
|
-
if (target.coverageProposal?.status === "awaiting-explicit-adoption") lines.push(` - Reviewable coverage proposal ready; never auto-applied. Next: \`tapp pr adopt <pr-plan.json> --item ${target.id} --project-dir .\``);
|
|
425
|
+
if (target.coverageProposal?.status === "awaiting-explicit-adoption") lines.push(` - Reviewable coverage proposal ready; never auto-applied. Next: \`npx -y @aarwitz/tapp@latest pr adopt <pr-plan.json> --item ${target.id} --project-dir .\``);
|
|
426
426
|
if (target.existingReleasePlanItem) {
|
|
427
427
|
lines.push(` - Existing release-plan item \`${target.existingReleasePlanItem.name}\` remains ${target.existingReleasePlanItem.decision}; no duplicate or decision change was made.`);
|
|
428
|
-
lines.push(` - Optional explicit evidence reconciliation: \`tapp pr adopt <pr-plan.json> --item ${target.id} --project-dir .\``);
|
|
428
|
+
lines.push(` - Optional explicit evidence reconciliation: \`npx -y @aarwitz/tapp@latest pr adopt <pr-plan.json> --item ${target.id} --project-dir .\``);
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
431
|
for (const candidate of prPlan.maintenanceCandidates || []) {
|
|
@@ -17,7 +17,7 @@ export function targetSlug(value) {
|
|
|
17
17
|
|
|
18
18
|
export function selectApplicationTarget(model, { platform = "", target = "", useDefault = false } = {}) {
|
|
19
19
|
if (model?.kind !== "tapp-application-model" || !Array.isArray(model.targets)) {
|
|
20
|
-
throw new Error("Expected a Tapp application model; run tapp init first");
|
|
20
|
+
throw new Error("Expected a Tapp application model; run npx -y @aarwitz/tapp@latest init first");
|
|
21
21
|
}
|
|
22
22
|
const selectedPlatform = String(platform || "").toLowerCase();
|
|
23
23
|
let candidates = model.targets.filter((item) => !selectedPlatform || item.platform === selectedPlatform);
|
|
@@ -215,7 +215,7 @@ function credentialConfiguration(model, targetContracts = []) {
|
|
|
215
215
|
if (requirements.has("email")) bindings.add("TAPP_TEST_EMAIL");
|
|
216
216
|
if (requirements.has("password")) bindings.add("TAPP_TEST_PASSWORD");
|
|
217
217
|
}
|
|
218
|
-
for (const name of bindings) if (!/^[A-Z_][A-Z0-9_]{0,127}$/.test(String(name))) throw new Error(`Actor credential binding '${name}' is not a safe environment-variable name; rerun tapp init from valid .tapp/project.json`);
|
|
218
|
+
for (const name of bindings) if (!/^[A-Z_][A-Z0-9_]{0,127}$/.test(String(name))) throw new Error(`Actor credential binding '${name}' is not a safe environment-variable name; rerun npx -y @aarwitz/tapp@latest init from valid .tapp/project.json`);
|
|
219
219
|
const inputs = {};
|
|
220
220
|
const primaryEmail = primary.credentialBindings?.email || (!primary.credentialBindings && requirements.has("email") ? "TAPP_TEST_EMAIL" : "");
|
|
221
221
|
const primaryPassword = primary.credentialBindings?.password || (!primary.credentialBindings && requirements.has("password") ? "TAPP_TEST_PASSWORD" : "");
|
|
@@ -241,7 +241,7 @@ function targetInputs(root, model, target) {
|
|
|
241
241
|
const androidProjectPath = path.resolve(root, androidProject);
|
|
242
242
|
const androidProjectRelative = path.relative(root, androidProjectPath);
|
|
243
243
|
if (path.isAbsolute(androidProjectRelative) || androidProjectRelative === ".." || androidProjectRelative.startsWith(`..${path.sep}`)) unresolved.push("Android Gradle project resolves outside the repository");
|
|
244
|
-
else if (!fs.existsSync(path.join(androidProjectPath, "gradlew"))) unresolved.push(`Gradle wrapper is missing from ${androidProject}; commit gradlew or rerun tapp init from the Gradle repository root`);
|
|
244
|
+
else if (!fs.existsSync(path.join(androidProjectPath, "gradlew"))) unresolved.push(`Gradle wrapper is missing from ${androidProject}; commit gradlew or rerun npx -y @aarwitz/tapp@latest init from the Gradle repository root`);
|
|
245
245
|
inputs["android-project"] = androidProject;
|
|
246
246
|
inputs["android-task"] = target.build?.task || "assembleDebug";
|
|
247
247
|
if (target.runtime?.applicationId) inputs["android-app-id"] = target.runtime.applicationId;
|
|
@@ -282,7 +282,7 @@ export function renderGithubWorkflow({ projectDir, model, actionRef, defaultBran
|
|
|
282
282
|
const root = fs.realpathSync(path.resolve(projectDir || process.cwd()));
|
|
283
283
|
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+@[A-Za-z0-9_.-]+$/.test(String(actionRef || ""))) throw new Error("--action-ref must be owner/repository@release-tag-or-sha");
|
|
284
284
|
if (!/^[A-Za-z0-9._\/-]+$/.test(defaultBranch) || defaultBranch.startsWith("/")) throw new Error("--default-branch contains unsupported characters");
|
|
285
|
-
if (model?.kind !== "tapp-application-model" || !Array.isArray(model.targets) || !model.targets.length) throw new Error("Application model has no targets; run tapp init first");
|
|
285
|
+
if (model?.kind !== "tapp-application-model" || !Array.isArray(model.targets) || !model.targets.length) throw new Error("Application model has no targets; run npx -y @aarwitz/tapp@latest init first");
|
|
286
286
|
const occupied = new Set();
|
|
287
287
|
const jobs = [];
|
|
288
288
|
const manifestTargets = [];
|
package/mcp-server/src/index.js
CHANGED
|
@@ -1335,9 +1335,9 @@ function fmtDuration(ms) {
|
|
|
1335
1335
|
export function qaNextSteps(report, surface = "mcp") {
|
|
1336
1336
|
if (surface === "cli") {
|
|
1337
1337
|
const next = [];
|
|
1338
|
-
if (report?.findings?.length) next.push("inspect the evidence with `tapp report latest`");
|
|
1339
|
-
next.push("re-run with `--baseline <report.json>` to compare a fix (then `tapp ci` to gate it)");
|
|
1340
|
-
next.push("replay a committed journey with `tapp flow run <file>`");
|
|
1338
|
+
if (report?.findings?.length) next.push("inspect the evidence with `npx -y @aarwitz/tapp@latest report latest`");
|
|
1339
|
+
next.push("re-run with `--baseline <report.json>` to compare a fix (then `npx -y @aarwitz/tapp@latest ci` to gate it)");
|
|
1340
|
+
next.push("replay a committed journey with `npx -y @aarwitz/tapp@latest flow run <file>`");
|
|
1341
1341
|
return next;
|
|
1342
1342
|
}
|
|
1343
1343
|
const next = [];
|
|
@@ -1390,7 +1390,7 @@ function formatQaReport(report, { regression, inputHint, timedOut, bundleId, aiC
|
|
|
1390
1390
|
// The merge decision is the gate's job (tapp ci), not exploration's (ADR-0005).
|
|
1391
1391
|
L.push("");
|
|
1392
1392
|
L.push(
|
|
1393
|
-
`**Since last run** — +${regression.counts.new} new · ${regression.counts.persisting} persisting · ${regression.counts.resolved} resolved (comparison only — run \`tapp ci\` to gate)`
|
|
1393
|
+
`**Since last run** — +${regression.counts.new} new · ${regression.counts.persisting} persisting · ${regression.counts.resolved} resolved (comparison only — run \`npx -y @aarwitz/tapp@latest ci\` to gate)`
|
|
1394
1394
|
);
|
|
1395
1395
|
}
|
|
1396
1396
|
if (inputHint) {
|
|
@@ -1801,7 +1801,7 @@ export async function runExploreTarget({
|
|
|
1801
1801
|
|
|
1802
1802
|
const modelPath = existingProjectArtifactPath(root, "application-model.json");
|
|
1803
1803
|
if (!fs.existsSync(modelPath)) {
|
|
1804
|
-
return { error: "No application model found — run `tapp init` first, or pass an explicit target (a bundle id, a path/to/App.app, a repo dir, --app-id/--apk, or an http(s) URL)." };
|
|
1804
|
+
return { error: "No application model found — call `tapp_init` or run `npx -y @aarwitz/tapp@latest init` first, or pass an explicit target (a bundle id, a path/to/App.app, a repo dir, --app-id/--apk, or an http(s) URL)." };
|
|
1805
1805
|
}
|
|
1806
1806
|
let model;
|
|
1807
1807
|
try { model = JSON.parse(fs.readFileSync(modelPath, "utf8")); }
|
|
@@ -1839,7 +1839,7 @@ export async function runExploreTarget({
|
|
|
1839
1839
|
|
|
1840
1840
|
if (selectedPlatform === "android") {
|
|
1841
1841
|
const appId = String(selected.runtime?.applicationId || "").trim();
|
|
1842
|
-
if (!appId) return { error: `The Android target '${selected.name}' has no confirmed application id — confirm it and rerun \`tapp init\`, or pass --app-id.` };
|
|
1842
|
+
if (!appId) return { error: `The Android target '${selected.name}' has no confirmed application id — confirm it and call \`tapp_init\` or rerun \`npx -y @aarwitz/tapp@latest init\`, or pass --app-id.` };
|
|
1843
1843
|
const task = selected.build?.task || "assembleDebug";
|
|
1844
1844
|
onStatus(`Building the Android APK (${task})…`);
|
|
1845
1845
|
const built = await buildAndroidApp({
|
|
@@ -2322,7 +2322,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
2322
2322
|
"Findings from a previous run (pass back the `findings` array a prior tapp_explore returned). " +
|
|
2323
2323
|
"When provided, the result adds a `regression` COMPARISON {counts:{new,persisting,resolved}, " +
|
|
2324
2324
|
"newFindings, resolved} vs. that baseline — an observation, not a gate signal. To gate a merge, " +
|
|
2325
|
-
"run `tapp ci` (or the GitHub Action): it applies the deterministic policy and returns the " +
|
|
2325
|
+
"run `npx -y @aarwitz/tapp@latest ci` (or the GitHub Action): it applies the deterministic policy and returns the " +
|
|
2326
2326
|
"pass/fail/inconclusive outcome.",
|
|
2327
2327
|
},
|
|
2328
2328
|
},
|
|
@@ -2858,7 +2858,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2858
2858
|
const icon = c.ok ? "✅" : c.required ? "❌" : "⚪️";
|
|
2859
2859
|
L.push(`- ${icon} **${c.check}** — ${String(c.value).split("\n")[0] || "—"}`);
|
|
2860
2860
|
}
|
|
2861
|
-
if (!ready) L.push("", "Run `tapp doctor` in the application repository for exact remediation.");
|
|
2861
|
+
if (!ready) L.push("", "Run `npx -y @aarwitz/tapp@latest doctor` in the application repository for exact remediation.");
|
|
2862
2862
|
return richResult(L.join("\n"), { ok: ready, workspaceRoot, checks, platforms: Object.fromEntries(platformChecks.map((check) => [check.check.toLowerCase(), check.ok])) });
|
|
2863
2863
|
}
|
|
2864
2864
|
|
|
@@ -410,7 +410,7 @@ export function adoptPrCoverageProposal({ projectDir, prPlanPath, item, releaseP
|
|
|
410
410
|
if (!prPlanPath || !fs.existsSync(source)) throw new Error(`PR plan not found: ${source || "(missing path)"}`);
|
|
411
411
|
const targetPath = releasePlanPath === ".tapp/release-plan.json" ? existingProjectArtifactPath(root, "release-plan.json") : path.resolve(root, releasePlanPath);
|
|
412
412
|
if (!inside(root, targetPath)) throw new Error("Release plan path must stay inside the project directory");
|
|
413
|
-
if (!fs.existsSync(targetPath)) throw new Error(`Release plan not found: ${targetPath}; run tapp init first`);
|
|
413
|
+
if (!fs.existsSync(targetPath)) throw new Error(`Release plan not found: ${targetPath}; run npx -y @aarwitz/tapp@latest init first`);
|
|
414
414
|
const prPlan = JSON.parse(fs.readFileSync(source, "utf8"));
|
|
415
415
|
if (prPlan?.schemaVersion !== 1 || !Array.isArray(prPlan.explorationTargets)) throw new Error("PR plan must be an executed Tapp PR plan v1");
|
|
416
416
|
const matches = prPlan.explorationTargets.filter((target) => target.id === item);
|
|
@@ -428,7 +428,7 @@ export function adoptPrCoverageProposal({ projectDir, prPlanPath, item, releaseP
|
|
|
428
428
|
if (!ground || !fs.existsSync(mapPath)) throw new Error("Coverage proposal requires the repository's persistent UI Map");
|
|
429
429
|
const map = JSON.parse(fs.readFileSync(mapPath, "utf8"));
|
|
430
430
|
const node = (map.nodes || []).find((candidate) => candidate.id === ground.id && candidate.status !== "proposed");
|
|
431
|
-
const refreshAdvice = "refresh the persistent UI Map with `tapp init --explore --refresh`, rerun `tapp pr gate`, then retry `tapp pr adopt`";
|
|
431
|
+
const refreshAdvice = "refresh the persistent UI Map with `npx -y @aarwitz/tapp@latest init --explore --refresh`, rerun `npx -y @aarwitz/tapp@latest pr gate`, then retry `npx -y @aarwitz/tapp@latest pr adopt`";
|
|
432
432
|
if (!node) throw new Error(`Coverage proposal UI Map node is stale or missing: ${ground.id}; ${refreshAdvice}`);
|
|
433
433
|
if (target.navigation?.route && !(node.routes || []).some((route) => route.platform === target.platform && route.path === target.navigation.route && route.replayable === true)) {
|
|
434
434
|
throw new Error(`Coverage proposal route is stale in the persistent UI Map: ${target.navigation.route}; ${refreshAdvice}`);
|
|
@@ -79,7 +79,7 @@ function initTargetChoices(model, platform = "") {
|
|
|
79
79
|
const candidates = (model.targets || []).filter((target) => !platform || target.platform === platform);
|
|
80
80
|
return candidates.map((target) => ({
|
|
81
81
|
target,
|
|
82
|
-
command: `tapp init . --explore --platform ${target.platform} --target ${target.sourcePath === "." ? JSON.stringify(target.name) : JSON.stringify(target.sourcePath)}`,
|
|
82
|
+
command: `npx -y @aarwitz/tapp@latest init . --explore --platform ${target.platform} --target ${target.sourcePath === "." ? JSON.stringify(target.name) : JSON.stringify(target.sourcePath)}`,
|
|
83
83
|
}));
|
|
84
84
|
}
|
|
85
85
|
|
package/package.json
CHANGED
package/skills/tapp/SKILL.md
CHANGED
|
@@ -20,21 +20,22 @@ screen or journey works from source inspection alone.
|
|
|
20
20
|
| Decide whether a merge passes policy | `ci`; exploration never decides this |
|
|
21
21
|
|
|
22
22
|
Prefer connected `tapp_*` MCP tools when available: they keep interactive sessions alive and return
|
|
23
|
-
screenshots inline. Otherwise run `npx -y @aarwitz/tapp` from the app repository. Do not require MCP,
|
|
23
|
+
screenshots inline. Otherwise run `npx -y @aarwitz/tapp@latest` from the app repository. Do not require MCP,
|
|
24
24
|
an account, an API key, or a global install for the core workflow.
|
|
25
25
|
|
|
26
26
|
## Start source-connected
|
|
27
27
|
|
|
28
28
|
When the user asks for a general first test of a repository:
|
|
29
29
|
|
|
30
|
-
1. If `.tapp/application-model.json` exists, run `npx -y @aarwitz/tapp explore`.
|
|
31
|
-
2. Otherwise run `npx -y @aarwitz/tapp init . --explore` or call `tapp_init` with
|
|
30
|
+
1. If `.tapp/application-model.json` exists, run `npx -y @aarwitz/tapp@latest explore`.
|
|
31
|
+
2. Otherwise run `npx -y @aarwitz/tapp@latest init . --explore` or call `tapp_init` with
|
|
32
32
|
`{operation:"explore", projectDir:"."}`.
|
|
33
33
|
3. If Tapp returns `target-selection-required`, present its actual choices and ask the user to pick.
|
|
34
34
|
Never guess among multiple targets. Re-run with the selected platform/target exactly as Tapp
|
|
35
35
|
instructs.
|
|
36
|
-
4. If a prerequisite is missing,
|
|
37
|
-
scope, and
|
|
36
|
+
4. If a prerequisite is missing, call `tapp_health` when MCP is connected or run
|
|
37
|
+
`npx -y @aarwitz/tapp@latest doctor`, apply only the stated remediation that is in scope, and
|
|
38
|
+
retry once.
|
|
38
39
|
|
|
39
40
|
For a focused request, use the requested target directly rather than forcing repository onboarding.
|
|
40
41
|
Targets may be a repository path, Xcode container, `.app`, iOS bundle id, APK plus Android app id,
|
|
@@ -6,34 +6,34 @@ Run from the application repository. `[target]` is optional when Tapp can read t
|
|
|
6
6
|
or detect one unambiguous target.
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
npx -y @aarwitz/tapp init . --explore
|
|
10
|
-
npx -y @aarwitz/tapp explore [target]
|
|
11
|
-
npx -y @aarwitz/tapp open [target]
|
|
12
|
-
npx -y @aarwitz/tapp tree [target] --json
|
|
13
|
-
npx -y @aarwitz/tapp shot
|
|
14
|
-
npx -y @aarwitz/tapp report latest
|
|
15
|
-
npx -y @aarwitz/tapp doctor
|
|
9
|
+
npx -y @aarwitz/tapp@latest init . --explore
|
|
10
|
+
npx -y @aarwitz/tapp@latest explore [target]
|
|
11
|
+
npx -y @aarwitz/tapp@latest open [target]
|
|
12
|
+
npx -y @aarwitz/tapp@latest tree [target] --json
|
|
13
|
+
npx -y @aarwitz/tapp@latest shot
|
|
14
|
+
npx -y @aarwitz/tapp@latest report latest
|
|
15
|
+
npx -y @aarwitz/tapp@latest doctor
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
Platform examples:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
21
|
# Web: Tapp may build/start/stop a source target; --watch is human-visible.
|
|
22
|
-
npx -y @aarwitz/tapp explore --platform web --target website --watch
|
|
23
|
-
npx -y @aarwitz/tapp explore https://staging.example.com
|
|
22
|
+
npx -y @aarwitz/tapp@latest explore --platform web --target website --watch
|
|
23
|
+
npx -y @aarwitz/tapp@latest explore https://staging.example.com
|
|
24
24
|
|
|
25
25
|
# iOS: source repo, .app, or bundle id.
|
|
26
|
-
npx -y @aarwitz/tapp explore MyApp.xcodeproj --platform ios
|
|
27
|
-
npx -y @aarwitz/tapp open com.example.MyApp --platform ios
|
|
26
|
+
npx -y @aarwitz/tapp@latest explore MyApp.xcodeproj --platform ios
|
|
27
|
+
npx -y @aarwitz/tapp@latest open com.example.MyApp --platform ios
|
|
28
28
|
|
|
29
29
|
# Android: app id is required; APK is optional if already installed.
|
|
30
|
-
npx -y @aarwitz/tapp explore app-debug.apk --platform android --app-id com.example.app
|
|
30
|
+
npx -y @aarwitz/tapp@latest explore app-debug.apk --platform android --app-id com.example.app
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
Focused web evidence can perform one semantic interaction and wait for async content:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
npx -y @aarwitz/tapp open https://example.com --tap "Not now" --wait-for "Dashboard"
|
|
36
|
+
npx -y @aarwitz/tapp@latest open https://example.com --tap "Not now" --wait-for "Dashboard"
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
## MCP mapping
|
|
@@ -84,8 +84,8 @@ ask the user rather than pretending the explored surface was complete.
|
|
|
84
84
|
Flow YAML belongs under `.tapp/flows/` and can replay without a model or API key:
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
|
-
npx -y @aarwitz/tapp flow run .tapp/flows/smoke.yml
|
|
88
|
-
npx -y @aarwitz/tapp ci
|
|
87
|
+
npx -y @aarwitz/tapp@latest flow run .tapp/flows/smoke.yml
|
|
88
|
+
npx -y @aarwitz/tapp@latest ci
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
`tapp explore` observes. `tapp ci` applies versioned deterministic policy to evidence, selected
|