@aarwitz/tapp 0.15.1 → 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/README.md +13 -10
- package/bin/tapp.js +14 -14
- package/browser/app.js +2 -2
- package/browser/index.html +1 -1
- package/docs/BROWSER-PRODUCT.md +1 -1
- package/docs/PRODUCT-ENGINE.md +7 -6
- package/docs/application-model.md +19 -19
- package/docs/scenarios.md +7 -7
- package/mcp-server/src/application-model.js +32 -26
- package/mcp-server/src/ci-report.js +5 -4
- package/mcp-server/src/ci-setup.js +16 -7
- package/mcp-server/src/index.js +28 -27
- package/mcp-server/src/maintenance-proposal.js +4 -4
- package/mcp-server/src/pr-selection.js +9 -8
- package/mcp-server/src/product-operations.js +19 -17
- package/mcp-server/src/project-config.js +8 -5
- package/mcp-server/src/project-paths.js +32 -0
- package/mcp-server/src/task-runtime.js +14 -10
- package/package.json +1 -1
- package/scripts/ci-gate.sh +12 -7
- package/scripts/flow_lib.py +1 -1
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.
|
package/README.md
CHANGED
|
@@ -90,9 +90,9 @@ npx -y @aarwitz/tapp ci install .
|
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
The baseline command writes only after autonomous QA and every selected deterministic suite pass
|
|
93
|
-
conclusively. It stores `.
|
|
93
|
+
conclusively. It stores `.tapp/baselines/<platform>/<target-id>.json`; the generated workflow
|
|
94
94
|
uses that exact target identity so two apps on the same platform never share a baseline. `ci
|
|
95
|
-
install` writes `.github/workflows/tapp.yml` plus `.
|
|
95
|
+
install` writes `.github/workflows/tapp.yml` plus `.tapp/ci.json`, refuses unresolved build
|
|
96
96
|
configuration and existing-file collisions, and never commits, pushes, enables branch protection,
|
|
97
97
|
or creates GitHub resources. Review and pin the generated Tapp release reference to its immutable
|
|
98
98
|
commit SHA before production.
|
|
@@ -182,8 +182,8 @@ Then ask your agent:
|
|
|
182
182
|
|
|
183
183
|
Full agent playbook: [AGENTS.md](./AGENTS.md) — ships inside the package so agents can read it too.
|
|
184
184
|
Application-model and import contract: [`docs/application-model.md`](docs/application-model.md).
|
|
185
|
-
The desktop Coverage view reads the same `.
|
|
186
|
-
`.
|
|
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
|
|
187
187
|
does not maintain a separate product model. Map nodes identify both the real launch entry and the
|
|
188
188
|
deterministic per-platform navigation root used for bounded changed-surface replay.
|
|
189
189
|
|
|
@@ -271,8 +271,8 @@ The first successful, conclusive run on `main` seeds a repository-scoped Actions
|
|
|
271
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**
|
|
272
272
|
high/critical findings or broken Flows—not pre-existing debt. No baseline commit or PAT is required.
|
|
273
273
|
If you prefer a reviewed, durable baseline, run `tapp baseline create` and commit the generated
|
|
274
|
-
`.
|
|
275
|
-
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
|
|
276
276
|
`pull-requests: write` as shown above. Secrets are unavailable to workflows from forks, so
|
|
277
277
|
auth-gated apps should either use a non-secret UI-testing launch argument or skip the gate for
|
|
278
278
|
untrusted forks.
|
|
@@ -305,14 +305,14 @@ or accept a prebuilt one:
|
|
|
305
305
|
android-app-id: com.acme.app
|
|
306
306
|
android-project: android
|
|
307
307
|
android-task: :app:assembleDebug
|
|
308
|
-
flows: android/.
|
|
308
|
+
flows: android/.tapp/flows/*.yml
|
|
309
309
|
```
|
|
310
310
|
|
|
311
311
|
For web, pass `platform: web` plus `web-target:` and Tapp uses the application model to run its
|
|
312
312
|
lockfile-backed install/build, start a detected package script or read-only static server, wait for
|
|
313
313
|
readiness, gate it, and stop it even on failure. Pass `url:` instead for an already-running owned
|
|
314
314
|
environment. Add
|
|
315
|
-
`scenarios: .
|
|
315
|
+
`scenarios: .tapp/scenarios/*.yml` to gate isolated cross-account journeys; see
|
|
316
316
|
[`docs/scenarios.md`](docs/scenarios.md). Automatic
|
|
317
317
|
baselines are isolated by platform and target, so two same-platform apps are never compared.
|
|
318
318
|
|
|
@@ -351,8 +351,11 @@ verdict calculation run entirely locally — no telemetry, nothing phones home.
|
|
|
351
351
|
features are explicit: finding enrichment requires `TAPP_ENABLE_REMOTE_AI=1` (an ambient
|
|
352
352
|
API key alone never changes data handling), and AI flow generation / `assert_ai` only run
|
|
353
353
|
when you invoke them; these send selected metadata (screen names, finding titles) to your
|
|
354
|
-
configured model provider. Runtime configuration uses `TAPP_*` environment variables
|
|
355
|
-
|
|
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.
|
|
356
359
|
|
|
357
360
|
Committed Flow replay, recording a driven session, autonomous exploration, exact assertions,
|
|
358
361
|
regression comparison, and CI gating require **no API key and no coding agent at runtime**. AI is
|
package/bin/tapp.js
CHANGED
|
@@ -22,16 +22,16 @@ import fs from "node:fs";
|
|
|
22
22
|
import os from "node:os";
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import { fileURLToPath } from "node:url";
|
|
25
|
+
import { existingProjectArtifactPath, isProjectArtifactDirectory } from "../mcp-server/src/project-paths.js";
|
|
25
26
|
|
|
26
27
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
28
|
const packageRoot = path.resolve(__dirname, "..");
|
|
28
29
|
const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
29
30
|
|
|
30
31
|
// Redirect all writable output away from the (possibly read-only) package dir.
|
|
31
|
-
// The old environment alias remains
|
|
32
|
+
// The old environment alias remains a read-only fallback for older integrations.
|
|
32
33
|
const tappHome = (process.env.TAPP_HOME || process.env.AUTOTAP_HOME || path.join(os.homedir(), ".tapp")).trim();
|
|
33
34
|
process.env.TAPP_HOME = tappHome;
|
|
34
|
-
if (!process.env.AUTOTAP_HOME) process.env.AUTOTAP_HOME = tappHome;
|
|
35
35
|
fs.mkdirSync(tappHome, { recursive: true });
|
|
36
36
|
|
|
37
37
|
const [, , command = "help", ...rest] = process.argv;
|
|
@@ -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`);
|
|
@@ -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); }
|
|
@@ -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 {
|
|
@@ -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
|
|
|
@@ -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
|
});
|
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
|
|
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,7 +181,7 @@ 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
187
|
Schema compatibility is exercised by the repository's protocol tests and retained desktop reader.
|
|
@@ -191,8 +191,8 @@ actor-attributed Task calls, captured output variables, bounded eventual asserti
|
|
|
191
191
|
reviewed project lifecycle, then compile through the isolated Scenario executor. Existing
|
|
192
192
|
Task-backed proposals compose those reviewed Tasks. For UI-Map-only proposals, it finds an observed path from each platform's
|
|
193
193
|
recorded entry state, deduplicates shared semantic transitions into compositional Task drafts under
|
|
194
|
-
`.
|
|
195
|
-
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
|
|
196
196
|
ordinary committed contract or CI glob cannot silently consume one.
|
|
197
197
|
|
|
198
198
|
Generation blocks when entry-state evidence is missing, the target is unreachable, an observed
|
|
@@ -200,7 +200,7 @@ action cannot be represented deterministically, or platform paths require incomp
|
|
|
200
200
|
composition. It never overwrites a draft, statically compiles each declared platform, and marks all
|
|
201
201
|
outputs untrusted. Missing non-secret Task inputs stay blocked until the plan has explicit bindings;
|
|
202
202
|
standard email/password secrets remain placeholders. Successful grounding and compilation are not
|
|
203
|
-
real-surface evidence and never promote drafts into `.
|
|
203
|
+
real-surface evidence and never promote drafts into `.tapp/tasks` or `.tapp/contracts`.
|
|
204
204
|
|
|
205
205
|
`plan validate` invokes the ordinary deterministic contract executor and records pass/fail evidence
|
|
206
206
|
per declared platform. A multi-platform draft remains only partially validated until every declared
|
|
@@ -209,8 +209,8 @@ substitution or automatic assertion update.
|
|
|
209
209
|
|
|
210
210
|
`plan promote` is the explicit acceptance boundary. It refuses any contract or generated Task that
|
|
211
211
|
has not passed every declared platform, preflights every destination, never overwrites a reviewed
|
|
212
|
-
artifact, moves accepted files from `.
|
|
213
|
-
`.
|
|
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.
|
|
214
214
|
Shared Task paths in still-unpromoted proposals are rewritten to the canonical file. Promotion does
|
|
215
215
|
not commit, push, or install CI; the resulting repository patch remains reviewable by the customer.
|
|
216
216
|
|
|
@@ -232,7 +232,7 @@ tapp ci install . --action-ref aarwitz/tapp@v0.13.1
|
|
|
232
232
|
|
|
233
233
|
Baseline creation rejects non-gate JSON, missing or mismatched target identity, platform mismatch,
|
|
234
234
|
failed Flows/Scenarios/contracts, `blocked`, and `inconclusive`. It writes atomically to
|
|
235
|
-
`.
|
|
235
|
+
`.tapp/baselines/<platform>/<target-id>.json` and requires `--replace` to supersede reviewed
|
|
236
236
|
evidence. Capture-local paths are replaced with portable `tapp-capture:` references before the
|
|
237
237
|
repository artifact is written. The gate also checks baseline platform and target identity before
|
|
238
238
|
comparing findings. On iOS, the same validated launch arguments and string-valued launch
|
|
@@ -240,7 +240,7 @@ environment are passed to autonomous exploration and deterministic Flow/contract
|
|
|
240
240
|
JSON or unsupported value types fail before execution rather than silently testing different app
|
|
241
241
|
configurations.
|
|
242
242
|
|
|
243
|
-
CI installation writes `.github/workflows/tapp.yml` and `.
|
|
243
|
+
CI installation writes `.github/workflows/tapp.yml` and `.tapp/ci.json`, never overwrites by
|
|
244
244
|
default, and refuses unresolved iOS schemes, Android ids, browser lockfiles, or runtimes. The
|
|
245
245
|
workflow uses exact contract paths, maps each actor environment binding to a same-named GitHub
|
|
246
246
|
Secret, uses the first/default actor for autonomous-login inputs, preserves the remaining bindings
|
package/docs/scenarios.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Multi-actor Scenarios
|
|
2
2
|
|
|
3
|
-
A Scenario is Tapp's low-level deterministic multi-actor execution format in `.
|
|
3
|
+
A Scenario is Tapp's low-level deterministic multi-actor execution format in `.tapp/scenarios/*.yml`. It uses the same semantic actions, polling assertions, timeouts, evidence markers, and merge policy as a Flow, but adds isolated named actors, shared variables, and explicit lifecycle steps. The customer-facing business authoring layer is a TypeScript [release contract](release-contracts.md), which composes reusable Tasks and compiles to this runtime instead of duplicating UI steps.
|
|
4
4
|
|
|
5
5
|
Ordinary replay is keyless. AI may propose a Scenario during authoring, but no model, API key, or coding agent participates when CI executes it.
|
|
6
6
|
|
|
@@ -61,13 +61,13 @@ teardown:
|
|
|
61
61
|
## Run it
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
tapp scenario validate .
|
|
64
|
+
tapp scenario validate .tapp/scenarios/social-system.yml
|
|
65
65
|
ALICE_EMAIL=alice@example.test ALICE_PASSWORD=demo \
|
|
66
66
|
BOB_EMAIL=bob@example.test BOB_PASSWORD=demo \
|
|
67
|
-
tapp scenario run .
|
|
67
|
+
tapp scenario run .tapp/scenarios/social-system.yml
|
|
68
68
|
|
|
69
69
|
tapp ci --platform web --url http://127.0.0.1:4180 \
|
|
70
|
-
--scenarios '.
|
|
70
|
+
--scenarios '.tapp/scenarios/*.yml' \
|
|
71
71
|
--json-out tapp-report.json --md-out tapp-report.md
|
|
72
72
|
```
|
|
73
73
|
|
|
@@ -78,16 +78,16 @@ GitHub Action:
|
|
|
78
78
|
with:
|
|
79
79
|
platform: web
|
|
80
80
|
url: http://127.0.0.1:4180
|
|
81
|
-
scenarios: .
|
|
81
|
+
scenarios: .tapp/scenarios/*.yml
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
MCP clients call `tapp_scenario_run` with `scenarioPath`, or an inline reviewed Scenario. The structured result identifies `kind: scenario`, actual executed/total steps, and actor-tagged steps.
|
|
85
85
|
|
|
86
86
|
## Verified fixture and boundaries
|
|
87
87
|
|
|
88
|
-
`SocialDemo/.
|
|
88
|
+
`SocialDemo/.tapp/contracts/social-system.contract.ts` is the reference system guarantee; the Scenario remains its low-level execution proof and backwards-compatible escape hatch. On 2026-08-04 the contract passed 41/41 compiled steps using two isolated contexts against one delayed shared backend. With `SOCIAL_DEMO_FAULT=hide-cross-actor-posts`, the unchanged contract failed for Bob at 19/41 and the portable merge gate exited non-zero specifically because one release contract failed.
|
|
89
89
|
|
|
90
|
-
The fixture's `.
|
|
90
|
+
The fixture's `.tapp/project.json` is the central actor contract. It records Alice and Bob's
|
|
91
91
|
roles, isolated sessions, seeded provisioning, reset lifecycle, and four environment-variable
|
|
92
92
|
names. The release contract and Scenario consume those names; neither stores the public fixture
|
|
93
93
|
values. Customer values belong in the local environment or CI secret store.
|