chore(memory): 21st sign-in gate — contract, plan r3, TODO, journal
This commit is contained in:
@@ -537,3 +537,4 @@ rules:
|
|||||||
- User go: feature/doctor-vendored-skills merged into develop via `gitflow finish` → 2c94a0c, no conflict, pushed, local + origin copies removed by the lib. develop == origin/develop, no working branch anywhere. `make doctor` now covers the 11 vendored externals.
|
- User go: feature/doctor-vendored-skills merged into develop via `gitflow finish` → 2c94a0c, no conflict, pushed, local + origin copies removed by the lib. develop == origin/develop, no working branch anywhere. `make doctor` now covers the 11 vendored externals.
|
||||||
- Skill-catalog audit (user: "tour des skills, doublons, économiser tokens"): 5 analyzers over 150 skills / 53.5k chars desc; 78 listed name-only this session (listing budget ≈1 % ctx, least-invoked lose desc → gain = routing quality + no broken 100 KB body invoked, not listing chars). Found: frontend-design plugin byte-dup of managed copy; brightdata 21 skills keyless + hostile WebFetch routing; gstack ship trunk-based (origin/HEAD=main), land-and-deploy auto-merge+deploy, autoplan/make-pdf/diagram/careful/guard/freeze dead paths (only bin + browse/dist linked); security-guidance = Opus call per code turn + agentic commit review, 0 findings/6 days; doctor.sh undercount ×6. User go: tier 1, superpowers vendor-7 (tier 2 later), 21st trio parked (CLI `Not logged in`), rule "full ⊇ every profile, max = everything". Live: brightdata disabled, frontend-design plugin uninstalled, `set full` → 75 skills (was 89).
|
- Skill-catalog audit (user: "tour des skills, doublons, économiser tokens"): 5 analyzers over 150 skills / 53.5k chars desc; 78 listed name-only this session (listing budget ≈1 % ctx, least-invoked lose desc → gain = routing quality + no broken 100 KB body invoked, not listing chars). Found: frontend-design plugin byte-dup of managed copy; brightdata 21 skills keyless + hostile WebFetch routing; gstack ship trunk-based (origin/HEAD=main), land-and-deploy auto-merge+deploy, autoplan/make-pdf/diagram/careful/guard/freeze dead paths (only bin + browse/dist linked); security-guidance = Opus call per code turn + agentic commit review, 0 findings/6 days; doctor.sh undercount ×6. User go: tier 1, superpowers vendor-7 (tier 2 later), 21st trio parked (CLI `Not logged in`), rule "full ⊇ every profile, max = everything". Live: brightdata disabled, frontend-design plugin uninstalled, `set full` → 75 skills (was 89).
|
||||||
- /feat by hand on feature/skill-catalog-prune: contract 18 criteria; plan r1→r4 (3 challengers, confirmation FATAL(4): nested SKILL.md in browser-skills/openclaw/node_modules, ./setup global symlink, update-all 3rd copy); 4 feater parallel DONE; GATE 0 UNMET(4) = MY heredoc CHECKs (gates.sh single-line) → oracles to `<contract>.oracles/*.py` → MET; verifier ECARTS(1) = floor-guard `xit(` false-positive on `sys.exit(` → restructure → CONFORME; security PASS. 41 suites green minus 2 pre-existing T16a, shellcheck clean. UNMERGED — human gate. Registries pending user approval.
|
- /feat by hand on feature/skill-catalog-prune: contract 18 criteria; plan r1→r4 (3 challengers, confirmation FATAL(4): nested SKILL.md in browser-skills/openclaw/node_modules, ./setup global symlink, update-all 3rd copy); 4 feater parallel DONE; GATE 0 UNMET(4) = MY heredoc CHECKs (gates.sh single-line) → oracles to `<contract>.oracles/*.py` → MET; verifier ECARTS(1) = floor-guard `xit(` false-positive on `sys.exit(` → restructure → CONFORME; security PASS. 41 suites green minus 2 pre-existing T16a, shellcheck clean. UNMERGED — human gate. Registries pending user approval.
|
||||||
|
- User: "quand on détecte qu'on a besoin de 21st, on demande de log si c'est pas fait et on attend". /feat by hand on the same branch: design gate gains exit 12 `SIGN-IN REQUIRED` (three-state whoami probe, unknown → 11 with diagnostic, explicit "proceed without 21st" only skip); challenge round dropped my shared-helper idea (would break 4 fixture suites + change installer semantics) and my in-session `export TWENTYFIRST_TOKEN` remedy (env does not persist across tool calls). Executor DONE first pass, GATE 0 MET, verifier CONFORME 7/7, security PASS, 8/8 hermetic. Gate now exits 12 live here until `21st login`.
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
|
## 2026-09-28 — design gate asks for `21st login` and waits (feature/skill-catalog-prune)
|
||||||
|
User: "si on veut l'utiliser, on demande à l'utilisateur de se log, plus simple que
|
||||||
|
dire c'est pas logged on utilise pas… on demande de log si c'est pas fait et on
|
||||||
|
attend". Contract `.claude/tasks/contracts/2026-09-28-21st-signin-gate-1215.md`.
|
||||||
|
- [x] S1 three-state probe `twentyfirst_auth_state` INLINE in design-tool-gate.sh
|
||||||
|
(challenge r2 dropped the shared helper: install-plugins/toggle-external keep
|
||||||
|
their own semantics); `in` (TWENTYFIRST_TOKEN / API_KEY_21ST, or whoami
|
||||||
|
"Logged in as") / `out` (exact "Not logged in") / `unknown:whoami: rc=…`
|
||||||
|
→ exit 11 with a CLI-specific remedy; exit 12 `SIGN-IN REQUIRED`;
|
||||||
|
`DESIGN_GATE_REPO_OVERRIDE`; hermetic suite 8/8 (stub control, in, out,
|
||||||
|
token, absent, INCOMPLETE wins, unknown ×2).
|
||||||
|
- [x] S2 design-gate.md §3 branch 12: STOP, ask `! 21st login` (or any terminal
|
||||||
|
on this machine), END THE TURN, re-run on reply; explicit "proceed without
|
||||||
|
21st" = the only skip, stated visibly, not re-asked in the run; no in-session
|
||||||
|
token export; §4 resume path; feat/bugfix STEP 0.5 name SIGN-IN REQUIRED.
|
||||||
|
- [x] S3 plan r1→r3 (3 challengers + confirmation), executor DONE, GATE 0 MET,
|
||||||
|
verifier CONFORME 7/7, security PASS. Live on this machine: gate exits 12
|
||||||
|
until `21st login`. Committed in place on feature/skill-catalog-prune.
|
||||||
|
|
||||||
## 2026-09-28 — skill-catalog prune, tier 1 (feature/skill-catalog-prune)
|
## 2026-09-28 — skill-catalog prune, tier 1 (feature/skill-catalog-prune)
|
||||||
User go after the 5-agent duplicate audit (150 skills, 53.5k chars of descriptions,
|
User go after the 5-agent duplicate audit (150 skills, 53.5k chars of descriptions,
|
||||||
78 listed name-only in session = listing budget exceeded). Contract
|
78 listed name-only in session = listing budget exceeded). Contract
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# CONTRACT — 21st-signin-gate
|
||||||
|
- date: 2026-09-28 | flow: feat (ad-hoc dispatch, /feat gates replayed by the orchestrator) | branch: feature/skill-catalog-prune (working branch, commit in place)
|
||||||
|
- status: active
|
||||||
|
|
||||||
|
## REQUEST (verbatim — IMMUTABLE)
|
||||||
|
> Il faudrait pour 21st. Que, si on veut l'utiliser. Alors on demande à l'utilisateur de se log. Plus simple que de dire ah bah c'est pas logged on utilise pas. Donc ajoute ça quelque part, quand on detect qu'on a besoin de 21st, on demande de log si c'est pas fait et on attend
|
||||||
|
|
||||||
|
## CLARIFICATIONS
|
||||||
|
- Pass A: none — request complete. "Detect we need 21st" = the design gate (lib/design-gate.md → lib/design-tool-gate.sh), the single place the 21st CLI is required (GATE-BLOCK of design.profile); the 21st skills themselves are machine-owned (`21st skills install`) and are not edited.
|
||||||
|
- Pass B: no visible / public-name / scope choice left open — the gate message wording follows the gate's existing style, the exit code and the helper file are internal. Proceeds silently.
|
||||||
|
- [challenge 2026-09-28, 3 lenses: simplicity CONCERNS(1), correctness FATAL(2), robustness FATAL(4); every BLOCKER/MAJOR closed by a named plan change, r2] (a) NO shared helper: the predicate lives inline in lib/design-tool-gate.sh, toggle-external.sh and install-plugins.sh are untouched (their inline checks keep their own semantics); (b) three-state predicate `in` / `out` (exact "Not logged in" sentence) / `unknown` (rc≠0, timeout, unexpected line) → `unknown` surfaces as exit 11 with the raw diagnostic, never as the sign-in remedy; (c) no in-session `export TWENTYFIRST_TOKEN` remedy (env does not persist across tool calls, secrets stay out of the transcript) — the env var is honored when already present; (d) explicit user opt-out "proceed without 21st", stated visibly, scoped to the run; silent skip forbidden.
|
||||||
|
- [confirmation pass 2026-09-28, robustness CONCERNS(1), all closed by named changes, r3] unknown diagnostic pinned to `whoami: rc=<rc> <line>` with a CLI-specific remedy in the 11 block; stdout-only classification, `</dev/null`, rc captured under pipefail (test proves rc≠0 beats the sentence); hermeticity precondition on the sanitized PATH; MIRROR note at both sites; doc offers any terminal on this machine and does not re-ask after an explicit opt-out; `API_KEY_21ST` honored next to `TWENTYFIRST_TOKEN` (the CLI's second token env).
|
||||||
|
- Sign-in predicate = the CLI's own auth paths: `TWENTYFIRST_TOKEN` or `API_KEY_21ST` non-empty, or `21st whoami` first line starting with `Logged in as ` (local token read, no network; same sentence lib/toggle-external.sh:245 and install-plugins.sh:1059 test today). `whoami` wrapped in `timeout 15`; any other answer = `unknown`.
|
||||||
|
- Waiting = the orchestrator asks the user to run `! 21st login` in the session (browser flow) and ENDS THE TURN; on the user's reply it re-runs the gate before continuing. The agent never runs `21st login` itself (opens a browser, needs the human). A signed-out 21st is never treated as absent and its steps are never skipped.
|
||||||
|
- Functions ≤ 25 logic lines, 80-char lines; shellcheck clean; hermetic tests neutralize the real machine (`HOME` and `PATH` point into the fixture so `ensure_21st_on_path` cannot find the real CLI).
|
||||||
|
- Executors never run `21st login`, `profile.sh set|apply|reset`, `claude plugin …`, never commit.
|
||||||
|
|
||||||
|
## ACCEPTANCE CRITERIA
|
||||||
|
1. The three-state predicate lives in the gate script only; toggle-external.sh and install-plugins.sh are byte-identical to HEAD. [challenge r2]
|
||||||
|
CHECK: grep -q '^twentyfirst_auth_state()' lib/design-tool-gate.sh && grep -q 'DESIGN_GATE_REPO_OVERRIDE' lib/design-tool-gate.sh && git diff --quiet HEAD -- lib/toggle-external.sh install-plugins.sh && [ ! -e lib/twentyfirst-auth.sh ] && echo GATE_ONLY
|
||||||
|
EXPECT: GATE_ONLY
|
||||||
|
EVIDENCE: MET exit=0 marker-found :: GATE_ONLY
|
||||||
|
2. Live gate on this machine (21st installed, not signed in, no TWENTYFIRST_TOKEN): exit 12, output names `21st login`, and does NOT claim INCOMPLETE nor READY.
|
||||||
|
CHECK: env -u TWENTYFIRST_TOKEN bash lib/design-tool-gate.sh >/tmp/dtg.out 2>&1; rc=$?; cat /tmp/dtg.out; [ "$rc" = 12 ] && grep -q '21st login' /tmp/dtg.out && grep -q 'SIGN-IN REQUIRED' /tmp/dtg.out && ! grep -q 'INCOMPLETE' /tmp/dtg.out && ! grep -qE 'toolchain: READY' /tmp/dtg.out && echo LIVE_SIGNIN_12
|
||||||
|
EXPECT: LIVE_SIGNIN_12
|
||||||
|
EVIDENCE: MET exit=0 marker-found :: design toolchain: SIGN-IN REQUIRED — 21st CLI installed, not signed in ask the user to run in this session: ! 21st login (browser flow, save…
|
||||||
|
3. Hermetic suite green: stub control; signed-in → 0 READY; signed-out → 12 with `21st login`; TWENTYFIRST_TOKEN or API_KEY_21ST set → 0; CLI absent → 10 INCOMPLETE; INCOMPLETE wins over signed-out; unknown whoami answer (garbage rc 0, or the signed-out sentence with rc 3) → 11 with `whoami: rc=` diagnostic, without the sign-in remedy and without the claude-unreachable remedy. [challenge r2, r3]
|
||||||
|
CHECK: out=$(make test suite=lib/tests/design-tool-gate.test.sh 2>&1); echo "$out" | grep -qE 'FAIL=[1-9]' && { echo "$out" | tail -15; exit 1; }; for k in STUB_CONTROL SIGNED_IN_READY SIGNED_OUT_12 TOKEN_READY CLI_ABSENT_10 INCOMPLETE_WINS UNKNOWN_11; do echo "$out" | grep -q "PASS $k" || { echo "missing PASS $k"; exit 1; }; done; echo "$out" | grep -qE 'PASS=[1-9]' && echo SUITE_GREEN
|
||||||
|
EXPECT: SUITE_GREEN
|
||||||
|
EVIDENCE: MET exit=0 marker-found :: SUITE_GREEN
|
||||||
|
4. Gate doc and its two citers carry the new branch: design-gate.md documents exit 12 / SIGN-IN REQUIRED with `! 21st login`, "end the turn", re-run, the explicit opt-out "proceed without 21st", and never an in-session `export TWENTYFIRST_TOKEN`; feat and bugfix STEP 0.5 name SIGN-IN REQUIRED. [challenge r2]
|
||||||
|
CHECK: grep -q 'SIGN-IN REQUIRED' lib/design-gate.md && grep -q '! 21st login' lib/design-gate.md && grep -qi 'end the turn' lib/design-gate.md && grep -qi 'proceed without 21st' lib/design-gate.md && ! grep -qiE 'export TWENTYFIRST_TOKEN' lib/design-gate.md lib/design-tool-gate.sh && grep -q 'SIGN-IN REQUIRED' skills/feat/SKILL.md && grep -q 'SIGN-IN REQUIRED' skills/bugfix/SKILL.md && echo DOC_WIRED
|
||||||
|
EXPECT: DOC_WIRED
|
||||||
|
EVIDENCE: MET exit=0 marker-found :: DOC_WIRED
|
||||||
|
5. shellcheck clean on the two touched shell files; doctrine-citers and design-toolchain-reminder suites still green.
|
||||||
|
CHECK: shellcheck lib/design-tool-gate.sh lib/tests/design-tool-gate.test.sh && for s in doctrine-citers design-toolchain-reminder; do out=$(make test suite=lib/tests/$s.test.sh 2>&1) || { echo "$s rc"; exit 1; }; echo "$out" | grep -qE 'FAIL=[1-9]' && { echo "$s FAIL"; exit 1; }; done; echo SHELL_SUITES_OK
|
||||||
|
EXPECT: SHELL_SUITES_OK
|
||||||
|
EVIDENCE: MET exit=0 marker-found :: SHELL_SUITES_OK
|
||||||
|
6. When the gate is also INCOMPLETE (a blocking tool missing), the INCOMPLETE verdict (exit 10) wins; the sign-in state surfaces on the re-run after `/profile design` (hermetic case `INCOMPLETE_WINS`). [challenge r2: no extra line]
|
||||||
|
CHECK: out=$(make test suite=lib/tests/design-tool-gate.test.sh 2>&1); echo "$out" | grep -q 'PASS INCOMPLETE_WINS' && echo PRECEDENCE_OK
|
||||||
|
EXPECT: PRECEDENCE_OK
|
||||||
|
EVIDENCE: MET exit=0 marker-found :: PRECEDENCE_OK
|
||||||
|
7. CHANGELOG `[Unreleased]` names the new gate state and the remedy.
|
||||||
|
|
||||||
|
## FILE SCOPE
|
||||||
|
- lib/design-tool-gate.sh
|
||||||
|
- lib/design-gate.md, skills/feat/SKILL.md, skills/bugfix/SKILL.md (STEP 0.5 bullet only), CHANGELOG.md
|
||||||
|
- lib/tests/design-tool-gate.test.sh (new)
|
||||||
|
- Orchestrator-only: .claude/tasks/**, .claude/memory/**
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
# PLAN — 21st-signin-gate (feat, ad-hoc dispatch) — r3 (after confirmation pass)
|
||||||
|
- r3 closes the confirmation pass (robustness CONCERNS(1)): MAJOR 1 — the
|
||||||
|
unknown diagnostic format is pinned to `unknown:whoami: rc=<rc> <line>`
|
||||||
|
(rendered `21st (whoami: rc=3 …)`) and the 11 block prints a CLI-specific
|
||||||
|
remedy for 21st instead of `claude plugin list`; MINOR 2 — classify on
|
||||||
|
stdout only (`2>/dev/null`), stderr never enters the match; MINOR 3 — rc
|
||||||
|
captured through `if line="$(…)"` under pipefail, and the `fail` stub prints
|
||||||
|
the signed-out sentence AND exits 3 so the test proves rc≠0 wins; MINOR 4 —
|
||||||
|
`</dev/null` on the whoami call (the gate loop reads the profile on stdin);
|
||||||
|
MINOR 5 — hermeticity precondition = `! PATH=/usr/bin:/bin command -v 21st`
|
||||||
|
and no `/usr/local/bin/21st`; MINOR 6 — MIRROR note at both sites: the auth
|
||||||
|
state is gate-only, `profile.sh:skill_status()` has no counterpart; MINOR 7
|
||||||
|
— doc offers "or run `21st login` in any terminal on this machine, then
|
||||||
|
reply"; MINOR 8 — a 12 after an explicit opt-out in the same run is
|
||||||
|
reported once, not re-asked. Also honors `API_KEY_21ST` next to
|
||||||
|
`TWENTYFIRST_TOKEN` (the CLI's second token env, per its getToken).
|
||||||
|
- date: 2026-09-28 | contract: contracts/2026-09-28-21st-signin-gate-1215.md
|
||||||
|
- branch: feature/skill-catalog-prune (working branch → commit in place)
|
||||||
|
- executor: 1 feater (sonnet-pinned)
|
||||||
|
- r2 closes: simplicity MAJOR 1 (no shared helper — predicate inline in the
|
||||||
|
gate, toggle-external.sh and install-plugins.sh untouched, which also
|
||||||
|
voids correctness BLOCKER 1 / MAJOR 2 / MINOR 3 and robustness BLOCKER 1 /
|
||||||
|
MINOR 5-6), robustness MAJOR 2 (three-state predicate: `in` / `out` on the
|
||||||
|
exact "Not logged in" sentence / `unknown` → exit 11 with the raw
|
||||||
|
diagnostic, never the sign-in remedy), MAJOR 3 (no in-session
|
||||||
|
`export TWENTYFIRST_TOKEN` remedy; token path documented as shell profile +
|
||||||
|
restart), MAJOR 4 (explicit user opt-out "proceed without 21st", scoped to
|
||||||
|
the run, stated visibly; silent skip stays forbidden), correctness MINOR 4
|
||||||
|
(fake profile.sh executable, per-case output), MINOR 5 / robustness MINOR 7
|
||||||
|
(`also unverified` line in the 12 block), MINOR 6 (design-gate.md §4 names
|
||||||
|
the resume-after-sign-in path), robustness MINOR 8 (test asserts no
|
||||||
|
system-wide 21st first), simplicity MINOR 2 (no extra INCOMPLETE line, the
|
||||||
|
precedence test case stays), MINOR 4 (helper cases dropped), MINOR 5
|
||||||
|
(feat/bugfix bullets point at design-gate.md §3, no restated remedy).
|
||||||
|
|
||||||
|
## Ground truth (verified 2026-09-28)
|
||||||
|
- `lib/design-tool-gate.sh` (`set -euo pipefail`) checks the `21st` cli entry
|
||||||
|
with `command -v` only (`tool_active`, case `cli`). `ensure_21st_on_path`
|
||||||
|
probes `~/.local/bin`, `/usr/local/bin` and `~/.nvm/versions/node/*/bin`.
|
||||||
|
Exit codes: 0 ready · 11 ready-but-unverified · 10 incomplete · 2 error.
|
||||||
|
`REPO` is derived from the script path (no override); `PROFILE_SH` has
|
||||||
|
`DESIGN_GATE_PROFILE_SH`; `[ -x "$PROFILE_SH" ]` is required.
|
||||||
|
- `21st whoami` is a local token read, rc 0 both ways: `Logged in as <user>
|
||||||
|
(saved …).` or `Not logged in. Run \`21st login\`, or set TWENTYFIRST_TOKEN.`
|
||||||
|
The CLI is `#!/usr/bin/env node` under nvm: with a sanitized PATH it can
|
||||||
|
fail (rc≠0, "env: node: No such file") — that is NOT "signed out".
|
||||||
|
On this machine: installed, not signed in; the live gate today returns 0.
|
||||||
|
- Consumers: lib/design-gate.md §3 (verdict branches) and §4 (resume list);
|
||||||
|
skills/feat and skills/bugfix STEP 0.5 bullets; hotfix skips the gate.
|
||||||
|
- No hermetic test covers design-tool-gate.sh today. Existing suites copy
|
||||||
|
toggle-external.sh / profile.sh into fixtures — NOT touched by this plan.
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
1. `lib/design-tool-gate.sh`:
|
||||||
|
- `REPO="${DESIGN_GATE_REPO_OVERRIDE:-$(cd -P … && pwd)}"` (fixture seam,
|
||||||
|
same idiom as PROFILE_REPO_OVERRIDE). Nothing new is sourced.
|
||||||
|
- New function `twentyfirst_auth_state` (≤ 25 logic lines): echoes `in`
|
||||||
|
when `${TWENTYFIRST_TOKEN:-}` or `${API_KEY_21ST:-}` is non-empty; else
|
||||||
|
`if line="$(timeout 15 21st whoami 2>/dev/null </dev/null | head -1)";
|
||||||
|
then rc=0; else rc=$?; fi` (pipefail is set: rc is 21st's rc, 124 on
|
||||||
|
timeout; stdout only, stderr never enters the match; stdin closed so a
|
||||||
|
CLI reading stdin cannot eat the gate's profile loop). `in` when rc=0
|
||||||
|
and the line starts with `Logged in as `; `out` when rc=0 and the line
|
||||||
|
starts with `Not logged in`; otherwise exactly
|
||||||
|
`unknown:whoami: rc=<rc> <first 60 chars of line, or "no output">`.
|
||||||
|
Comment: the token envs are honored when already present (a shell-
|
||||||
|
profile export), never requested in-session.
|
||||||
|
- `tool_active` case `cli`: `command -v` fails → `inactive`; name `21st` →
|
||||||
|
`case "$(twentyfirst_auth_state)"` in `in` → `active`, `out` →
|
||||||
|
`signedout`, `unknown:*` → `unknown:<diag>`; other cli names → `active`.
|
||||||
|
- Main loop: state `signedout` → `signedout+=("$name")`; state `unknown:*`
|
||||||
|
→ `unverified_cli+=("$name (${state#unknown:})")`, rendered
|
||||||
|
`21st (whoami: rc=3 Something unexpected)`; the existing bare `unknown`
|
||||||
|
(claude unreachable) keeps filling `unverified`.
|
||||||
|
- Verdict order: blocking/manual → INCOMPLETE exit 10 (block unchanged).
|
||||||
|
Else signedout non-empty → print
|
||||||
|
`design toolchain: SIGN-IN REQUIRED — 21st CLI installed, not signed in`
|
||||||
|
` ask the user to run in this session: ! 21st login (browser flow, saves a local token)`
|
||||||
|
` then re-run this gate before any 21st step — never skip 21st silently`
|
||||||
|
plus the `also unverified` line(s) when either unverified array is
|
||||||
|
non-empty; exit 12.
|
||||||
|
Else unverified or unverified_cli non-empty → 11: one helper
|
||||||
|
`print_unverified` (≤ 25 logic lines) prints, for `unverified`, the
|
||||||
|
existing claude-unreachable block (`claude plugin list` remedy) and, for
|
||||||
|
`unverified_cli`, ` 21st could not answer: <diag> — a CLI runtime/PATH
|
||||||
|
problem (node under nvm?), not a sign-in problem; fix it, then re-run`.
|
||||||
|
The 10 and 12 blocks reuse the same helper for their `also unverified`
|
||||||
|
lines so no block ever says "claude CLI unreachable" about 21st. Else 0.
|
||||||
|
- Header comment: exit codes line gains `12 = sign-in required (21st)`;
|
||||||
|
the `required-manual` paragraph gets two lines on the three auth states;
|
||||||
|
the MIRROR sentence ("tool_active MIRRORS profile.sh:skill_status()")
|
||||||
|
gains "except the 21st auth state, gate-only, no skill_status
|
||||||
|
counterpart".
|
||||||
|
2. `lib/design-gate.md`:
|
||||||
|
- Exit-codes line: add `12 = sign-in required (21st installed, signed out)`.
|
||||||
|
- §3 new branch **12 / `SIGN-IN REQUIRED`** → STOP. Relay the script's
|
||||||
|
block. Ask the user to run `! 21st login` (the `!` prefix runs it in this
|
||||||
|
session, browser flow, saves a local token). END THE TURN and wait. On
|
||||||
|
the user's reply, re-run `design-tool-gate.sh` before any 21st step:
|
||||||
|
READY → continue; still 12 → ask again once, then offer the opt-out.
|
||||||
|
Explicit refusal — the user answers "proceed without 21st" (or words to
|
||||||
|
that effect) → say visibly `21st skipped for this run at your request`
|
||||||
|
and continue with the rest of the toolchain, 21st steps left out. Never
|
||||||
|
skip silently ("not logged in, so we don't use it" is the failure this
|
||||||
|
branch closes). Never run `21st login` yourself. `TWENTYFIRST_TOKEN` is
|
||||||
|
a shell-profile setting followed by a session restart, never an
|
||||||
|
in-session `export` (tool calls do not share a shell, and a secret does
|
||||||
|
not belong in the transcript).
|
||||||
|
- §3 **11** bullet: a `21st (whoami: rc=… …)` entry means the CLI could
|
||||||
|
not answer (runtime/PATH problem, node under nvm), so the remedy is the
|
||||||
|
diagnostic, not a sign-in; relay the script's own line.
|
||||||
|
- §3 **12** bullet also says: "or run `21st login` in any terminal on this
|
||||||
|
machine, then reply" (the token is a local file, any terminal works;
|
||||||
|
`! …` in-session is the convenient form, not the only one); and: after
|
||||||
|
an explicit opt-out, a later 12 in the same run is reported in one line,
|
||||||
|
never re-asked.
|
||||||
|
- §4 first paragraph: "(READY, after the user ran `/profile design`, or
|
||||||
|
after the sign-in re-run returns READY)".
|
||||||
|
- §IMPORTANT 21st bullet: add "signed out → exit 12: ask `! 21st login`,
|
||||||
|
wait, re-run; explicit opt-out only". §IMPORTANT MIRROR bullet: add
|
||||||
|
"except the 21st auth state: gate-only, no skill_status counterpart".
|
||||||
|
3. `skills/feat/SKILL.md` and `skills/bugfix/SKILL.md` STEP 0.5 bullet →
|
||||||
|
"If signals found → run `design-tool-gate.sh`; INCOMPLETE → tell the user
|
||||||
|
to run `/profile design`; SIGN-IN REQUIRED → design-gate.md §3 (ask
|
||||||
|
`! 21st login`, wait) before proceeding." No restated remedy beyond that.
|
||||||
|
4. `lib/tests/design-tool-gate.test.sh` (hermetic, `set -u`, `check` helper,
|
||||||
|
`trap 'rm -rf "$WORK"' EXIT`, style of lib/tests/skill-routing-census.test.sh):
|
||||||
|
- Precondition, loud: `! PATH=/usr/bin:/bin command -v 21st` and
|
||||||
|
`[ ! -e /usr/local/bin/21st ]` (the two places the sanitized test PATH
|
||||||
|
and `ensure_21st_on_path` could still find a real CLI) else print
|
||||||
|
`FAIL precondition: system-wide 21st present, CLI_ABSENT case not
|
||||||
|
hermetic` and count a FAIL.
|
||||||
|
- Fixture `$WORK/repo`: `lib/profiles/design.profile` holding
|
||||||
|
`# GATE-BLOCK: 21st ghost-skill` and entries `21st cli`,
|
||||||
|
`ghost-skill external`; `lib/profile.sh` = an executable stub that
|
||||||
|
`cat`s `$WORK/plain.txt` for `show design --plain` (per case the test
|
||||||
|
writes `cli\t21st` alone, or `cli\t21st` + `external\tghost-skill`);
|
||||||
|
`skills/` empty. `$WORK/bin/21st` = executable stub: `whoami` prints per
|
||||||
|
`$FAKE_21ST_MODE`: `in` → `Logged in as tester (saved locally).`,
|
||||||
|
`out` → `Not logged in. Run \`21st login\`, or set TWENTYFIRST_TOKEN.`,
|
||||||
|
`garbage` → `Something unexpected` (rc 0), `fail` → prints the exact
|
||||||
|
signed-out sentence AND exits 3 (proves rc≠0 overrides the sentence).
|
||||||
|
- Every gate run: `env -u TWENTYFIRST_TOKEN HOME=$WORK/home
|
||||||
|
PATH=$WORK/bin:/usr/bin:/bin DESIGN_GATE_REPO_OVERRIDE=$WORK/repo
|
||||||
|
DESIGN_GATE_PROFILE_SH=$WORK/repo/lib/profile.sh FAKE_21ST_MODE=<mode>
|
||||||
|
bash "$ROOT/lib/design-tool-gate.sh"` (CLAUDE_BIN irrelevant: no
|
||||||
|
plugin/mcp entry in the fixture).
|
||||||
|
- Stub positive control first: the stub prints the expected sentence for
|
||||||
|
`in` and `out` (`PASS STUB_CONTROL`).
|
||||||
|
- Cases, each `PASS <NAME>`: `SIGNED_IN_READY` (rc 0, `READY`);
|
||||||
|
`SIGNED_OUT_12` (rc 12, `SIGN-IN REQUIRED`, `21st login`, no
|
||||||
|
`INCOMPLETE`); `TOKEN_READY` (mode out + `TWENTYFIRST_TOKEN=x` → rc 0);
|
||||||
|
`CLI_ABSENT_10` (PATH without `$WORK/bin` → rc 10, `INCOMPLETE`);
|
||||||
|
`INCOMPLETE_WINS` (plain adds ghost-skill, mode out → rc 10,
|
||||||
|
`INCOMPLETE`, no `SIGN-IN REQUIRED` line); `UNKNOWN_11` (mode garbage →
|
||||||
|
rc 11, output has `whoami: rc=0` and `Something unexpected`, lacks
|
||||||
|
`21st login` and lacks `claude CLI unreachable`; mode fail → rc 11 with
|
||||||
|
`whoami: rc=3`). `TOKEN_READY` also checks `API_KEY_21ST=x` alone → rc 0.
|
||||||
|
Summary `PASS=n FAIL=m`, rc 1 on any FAIL.
|
||||||
|
5. CHANGELOG `[Unreleased]` → Added: design gate `SIGN-IN REQUIRED` (exit 12)
|
||||||
|
when the 21st CLI is installed but signed out — the agent asks for
|
||||||
|
`! 21st login` and waits, explicit opt-out only; unknown whoami answers
|
||||||
|
surface as unverified with the diagnostic.
|
||||||
|
|
||||||
|
## Edge cases
|
||||||
|
- `21st whoami` hang: `timeout 15` → rc 124 → `unknown`, exit 11 with the
|
||||||
|
diagnostic (not a sign-in loop).
|
||||||
|
- `TWENTYFIRST_TOKEN` set but invalid: the CLI decides at call time; the gate
|
||||||
|
honors the env var as `in` (documented).
|
||||||
|
- INCOMPLETE and signed out at once: 10 wins by construction (the re-run
|
||||||
|
after `/profile design` returns 12); no extra line.
|
||||||
|
- The fixture never sees the real `~/.nvm` (HOME redirected) and the test
|
||||||
|
fails loudly if a system-wide 21st exists.
|
||||||
|
- `set -euo pipefail`: the `whoami` capture must not abort the script on a
|
||||||
|
nonzero rc (run inside `if`, or `|| true`).
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
- lib/tests/design-tool-gate.test.sh (new); `make test suite=` for
|
||||||
|
doctrine-citers, design-toolchain-reminder (unchanged suites, stay green).
|
||||||
|
- shellcheck lib/design-tool-gate.sh lib/tests/design-tool-gate.test.sh.
|
||||||
|
|
||||||
|
## Disposition (RELATED MEMORY)
|
||||||
|
- honors BDR-025 — GATE-BLOCK single source untouched; a state is added, not
|
||||||
|
a scope.
|
||||||
|
- honors BDR-093 — 21st auth is `21st login` / TWENTYFIRST_TOKEN, no key, no MCP.
|
||||||
|
- honors LRN-102 — the STOP asks in the turn's final text and ends the turn.
|
||||||
|
- honors "ask, don't guess" — a signed-out tool becomes a question to the
|
||||||
|
human, and an explicit refusal is an answer, never a silent skip.
|
||||||
|
- honors LRN-096 (vacuous guard class) — an unknown answer is surfaced, not
|
||||||
|
swallowed as "signed out".
|
||||||
Reference in New Issue
Block a user