From e2e0091ae1ddd6251d5baedf1facced5e92c9769 Mon Sep 17 00:00:00 2001 From: Franco Zalamena Date: Sun, 25 Oct 2026 18:59:40 +0000 Subject: [PATCH] Ask the scope before provisioning when an agent enters there The provisioning skill's first command, agent question opening, now prints the scope screen until Step 0 is recorded, and a status read reports next.kind scope ahead of every Google and Iterable step. Code still waits for provisioning, as preflight already routes it. Plugin 26.10.9-beta. Co-authored-by: Cursor --- .claude-plugin/plugin.json | 2 +- .cursor-plugin/marketplace.json | 2 +- .cursor-plugin/plugin.json | 2 +- INTENDED-FLOW.md | 2 ++ bin/agent | 11 +++++++++++ bin/questions.sh | 6 ++++-- iterable-provision/SKILL.md | 8 ++++++++ tests/agent-questions.sh | 5 ++++- tests/agent-scope.sh | 29 +++++++++++++++++++++++++++++ tests/agent-transitions.sh | 3 +++ tests/text-surface.sh | 3 +++ 11 files changed, 67 insertions(+), 6 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index e0b6b60..07b1b05 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "iterable-sdk", - "version": "26.10.8-beta", + "version": "26.10.9-beta", "description": "Takes an Android or iOS app — native or React Native — from nothing to a push notification proven on a device: Firebase or Apple (APNs) provisioning, version-pinned SDK integration, silent-failure pitfalls, and device-side proof that a push actually arrived. Android proof runs on a device or emulator; iOS proof runs on the iOS Simulator.", "author": { "name": "Iterable" }, "repository": "https://github.com/Iterable/iterable-sdk-skill", diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index 975c1a0..ef20b20 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -3,7 +3,7 @@ "owner": { "name": "Iterable" }, "metadata": { "description": "Iterable's official skills for AI coding assistants — mobile SDK integration guidance.", - "version": "26.10.8-beta" + "version": "26.10.9-beta" }, "plugins": [ { diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 41321a5..6fbb488 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "iterable-sdk", - "version": "26.10.8-beta", + "version": "26.10.9-beta", "description": "Takes an Android or iOS app — native or React Native — from nothing to a push notification proven on a device: Firebase or Apple (APNs) provisioning, version-pinned SDK integration, silent-failure pitfalls, and device-side proof that a push actually arrived. Android proof runs on a device or emulator; iOS proof runs on the iOS Simulator.", "author": { "name": "Iterable" }, "repository": "https://github.com/Iterable/iterable-sdk-skill", diff --git a/INTENDED-FLOW.md b/INTENDED-FLOW.md index 887bda3..3a21cb0 100644 --- a/INTENDED-FLOW.md +++ b/INTENDED-FLOW.md @@ -27,6 +27,8 @@ Numbered because the auditor reports divergence by waypoint. confirms it. On Android it is recorded, not remembered: `agent preflight` returns `scope` ahead of provisioning and code until `agent question scope` has put the features, the identity and any existing-push choice to the developer and their picks are on disk. +An agent that enters through provisioning meets the same gate: `agent question opening` +prints the scope screen, and a status read reports `scope`, until those picks are on disk. **W2 — The Android work.** The agent writes config, init and identity, guided by the skill and `PITFALLS.md`. It never fakes an input to make a build pass: no placeholder diff --git a/bin/agent b/bin/agent index d7ae6e9..cd9d470 100755 --- a/bin/agent +++ b/bin/agent @@ -365,6 +365,17 @@ esac NEXT="$(next_action)" IFS="$NEXT_SEP" read -r NEXT_OWNER NEXT_KIND NEXT_GATE NEXT_CMD NEXT_SUMMARY <<< "$NEXT" +# Same gate as preflight's, for an agent that entered through provisioning: nothing in the +# Google or Iterable half starts until Step 0 is recorded. Device steps are not gated, +# because by then the code exists and the scope was asked before it was written. +case "$NEXT_KIND" in + choose_driver|run_in_terminal|authenticate|choose_target|project_unreachable|approve_firebase|register_app|provision|iterable_keys) + if [[ -n "$(scope_missing)" ]]; then + NEXT_OWNER=human NEXT_KIND=scope NEXT_CMD="$BIN/agent question scope" + NEXT_SUMMARY="Step 0 is not recorded yet. Put the scope screens to the developer before any setup starts; then run $BIN/agent question opening again." + fi ;; +esac + # The same step, in words a developer who has never read this repo can use. `gate` # stays because the state file and our own vocabulary are keyed on it, but an agent # that has only an id to quote says "G13" to somebody it means nothing to. diff --git a/bin/questions.sh b/bin/questions.sh index 3f5f8f1..f067694 100644 --- a/bin/questions.sh +++ b/bin/questions.sh @@ -1234,7 +1234,9 @@ question_for() { case "${1:-}" in scope) q_scope ;; scope_more) q_scope_more ;; - opening) q_opening ;; + # Provisioning asks for the test identity the scope decides, so the scope comes first + # even when an agent enters here instead of through the platform skill's preflight. + opening) if [[ -n "$(scope_missing)" ]]; then q_scope; else q_opening; fi ;; # Two kinds, one fork. next_action reports `run_in_terminal` when the developer is # the driver and `approve_firebase` when the agent is, because the terminal path # asks for consent in person and needs no agent to collect it. Asked in a chat @@ -1248,7 +1250,7 @@ question_for() { # The fork itself, reported while it is still unanswered. Same screen as the opening # one because it is the same question — a second, smaller version of it would be a # second wording to keep true, and the one that goes stale is whichever they read. - choose_driver) q_opening ;; + choose_driver) if [[ -n "$(scope_missing)" ]]; then q_scope; else q_opening; fi ;; sign_in|authenticate) q_sign_in ;; # The door, then the picker, and they are different questions: one asks whether the # listing may happen, the other shows what came back. next_action calls both diff --git a/iterable-provision/SKILL.md b/iterable-provision/SKILL.md index b533cab..58ea7b7 100644 --- a/iterable-provision/SKILL.md +++ b/iterable-provision/SKILL.md @@ -178,6 +178,13 @@ credentials, reads nothing, and writes nothing — not even a workspace director is safe to be the first thing that happens. Render it exactly as *Rendering a tool screen* says: `context` printed above the selector, verbatim, then `prompt` whole and the options. +**Step 0 comes first.** Until the developer's scope is recorded, that command prints a +scope screen (**Features**, then **Identity**) instead of the setup question, and a +status read reports `next.kind: scope`. Put each one to the developer, run the commands +of the options they pick, and follow each `next.command` until it names `preflight`. +Then run `/bin/agent question opening` again: with the scope recorded it is the +setup question. Never record a scope answer the developer did not pick. + This ordering is the point and it is easy to get wrong. A status read runs the whole eighteen-gate ladder against their Firebase projects, so a question carried in its output arrives *after* the work it was asking about. The usual mistake is to decide provisioning @@ -339,6 +346,7 @@ relay the block, do not look for a flag that gets you past it, and never set | `next.kind` | What you do | |---|---| +| `scope` | Step 0 is not recorded. Put `next.question` to the developer, run the commands of the option they pick, and follow each `next.command` until it names `preflight`. Then ask `agent question opening`. | | `choose_driver` | Nobody has said who runs the Google half yet. Ask `next.question` (`agent question opening`) and wait for their pick. **Do not relay a terminal block here** — that is one of the two answers. | | `run_in_terminal` | They chose their own terminal. Relay the handoff block verbatim, say you'll wait, stop. | | `install_tools` | Report exactly which binaries are missing (`missing_tools`) and what each unlocks: `gcloud` the Google half, `adb` the device half, `node` the JSON parsing. Do not install them. | diff --git a/tests/agent-questions.sh b/tests/agent-questions.sh index 41eefaf..c90ef8d 100644 --- a/tests/agent-questions.sh +++ b/tests/agent-questions.sh @@ -12,6 +12,9 @@ # No network, no credentials, no project. set -uo pipefail +# Step 0 is recorded: these checks are about the setup screens, which the scope gate +# holds back until it is (tests/agent-scope.sh covers that gate). +export SCOPE_FEATURES=push-notifications,sdk-initialization SCOPE_IDENTITY=email SCOPE_PUSH=keep-both cd "$(dirname "$0")/.." FAILED=0 @@ -433,7 +436,7 @@ ok "every step leaves 'that did not work' as an answer that changes nothing" # so the identity, an email or a user ID and not a secret, is the only value in part 2 # collected that way. for n in 1 2 3 4; do - ft="$(q iterable_step "$n" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const q=JSON.parse(s);console.log(q.free_text?q.free_text.command:"")})')" + ft="$(SCOPE_IDENTITY= q iterable_step "$n" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const q=JSON.parse(s);console.log(q.free_text?q.free_text.command:"")})')" case "$n" in 4) [[ "$ft" == *"set ITBL_IDENTITY="* ]] \ && ok "step 4 takes the test identity as typed text — an email or user ID, not a secret" \ diff --git a/tests/agent-scope.sh b/tests/agent-scope.sh index 919a5c3..508bdef 100755 --- a/tests/agent-scope.sh +++ b/tests/agent-scope.sh @@ -257,6 +257,35 @@ done && ok "every dashboard step's prompt says no app code is written while it is open" \ || bad "the prompt says nothing about writing code meanwhile on step(s):$NOCODE" +echo +echo " Entering through provisioning still asks the scope first" +echo + +P="$TMP/provision-first"; native_app "$P" +O="$(in_app "$P" question opening)" +[[ "$(field header <<< "$O")" == Features && ! -d "$P/.iterable" ]] \ + && ok "with no scope, the provisioning skill's first question is the features screen, and nothing is written" \ + || bad "question opening with no scope: header $(field header <<< "$O")" +[[ "$(in_app "$P" question choose_driver | field header)" == Features ]] \ + && ok "the setup fork is held behind the scope too" || bad "choose_driver asked before the scope" +recorded "$P" push-notifications,sdk-initialization email +in_app "$P" scope existing-push keep-both >/dev/null +O="$(in_app "$P" question opening)" +[[ "$(field header <<< "$O")" != Features && "$(field header <<< "$O")" != Identity && -n "$(field header <<< "$O")" ]] \ + && ok "with the scope recorded, the same command is the setup question" \ + || bad "question opening with a scope: header $(field header <<< "$O")" + +S="$TMP/status-first"; native_app "$S" +O="$(in_app "$S")" +[[ "$(field next.kind <<< "$O")" == scope && "$(field next.command <<< "$O")" == "$ROOT/bin/agent question scope" \ + && "$(field next.question.header <<< "$O")" == Features ]] \ + && ok "a status read before the scope reports scope, with the features screen as its question" \ + || bad "status with no scope: $(field next.kind <<< "$O") / $(field next.command <<< "$O")" +recorded "$S" push-notifications,sdk-initialization email +in_app "$S" scope existing-push keep-both >/dev/null +[[ "$(in_app "$S" | field next.kind)" != scope ]] \ + && ok "with the scope recorded, a status read routes to setup again" || bad "status still gated after the scope" + echo echo " Every scope screen can be drawn" echo diff --git a/tests/agent-transitions.sh b/tests/agent-transitions.sh index 1e2b6c9..14f92de 100755 --- a/tests/agent-transitions.sh +++ b/tests/agent-transitions.sh @@ -23,6 +23,9 @@ # project "looked at" and the screens branch on whether it has been. set -uo pipefail +# Step 0 is recorded: these checks are about the setup screens, which the scope gate +# holds back until it is (tests/agent-scope.sh covers that gate). +export SCOPE_FEATURES=push-notifications,sdk-initialization SCOPE_IDENTITY=email SCOPE_PUSH=keep-both cd "$(dirname "$0")/.." FAILED=0 diff --git a/tests/text-surface.sh b/tests/text-surface.sh index 8715f4b..73cf1c7 100755 --- a/tests/text-surface.sh +++ b/tests/text-surface.sh @@ -16,6 +16,9 @@ # see from the text they are editing. set -uo pipefail +# Step 0 is recorded: these checks are about the setup screens, which the scope gate +# holds back until it is (tests/agent-scope.sh covers that gate). +export SCOPE_FEATURES=push-notifications,sdk-initialization SCOPE_IDENTITY=email SCOPE_PUSH=keep-both cd "$(dirname "$0")/.." FAILED=0