@aarwitz/tapp 0.15.0 → 0.16.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 (48) hide show
  1. package/AGENTS.md +4 -4
  2. package/Harness/OCQAHarness/AppDelegate.swift +1 -1
  3. package/Harness/OCQAHarness.xcodeproj/project.pbxproj +2 -2
  4. package/Harness/OCQAHarnessUITests/ExplorerTests.swift +2 -2
  5. package/Harness/generate-harness-xcodeproj.rb +3 -3
  6. package/README.md +17 -15
  7. package/bin/tapp.js +28 -28
  8. package/browser/app.js +2 -2
  9. package/browser/index.html +1 -1
  10. package/docs/BROWSER-PRODUCT.md +2 -2
  11. package/docs/PRODUCT-ENGINE.md +7 -6
  12. package/docs/application-model.md +20 -25
  13. package/docs/scenarios.md +7 -7
  14. package/mcp-server/src/application-model.js +32 -26
  15. package/mcp-server/src/browser-product.js +1 -1
  16. package/mcp-server/src/ci-report.js +5 -4
  17. package/mcp-server/src/ci-setup.js +16 -7
  18. package/mcp-server/src/enrich.js +1 -1
  19. package/mcp-server/src/index.js +63 -62
  20. package/mcp-server/src/maintenance-proposal.js +4 -4
  21. package/mcp-server/src/pr-selection.js +9 -8
  22. package/mcp-server/src/product-execution.js +1 -1
  23. package/mcp-server/src/product-operations.js +20 -18
  24. package/mcp-server/src/project-config.js +8 -5
  25. package/mcp-server/src/project-paths.js +32 -0
  26. package/mcp-server/src/report.js +3 -3
  27. package/mcp-server/src/task-runtime.js +14 -10
  28. package/package.json +15 -3
  29. package/scripts/ci-gate.sh +15 -10
  30. package/scripts/flow_ai_judge.py +1 -1
  31. package/scripts/flow_lib.py +1 -1
  32. package/scripts/quick-capture.sh +15 -14
  33. package/scripts/run-flow.sh +6 -6
  34. package/scripts/android-corpus-e2e.sh +0 -30
  35. package/scripts/cleanup-xcode.sh +0 -157
  36. package/scripts/corpus-apps.txt +0 -9
  37. package/scripts/corpus-sweep.sh +0 -121
  38. package/scripts/coverage-eval.sh +0 -92
  39. package/scripts/coverage_eval_parse.py +0 -95
  40. package/scripts/deploy-and-build.sh +0 -99
  41. package/scripts/mutation-recall-desktop.sh +0 -186
  42. package/scripts/mutation-recall.sh +0 -121
  43. package/scripts/mutation_lib.py +0 -128
  44. package/scripts/mutation_operators.py +0 -144
  45. package/scripts/validation-matrix.sh +0 -146
  46. package/scripts/vision-fp-eval.sh +0 -206
  47. package/scripts/vision_escalation_responder.py +0 -147
  48. package/scripts/vision_fp_probe.py +0 -221
package/AGENTS.md CHANGED
@@ -18,7 +18,7 @@ npx -y @aarwitz/tapp shot # screenshot the booted sim → file path (
18
18
  npx -y @aarwitz/tapp apps # what's installed on the simulator, with bundle ids
19
19
  npx -y @aarwitz/tapp build [dir] # build the app in an Xcode repo + install it (≈ tapp_build)
20
20
  npx -y @aarwitz/tapp qa app.apk --platform android --app-id com.acme.app
21
- npx -y @aarwitz/tapp flow run .autotap/flows/smoke.yml # committed, keyless E2E replay
21
+ npx -y @aarwitz/tapp flow run .tapp/flows/smoke.yml # committed, keyless E2E replay
22
22
  ```
23
23
 
24
24
  **Seeing the screen, per client:** if you can read image files into your context (Claude
@@ -92,13 +92,13 @@ Returns `{verdict, confidence, headline, screensExplored, actionsPerformed, find
92
92
 
93
93
  ## Flows (deterministic E2E tests)
94
94
 
95
- Flow YAML is repository-native test code. Commit it under `.autotap/flows/`; CI can replay it
95
+ Flow YAML is repository-native test code. Commit it under `.tapp/flows/`; CI can replay it
96
96
  without a coding agent, model, subscription, or API key. AI generation and `assert_ai` are optional.
97
97
 
98
98
  - **Record:** every successful `session_act` is recorded. After driving a flow, call
99
- `tapp_flow_save { name: "checkout" }` → writes `.autotap/flows/checkout.yml` with waits and
99
+ `tapp_flow_save { name: "checkout" }` → writes `.tapp/flows/checkout.yml` with waits and
100
100
  a final screen assertion auto-inserted; typed credentials are templated to `$TEST_EMAIL`/`$TEST_PASSWORD`.
101
- - **Replay:** `tapp_flow_run { flowPath: ".autotap/flows/checkout.yml" }` — exact steps,
101
+ - **Replay:** `tapp_flow_run { flowPath: ".tapp/flows/checkout.yml" }` — exact steps,
102
102
  deterministic assertions, same result every time. A failed assertion is a finding.
103
103
  - **Generate:** `tapp_flow_generate { goal: "log in and add the first item to cart" }` —
104
104
  grounded in the app's actually-explored screens, so it can't invent steps.
@@ -8,7 +8,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
8
8
  let vc = UIViewController()
9
9
  vc.view.backgroundColor = .black
10
10
  let label = UILabel()
11
- label.text = "AutoTap Harness"
11
+ label.text = "Tapp Harness"
12
12
  label.textColor = .white
13
13
  label.textAlignment = .center
14
14
  label.frame = vc.view.bounds
@@ -166,7 +166,7 @@
166
166
  CODE_SIGN_STYLE = Automatic;
167
167
  DEVELOPMENT_TEAM = "";
168
168
  INFOPLIST_FILE = OCQAHarness/Info.plist;
169
- PRODUCT_BUNDLE_IDENTIFIER = ai.autotap.harness;
169
+ PRODUCT_BUNDLE_IDENTIFIER = io.github.aarwitz.tapp.harness;
170
170
  PRODUCT_NAME = "$(TARGET_NAME)";
171
171
  SWIFT_VERSION = 5.0;
172
172
  TARGETED_DEVICE_FAMILY = "1,2";
@@ -186,7 +186,7 @@
186
186
  CODE_SIGN_STYLE = Automatic;
187
187
  DEVELOPMENT_TEAM = "";
188
188
  INFOPLIST_FILE = OCQAHarnessUITests/Info.plist;
189
- PRODUCT_BUNDLE_IDENTIFIER = ai.autotap.harness.uitests;
189
+ PRODUCT_BUNDLE_IDENTIFIER = io.github.aarwitz.tapp.harness.uitests;
190
190
  PRODUCT_NAME = "$(TARGET_NAME)";
191
191
  SWIFT_VERSION = 5.0;
192
192
  TARGETED_DEVICE_FAMILY = "1,2";
@@ -670,7 +670,7 @@ class ExplorerTests: XCTestCase {
670
670
 
671
671
  /// Replays an explicit, config-driven login flow (OCQA_LOGIN_STEPS) before exploration — for the
672
672
  /// custom login UIs the heuristic preamble can't parse, which are the #1 reason a real app stays
673
- /// invisible to AutoTap. Steps are a JSON array of {action: type|tap|wait, target: <id-or-label>,
673
+ /// invisible to Tapp. Steps are a JSON array of {action: type|tap|wait, target: <id-or-label>,
674
674
  /// value?: <text; "$TEST_EMAIL"/"$TEST_PASSWORD" substituted from stored creds>, timeoutMs?}.
675
675
  /// Returns true if any step ran (so the caller skips the heuristic preamble). Tolerant: a failed
676
676
  /// step is logged but doesn't abort — exploration still proceeds, and the coverage eval reveals
@@ -1320,7 +1320,7 @@ class ExplorerTests: XCTestCase {
1320
1320
  // so it surfaces here at the next tree read rather than at the early crash check.
1321
1321
  // Report it against the action that led here instead of breaking silently. (Real:
1322
1322
  // Wikipedia's WMFCaptchaViewController.refreshImage assertionFailure crashes the
1323
- // app during login — a genuine production crash AutoTap must surface.)
1323
+ // app during login — a genuine production crash Tapp must surface.)
1324
1324
  if app.state != .runningForeground {
1325
1325
  let where_ = pendingTransitionFrom?.title ?? "the previous screen"
1326
1326
  let crashKey = "crash-async:\(where_)"
@@ -182,7 +182,7 @@ pbxproj = <<~PBX
182
182
  CODE_SIGN_STYLE = Automatic;
183
183
  DEVELOPMENT_TEAM = "";
184
184
  INFOPLIST_FILE = OCQAHarness/Info.plist;
185
- PRODUCT_BUNDLE_IDENTIFIER = ai.autotap.harness;
185
+ PRODUCT_BUNDLE_IDENTIFIER = io.github.aarwitz.tapp.harness;
186
186
  PRODUCT_NAME = "$(TARGET_NAME)";
187
187
  SWIFT_VERSION = 5.0;
188
188
  TARGETED_DEVICE_FAMILY = "1,2";
@@ -202,7 +202,7 @@ pbxproj = <<~PBX
202
202
  CODE_SIGN_STYLE = Automatic;
203
203
  DEVELOPMENT_TEAM = "";
204
204
  INFOPLIST_FILE = OCQAHarnessUITests/Info.plist;
205
- PRODUCT_BUNDLE_IDENTIFIER = ai.autotap.harness.uitests;
205
+ PRODUCT_BUNDLE_IDENTIFIER = io.github.aarwitz.tapp.harness.uitests;
206
206
  PRODUCT_NAME = "$(TARGET_NAME)";
207
207
  SWIFT_VERSION = 5.0;
208
208
  TARGETED_DEVICE_FAMILY = "1,2";
@@ -249,6 +249,6 @@ SCHEME
249
249
  File.write("#{scheme_dir}/OCQAHarnessUITests.xcscheme", scheme)
250
250
 
251
251
  puts "✅ Generated OCQAHarness.xcodeproj"
252
- puts " Host app: ai.autotap.harness"
252
+ puts " Host app: io.github.aarwitz.tapp.harness"
253
253
  puts " Test target: OCQAHarnessUITests"
254
254
  puts " Platform: iOS 16.0+"
package/README.md CHANGED
@@ -65,8 +65,7 @@ npx -y @aarwitz/tapp qa # finds your Xcode project → builds → installs on
65
65
  ```
66
66
 
67
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.
68
+ `@aarwitz/tapp`, while the installed command remains `tapp`.
70
69
 
71
70
  To bootstrap maintained release infrastructure, preview the repository model and grounded plan
72
71
  before Tapp writes anything:
@@ -91,9 +90,9 @@ npx -y @aarwitz/tapp ci install .
91
90
  ```
92
91
 
93
92
  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
93
+ conclusively. It stores `.tapp/baselines/<platform>/<target-id>.json`; the generated workflow
95
94
  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
95
+ install` writes `.github/workflows/tapp.yml` plus `.tapp/ci.json`, refuses unresolved build
97
96
  configuration and existing-file collisions, and never commits, pushes, enables branch protection,
98
97
  or creates GitHub resources. Review and pin the generated Tapp release reference to its immutable
99
98
  commit SHA before production.
@@ -183,8 +182,8 @@ Then ask your agent:
183
182
 
184
183
  Full agent playbook: [AGENTS.md](./AGENTS.md) — ships inside the package so agents can read it too.
185
184
  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
185
+ The desktop Coverage view reads the same `.tapp/application-model.json`,
186
+ `.tapp/release-plan.json`, and `.tapp/ui-map.json`, including explicit proposal review; it
188
187
  does not maintain a separate product model. Map nodes identify both the real launch entry and the
189
188
  deterministic per-platform navigation root used for bounded changed-surface replay.
190
189
 
@@ -272,8 +271,8 @@ The first successful, conclusive run on `main` seeds a repository-scoped Actions
272
271
  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
272
  high/critical findings or broken Flows—not pre-existing debt. No baseline commit or PAT is required.
274
273
  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
274
+ `.tapp/baselines/<platform>/<target-id>.json`; `tapp ci install` wires its explicit path into the
275
+ corresponding job. The legacy `.tapp/baseline.json` is still recognized. Automatic baseline restore and the PR comment need `actions: read` and
277
276
  `pull-requests: write` as shown above. Secrets are unavailable to workflows from forks, so
278
277
  auth-gated apps should either use a non-secret UI-testing launch argument or skip the gate for
279
278
  untrusted forks.
@@ -292,7 +291,7 @@ npx -y @aarwitz/tapp ci --app path/to/MyApp.app \
292
291
  ```
293
292
 
294
293
  See the self-test at
295
- [.github/workflows/autotap-gate-example.yml](.github/workflows/autotap-gate-example.yml) for
294
+ [.github/workflows/tapp-gate-example.yml](.github/workflows/tapp-gate-example.yml) for
296
295
  Flows, auth inputs, and other controls. GitHub-hosted iOS runs require a macOS runner; the first
297
296
  run also builds the XCUITest harness, so budget roughly 5–10 minutes depending on app size.
298
297
 
@@ -306,19 +305,19 @@ or accept a prebuilt one:
306
305
  android-app-id: com.acme.app
307
306
  android-project: android
308
307
  android-task: :app:assembleDebug
309
- flows: android/.autotap/flows/*.yml
308
+ flows: android/.tapp/flows/*.yml
310
309
  ```
311
310
 
312
311
  For web, pass `platform: web` plus `web-target:` and Tapp uses the application model to run its
313
312
  lockfile-backed install/build, start a detected package script or read-only static server, wait for
314
313
  readiness, gate it, and stop it even on failure. Pass `url:` instead for an already-running owned
315
314
  environment. Add
316
- `scenarios: .autotap/scenarios/*.yml` to gate isolated cross-account journeys; see
315
+ `scenarios: .tapp/scenarios/*.yml` to gate isolated cross-account journeys; see
317
316
  [`docs/scenarios.md`](docs/scenarios.md). Automatic
318
317
  baselines are isolated by platform and target, so two same-platform apps are never compared.
319
318
 
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
319
+ **The hosted service is under development and is not currently offered for customer repositories.**
320
+ Do not upload private code or credentials to an old preview. The retained
322
321
  cloud prototype is not the production SaaS boundary. Use the local Release Studio and the portable
323
322
  GitHub Action in infrastructure you control until the new account, tenant authorization, private
324
323
  evidence, and isolated-worker boundary passes security review.
@@ -352,8 +351,11 @@ verdict calculation run entirely locally — no telemetry, nothing phones home.
352
351
  features are explicit: finding enrichment requires `TAPP_ENABLE_REMOTE_AI=1` (an ambient
353
352
  API key alone never changes data handling), and AI flow generation / `assert_ai` only run
354
353
  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.
354
+ configured model provider. Runtime configuration uses `TAPP_*` environment variables. Repository
355
+ artifacts live under `.tapp/`, and desktop run configuration uses `.tapp.yml`. Existing
356
+ `.autotap/`, `.autotap.yml`, and `AUTOTAP_*` inputs remain readable as migration fallbacks; when
357
+ both names exist, Tapp uses the canonical one. Rename the old directory/config in your next
358
+ reviewed repository change—no secrets or evidence formats need to be rewritten.
357
359
 
358
360
  Committed Flow replay, recording a driven session, autonomous exploration, exact assertions,
359
361
  regression comparison, and CI gating require **no API key and no coding agent at runtime**. AI is
package/bin/tapp.js CHANGED
@@ -16,23 +16,23 @@
16
16
  //
17
17
  // All writable output (captures, harness build cache) goes to ~/.tapp (override
18
18
  // with TAPP_HOME). The package directory itself is never written to.
19
- // (Internally exported as AUTOTAP_HOME — the env name the bundled scripts read.)
20
19
 
21
20
  import { spawnSync, spawn } from "node:child_process";
22
21
  import fs from "node:fs";
23
22
  import os from "node:os";
24
23
  import path from "node:path";
25
24
  import { fileURLToPath } from "node:url";
25
+ import { existingProjectArtifactPath, isProjectArtifactDirectory } from "../mcp-server/src/project-paths.js";
26
26
 
27
27
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
28
28
  const packageRoot = path.resolve(__dirname, "..");
29
29
  const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
30
30
 
31
31
  // Redirect all writable output away from the (possibly read-only) package dir.
32
- if (!process.env.AUTOTAP_HOME) {
33
- process.env.AUTOTAP_HOME = process.env.TAPP_HOME || path.join(os.homedir(), ".tapp");
34
- }
35
- fs.mkdirSync(process.env.AUTOTAP_HOME, { recursive: true });
32
+ // The old environment alias remains a read-only fallback for older integrations.
33
+ const tappHome = (process.env.TAPP_HOME || process.env.AUTOTAP_HOME || path.join(os.homedir(), ".tapp")).trim();
34
+ process.env.TAPP_HOME = tappHome;
35
+ fs.mkdirSync(tappHome, { recursive: true });
36
36
 
37
37
  const [, , command = "help", ...rest] = process.argv;
38
38
 
@@ -88,7 +88,7 @@ function bootBestSimulator(preferredName = "iPhone 16 Pro") {
88
88
  }
89
89
 
90
90
  function harnessXctestrun() {
91
- const dir = path.join(process.env.AUTOTAP_HOME, "harness-derived", "Build", "Products");
91
+ const dir = path.join(tappHome, "harness-derived", "Build", "Products");
92
92
  try {
93
93
  const found = fs.readdirSync(dir).find((f) => f.endsWith(".xctestrun"));
94
94
  return found ? path.join(dir, found) : null;
@@ -171,7 +171,7 @@ function androidTarget(flags, target = "") {
171
171
  }
172
172
 
173
173
  function saveShot(img, outFlag, name) {
174
- const out = outFlag || path.join(process.env.AUTOTAP_HOME, "shots", name);
174
+ const out = outFlag || path.join(tappHome, "shots", name);
175
175
  fs.mkdirSync(path.dirname(out), { recursive: true });
176
176
  fs.writeFileSync(out, Buffer.from(img.data, "base64"));
177
177
  return out;
@@ -221,7 +221,7 @@ switch (command) {
221
221
  console.error("❌ --explore writes grounded UI Map evidence and cannot be combined with --dry-run");
222
222
  process.exit(2);
223
223
  }
224
- const outDir = typeof flags["out-dir"] === "string" ? flags["out-dir"] : ".autotap";
224
+ const outDir = typeof flags["out-dir"] === "string" ? flags["out-dir"] : ".tapp";
225
225
  const artifactDir = path.resolve(projectDir, outDir);
226
226
  if (!artifactDir.startsWith(projectDir + path.sep) && artifactDir !== projectDir) {
227
227
  console.error("❌ --out-dir must remain inside the repository");
@@ -292,7 +292,7 @@ switch (command) {
292
292
  case "plan": {
293
293
  const { flags, positionals } = parseVerbArgs(rest);
294
294
  const verb = positionals[0] || "show";
295
- const planPath = path.resolve(positionals[1] || ".autotap/release-plan.json");
295
+ const planPath = positionals[1] ? path.resolve(positionals[1]) : existingProjectArtifactPath(process.cwd(), "release-plan.json");
296
296
  if (!fs.existsSync(planPath)) {
297
297
  console.error(`❌ Release plan not found: ${planPath}`);
298
298
  process.exit(2);
@@ -366,7 +366,7 @@ switch (command) {
366
366
  break;
367
367
  }
368
368
  if (verb !== "review") {
369
- console.error("usage: tapp plan show [.autotap/release-plan.json]\n tapp plan review [.autotap/release-plan.json] --approve name[,name] --reject name[,name] --defer name[,name]\n tapp plan generate [.autotap/release-plan.json] [--project-dir DIR]\n tapp plan validate [.autotap/release-plan.json] --project-dir DIR --platform web [--url URL] [--target NAME|PATH]\n tapp plan promote [.autotap/release-plan.json] --project-dir DIR [--item name[,name]]");
369
+ console.error("usage: tapp plan show [.tapp/release-plan.json]\n tapp plan review [.tapp/release-plan.json] --approve name[,name] --reject name[,name] --defer name[,name]\n tapp plan generate [.tapp/release-plan.json] [--project-dir DIR]\n tapp plan validate [.tapp/release-plan.json] --project-dir DIR --platform web [--url URL] [--target NAME|PATH]\n tapp plan promote [.tapp/release-plan.json] --project-dir DIR [--item name[,name]]");
370
370
  process.exit(2);
371
371
  }
372
372
  const list = (value) => typeof value === "string" ? value.split(",").map((item) => item.trim()).filter(Boolean) : [];
@@ -377,7 +377,7 @@ switch (command) {
377
377
  }
378
378
  const { reviewProductPlan } = await import(path.join(packageRoot, "mcp-server", "src", "product-operations.js"));
379
379
  const reviewProjectDir = typeof flags["project-dir"] === "string" ? path.resolve(flags["project-dir"])
380
- : path.basename(path.dirname(planPath)) === ".autotap" ? path.dirname(path.dirname(planPath)) : path.dirname(planPath);
380
+ : isProjectArtifactDirectory(path.basename(path.dirname(planPath))) ? path.dirname(path.dirname(planPath)) : path.dirname(planPath);
381
381
  try { plan = reviewProductPlan({ projectDir: reviewProjectDir, planPath, ...decisions }).plan; }
382
382
  catch (error) { console.error(`❌ Could not review release plan: ${error.message || String(error)}`); process.exit(2); }
383
383
  console.log(`✅ Release plan updated — ${plan.items.filter((item) => ["approved", "accepted"].includes(item.decision)).length} accepted/approved · ${plan.items.filter((item) => item.decision === "rejected").length} rejected · ${plan.items.filter((item) => item.decision === "pending").length} pending`);
@@ -472,7 +472,7 @@ switch (command) {
472
472
  await driver.forceStop();
473
473
  console.log(`🚀 Launched \`${target.appId}\` (Android)\n`);
474
474
  console.log(engine.formatScreen(snap.screenTitle, snap.elements));
475
- const out = typeof flags.out === "string" ? flags.out : path.join(process.env.AUTOTAP_HOME, "shots", `${target.appId}-${Date.now()}.png`);
475
+ const out = typeof flags.out === "string" ? flags.out : path.join(tappHome, "shots", `${target.appId}-${Date.now()}.png`);
476
476
  fs.mkdirSync(path.dirname(out), { recursive: true });
477
477
  fs.writeFileSync(out, data);
478
478
  console.log(`\n📸 Screenshot: ${out}`);
@@ -608,7 +608,7 @@ switch (command) {
608
608
  const verb = positionals[0] || "validate";
609
609
  const taskPath = positionals[1] ? path.resolve(positionals[1]) : "";
610
610
  if (!["validate", "compile", "run"].includes(verb) || !taskPath) {
611
- console.error("usage: tapp task validate <task.yml> [--platform ios|android|web] [--map .autotap/ui-map.json]\n tapp task compile <task.yml> --platform PLATFORM [--inputs '{\"name\":\"value\"}'] [--out compiled.json]\n tapp task run <task.yml> --platform PLATFORM [--url URL|--bundle-id ID|--app-id ID] [--inputs JSON]");
611
+ console.error("usage: tapp task validate <task.yml> [--platform ios|android|web] [--map .tapp/ui-map.json]\n tapp task compile <task.yml> --platform PLATFORM [--inputs '{\"name\":\"value\"}'] [--out compiled.json]\n tapp task run <task.yml> --platform PLATFORM [--url URL|--bundle-id ID|--app-id ID] [--inputs JSON]");
612
612
  process.exit(2);
613
613
  }
614
614
  if (!fs.existsSync(taskPath)) { console.error(`❌ Task not found: ${taskPath}`); process.exit(2); }
@@ -663,7 +663,7 @@ switch (command) {
663
663
  url: typeof flags.url === "string" ? flags.url : task.url || "",
664
664
  reset: task.reset || "launch", vars: compiled.vars, steps: compiled.steps, taskPlan: compiled.plan,
665
665
  };
666
- const out = path.resolve(typeof flags.out === "string" ? flags.out : path.join(process.env.AUTOTAP_HOME, "tasks", `${task.name}-${process.pid}-${Date.now()}.json`));
666
+ const out = path.resolve(typeof flags.out === "string" ? flags.out : path.join(tappHome, "tasks", `${task.name}-${process.pid}-${Date.now()}.json`));
667
667
  fs.mkdirSync(path.dirname(out), { recursive: true });
668
668
  fs.writeFileSync(out, JSON.stringify(flow, null, 2) + "\n");
669
669
  if (verb === "compile") {
@@ -712,8 +712,8 @@ switch (command) {
712
712
  break;
713
713
  }
714
714
  const token = `${process.pid}-${Date.now()}`;
715
- const flowLog = path.join(process.env.AUTOTAP_HOME, "flows", `${token}.log`);
716
- const evidenceDir = path.join(process.env.AUTOTAP_HOME, "captures", `flow-${platform}-${token}`);
715
+ const flowLog = path.join(tappHome, "flows", `${token}.log`);
716
+ const evidenceDir = path.join(tappHome, "captures", `flow-${platform}-${token}`);
717
717
  fs.mkdirSync(path.dirname(flowLog), { recursive: true });
718
718
  const env = { ...process.env, FLOW_LOG: flowLog, TAPP_FLOW_EVIDENCE_DIR: evidenceDir };
719
719
  if (typeof flags.email === "string") env.OCQA_TEST_EMAIL = flags.email;
@@ -741,7 +741,7 @@ switch (command) {
741
741
  const verb = positionals[0] || "validate";
742
742
  const contractPath = positionals[1] ? path.resolve(positionals[1]) : "";
743
743
  if (!["validate", "compile", "run"].includes(verb) || !contractPath) {
744
- console.error("usage: tapp contract validate <name.contract.ts> [--platform ios|android|web] [--map .autotap/ui-map.json]\n tapp contract compile <name.contract.ts> --platform PLATFORM [--out compiled.json]\n tapp contract run <name.contract.ts> --platform PLATFORM [--url URL|--bundle-id ID|--app-id ID]");
744
+ console.error("usage: tapp contract validate <name.contract.ts> [--platform ios|android|web] [--map .tapp/ui-map.json]\n tapp contract compile <name.contract.ts> --platform PLATFORM [--out compiled.json]\n tapp contract run <name.contract.ts> --platform PLATFORM [--url URL|--bundle-id ID|--app-id ID]");
745
745
  process.exit(2);
746
746
  }
747
747
  const {
@@ -779,7 +779,7 @@ switch (command) {
779
779
  let compiled;
780
780
  try { compiled = compileReleaseContract(contract, { platform, sourcePath: contractPath }); }
781
781
  catch (error) { console.error(`❌ Could not compile Release Contract: ${error.message}`); process.exit(2); }
782
- const out = path.resolve(typeof flags.out === "string" ? flags.out : path.join(process.env.AUTOTAP_HOME, "contracts", `${contract.name}-${process.pid}-${Date.now()}.json`));
782
+ const out = path.resolve(typeof flags.out === "string" ? flags.out : path.join(tappHome, "contracts", `${contract.name}-${process.pid}-${Date.now()}.json`));
783
783
  fs.mkdirSync(path.dirname(out), { recursive: true });
784
784
  fs.writeFileSync(out, JSON.stringify(compiled, null, 2) + "\n");
785
785
  if (verb === "compile") {
@@ -893,8 +893,8 @@ switch (command) {
893
893
  break;
894
894
  }
895
895
  const token = `${process.pid}-${Date.now()}`;
896
- const flowLog = path.join(process.env.AUTOTAP_HOME, "scenarios", `${token}.log`);
897
- const evidenceDir = path.join(process.env.AUTOTAP_HOME, "captures", `scenario-web-${token}`);
896
+ const flowLog = path.join(tappHome, "scenarios", `${token}.log`);
897
+ const evidenceDir = path.join(tappHome, "captures", `scenario-web-${token}`);
898
898
  const env = { ...process.env, FLOW_LOG: flowLog, TAPP_FLOW_EVIDENCE_DIR: evidenceDir };
899
899
  const url = typeof flags.url === "string" ? flags.url : scenario.url || scenario.app || "";
900
900
  const result = spawnSync(process.execPath, [path.join(packageRoot, "scripts", "run-web-scenario.js"), absolute, url], { stdio: "inherit", env });
@@ -918,7 +918,7 @@ switch (command) {
918
918
  console.error("❌ --platform must be ios|android|web");
919
919
  process.exit(2);
920
920
  }
921
- const out = path.resolve(typeof flags.out === "string" ? flags.out : path.join(".autotap", "ui-map.json"));
921
+ const out = path.resolve(typeof flags.out === "string" ? flags.out : path.join(".tapp", "ui-map.json"));
922
922
  const observed = buildUiMapFromMarkers({
923
923
  markersPath,
924
924
  platform,
@@ -936,7 +936,7 @@ switch (command) {
936
936
  break;
937
937
  }
938
938
  if (verb === "inspect") {
939
- const mapPath = path.resolve(positionals[1] || path.join(".autotap", "ui-map.json"));
939
+ const mapPath = positionals[1] ? path.resolve(positionals[1]) : existingProjectArtifactPath(process.cwd(), "ui-map.json");
940
940
  if (!fs.existsSync(mapPath)) { console.error(`❌ UI Map not found: ${mapPath}`); process.exit(2); }
941
941
  const map = JSON.parse(fs.readFileSync(mapPath, "utf8"));
942
942
  const errors = validateUiMap(map);
@@ -959,7 +959,7 @@ switch (command) {
959
959
  if (!diff.comparableFullSweep && (diff.notObservedNodes.length || diff.notObservedEdges.length)) console.log("ℹ️ Absence is not labeled a regression because the runs were not declared comparable full sweeps.");
960
960
  process.exit(diff.lostReachability.length || diff.lostTransitions.length ? 1 : 0);
961
961
  }
962
- console.error("usage: tapp map build <ocqa-markers.txt> [--platform ios|android|web] [--out .autotap/ui-map.json]\n tapp map inspect [ui-map.json]\n tapp map diff <before.json> <after.json> [--comparable]");
962
+ console.error("usage: tapp map build <ocqa-markers.txt> [--platform ios|android|web] [--out .tapp/ui-map.json]\n tapp map inspect [ui-map.json]\n tapp map diff <before.json> <after.json> [--comparable]");
963
963
  process.exit(2);
964
964
  }
965
965
 
@@ -1007,7 +1007,7 @@ switch (command) {
1007
1007
  console.log(" ⬜ Web — install Playwright in the app workspace: npm install -D playwright && npx playwright install chromium");
1008
1008
  }
1009
1009
 
1010
- console.log(`\n Home: ${process.env.AUTOTAP_HOME}`);
1010
+ console.log(`\n Home: ${tappHome}`);
1011
1011
  console.log(healthy ? "\nReady. Add to your agent: claude mcp add tapp -- npx -y @aarwitz/tapp mcp" : "\nFix the ❌ items above, then re-run: tapp doctor");
1012
1012
  process.exit(healthy ? 0 : 1);
1013
1013
  }
@@ -1096,7 +1096,7 @@ switch (command) {
1096
1096
  process.exit(2);
1097
1097
  }
1098
1098
  const projectDir = fs.realpathSync(path.resolve(positionals[1] || (typeof flags["project-dir"] === "string" ? flags["project-dir"] : process.cwd())));
1099
- const modelPath = path.resolve(projectDir, typeof flags.model === "string" ? flags.model : path.join(".autotap", "application-model.json"));
1099
+ const modelPath = typeof flags.model === "string" ? path.resolve(projectDir, flags.model) : existingProjectArtifactPath(projectDir, "application-model.json");
1100
1100
  if (!modelPath.startsWith(projectDir + path.sep) || !fs.existsSync(modelPath)) {
1101
1101
  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.`);
1102
1102
  process.exit(2);
@@ -1203,7 +1203,7 @@ switch (command) {
1203
1203
  let projectDir;
1204
1204
  try { projectDir = fs.realpathSync(path.resolve(positionals[0] || (typeof flags["project-dir"] === "string" ? flags["project-dir"] : process.cwd()))); }
1205
1205
  catch { console.error(`❌ Repository directory not found: ${positionals[0] || flags["project-dir"] || process.cwd()}`); process.exit(2); }
1206
- const modelPath = path.resolve(projectDir, typeof flags.model === "string" ? flags.model : path.join(".autotap", "application-model.json"));
1206
+ const modelPath = typeof flags.model === "string" ? path.resolve(projectDir, flags.model) : existingProjectArtifactPath(projectDir, "application-model.json");
1207
1207
  if (!modelPath.startsWith(projectDir + path.sep) || !fs.existsSync(modelPath)) {
1208
1208
  console.error(`❌ Application model not found inside the repository: ${modelPath}\n Run tapp init --explore first.`);
1209
1209
  process.exit(2);
@@ -1242,7 +1242,7 @@ switch (command) {
1242
1242
  actionRef,
1243
1243
  defaultBranch: typeof flags["default-branch"] === "string" ? flags["default-branch"] : "main",
1244
1244
  workflowPath: typeof flags.out === "string" ? flags.out : ".github/workflows/tapp.yml",
1245
- manifestPath: typeof flags.manifest === "string" ? flags.manifest : ".autotap/ci.json",
1245
+ manifestPath: typeof flags.manifest === "string" ? flags.manifest : ".tapp/ci.json",
1246
1246
  replace: flags.replace === true,
1247
1247
  allowUnresolved: flags["allow-unresolved"] === true,
1248
1248
  });
@@ -1284,7 +1284,7 @@ switch (command) {
1284
1284
 
1285
1285
  case "report": {
1286
1286
  // Regenerate + open the HTML evidence page for a capture (default: the latest).
1287
- const capturesDir = path.join(process.env.AUTOTAP_HOME, "captures");
1287
+ const capturesDir = path.join(tappHome, "captures");
1288
1288
  const repoCaptures = path.join(packageRoot, "captures");
1289
1289
  const roots = [capturesDir, repoCaptures].filter((d) => fs.existsSync(d));
1290
1290
  const runs = roots
package/browser/app.js CHANGED
@@ -471,7 +471,7 @@ async function entriesFromDrop(event) {
471
471
  }
472
472
 
473
473
  // The webkitdirectory input (and some drop implementations) silently exclude hidden
474
- // entries, which destroys committed .autotap/ config. The File System Access picker
474
+ // entries, which destroys committed .tapp/ config. The File System Access picker
475
475
  // enumerates hidden files, so it is preferred wherever it exists.
476
476
  async function entriesFromDirectoryHandle(handle, prefix = "") {
477
477
  const entries = [];
@@ -526,7 +526,7 @@ async function uploadRepository(name, entries, { hiddenComplete = false } = {})
526
526
  $("#operation-progress-bar").style.width = "100%";
527
527
  const hasHiddenEntries = entries.some((entry) => entry.path.split("/").some((part) => part.startsWith(".")));
528
528
  if (!hiddenComplete && !hasHiddenEntries) {
529
- toast("Imported, but no hidden files (like .autotap/) came through — this browser's folder picker skips them. Committed Tapp config was not uploaded.", true);
529
+ toast("Imported, but no hidden files (like .tapp/) came through — this browser's folder picker skips them. Committed Tapp config was not uploaded.", true);
530
530
  } else {
531
531
  toast("Repository imported into an isolated Tapp workspace");
532
532
  }
@@ -41,7 +41,7 @@
41
41
  <div class="source-icon">↥</div>
42
42
  <p class="eyebrow">CLOUD COPY</p>
43
43
  <h2>Upload a project folder</h2>
44
- <p>Upload an isolated working copy from this computer. Committed hidden config (like <code>.autotap/</code>) is included; dependency and build output folders are skipped.</p>
44
+ <p>Upload an isolated working copy from this computer. Committed hidden config (like <code>.tapp/</code>) is included; dependency and build output folders are skipped.</p>
45
45
  <button class="primary" id="browse-folder">Browse folder</button>
46
46
  <input id="folder-input" type="file" webkitdirectory multiple hidden>
47
47
  <small>For direct edits to an existing checkout, start with <code>tapp app /path/to/repo</code>.</small>
@@ -39,7 +39,7 @@ in the local process/filesystem. This is a local trust boundary, not hosted mult
39
39
  11. **Install CI** by previewing and writing a reviewable repository patch. Tapp does not commit,
40
40
  push, create GitHub secrets, or enable branch protection.
41
41
 
42
- Successful semantic actions can be saved in `.autotap/flows/`; credential values are templated to
42
+ Successful semantic actions can be saved in `.tapp/flows/`; credential values are templated to
43
43
  environment references. Long-lived repository artifacts store binding names, not resolved secret
44
44
  values.
45
45
 
@@ -62,7 +62,7 @@ intent.
62
62
 
63
63
  ## Hosted relationship
64
64
 
65
- `app.runtapp.com` will present the same product journey through a different adapter: application
65
+ The future hosted application will present the same product journey through a different adapter: application
66
66
  accounts/organizations, GitHub App repository authorization, private storage, a durable queue, and
67
67
  isolated managed workers. It cannot reuse the loopback session, local `gh` authority, filesystem
68
68
  boundary, or in-memory ownership assumptions.
@@ -51,7 +51,7 @@ Current convergence:
51
51
  installation call the same operations;
52
52
  - the GitHub Action and `runProductGate` call the same portable gate and evidence protocol;
53
53
  - VS Code remains a thin MCP client;
54
- - desktop reads the same `.autotap` artifacts but still has legacy import/build orchestration. It is
54
+ - desktop reads the same `.tapp` artifacts but still has legacy import/build orchestration. It is
55
55
  retained, not the launch UX, until that orchestration is removed;
56
56
  - `cloud/runner` is retained prototype evidence for exact checkout, versioned operation envelopes,
57
57
  leases, and cleanup. It is not the production hosted adapter or an adequate arbitrary-customer
@@ -60,10 +60,10 @@ Current convergence:
60
60
 
61
61
  ## Canonical repository protocol
62
62
 
63
- New product behavior writes only `.autotap/`:
63
+ New product behavior writes only `.tapp/`:
64
64
 
65
65
  ```text
66
- .autotap/
66
+ .tapp/
67
67
  project.json # actors, env binding names, controlled lifecycle; never secret values
68
68
  application-model.json # detected/observed/declared product facts
69
69
  ui-map.json # grounded screen/action/transition graph
@@ -74,9 +74,10 @@ New product behavior writes only `.autotap/`:
74
74
  ci.json # generated CI installation manifest
75
75
  ```
76
76
 
77
- `.autotap.yml` and `.tapp.yml` are legacy compatibility inputs, not parallel sources of product
78
- truth. Do not add a fourth configuration format. Migration readers may normalize old input into the
79
- canonical model; only an explicit reviewed operation may write new repository artifacts.
77
+ `.tapp.yml` is the canonical run configuration. Existing `.autotap.yml`, `.autotap/`, and
78
+ `AUTOTAP_*` inputs remain readable during migration, but new examples and output use the Tapp names.
79
+ Do not add another configuration format. Migration readers may normalize old input into the canonical
80
+ model; only an explicit reviewed operation may write new repository artifacts.
80
81
 
81
82
  ## Anti-duplication rules
82
83
 
@@ -3,9 +3,9 @@
3
3
  `tapp init` is the deterministic import, exploration, and planning entrypoint of Tapp's customer
4
4
  journey. It turns a repository into three platform-neutral, repository-native artifacts:
5
5
 
6
- - `.autotap/ui-map.json` — observed UI states, controls, and transitions from real exploration;
7
- - `.autotap/application-model.json` — what Tapp can support with evidence;
8
- - `.autotap/release-plan.json` — the compact set of committed and proposed business guarantees a
6
+ - `.tapp/ui-map.json` — observed UI states, controls, and transitions from real exploration;
7
+ - `.tapp/application-model.json` — what Tapp can support with evidence;
8
+ - `.tapp/release-plan.json` — the compact set of committed and proposed business guarantees a
9
9
  customer must review before generation.
10
10
 
11
11
  Plain `tapp init` performs source/artifact inspection only. `tapp init --explore` additionally uses
@@ -73,7 +73,7 @@ tapp actor list .
73
73
  tapp init . --refresh
74
74
  ```
75
75
 
76
- This writes `.autotap/project.json`. The file contains roles, `default`/`isolated` session policy,
76
+ This writes `.tapp/project.json`. The file contains roles, `default`/`isolated` session policy,
77
77
  provisioning mode, same-origin lifecycle declarations, and environment-variable *names*. The CLI
78
78
  and MCP `tapp_actor_config` reject credential values and refuse to replace an actor without an
79
79
  explicit `--replace`/`replace: true`. Contracts refer to `$ALICE_EMAIL`-style placeholders. Tapp
@@ -144,24 +144,24 @@ and the real-surface validation required before it can be trusted.
144
144
  ## Explicit review
145
145
 
146
146
  ```bash
147
- tapp plan show .autotap/release-plan.json
148
- tapp plan review .autotap/release-plan.json \
147
+ tapp plan show .tapp/release-plan.json
148
+ tapp plan review .tapp/release-plan.json \
149
149
  --approve signInWorks,checkoutWorks \
150
150
  --reject marketingPageReachable \
151
151
  --defer adminAuditWorks
152
152
 
153
- # Only after review: generate grounded Task + contract drafts under .autotap/proposals/.
154
- tapp plan generate .autotap/release-plan.json --project-dir .
153
+ # Only after review: generate grounded Task + contract drafts under .tapp/proposals/.
154
+ tapp plan generate .tapp/release-plan.json --project-dir .
155
155
 
156
156
  # Replay the draft on the real target and attach evidence to the plan.
157
157
  # Omit --url to build/start/stop the detected managed browser target.
158
- tapp plan validate .autotap/release-plan.json --project-dir . --platform web
158
+ tapp plan validate .tapp/release-plan.json --project-dir . --platform web
159
159
  # Or connect to an already-running owned environment.
160
- tapp plan validate .autotap/release-plan.json --project-dir . \
160
+ tapp plan validate .tapp/release-plan.json --project-dir . \
161
161
  --platform web --url http://127.0.0.1:3000
162
162
 
163
163
  # Explicitly accept only fully validated drafts into canonical reviewed locations.
164
- tapp plan promote .autotap/release-plan.json --project-dir . \
164
+ tapp plan promote .tapp/release-plan.json --project-dir . \
165
165
  --item checkoutWorks
166
166
  ```
167
167
 
@@ -181,23 +181,18 @@ The macOS desktop Coverage experience reads these same files. Its **Application*
181
181
  detected targets, actors, capabilities, journeys, Tasks, contracts, and exact remediation. Its
182
182
  **Release Plan** tab writes explicit approve/reject/defer decisions atomically into the canonical
183
183
  plan while preserving fields from newer engine versions; committed contract intent is not editable
184
- through these proposal controls. Flow Map merges the repository `.autotap/ui-map.json` with current
184
+ through these proposal controls. Flow Map merges the repository `.tapp/ui-map.json` with current
185
185
  run evidence instead of building a separate desktop-only graph.
186
186
 
187
- Schema compatibility can be rehearsed headlessly after building the desktop app:
188
-
189
- ```bash
190
- AutoTap.app/Contents/MacOS/AutoTap --verify-artifacts /path/to/repository \
191
- --out /tmp/tapp-desktop-artifacts.json
192
- ```
187
+ Schema compatibility is exercised by the repository's protocol tests and retained desktop reader.
193
188
 
194
189
  `plan generate` handles only explicitly approved proposals. Grounded cross-actor proposals preserve
195
190
  actor-attributed Task calls, captured output variables, bounded eventual assertions, and the
196
191
  reviewed project lifecycle, then compile through the isolated Scenario executor. Existing
197
192
  Task-backed proposals compose those reviewed Tasks. For UI-Map-only proposals, it finds an observed path from each platform's
198
193
  recorded entry state, deduplicates shared semantic transitions into compositional Task drafts under
199
- `.autotap/proposals/tasks/`, grounds every Task in exact node/edge ids, and writes the contract draft
200
- under `.autotap/proposals/contracts/`. Proposal Tasks are visible only to proposal contracts; an
194
+ `.tapp/proposals/tasks/`, grounds every Task in exact node/edge ids, and writes the contract draft
195
+ under `.tapp/proposals/contracts/`. Proposal Tasks are visible only to proposal contracts; an
201
196
  ordinary committed contract or CI glob cannot silently consume one.
202
197
 
203
198
  Generation blocks when entry-state evidence is missing, the target is unreachable, an observed
@@ -205,7 +200,7 @@ action cannot be represented deterministically, or platform paths require incomp
205
200
  composition. It never overwrites a draft, statically compiles each declared platform, and marks all
206
201
  outputs untrusted. Missing non-secret Task inputs stay blocked until the plan has explicit bindings;
207
202
  standard email/password secrets remain placeholders. Successful grounding and compilation are not
208
- real-surface evidence and never promote drafts into `.autotap/tasks` or `.autotap/contracts`.
203
+ real-surface evidence and never promote drafts into `.tapp/tasks` or `.tapp/contracts`.
209
204
 
210
205
  `plan validate` invokes the ordinary deterministic contract executor and records pass/fail evidence
211
206
  per declared platform. A multi-platform draft remains only partially validated until every declared
@@ -214,8 +209,8 @@ substitution or automatic assertion update.
214
209
 
215
210
  `plan promote` is the explicit acceptance boundary. It refuses any contract or generated Task that
216
211
  has not passed every declared platform, preflights every destination, never overwrites a reviewed
217
- artifact, moves accepted files from `.autotap/proposals/{tasks,contracts}` into
218
- `.autotap/{tasks,contracts}`, and applies their exact node/edge coverage to the canonical UI Map.
212
+ artifact, moves accepted files from `.tapp/proposals/{tasks,contracts}` into
213
+ `.tapp/{tasks,contracts}`, and applies their exact node/edge coverage to the canonical UI Map.
219
214
  Shared Task paths in still-unpromoted proposals are rewritten to the canonical file. Promotion does
220
215
  not commit, push, or install CI; the resulting repository patch remains reviewable by the customer.
221
216
 
@@ -237,7 +232,7 @@ tapp ci install . --action-ref aarwitz/tapp@v0.13.1
237
232
 
238
233
  Baseline creation rejects non-gate JSON, missing or mismatched target identity, platform mismatch,
239
234
  failed Flows/Scenarios/contracts, `blocked`, and `inconclusive`. It writes atomically to
240
- `.autotap/baselines/<platform>/<target-id>.json` and requires `--replace` to supersede reviewed
235
+ `.tapp/baselines/<platform>/<target-id>.json` and requires `--replace` to supersede reviewed
241
236
  evidence. Capture-local paths are replaced with portable `tapp-capture:` references before the
242
237
  repository artifact is written. The gate also checks baseline platform and target identity before
243
238
  comparing findings. On iOS, the same validated launch arguments and string-valued launch
@@ -245,7 +240,7 @@ environment are passed to autonomous exploration and deterministic Flow/contract
245
240
  JSON or unsupported value types fail before execution rather than silently testing different app
246
241
  configurations.
247
242
 
248
- CI installation writes `.github/workflows/tapp.yml` and `.autotap/ci.json`, never overwrites by
243
+ CI installation writes `.github/workflows/tapp.yml` and `.tapp/ci.json`, never overwrites by
249
244
  default, and refuses unresolved iOS schemes, Android ids, browser lockfiles, or runtimes. The
250
245
  workflow uses exact contract paths, maps each actor environment binding to a same-named GitHub
251
246
  Secret, uses the first/default actor for autonomous-login inputs, preserves the remaining bindings