From 518755da9da16f16b1321450f9006739adeb0dc0 Mon Sep 17 00:00:00 2001 From: gus Date: Sun, 26 Apr 2026 19:04:39 -0300 Subject: [PATCH 1/2] README claim locks: lock the new narrative against drift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR 2 of the README narrative refresh round. PR 1 (#207) rewrote the public copy; this PR adds the lint job that prevents the new claims from drifting back to the older wording or accepting the disallowed phrases the spec called out. Spec readme-product-narrative-refresh-2026-04-26 lists the required and forbidden surfaces. The existing readme-public-copy job already tracks an earlier round's claim set; this new readme-narrative-locks job is additive. Five new check groups: 1. Required English tokens. Five-adapter set + opt-in E2E framing must appear by name so the public claim cannot quietly degrade to "and more" or imply always-on coverage: - opt-in E2E workflow - Verified adapters - OpenAI Codex - OpenCode - Gemini CLI 2. Required Spanish form. Either "E2E opt-in" or "workflow E2E opt-in" must appear in README.es.md so the Spanish surface is honest about the runtime harness being workflow_dispatch. 3. Forbidden literal phrases (across both READMEs). Tightens the stale-strings list with phrases that have appeared in drafts: - npx create-nanostack install (specific bad install command) - on every workflow run / every workflow run - 4 commands / Cuatro comandos - zero dependencies / Zero dependencies / cero dependencias / Cero dependencias - marketplace / plugin ecosystem - GDPR ready / SOC2 ready / compliance certified - works in every agent identically - full engineering team 4. Forbidden agent names with bare-word boundary. Spec rejects bare references to Amp, Cline, and Antigravity because Nanostack does not have verified adapters for them today. Word-boundary regex (\bAmp\b, \bCline\b, \bAntigravity\b) so legitimate uses like "amplifier" or "decline" do not trip the lock. 5. Sprint-order arrow check. Any arrow form that places /qa before /security (-> or unicode →) is a regression on the "do not show /qa before /security" rule. Each rule was sabotage-tested locally: drop a required token, inject a forbidden phrase, plant a bare agent name, plant a mis-ordered arrow, drop the Spanish E2E form -- every sabotage fired the corresponding lock, the happy path passes clean. YAML parses. All 10 suites green: 83 unit, 100 user-flows, 30 framework e2e, 51 stack examples e2e, 49 stack contract, 32 examples contract, 25 think archetypes, 32 think, 34 onboarding, 17 delivery matrix. --- .github/workflows/lint.yml | 106 +++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b5723a8..afc931a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1993,6 +1993,112 @@ jobs: done exit $fail + readme-narrative-locks: + name: README narrative claim locks + runs-on: ubuntu-latest + # Spec readme-product-narrative-refresh-2026-04-26. PR 1 of the + # round rewrote the README around "default sprint + framework + # for workflow stacks". This job locks the new claim surface so + # later edits do not silently drift back to the older wording or + # reintroduce the disallowed phrases the spec called out (Amp, + # Cline, Antigravity, "4 commands", "every workflow run", etc.). + # The existing readme-public-copy job above already locks an + # earlier round's required + stale set; this job is additive. + steps: + - uses: actions/checkout@v4 + - name: New required tokens in README.md + run: | + set -e + fail=0 + # Five-adapter set + opt-in E2E framing must appear by name + # so the public claim cannot quietly degrade to "and more" + # or "always-on" again. + for tok in \ + 'opt-in E2E workflow' \ + 'Verified adapters' \ + 'OpenAI Codex' \ + 'OpenCode' \ + 'Gemini CLI'; do + if ! grep -qF "$tok" README.md; then + echo "FAIL: README.md missing required token: $tok" + fail=1 + fi + done + exit $fail + - name: Spanish README declares the same E2E opt-in framing + run: | + set -e + # Spec accepts either "E2E opt-in" or "workflow E2E opt-in" + # as long as the opt-in nature of the runtime harness is + # spelled out in Spanish too. + if ! grep -qE 'E2E opt-in|workflow E2E opt-in' README.es.md; then + echo "FAIL: README.es.md does not declare E2E opt-in framing" + exit 1 + fi + - name: Forbidden phrases must NOT appear in README.md or README.es.md + run: | + set -e + fail=0 + # Literal substrings handled with grep -F. Includes both + # Spanish and English variants and tightens the existing + # stale-strings list with phrases that have been seen in + # drafts but did not yet have a hard lock. + for bad in \ + 'npx create-nanostack install' \ + 'on every workflow run' \ + 'every workflow run' \ + '4 commands' \ + 'Cuatro comandos' \ + 'zero dependencies' \ + 'Zero dependencies' \ + 'cero dependencias' \ + 'Cero dependencias' \ + 'marketplace' \ + 'plugin ecosystem' \ + 'GDPR ready' \ + 'SOC2 ready' \ + 'compliance certified' \ + 'works in every agent identically' \ + 'full engineering team'; do + hits=$(grep -nF "$bad" README.md README.es.md 2>/dev/null || true) + if [ -n "$hits" ]; then + echo "FAIL: forbidden phrase '$bad' present:" + echo "$hits" + fail=1 + fi + done + exit $fail + - name: Forbidden agent names (bare-word boundary) must NOT appear + run: | + set -e + fail=0 + # Use word boundaries so legitimate uses like "amplifier", + # "decline", or "incline" do not trip the lock. The spec + # set blocks bare references to Amp, Cline, and Antigravity + # because Nanostack does not have verified adapters for + # them today; the README must claim only the five verified + # hosts. + for name in 'Amp' 'Cline' 'Antigravity'; do + hits=$(grep -nE "\\b${name}\\b" README.md README.es.md 2>/dev/null || true) + if [ -n "$hits" ]; then + echo "FAIL: forbidden agent name '$name' (bare word) present:" + echo "$hits" + fail=1 + fi + done + exit $fail + - name: Sprint-order arrow MUST NOT show /qa before /security + run: | + set -e + # The canonical sprint order is /review -> /security -> /qa + # -> /ship. Any arrow form (-> or →) that shows /qa + # immediately before /security is a regression on the + # spec's "do not show /qa before /security" rule. + if grep -nE '/qa[[:space:]]*(->|→)[[:space:]]*/security' README.md README.es.md; then + echo "FAIL: README contains /qa -> /security arrow form (canonical order is /security -> /qa)" + exit 1 + fi + examples-library: name: Examples library contract runs-on: ubuntu-latest From 4d3bb020d7f1e91180f620464db96c72aaa88be1 Mon Sep 17 00:00:00 2001 From: gus Date: Sun, 26 Apr 2026 19:16:32 -0300 Subject: [PATCH 2/2] readme-narrative-locks: full adapter set + case-insensitive sweep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex's PR 2 review caught two real gaps in the lock surface. P2.1 The required-tokens loop only locked OpenAI Codex, OpenCode, and Gemini CLI by name — Claude Code and Cursor lived under the generic "Verified adapters" header. A future edit could drop them from the public claim and the lint would still pass. Added Claude Code and Cursor as required tokens so the five-adapter set is locked element-wise, not just by header. P2.2 The forbidden-phrase sweep used grep -nF / grep -nE without -i, so trivial case variants like Marketplace, GDPR Ready, Every workflow run, Full engineering team, AMP, Cline, ANTIGRAVITY all slipped through. For a public-narrative lock the casing should not matter; switched both the literal sweep (now grep -niF) and the bare-word agent-name sweep (now grep -niE "\b...\b") to case-insensitive. Spanish forms collapse with -i (cero/Cero, cuatro/Cuatro), so the previous "two entries per Spanish form" duplication is gone. Word-boundary semantics still hold under -i: \bamp\b (case-i) matches "Amp" / "AMP" / "amp" but not "amplifier" / "ramp", because the boundary is at start of "amplifier" / end of "ramp". Sabotage replay (case-flipped): - Marketplace, Plugin Ecosystem, Every workflow run, GDPR Ready, Full engineering team, Zero Dependencies, Cuatro Comandos - bare AMP, CLINE, antigravity - dropping Claude Code or Cursor from the required set All caught. Happy path stays clean on current README.md + README.es.md. 10 suites green; lint.yml parses. --- .github/workflows/lint.yml | 46 +++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index afc931a..1927fc4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2010,12 +2010,17 @@ jobs: run: | set -e fail=0 - # Five-adapter set + opt-in E2E framing must appear by name - # so the public claim cannot quietly degrade to "and more" - # or "always-on" again. + # Every name in the verified-adapter set must appear by + # itself (not just under a generic "Verified adapters" + # heading), so a future edit cannot drop Claude Code or + # Cursor from the public claim while keeping the rest. + # The opt-in E2E framing is locked here too so the claim + # cannot quietly degrade to "always-on" again. for tok in \ 'opt-in E2E workflow' \ 'Verified adapters' \ + 'Claude Code' \ + 'Cursor' \ 'OpenAI Codex' \ 'OpenCode' \ 'Gemini CLI'; do @@ -2039,20 +2044,21 @@ jobs: run: | set -e fail=0 - # Literal substrings handled with grep -F. Includes both - # Spanish and English variants and tightens the existing - # stale-strings list with phrases that have been seen in - # drafts but did not yet have a hard lock. + # Case-insensitive literal-substring sweep. Earlier draft + # used grep -F (case-sensitive) which would have let trivial + # variants slip through (Marketplace, GDPR Ready, Every + # workflow run, Full engineering team, etc.). For a public- + # narrative lock the casing should not matter; -iF locks + # the claim regardless of how it is capitalized. + # Spanish forms collapse with -i (cero/Cero, cuatro/Cuatro). for bad in \ 'npx create-nanostack install' \ 'on every workflow run' \ 'every workflow run' \ '4 commands' \ - 'Cuatro comandos' \ + 'cuatro comandos' \ 'zero dependencies' \ - 'Zero dependencies' \ 'cero dependencias' \ - 'Cero dependencias' \ 'marketplace' \ 'plugin ecosystem' \ 'GDPR ready' \ @@ -2060,9 +2066,9 @@ jobs: 'compliance certified' \ 'works in every agent identically' \ 'full engineering team'; do - hits=$(grep -nF "$bad" README.md README.es.md 2>/dev/null || true) + hits=$(grep -niF "$bad" README.md README.es.md 2>/dev/null || true) if [ -n "$hits" ]; then - echo "FAIL: forbidden phrase '$bad' present:" + echo "FAIL: forbidden phrase '$bad' present (case-insensitive):" echo "$hits" fail=1 fi @@ -2072,16 +2078,16 @@ jobs: run: | set -e fail=0 - # Use word boundaries so legitimate uses like "amplifier", - # "decline", or "incline" do not trip the lock. The spec - # set blocks bare references to Amp, Cline, and Antigravity - # because Nanostack does not have verified adapters for - # them today; the README must claim only the five verified - # hosts. + # Word-boundary regex so legitimate words like "amplifier", + # "decline", or "incline" do not trip the lock. Match is + # case-insensitive so trivial variants (AMP, amp, Cline, + # CLINE, ANTIGRAVITY) are caught the same way. Nanostack + # does not ship verified adapters for any of these hosts; + # the README must claim only the five verified ones. for name in 'Amp' 'Cline' 'Antigravity'; do - hits=$(grep -nE "\\b${name}\\b" README.md README.es.md 2>/dev/null || true) + hits=$(grep -niE "\\b${name}\\b" README.md README.es.md 2>/dev/null || true) if [ -n "$hits" ]; then - echo "FAIL: forbidden agent name '$name' (bare word) present:" + echo "FAIL: forbidden agent name '$name' (bare word, case-insensitive) present:" echo "$hits" fail=1 fi