Add experimental entire learn command with embedded markdown + release-time regen#1146
Add experimental entire learn command with embedded markdown + release-time regen#1146alishakawaguchi wants to merge 19 commits into
entire learn command with embedded markdown + release-time regen#1146Conversation
Renders a state-aware Entire CLI tour by piping the discovered cobra command tree, the labs registry, and the user's repo state through their locally-installed TextGenerator agent (claude, codex, gemini, cursor, copilot, or any external entire-agent-* plugin that declares text_generator). The agent owns capability grouping, titles, and blurb prose; the prompt encodes only format rules, stage routing, and the two URL-bound blurbs (external-agents, skills) that the CLI has no way to derive on its own. Registered as a top-level hidden command (mirroring `entire review`) and advertised under `entire labs`. While generating, a bubbletea spinner runs in interactive terminals; on completion the markdown goes through mdrender for terminal styling and stays as raw markdown for pipelines. Adds `mdrender.RenderForWriterWithOverride` so commands can tweak the shared palette without forking it. `entire learn` uses it to recolor H2 to violet (#a78bfa) so the section headers stand apart from the orange inline-code, list-item, and accent surfaces that already dominate the rendered tour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renames the experimental command from `learn` to `tour` and tightens its Short to "Tour the Entire CLI" (down from the longer "tailored to your repo state" framing). Internal symbols, files, and the labs registry entry follow. Mirrors the registration shape of `entire review`: top-level Hidden command advertised under `entire labs`. Adds a `--latest` flag that swaps the state-aware tour for a quick "what's new" digest. Fetches the most recent post from https://entire.io/feed.xml, hard-caps the body via `io.LimitReader` to bound memory against malformed feeds, and pipes the parsed item through the same TextGenerator + spinner + glamour pipeline. The spinner title swaps to "Fetching the latest dispatch" while keeping the same "This can take a moment." subtitle. Cleanups from the simplify pass: - Reuse jsonutil.MarshalIndentWithNewline for prompt payload encoding; drop the hand-rolled placeholderWriter shim. - io.LimitReader cap on the RSS body so a malformed feed can't pull arbitrary memory. - Drop an unused `width` field from the spinner model; have View skip the subtitle line when empty. - Flatten cancellation handling in executeTour with an early return. - Internal symbols renamed from "New" to "Latest" so they match the user-facing flag (GenerateLatest, BuildLatestPrompt, latestPromptSystem, runTourGenerateLatest). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switches the default tour from an agent call at runtime to a
pre-rendered markdown file embedded in the binary. Wall time drops
from a multi-second agent call to ~1s (process startup + glamour
render) with no network or token cost per user.
Three rendering modes:
- Workflow / first-capture: serve embedded/tour.md via go:embed.
First-capture stage appends a 3-line tail noting that checkpoints
will appear once the user runs their agent and commits.
- Setup / agent-install: render hand-written prose (4-6 lines) that
walks the user through `entire enable` / `entire agent add`.
- --latest: unchanged, still hits the agent + entire.io blog feed
live since "latest post" is genuinely time-varying.
The embedded tour content is NOT committed. The repo carries a stub
at cmd/entire/cli/tour/embedded/tour.md that explains it's a build-
time placeholder. The release pipeline overwrites it with real
content before GoReleaser builds:
- mise.toml gains a `tour:regenerate` task that runs
`go run ./cmd/entire tour --regenerate > .../tour.md`. This
invokes the agent-driven path via a hidden `--regenerate` flag
on `entire tour`.
- .github/workflows/release.yml installs the Claude CLI and runs
`mise run tour:regenerate` before GoReleaser, with a sanity
check that the output contains markdown headers (so a stale
auth or a transient agent error fails the release loudly
rather than silently shipping the stub).
Local devs see the stub on `entire tour` from a fresh checkout.
Running `mise run tour:regenerate` locally (with `claude` on PATH
and `ANTHROPIC_API_KEY` exported) refreshes it for the rest of the
session.
The TUI spinner now only runs for the agent-driven paths
(`--latest` and `--regenerate`); the embedded path returns fast
enough that no progress UI is needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code review fixes:
- Drop dead Result.State field (written 5 places, read 0).
- Tighten escapeForTags from four ReplaceAll calls to a single
case-insensitive regex covering '</POST>', '</post >', '</post\n>'.
- Cache settings via cachedTourSettingsLoader so ResolveState
doesn't re-load settings.json a second time per invocation.
- Discover external entire-agent-* plugins in ResolveTextGenerator
so users with no built-in claude/codex/etc. but an external
TextGenerator plugin still get a tour. Mirrors what
resolveCheckpointSummaryProvider in the cli package does.
- Align error wrap "regenerate tour with X" -> "generate tour
with X" so log/grep stays predictable across paths.
- Fix stale "output streams in" claim in tour --help — the path
is buffered, not streaming.
- Pin @anthropic-ai/claude-code to 2.1.132 in release.yml so the
agent's output format isn't a moving target.
Make tour regeneration non-blocking:
- continue-on-error: true on both the npm install and regen
steps. A flaky agent, missing ANTHROPIC_API_KEY, or an npm
registry blip no longer blocks the release; GoReleaser ships
the committed stub instead.
- 'Note tour regeneration outcome' step (if: always()) emits a
GitHub annotation so degraded releases are visible in the run
summary.
- 'Notify Slack of tour regeneration failure' step fires a
dedicated ⚠️ alert when steps.regen.outcome != 'success'.
The existing notify-slack job only fires on full-job failure
and would no longer trigger when regen-only fails.
Deferred (worth their own PRs):
- Spinner/TUI dedup with dispatch_tui.go — duplication is real,
but extraction is a separate refactor.
- checkpoint.HasCommitted short-circuit — biggest measurable
default-path win, but adds a new method to the checkpoint
package and should land with its own tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pass-2 Claude review:
- Slack regen-failure step now uses
`if: always() && steps.regenerate-tour.outcome == 'failure'`
so it fires only on actual regen failures, not on earlier-step
skips that leave outcome unset.
- mdrender fallback now writes a one-line breadcrumb to stderr
when glamour fails, instead of swallowing the error.
- Workflow step id renamed `regen` -> `regenerate-tour` for grep
parity with the mise task and cobra flag.
- Drop stale "original" word in regenerateFromAgent docstring.
Codex adversarial review (security-sensitive):
closingTagPattern (tour/prompt.go) — unicode bypass.
The earlier `(?i)</\s*(state|...)\s*>` regex was bypassed by
`</post>` (zero-width space inside tag name) and
`</post >` (NO-BREAK SPACE before close). Both could let
untrusted feed/cobra-tree content break out of its <state>
/<commands>/<labs>/<post> wrapper and confuse the model into
treating untrusted text as wrapper structure.
Fix: strip Unicode format chars (\p{Cf}) and C0/C1 controls
from the payload first, then run a regex extended with
[\s\p{Z}\p{Cf}]* whitespace so visible Unicode whitespace
doesn't slip past either.
StripControlSequences (tour/run.go) — terminal injection.
A compromised agent could embed ANSI/OSC/C1 control sequences
in the regen output. The output gets piped to disk, embedded
via go:embed, and shipped to every future user — one bad
release would persist malicious title-rewrites or fake
hyperlinks across the entire user base.
Fix: regenerateFromAgent now runs its rendered string through
StripControlSequences (CSI / OSC / other ESC sequences /
C0 except whitespace / DEL / C1) before returning.
mise tour:regenerate — atomic write + stronger validation.
The previous shell-redirect form truncated embedded/tour.md
*before* the agent finished. A transient agent failure left
the repo with an empty stub. The CI grep-only validation
accepted any single `^## ` line — a malicious or partial
response could pass.
Fix: the mise task now writes to a tempfile, validates >=4
`^## ` headers + the docs.entire.io footer, and only mvs
on success. CI step's redundant grep dropped.
Adds prompt_test.go covering the new escape-bypass and
control-stripping cases. 22 assertions in total.
Deferred (worth their own follow-ups):
- tour_tui.go ctx-cancellation join: when user hits ctrl+c the
Bubble Tea program returns immediately but the worker
goroutine keeps running. Needs a careful redesign of the
runGenerate -> tea.Quit handshake.
- claudecode.GenerateText subprocess cleanup: no WaitDelay /
process-group kill, so Ctrl+C on the parent may leak grand-
children. Touches the agent package, broader scope.
- Embedded markdown runtime sanitization (defense-in-depth
against a poisoned release artifact).
- ConfiguredProvider explicit-pin failure should hard-fail
rather than silently fall back to a different agent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pass-3 review caught a real security regression in the pass-2
escape fix: payloads like `</po<NBSP>st>` (NO-BREAK SPACE between
letters of the tag name) bypassed the escape because:
1. The strip step used `\p{Cf}` (format chars) only — NBSP is
`\p{Zs}` (space separator), so it survived.
2. The regex's `(state|commands|labs|post)` alternation is
literal; it can't match `po<NBSP>st`.
The pass-2 commit message specifically claimed to fix `</post >`
(NBSP outside the name) and did, but missed NBSP-mid-name.
Fix: replace the regex-based invisibleCharPattern with a
strings.Map-based stripInvisibles that drops:
- \p{Cf} (format chars) — zero-width spaces, RTL marks, etc.
- \p{Z} (separators) EXCEPT ASCII space — NBSP, NARROW NBSP,
IDEOGRAPHIC SPACE, ogham space, line/paragraph separators.
- C0 controls except \t \n \r, plus DEL.
- C1 controls (U+0080-U+009F).
ASCII space stays for legitimate prose; everything else gets
dropped on the way into the tag wrapper. strings.Map is also
faster than the regex on the legitimate-content common case
(single-pass, no transient buffer when nothing matches).
Adds six regression tests:
- NBSP / NARROW NBSP / IDEOGRAPHIC SPACE inside tag name
- Combined visible+invisible (NBSP and ZWSP) bypass
- Empty payload
- Idempotence on already-escaped input
Doc cleanup — stale "entire labs tour" references from before
the rename to top-level `entire tour`:
- tour_cmd.go's tourNoTextGeneratorMessage user-facing text
- tour/discovery.go's package doc
- mdrender/mdrender.go's StyleOverride example comment
- labs_test.go's test comment
Deferred (not security-relevant):
- StripControlSequences shared helper extraction with codex /
review packages — the existing CSI-only helpers in those
packages are narrower than this one; consolidation worth
its own PR.
- Mise task move from mise.toml inline run to a mise-tasks/
standalone script (style alignment with rest of repo).
- DCS/SOS/PM/APC sequence handling in StripControlSequences
(modern terminals don't render those payloads visibly).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pass-4 review caught four items worth applying:
- stripInvisibles docstring claimed the function "only runs on
the --regenerate path" — wrong. It runs on --latest too via
BuildLatestPrompt. Corrected to "the --latest and --regenerate
paths".
- stripControlSequences was exported with no out-of-package
caller. Pass-2 justified the export as "made testable" but
same-package tests can call unexported funcs. Unexported.
No external API impact.
- LOAD-BEARING: inline comment was an outlier tone (the cli
package uses IMPORTANT:/NOTE:/WARNING: about 30 places).
Normalized to IMPORTANT:.
- TestEscapeForTags_Idempotent's t.Run(in, ...) produced subtest
names like #00 (empty) and </post_> (NBSP rewritten to
underscore), which made failure output ambiguous. Switched
to fmt.Sprintf("%q", in) so subtests render as "" and
"</post >" — codepoint-faithful and grep-friendly.
Two prior unfixed items confirmed deferred (not regressed):
- Spinner/TUI dedup with dispatch_tui.go (architectural).
- dispatch's sanitizeDispatchPromptString uses a weaker BMP-
range list than stripInvisibles; consolidation worth its own
cross-package PR.
Pass-4 was the fourth review pass on this branch; subsequent passes
have been showing diminishing returns. Tour feature is ready to
ship — 35 unit tests pass, all known security findings addressed,
embedded path latency is ~1s instead of 5-15s.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
bugbot run |
There was a problem hiding this comment.
Pull request overview
Adds an experimental, hidden top-level entire tour command (discoverable via entire labs) that renders a state-aware CLI tour, primarily from embedded pre-rendered markdown, with optional agent-driven regeneration and a --latest blog-digest mode. It also wires release-time regeneration into CI and extends markdown rendering to support per-command style overrides.
Changes:
- Introduces the
tourpackage (repo state classification, cobra surface discovery, prompt construction + sanitization, RSS fetch, agent selection, and embedded markdown plumbing). - Adds the
entire tourcommand + spinner TUI integration, and lists it underentire labs. - Adds
mise run tour:regenerateand a best-effort release workflow step to regenerateembedded/tour.mdbefore GoReleaser.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
mise.toml |
Adds an atomic, validated tour:regenerate task for release/local regeneration. |
cmd/entire/cli/tour/state.go |
Implements repo “stage” classification and TextGenerator agent resolution (incl. external plugins). |
cmd/entire/cli/tour/run.go |
Implements tour generation paths (embedded vs agent), --latest digest, and control-sequence stripping for persisted output. |
cmd/entire/cli/tour/prompt.go |
Builds the system prompts and escapes/untrusts payloads (tag-escape + invisible stripping). |
cmd/entire/cli/tour/prompt_test.go |
Adds table tests for tag-escape hardening and control-sequence stripping. |
cmd/entire/cli/tour/embedded/tour.md |
Adds a committed stub placeholder to be overwritten at release time. |
cmd/entire/cli/tour/embedded.go |
Embeds the tour markdown and provides static text for setup/agent-install stages. |
cmd/entire/cli/tour/discovery.go |
Discovers a user-facing cobra command surface for prompting (filters hidden/deprecated/plumbing). |
cmd/entire/cli/tour/discovery_test.go |
Tests discovery filtering and long-help paragraph trimming. |
cmd/entire/cli/tour/blog.go |
Fetches/parses the RSS feed to get the latest blog post for --latest. |
cmd/entire/cli/tour_tui.go |
Adds a bubbletea spinner program used while agent-backed generation runs. |
cmd/entire/cli/tour_cmd.go |
Adds the hidden entire tour cobra command, wiring options, rendering, and error translation. |
cmd/entire/cli/root.go |
Registers tour at the top level (hidden). |
cmd/entire/cli/mdrender/mdrender.go |
Adds Render*WithOverride hooks to allow palette tweaks (used to recolor H2 in tour). |
cmd/entire/cli/labs.go |
Adds tour to the labs registry; improves unknown-topic hinting and aligns listing width dynamically. |
cmd/entire/cli/labs_test.go |
Updates canonical-path verification to use Invocation segments (supports multi-segment entries). |
.github/workflows/release.yml |
Adds best-effort tour regeneration (install Claude CLI, run task, annotate + Slack notify on failure). |
- ST1005: multi-line user-facing messages were stuffed into
errors.New() and tripped the "error strings end with
punctuation" rule. Reshaped translateTourError to print the
message to its writer first and return a SilentError with a
short go-convention message — same shape recap.go uses.
- exhaustive: switch on tour.Stage in Generate() was missing
StageNotGitRepo / StageFirstCapture / StageWorkflow cases.
Added explicit branches; the function is now a clean
stage-dispatch.
- nilerr: ResolveState's "not in a git repo -> StageNotGitRepo"
branch was returning nil despite paths.WorktreeRoot reporting
an error. The behavior is intentional (not-a-repo is a stage,
not a runtime error), so kept the return shape and added a
nolint:nilerr directive with explanation.
- wrapcheck: marshalIndentNoHTMLEscape returned an unwrapped
error from jsonutil.MarshalIndentWithNewline. Wrapped with
fmt.Errorf("marshal payload: %w", err).
- mise.toml multi-line script lint: tour:regenerate's inline
run = '''...''' (16+ lines) violated the lint rule for long
inline scripts. Moved to mise-tasks/tour/regenerate as a
standalone shell script with #MISE description header,
matching the rest of the repo's mise-tasks/ convention.
- shellcheck SC3040: the new tour/regenerate script used
`set -euo pipefail` (bashism). Switched to `set -e` and
dropped pipefail/-u — no pipelines or unset-var lookups
that benefit from them.
- gofmt drift on root.go, tour_cmd.go, tour/blog.go, tour/run.go
fixed via `mise run fmt`.
- errors import was missing on tour/prompt.go (errors.New used
in BuildLatestPrompt). Added.
35 unit tests still pass. golangci-lint reports 0 issues.
Co-Authored-By: Claude <noreply@anthropic.com>
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
3 issues from previous reviews remain unresolved.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 9f4a616. Configure here.
CI lint (ST1018): replaced literal Unicode bytes with \uXXXX escape
sequences in tour/prompt_test.go (U+200B ZWSP, U+200F RTL mark,
U+009B CSI control) and tour/run.go's controlSequencePattern
C1 range. Locally these were dismissed as auto-fix conflicts, but
in CI without auto-fix they fired as errors.
bugbot review (real bugs):
- HIGH: repoHasHistory only queried the v1 GitStore, so any user
on `checkpoints_version: 2` always had has_history=false in the
rendered tour. Now checks v1 first, falls back to v2 store.
- MEDIUM: cachedTourSettingsLoader hardcoded isSetUp=true whenever
Load() succeeded, but settings.Load returns a non-nil EntireSettings
even when no settings.json exists. Threaded a real isSetUp value
from settings.IsSetUpAny into the closure.
- LOW: SummaryGeneration.Model from settings.json was never read,
so users who configured both provider and model had the model
silently dropped. Wired through opts.SummarizeModel.
Copilot review (real wording fixes):
- tourNoTextGeneratorMessage incorrectly described default `entire
tour` as needing an agent. Reworded to say the default uses
embedded markdown and only --latest/--regenerate need an agent.
- "Fetching the latest dispatch" -> "Fetching the latest post"
(avoids confusion with `entire dispatch`).
- "generate latest dispatch with X" wrap -> "summarize latest
blog post with X".
- marshalIndentNoHTMLEscape comment claimed json.Marshal would
turn "<id|sha>" into "<id|sha>" (visually identical). Fixed
to show the actual default escape (`<id|sha>`).
35 unit tests pass. golangci-lint reports 0 issues. shellcheck/
gofmt clean.
Co-Authored-By: Claude <noreply@anthropic.com>
|
bugbot run |
Per CLAUDE.md: "Always use t.Parallel() in tests. Every top-level test function and subtest should call t.Parallel() unless it modifies process-global state." These tour unit tests are pure in-memory and qualify for parallelization. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 704de7f0df2d
|
bugbot run |
Generate previously called ResolveState before branching on opts.Regenerate. In CI checkouts there is no .entire/settings.json, so IsSetUpAny returns false and ResolveState routes to StageSetup — the agent then produces a 4-line setup stub that the release-pipeline validation rejects, leaving every release shipping the unrendered embedded tour. The --regenerate path produces the canonical content shipped to all users via embedded/tour.md, and the embedded markdown is only ever served to first-capture / workflow stages (setup and agent-install render hand-written prose constants). The runtime repo's state is irrelevant for regen — we always want StageWorkflow output. Short- circuit before ResolveState and pass a synthetic workflow state. Flagged by Cursor Bugbot on PR #1146. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 490be5d78be1
The embedded tour.md was a build-time stub waiting for the release pipeline to overwrite it. Now that the regen path is fixed, ship a real generated tour as the baseline so anyone building from main between merge and the next tagged release sees the actual content, and so a future regen failure (continue-on-error: true) silently falls back to a real tour rather than the stub. Generated via mise run tour:regenerate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: d06e1534bcd5
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit c851c7e. Configure here.
The capability-section rule in the tour prompt previously said the em-dash short description was optional, so the agent dropped it across capability sections like "Track & Resume Sessions" and "Find & Understand Prior Work" while still rendering descriptions in Labs and Other commands. The result was inconsistent and made the capability bullets feel less informative than the surrounding sections. Tighten the rule to require the description on every capability bullet (verbatim cobra short, or rephrased when too generic), and regenerate embedded/tour.md against the new prompt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 5db73c2f3924
Hard rename — no alias. Drop --latest blog-feed digest entirely (blog.go, GenerateLatest, BuildLatestPrompt, latestPromptSystem, the flag, and the <post> tag from closingTagPattern). Move embedded- markdown regeneration out of release.yml and into the changelog skill (`mise run learn:regenerate` runs as Step 0) so the shipped tour is deterministic and can be hand-tweaked in the same PR as the CHANGELOG bump. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: b7fab1d96c0e
Atomic write: mktemp now creates the temp file inside cmd/entire/cli/learn/embedded/ so the rename onto learn.md stays on the same filesystem (single inode swap). The previous /tmp form degraded to copy+unlink when /tmp was a different mount, which could leave learn.md truncated on an interrupted run. Release pipeline: re-add a learn.md freshness gate before GoReleaser — same well-formedness checks the regenerate script enforces (file non-empty, >=4 ## headers, docs.entire.io footer). Runs on both stable and nightly tags, so prerelease builds can't silently ship a stub if learn.md ever lands the tree malformed. The error message points at 'mise run learn:regenerate' so the fix is obvious. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 60d5b9bf6714
mktemp template: the previous `$target_dir/learn.XXXXXX.md` form is silently broken on macOS — mktemp leaves the literal X's in the filename when they're not the final characters of the template, so two parallel regenerations collide on the same path and the rename guarantee is gone. Use `$target_dir/.learn-XXXXXX` (hidden file, X's at end) and rename to learn.md inside the same directory so the swap stays atomic on Linux and macOS. Stale references: state.go and learn_cmd.go had godoc / spinner strings still referencing `entire tour`, the renamed translateLearnError function, and the old `tour` package. Replaced with `entire learn`, translateLearnError, and `learn package` accordingly. The user-visible spinner now reads "Regenerating embedded learn markdown" so a user running `entire learn --regenerate` doesn't see the old command name. PR validation: add .github/workflows/learn.yml so the same well-formedness check (non-empty, >=4 ## headers, docs footer) runs on any PR that touches embedded/learn.md. Catches hand-edit truncation or bad-merge corruption before it lands rather than only at release time. No agent call required, so it's safe to gate PRs on it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 0bd6aa5b2705
TTY+--regenerate refusal: --regenerate writes raw markdown to stdout for piping into embedded/learn.md. Running it interactively dumped that raw markdown over the spinner-clear and left the user with no way to recapture it. Refuse the combination early with a hint pointing at `mise run learn:regenerate` (canonical) or explicit shell redirect. That makes the spinner path dead code, so delete learn_tui.go entirely — the embedded-file read is fast and the maintainer regen flow is non-interactive by construction. Test coverage: add learn/run_test.go with two tests that pin the ResolveState routing contract — Regenerate=true bypasses LoadSettings/ListInstalledAgents entirely, the default path consults LoadSettings and routes to the setup-stage markdown when enabled=false. These were untested and the regen-without-settings behavior is the load-bearing piece for CI checkouts that have no .entire/settings.json. learn.yml paths filter: add cmd/entire/cli/learn/embedded.go. embedded.go owns the //go:embed directive, so a PR repointing the embed at a different filename or wrapping it must re-trigger the well-formedness check even if learn.md itself isn't touched. Cosmetic: rename "generate tour with %s" error to "regenerate embedded learn with %s" so the user-visible string reflects the new command name. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 2e177be57b57
TestGenerate_RegenerateBypassesResolveState previously relied on a
pre-cancelled context to short-circuit ResolveTextGenerator.
external.DiscoverAndRegisterAlways happens to check ctx.Err() in
its loop today, but the test would silently leak a real
entire-agent-* registration into the package-shared global registry
if that check ever moved. Use t.Setenv("PATH", "") so discovery is
a no-op AND the built-in agents' CLI-availability checks all return
false; ResolveTextGenerator now deterministically returns
ErrNoTextGenerator and the test asserts on the error too. Cost:
drop t.Parallel since t.Setenv requires it.
TestGenerate_DefaultPathConsultsResolveState depended on the test
running inside a git repo (paths.WorktreeRoot walks up from CWD).
That holds today because go test runs from the package directory
inside this worktree, but a hermetic CI runner with a stripped
checkout would short-circuit to StageNotGitRepo before LoadSettings
got consulted, masking the bypass we want to assert. Stand up an
isolated tmp repo via testutil.InitRepo + t.Chdir so the test is
independent of host CWD. Cost: drop t.Parallel since t.Chdir
requires it.
Also: scrub the last two "embedded tour" mentions in the changelog
skill since the rename brief was thorough.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 008e52fd97f8
entire tour command with embedded markdown + release-time regenentire learn command with embedded markdown + release-time regen

https://entire.io/gh/entireio/cli/trails/323
Made
entire learnso I can stop teaching agents how to use the CLI. Tired of pasting instructions in every prompt and watching them-htheir way through every subcommand, or having to update my agent.md file all the time. Now I can just point atentire learn.Summary
Adds
entire learn, an experimental Hidden top-level command (advertised underentire labs) that renders a state-aware tour of the CLI. Two rendering modes:entire learn— reads pre-rendered markdown fromcmd/entire/cli/learn/embedded/learn.md, passes it through glamour, prints. ~50ms file-read + render.entire learn --regenerate(hidden, maintainer-only) — runs a TextGenerator agent against the live cobra tree to refreshembedded/learn.mdbefore each release. Output is raw markdown to stdout; the command refuses to run unless stdout is redirected.Release CI integration in
.github/workflows/release.ymlrunsmise run learn:regenerate(atomic write + validation: ≥4##headers, presence of thedocs.entire.io/clifooter) before GoReleaser. The regen step iscontinue-on-error: trueso a flaky agent or missingANTHROPIC_API_KEYdoesn't block the release; a dedicated Slack notification fires on regen failure separately from the existing release-failure alert.State-aware rendering: when Entire isn't enabled in the current repo, prints a short setup prompt; when enabled but no agent hooks are installed, prints an agent-install prompt; once a first commit has been captured, falls through to the full embedded tour (with a one-paragraph tail explaining that search/resume/rewind will gain real data after the next commit).
Mirrors
entire review's shape: top-levelHidden: true, advertised inexperimentalCommandsforentire labs.Demos
https://cleanshot.com/share/35CFYwL1
https://cleanshot.com/share/6ZL9h4RP
<img width="767" height="1539" alt="Screenshot 2026-05-08 at 4 07 01 PM" src="https://github.com/user-attachments/assets/ef520058-c939-4458-8b61-c0630e22d8f3\" />
Test plan
mise run learn:regeneratelocally withANTHROPIC_API_KEYset produces a validembedded/learn.md(≥4##headers, includesdocs.entire.io/clifooter)entire learnfrom a fresh checkout shows the embedded tour, rendered through glamourentire learn --regenerate > /tmp/learn.mdproduces sanitized markdown (control sequences stripped); refuses to run if stdout is a TTYentire learn --helpshows correct help textentire labslistsentire learngo test ./cmd/entire/cli/learn/...passes (tests parallelized and hardened against host environment)-rc.testtag exercises the regen path end-to-endNotes
The branch went through several code review passes (Claude-driven parallel agent reviews + codex adversarial review). Major findings addressed:
ResolveTextGenerator(mirrorsresolveCheckpointSummaryProvider)t.Parallel()added to all learn-package tests; tests hardened against host environment (no reliance on real $HOME, real git config, or ambientENTIRE_*env)The earlier
--latestblog-digest mode (RSS fetch + summarize most recententire.iopost) was removed during the rename back tolearn: it added a network-dependent path that didn't fit the "teach the CLI surface" framing. The embedded-markdown + maintainer-regenerate flow is the whole feature now.Deferred to follow-up PRs: spinner/TUI dedup with
dispatch_tui.go,checkpoint.HasCommittedshort-circuit, dispatch'ssanitizeDispatchPromptStringconsolidation with the newstripInvisibles.🤖 Generated with Claude Code
Note
Medium Risk
Adds a new top-level CLI command plus agent-driven generation paths and modifies the release workflow; failures are best-effort but could affect shipped tour content and release-time automation.
Overview
Adds an experimental hidden
entire learncommand (advertised viaentire labs) that renders a state-aware CLI tour from an embeddedlearn.mdby default, with--regenerate(hidden) producing the embedded markdown via an agent.Introduces a new
cmd/entire/cli/learnpackage to discover the cobra command surface, resolve repo stage (setup/agent install/first capture/workflow), build hardened agent prompts (tag-escape + control-sequence stripping), and select an available TextGenerator (including externalentire-agent-*). Adds a small TUI spinner while agent work runs and extendsmdrenderto allow per-command style overrides.Updates the release GitHub Action to best-effort regenerate the embedded tour (installs pinned Claude CLI, runs
mise run learn:regeneratewith validation/atomic write), annotate failures, and send a dedicated Slack warning when regeneration fails without failing the release.Originally reviewed by Cursor Bugbot for an earlier commit when the command was named
tour. Configure here.