@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.
- package/AGENTS.md +4 -4
- package/Harness/OCQAHarness/AppDelegate.swift +1 -1
- package/Harness/OCQAHarness.xcodeproj/project.pbxproj +2 -2
- package/Harness/OCQAHarnessUITests/ExplorerTests.swift +2 -2
- package/Harness/generate-harness-xcodeproj.rb +3 -3
- package/README.md +17 -15
- package/bin/tapp.js +28 -28
- package/browser/app.js +2 -2
- package/browser/index.html +1 -1
- package/docs/BROWSER-PRODUCT.md +2 -2
- package/docs/PRODUCT-ENGINE.md +7 -6
- package/docs/application-model.md +20 -25
- package/docs/scenarios.md +7 -7
- package/mcp-server/src/application-model.js +32 -26
- package/mcp-server/src/browser-product.js +1 -1
- package/mcp-server/src/ci-report.js +5 -4
- package/mcp-server/src/ci-setup.js +16 -7
- package/mcp-server/src/enrich.js +1 -1
- package/mcp-server/src/index.js +63 -62
- package/mcp-server/src/maintenance-proposal.js +4 -4
- package/mcp-server/src/pr-selection.js +9 -8
- package/mcp-server/src/product-execution.js +1 -1
- package/mcp-server/src/product-operations.js +20 -18
- package/mcp-server/src/project-config.js +8 -5
- package/mcp-server/src/project-paths.js +32 -0
- package/mcp-server/src/report.js +3 -3
- package/mcp-server/src/task-runtime.js +14 -10
- package/package.json +15 -3
- package/scripts/ci-gate.sh +15 -10
- package/scripts/flow_ai_judge.py +1 -1
- package/scripts/flow_lib.py +1 -1
- package/scripts/quick-capture.sh +15 -14
- package/scripts/run-flow.sh +6 -6
- package/scripts/android-corpus-e2e.sh +0 -30
- package/scripts/cleanup-xcode.sh +0 -157
- package/scripts/corpus-apps.txt +0 -9
- package/scripts/corpus-sweep.sh +0 -121
- package/scripts/coverage-eval.sh +0 -92
- package/scripts/coverage_eval_parse.py +0 -95
- package/scripts/deploy-and-build.sh +0 -99
- package/scripts/mutation-recall-desktop.sh +0 -186
- package/scripts/mutation-recall.sh +0 -121
- package/scripts/mutation_lib.py +0 -128
- package/scripts/mutation_operators.py +0 -144
- package/scripts/validation-matrix.sh +0 -146
- package/scripts/vision-fp-eval.sh +0 -206
- package/scripts/vision_escalation_responder.py +0 -147
- 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 .
|
|
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 `.
|
|
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 `.
|
|
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: ".
|
|
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 = "
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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:
|
|
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`.
|
|
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 `.
|
|
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 `.
|
|
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 `.
|
|
187
|
-
`.
|
|
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
|
-
`.
|
|
276
|
-
corresponding job. The legacy `.
|
|
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/
|
|
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/.
|
|
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: .
|
|
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
|
|
321
|
-
|
|
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.
|
|
356
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
fs.mkdirSync(
|
|
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(
|
|
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(
|
|
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"] : ".
|
|
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]
|
|
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 [.
|
|
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))
|
|
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(
|
|
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 .
|
|
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(
|
|
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(
|
|
716
|
-
const evidenceDir = path.join(
|
|
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 .
|
|
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(
|
|
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(
|
|
897
|
-
const evidenceDir = path.join(
|
|
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(".
|
|
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]
|
|
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 .
|
|
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: ${
|
|
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 =
|
|
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 =
|
|
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 : ".
|
|
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(
|
|
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 .
|
|
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 .
|
|
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
|
}
|
package/browser/index.html
CHANGED
|
@@ -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>.
|
|
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>
|
package/docs/BROWSER-PRODUCT.md
CHANGED
|
@@ -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 `.
|
|
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
|
-
|
|
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.
|
package/docs/PRODUCT-ENGINE.md
CHANGED
|
@@ -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 `.
|
|
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 `.
|
|
63
|
+
New product behavior writes only `.tapp/`:
|
|
64
64
|
|
|
65
65
|
```text
|
|
66
|
-
.
|
|
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
|
-
`.
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
- `.
|
|
7
|
-
- `.
|
|
8
|
-
- `.
|
|
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 `.
|
|
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 .
|
|
148
|
-
tapp plan review .
|
|
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 .
|
|
154
|
-
tapp plan generate .
|
|
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 .
|
|
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 .
|
|
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 .
|
|
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 `.
|
|
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
|
|
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
|
-
`.
|
|
200
|
-
under `.
|
|
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 `.
|
|
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 `.
|
|
218
|
-
`.
|
|
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
|
-
`.
|
|
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 `.
|
|
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
|