Documentation review, 2026-09-19
An adversarial read of the user-facing documentation from the standpoint of a sceptical new user on
macOS 27 who wants to install wisp, use it from the terminal and from Claude Code over MCP, and trust
it with commands on their Mac. Every claim was checked against harness/Sources at commit 0aae5da and,
where safe, by running the debug build (harness/.build/debug/wisp, version 0.1.2) with
WISP_HOME=/tmp/wisp-docs-review. The scratch home was deleted afterwards; nothing was written to
~/.wisp or the repository. Private Cloud Compute was not exercised.
Summary
| Severity | Count |
|---|---|
| High (a user would be misled or blocked) | 5 distinct issues (6 entries; 44 restates 37 for approval.md) |
| Medium (confusing or incomplete) | 21 |
| Low (polish) | 45 |
| Total entries | 72 |
Verdict. A new user can install wisp from these docs: the Homebrew tap is live at 0.1.2,
wisp doctor does what the README says, and the CLI reference matches --help almost flag for flag.
Using it from the command line mostly works, but the README’s own second quick-start example is refused
as written (the model’s swift test needs approval and plain wisp cannot ask), a refusal exits 0
with model prose rather than a detectable error, the documented exit code for a bad config.json is
wrong, and the one config section a cautious user is most likely to edit (commandPolicy) rejects any
partial object although the reference says every field is optional. Using it over MCP is where the docs
diverge most from the binary: mcp.md promises a structured isError on refusal and four approval
scopes including persisted project and always; in fact the refusal is ordinary model output with
isError: false, and the MCP server builds its approval gate without the approval store, so nothing is
persisted or honoured over MCP and a “session” approval lives only as long as one thread. Trusting wisp
is the weakest part: the mechanisms are sound and honestly described at the component level, but the
front door says “kernel-enforced sandbox” and “nothing leaves your machine” without the qualifications
(only writes are confined, network is on by default, --yes removes every human check, reads are
unconfined), and the facts a user needs to answer “what can this do to my Mac and how do I undo it” are
scattered across six pages and two ADRs with no single trust page. The set is close: most fixes are
small edits, plus one code fix (persisted approvals over MCP) and one decision (partial commandPolicy).
Findings by page
Severity in brackets. Line numbers refer to the files at 0aae5da.
README.md
- [High] L26-27, quick start:
wisp "Run the tests in $PWD and tell me if they pass". In any real project the model runsswift test,npm test, or similar, whichRuleRiskClassifierratesmoderate(“runs a build or tests”), andrespondhas no way to ask. Verified from the repo: the equivalent prompt returnserror: command not approved: swift test 2>&1: approval required; re-run with --yes, use wisp chat to be asked, or lower approval.thresholdand exits 0. The explanation (“plainwisprefuses unless you pass--yes”) comes two lines after the example that fails. Either add--yesto the example, make it achatexample, or move the sentence above the block. - [Medium] L5-6, “By default nothing leaves your machine”. True of inference. Not true of what the model
may run:
sandbox.allowNetworkdefaults totrueand the deny list does not covercurl,ssh, orscp. Network use is ratedmoderateand asks, so a person is in the loop, but with--yesorapproval.threshold: nevernothing asks. Qualify the sentence: “no inference leaves your machine; commands the model runs may use the network unless you turn it off, and are asked about first”. - [Medium] L15-16, “runs inside a kernel-enforced sandbox”. The profile is
(allow default)withfile-write*denied outside the writable set andnetwork*denied only when configured (CommandPolicy.seatbeltProfile). Reads, executing any binary, Mach IPC, and AppleEvents (osascript) are not confined. The claim is technically true and practically overread; say “a sandbox that blocks writes outside the project (and, if you choose, the network)”. - [Medium] L32-34, “State lives in
~/.wisp: an optionalconfig.json, saved chat transcripts, and the audit log”. Omitsapprovals.json, the file that holds standing permissions for 30 days, andwisp approvalsto revoke them. The front door never tells a user that approvals can outlive the session or how to undo one. Add one sentence and theapprovalsline to the quick start. - [Low] L42,
"command": "/path/to/wisp". Afterbrew installthe path is/opt/homebrew/bin/wisp(release.md L13); say so, or use"wisp"since it is onPATH. - [Low] Missing: uninstall (
brew uninstall wisp,rm -rf ~/.wisp) and upgrade (brew upgrade), and a note that the firstwisp "..."creates~/.wisp/logs/audit.jsonlholding the prompt and reply verbatim. - [Low] L21, “Homebrew” is a requirement only for the quick start; building from source is the alternative (ADR 0012). Fine as written, but the sentence reads as absolute.
docs/README.md (index)
- [Medium] The table (L3-19) is flat and unordered for a newcomer: user pages, background surveys,
contributor pages, and one ADR (
decisions/0013, L15) sit side by side with no suggested reading order. Group into “Using wisp” (wisp, tools, mcp, approval, logging), “How it works” (design, context-management, policy-and-sandboxing, decisions), and “Contributing and releasing” (engineering, release, backlog, fm-cli, reviews), and state the order for a first read. - [Low]
docs/reviews/is not indexed although it is underdocs/and the docs rule says every page gets a row. - [Low] L15, an ADR is listed as a document with its own purpose text while the other fourteen are only reachable through L19. Either list none individually or explain why this one.
docs/wisp.md
- [Medium] L3-4, “It has six subcommands (
respond,chat,tools,logs,doctor,mcp)”. There are seven;approvalsis documented on L83 of the same page and appears inwisp --help. - [High] L114, “
config.jsonfields, all optional”, and L123,commandPolicydescribed as deny/allow patterns and sandbox settings with defaults listed in run_command.md. Verified: the object is all-or-nothing.{"commandPolicy":{"sandbox":{"allowNetwork":false}}}fails withmalformed …/config.json: DecodingError.keyNotFound: Key 'deny' not found;{"commandPolicy":{"deny":["sudo"]}}fails withKey 'allow' not found;sandboxlikewise needs all ofenabled,allowNetwork,writablePaths.CommandPolicyandCommandPolicy.Sandboxare synthesisedCodableover non-optional properties (CommandPolicy.swift L14-58). policy-and- sandboxing.md L58 callssandbox.allowNetwork“the one-field switch”; it is a nine-field switch. Fix the decoder (optional fields with defaults, likeAuditConfigandApprovalConfig) or document thatcommandPolicymust be given whole, with the full default object to copy. - [Medium] L148, exit code
1for “a malformedconfig.json”. Verified:respondandchatexit 64 (Wisp.beginmapsSession.FailuretoValidationError, Wisp.swift L111-116),logsexits 1 (it throwsSession.Failuredirectly),doctorexits 1 by design. Pick one and document it; a script cannot distinguish “bad flags” from “bad config” today. - [Medium] L54, “Status lines go to stderr, replies to stdout, so
wisp chat 2>/dev/nullpipes cleanly”. Verified: stdout carries the>prompt before every line (print("> ", terminator: ""), Wisp.swift L228);od -cof the piped output shows> ok\n>. Either move the prompt to stderr or drop the claim. - [Medium] Missing, and important for command-line use: a refusal is not an error. When approval is
denied,
respondprints whatever the model says about theerror: command not approvedtool result and exits 0 (verified three times; once the model wrote “The command failed due to an approval requirement. Please re-run…”). The page should say that exit status does not reflect tool refusals and that the audit log (wisp logs --kind approval.decided) is the reliable record. - [Low] L23,
echo "Summarise this" | wisp --tool current_date. Works, but the stdin text is the prompt, not material to summarise, so the model replies “Could you clarify what you would like summarised?” (verified). Use an example whose stdin is the prompt, such asecho "What time is it in Tokyo?" | wisp --tool current_date. - [Low] L51-52, chat answers
p“(30 days, this directory)” anda“(30 days)”. The 30 is hard-coded in the prompt text (Approval.swift L81) and does not followapproval.persistDays. Say “forapproval.persistDays” or fix the prompt to print the configured value. - [Low] L85-88 and the binary:
wisp approvals --help(Wisp.swift L366) says approvals “are exact command lines”. Stale since ADR 0015; they are patterns such astouch *(verified output:ddc40e90 project expires 19 Oct 2026 /Users/…/wisp touch *). - [Low] L103-105, “
toolsandlogsnever create it”. Also true ofapprovals(list);approvals revokeandclearcreate the directory when writing (ApprovalStore.save). - [Low] L134, “A malformed file or an invalid
commandPolicypattern is an error”. Also an invalidapproval.threshold(use safe, moderate, dangerous, or never) and an unknownmodel. Both messages verified. - [Low] L118,
instructions“built-in”. State the built-in text (“You are wisp, a concise assistant. Use the available tools when they help answer accurately.”); it is stamped on everysession.startand a user will wonder where it came from. - [Low] L48, chat commands:
/exit,/q, and/?are accepted too (ChatInput.swift L32-33). - [Low] L64 says “oldest first” and logging.md L58 says “newest last”; same thing, two spellings.
Also note that events are emitted in file order, so two wisps writing concurrently interleave in
write order, not strictly by
time(observed with the MCP server and a CLI run).
docs/tools/README.md
- [Low] L25, “Append it to
ToolRegistry.init” versus design.md L96 (“A static list,all”) and L166 (“append toToolRegistry.all”). The code is an instancelet allbuilt ininit(ToolRegistry.swift L6-29); the tools page is right, design.md is stale.
docs/tools/run_command.md
- [High] L47,
sandbox.writablePaths: “Writable in addition to the working directory,$TMPDIR, and/private/tmp” contradicts L103-109 on the same page (“The sandbox’s writable set is rooted at the directory wisp was launched in … AworkingDirectorychosen by the model changes where the command runs, never what it may write”). The code agrees with L103 (Options.writableRoot, CommandRunner.swift L18-21), and so does the live tool guidance (“rooted at wisp’s launch directory”); theSandbox.writablePathsdoc comment (CommandPolicy.swift L21) still says working directory. Verified: from the repo,touch /Users/pidster/wisp-docs-probewith--yesgivesOperation not permitted. Security-relevant because a user reading the table will believe that passingworkingDirectorywidens the writable set. Fix the table row and the code comment, and define “launch directory” once (for MCP it is wherever the client started the server, see 39). - [Medium] L118-120, “Reads are not restricted; omit the tool … where even that is too much”, placed
under the heading “Process tree and timeouts”. Half true: the sandbox does not restrict reads, but
cat ~/.ssh/id_rsais rateddangerousby the rules and asks. The full statement a user needs, in one place: reads are never blocked by the sandbox; credential-looking reads ask; with--yesorthreshold: neverany file can be read and, with network on, sent anywhere. Move the paragraph to “Policy and sandbox” and say that. - [Medium] L49, “Inside the sandbox everything is readable and executable”. Add what else is allowed:
the profile is
(allow default), so IPC, AppleEvents (osascript,open), and signals are permitted;osascript/openare ratedmoderateby the rules and ask, but nothing in the sandbox stops them. Users deciding whether to run--yesneed this. - [Medium] L53-56, the configuration example is valid only because it spells out every field (see 12). Add “the object must be complete” or fix the decoder.
- [Low] L43, deny list: also matches
newfs_, and| shalso coversbash,zsh,dash(CommandPolicy.swift L68-74). The code calls the list “illustrative, not exhaustive; the sandbox is the real barrier” (L67); the page should say the same, so nobody reads it as a blocklist. - [Low] L96-101 and L108-109 carry change history (“found by review, fixed 2026-09-19”, “Before 2026-09-19 the per-command directory was the writable root”). ADR 0009 already records it; the user page should describe the current state only (docs rule: “Documents describe the current state”).
- [Low] L12,
workingDirectorydefault “wisp’s current directory”. Formcpthat is the directory the client launched the server in; say so here or in mcp.md (see 39). - [Low] L110-116 (process group, SIGTERM/SIGKILL, one-second drain) verified against CommandRunner.swift L192-206.
docs/tools/read_file.md
- [Low] L10,
path. Relative paths resolve against wisp’s current directory (theArgumentsdoc comment says so; the@Guidetext and the page do not). - [Low] L29-33. Two details worth stating: the approval uses wisp’s process directory as the
“working directory” for the
cat *pattern (ReadFileTool.swift L51-52), so aprojectapproval ofcat *there covers every laterread_filecall in that directory; and the deny patterns are not applied to reads, only the rule classifier. Verified refusal without--yeson a dummy.netrc(decision=denied … pattern=cat *). - [Low] L32-33, “Refusals come back as
error: command not approved: …”. That is the tool result; the user sees the model’s paraphrase (see 15).
docs/tools/current_date.md
No findings. Output format verified (2026-09-20T…+09:00 (Asia/Tokyo) via the README example).
docs/mcp.md
- [High] L81-83, “Otherwise the call returns
command not approved: … this client does not support elicitation …withisError: true, and the calling harness should run the command itself”. False. The denial is returned to the model as tool output;respondreturns the model’s reply withisError: false. Verified with a client that advertised no elicitation:{"content":[{"text":"error: command not approved: approval required (moderate: modifies files; …) and this client does not support elicitation; …"}],"isError":false,"structuredContent":{…}}. A harness cannot detect a refusal structurally; it has to read prose that the model may reword. Document the real shape, and consider the backlog “receipts” item (approvals asked and answered instructuredContent) as the fix. - [High] L78-80, Accept “with the scope picked in the form (this turn by default; session; project,
30 days in this directory; always, 30 days anywhere)”, and approval.md L96, ADR 0014. Over MCP,
projectandalwaysdo nothing beyond the thread, and nothing persisted is consulted:WispServer.gate(audit:)(WispServer.swift L71-74) constructsApprovalGatewithout astoreand withoutsource, unlikeSession.begin. InApprovalGate.clearSegmenta persistent scope with a nil store falls through to the session cache andstore?.findreturns nil. Verified: atouch *projectapproval granted inchatfor the repository directory was ignored bywisp mcplaunched from that directory (denied “this client does not support elicitation”). Further, the gate is created per thread at thread creation, so a “session” approval over MCP covers onethread_idonly and dies with it (approval.md L57 says “until the process exits”). This is a code bug that the docs faithfully misdescribe; fix the server to pass the store and source, or document the MCP scopes as “this turn” and “this thread” until it does. - [Medium] L85-88, a table of arguments
command(required) andworking_directorysits under “Approval” with no tool to belong to. It is a remnant of the directrun_commandMCP tool removed on 2026-09-17 (ADR 0006). Delete it. - [Medium] Missing: where an MCP-launched wisp runs. The sandbox’s writable root is the launch
directory; for Claude Code that is the project directory where
.mcp.jsonlives, and a relativecommandsuch asscripts/wisp-mcpresolves from there. The page never says what “current directory” means for a server the user did not start by hand. Also missing: a description of the elicitation dialog (title, fields, the picker defaulting to “This turn”) so a user recognises it, and aresources/readexample (verified working:{"method":"resources/read","params":{"uri": "wisp://tools.md"}}). - [Low] L105-108, “unknown tool names are JSON-RPC
invalidParams” and two lines later “unknown tool name …isError: true”. Both are right but about different things: an unknown MCP tool (tools/callname) is-32602; an unknown wisp tool in thetoolsarray is a tool error (verified:Unknown tool(s): nope,isError: true). Say which is which. - [Low] L112-118, smoke test verified as written.
sleep 10suffices for “Say hi” (about 1.5 s); a prompt that triggers the classifier needs longer (about 3-4 s per simple command). Mention it. - [Low] L18-24, the Codex TOML block was not verified against Codex. The repository’s own
.mcp.jsonuses"type": "stdio"and a launcher script rather than the L15 shape; either is fine but the page could point at the launcher as the dogfooding example. - [Low] L67-69, eviction audited as
session.endwithreason: evicted: verified in code (WispServer.swift L179-181), not exercised.
docs/approval.md
- [High] L54-59 (scope table) and L96 (mcp row): see 37. As written, the table promises persistence and process-wide session scope for every entry point; over MCP neither holds.
- [Medium] L84-85, “Decisions: approve once, approve this exact command for the rest of the session,
deny with a reason, or unanswered”. Stale twice: approvals are by pattern, not exact command (ADR
0015), and the sentence omits
projectandalwaysfour lines below their table. Delete or rewrite. - [Medium] L85-88, “an approver that hears nothing within
approval.timeoutSeconds… reportsunanswered”, and L108,timeoutSeconds“How long an approval may go unanswered”. Only the MCPElicitationApproverhas a timeout (withOptionalTimeout, ElicitationApprover.swift L78).TerminalApprover.decideblocks onreadLine()indefinitely (Approval.swift L84). The general claim and the config row should say “over MCP”; or add the timeout to chat, which the “no answer is not an answer” principle would seem to require. - [Medium] Missing for trust: the rules themselves. A user deciding on
useModel: false, or wanting to know whethergit commitwill ask, must readRuleRiskClassifier.swift. Add a table of the 24 rules (pattern in words, level, reason) or at least the reason strings, which are already written for humans. - [Low] L28, “thirteen labelled examples”. The instructions list fifteen (ModelRiskClassifier.swift L44-58).
- [Low] L34, “45 labelled commands, ten of them held out”: verified (35 + 10 in
ClassifierEvalTests.labelled). The eval was not rerun. - [Low] L3, “This is layer 3 of policy-and-sandboxing.md”. A reader who starts here (the README sends them here) has no idea what a layer is. Drop the cross-reference or spell it out.
- [Low] L18,
CompositeRiskClassifierand L24,ModelRiskClassifier: type names in a user page. Fine for design.md; here “rules” and “model” suffice. - [Low] L117-119, audit kinds: verified.
approval.decidedmay also carrypersistErrorwhen the store cannot be written (Approval.swift L241-243); not in logging.md either.
docs/logging.md
- [Low] L38, “(all entry points record the same fields via
Session.begin)”. Thread sessions (entryPoint: mcp-thread) record onlyentryPoint,instructions,tools,model(WispServer.swift L183-188); verified in--jsonoutput. Notunsafe,autoApprove,resume. - [Low] L39,
session.end“reason:closed(explicit),evicted”. CLI sessions end with emptydetails(verified"details":{}); only threads carry a reason. - [Low] L52,
approval.decided: addpersistError(see 52). - [Medium] L88, “Messages are marked public so they are not redacted”. The diagnostic log carries
command lines (
Diagnostics.policy.info("denied: …: \(command)"), CommandRunner.swift L145) and, at debug, tool arguments and outputs (AuditedTool.swift L42), into the system-wide unified log, which any administrator can read withlog showand which persists. The audit file is mode 0600 for exactly this content. Say so, and considerprivacy: .privatefor command text. - [Medium] Not on this page or wisp.md: saved transcripts (
~/.wisp/transcripts/<name>.json) contain the same verbatim conversation as the audit log but are written with the default umask (verified-rw-r--r--ont1.json), whileaudit.jsonlandapprovals.jsonare 0600. Either protect them the same way or tell the user. - [Low] L58-65, example commands verified. Add one example summary line so the reader knows what a summary looks like before running it.
docs/release.md
- [Low] Maintainer page; claims verified where possible: tags
v0.1.0-v0.1.2exist, the tap formula serves 0.1.2 withdepends_on macos: :golden_gate,scripts/releasematches the six steps. The unsigned-binary and Gatekeeper claims were not tested. User-facing pieces (install path, upgrade, uninstall) belong in README or wisp.md, not here.
docs/context-management.md
- [Low] Background page;
chatnote (“(context was full; older turns were dropped to continue)”) andcondensedflag verified in code. TheContextOptionsclaim (L15) was not verified against the SDK. Only structural note: wisp.md L139 uses “turn” before this page defines it (L29-31); a half-sentence definition in wisp.md would do.
docs/policy-and-sandboxing.md
- [Medium] L42-43, “Confirmation in interactive
wisp chat… Not applicable to MCP, where the calling harness has its own confirmation UX”. Contradicts L3-5 of the same page, approval.md, and mcp.md: MCP elicits. The survey was left as written when layer 3 changed. - [Low] L40, “evaluated in
RunCommandTool.call” (it isCommandRunner.run); L45, “defaulting to the working directory and$TMPDIR” (launch directory, see 25); L47, “persist transcripts … so every tool call and output is on disk” (superseded by the audit log, ADR 0010). - [Low] The page mixes a dated survey with a “What was done” section that has drifted. Either date every section as historical and keep only the survey, or move “What was done” into run_command.md.
docs/objective.md
- [Medium] L9, “On-device: every inference runs on the local Apple silicon model. No network calls
for generation”, and L34, non-goal “Remote or third-party models”. Both contradict ADR 0013 and
README L5-6:
--model private-cloudsends prompts and tool output to Apple’s servers. Add the amendment (on device by default; PCC as explicit opt-in) or the objective reads as if PCC were a violation of it. - [Low] L37, non-goal “configuration files for tools” versus
commandPolicyinconfig.json, which is configuration for a tool. Reword to what is meant (no per-tool config format or plugin manifest).
docs/design.md
- [Medium] L79-85 present
Session.beginas “the one place an entry point’s flags become a running configuration”, and L63-69 describe oneApprovalGateper session.WispServer.respondbuilds its own registry and gate per thread (WispServer.swift L157, L71-74) and omits the store and source; that is the cause of 37. Either route thread creation throughSessionor document the divergence so the next reader does not assume parity. - [Low] L26, the
harness/row omitsWispMCP; L37 the subcommand list omitsapprovals; L89Homeomitsapprovals.json; L90Configlists three of eight fields; L148 chat commands omit/exit. - [Low] L96 and L166,
ToolRegistry.allas “a static list”: see 24. - [Low] L172, “Session persistence (transcript save/resume …) would live in
Agent”: it exists (TranscriptStore,--resume,--save). Stale.
CLAUDE.md (as documentation for agents)
- [Low] L8, “a CLI (
respond,chat,tools,logs)”: omitsdoctorandapprovals. - [Low] L104, ‘choose “Approve for this session” for repeated shapes’. The picker label is “This
session”, and per 37 it covers only the
gitthread;project/alwayscurrently do nothing over MCP. Update once 37 is resolved either way. - [Low] L16, “
.claude/rules/(Swift, Rust, docs, shell)”: verified, four files.
Structural improvements across the set
- One trust page. Nothing answers, on one screen, “what can this do to my Mac?” A page (or a
section at the top of approval.md, linked from the README quick start) should state in order: what
runs (
/bin/sh -cas you, with your files readable and any binary executable); what the sandbox blocks (writes outside the launch directory, temp,/private/tmp, caches; network only if you setallowNetwork: false); what always asks and what never asks (the rule table); what--yesandthreshold: neverremove; what is remembered and for how long (approvals.json, 30 days, revoke withwisp approvals); what is recorded and where (audit log 0600, transcripts, unified log); what leaves the machine (nothing by default; PCC on request; anything a network-using command sends). Every one of these facts exists today, spread over README, wisp.md, run_command.md, approval.md, logging.md, ADR 0009, and ADR 0014. - One canonical description of approval scopes. They are described five times (wisp.md L50-52, run_command.md L60-68, mcp.md L72-83, approval.md L54-96, ADR 0014/0015) and have already diverged (45, 37). Make approval.md canonical, reduce the others to one sentence and a link.
- One name per thing. “working directory” / “launch directory” / “writable root” (25); “standing” / “persisted” / “remembered” approvals; “once” / “this turn” / “y”; “pattern” / “essential command” / “program”; “risky” (README) versus “moderate or above” (everywhere else). Define each once and reuse.
- Reading order. The index should say: README, then wisp.md, then approval.md, then tools/run_command.md, then mcp.md, then logging.md; design and ADRs afterwards; engineering, release, backlog, fm-cli, reviews for contributors.
- Keep history in ADRs. run_command.md and policy-and-sandboxing.md carry dated change notes; the docs rule says pages describe the current state. Move the narrative to the ADRs that already hold it.
- Split policy-and-sandboxing.md. Keep the survey (dated, historical) and move the live facts into run_command.md; today the page is half survey, half stale summary (61-63).
- Describe outputs, not only inputs. wisp.md and mcp.md document flags and arguments thoroughly
but rarely show what the user sees: a refusal, an approval prompt, a
wisp logsline, an elicitation dialog. Each page needs one real example of each (all captured in this review). - Binary help text is documentation too.
wisp approvals --help(18) is stale; the tool guidance strings and the elicitation text are read by users and harnesses and should be reviewed with the docs.
Todo
Ordered by severity, then by leverage. Effort: S under an hour, M a half day, L longer or needs a decision.
- [High] Fix persisted and session approvals over MCP: pass the
ApprovalStoreandsourcefrom the server session into thread gates (or share one gate per server), then re-verify mcp.md L78-80 and approval.md L54-59, L96 against the binary (37, 44, 66). Needs a test with an in-memory store. M - [High] Make
commandPolicyandcommandPolicy.sandboxdecode partial objects with defaults, or document that both must be complete and give the full default JSON to copy (12, 28). S for the doc, M for the code. - [High] Rewrite mcp.md “Approval” to describe the real refusal shape (
isError: false, model prose) and how a harness should detect it; delete the orphan argument table (36, 38). S. Longer term, add approvals tostructuredContent(backlog “receipts”). M - [High] Fix run_command.md L47 and the
Sandbox.writablePathsdoc comment to say “launch directory”, define the term once, and say what it means when Claude Code launches the server (25, 31, 39). S - [High] Fix the README second quick-start example: add
--yes, or make it achatexample, and move the “refuses unless” sentence above the block (1). S - [Medium] Add a trust page or section (see Structural improvements) and link it from the README
quick start; include the rule table (47), the
--yesconsequences, and undo steps (2, 3, 4, 26, 27). M - [Medium] Qualify README L5 and L15-16 (network on by default; sandbox confines writes) (2, 3). S
- [Medium] Add
approvals.jsonandwisp approvalsto README L33 and the quick start (4). S - [Medium] Decide the exit code for a malformed config and make
respond,chat,logsagree; then fix wisp.md L148 (13). S - [Medium] State in wisp.md that refusals exit 0 with model prose and that the audit log is the record (15, 35). S
- [Medium] Fix wisp.md L3 subcommand count and list (11). S
- [Medium] Move the chat
>prompt to stderr or drop the “pipes cleanly” claim (14). S - [Medium] Delete or rewrite approval.md L84-85 (stale “exact command” decisions list) (45). S
- [Medium] Either scope the timeout claims to MCP or add a timeout to
TerminalApprover(46). S for docs, M for code. - [Medium] Fix policy-and-sandboxing.md L42-43 and mark the survey as historical, or fold the live parts into run_command.md (61, 62, 63). S
- [Medium] Amend objective.md for ADR 0013 (64). S
- [Medium] Document the diagnostic log’s exposure of command text, and consider
.private(56). S - [Medium] Protect transcripts (0600) or document that they are not protected (57). S
- [Medium] Document the
Session/WispServerdivergence in design.md or remove it (66). S - [Medium] Regroup and order the docs index; add
reviews/(8, 9, 10). S - [Low] Update
wisp approvals --helptext to patterns (18). S - [Low] Make the chat prompt print
persistDaysinstead of a hard-coded 30 (17). S - [Low] Replace the wisp.md stdin example (16); list
/exit,/q,/?(22); state the default instructions (21); list the other config errors (20); noteapprovalsin the “never creates” sentence (19). S - [Low] Strip change history from run_command.md L96-101, L108-109 (30); mark the deny list
illustrative (29); mention
newfs_and the shell variants (29). S - [Low] read_file.md: relative path resolution, the
cat *pattern, deny patterns not applied (33, 34). S - [Low] mcp.md: disambiguate the two “unknown tool” errors (40), note the smoke-test sleep (41),
point at
scripts/wisp-mcp(42). S - [Low] approval.md: fifteen examples (48), drop “layer 3” jargon (50), plain names for
classifiers (51),
persistError(52). S - [Low] logging.md: thread
session.startfields (53), CLIsession.endhas no reason (54),persistError(55), an example summary line (58). S - [Low] design.md:
WispMCPin layout,approvalssubcommand,approvals.json, full config field list,/exit,ToolRegistryinstance property, transcript persistence exists (67, 68, 69). S - [Low] CLAUDE.md: subcommand list and the picker label (70, 71). S
- [Low] README: Homebrew path in the MCP snippet, uninstall and upgrade lines (5, 6). S
- [Low] wisp.md: mention
/q, and add a half-sentence definition of “turn” (22, 60). S
What was verified and how
- Built at
0aae5da;wisp --versionprinted0.1.2; every subcommand’s--helpcompared with wisp.md. - Ran under
WISP_HOME=/tmp/wisp-docs-review:doctor,tools(plain,--json,--markdown),logswith every documented filter and an unknown kind,approvalslist, revoke, clear,chat --list,chat --save,chat --resume, the threerespondexamples from wisp.md,--yeswithuname -m, refusals without--yesfortouch,swift test, and a credential-named file viaread_file, a sandboxed write outside the launch directory,--unsafe, exit codes for empty stdin, unknown--tool, unknown--model, and four malformed configs, nine partial config shapes, a pipedchatsession answeringp, andwisp mcpwith the documented smoke test plustools/list,resources/list,resources/read, a refusal without elicitation, a second call on a thread withtools,close_threadon a missing and an existing thread, and a missingprompt. - Checked the Homebrew tap and release tags over HTTPS (read-only).
- Not exercised: Private Cloud Compute, log rotation, context condensation, the eval suite, a real elicitation-capable client, Codex configuration, Gatekeeper behaviour.
Status
2026-09-19: all five High items and the Medium items addressed in the commit “Act on the documentation review: fix MCP approvals, partial policies, and the trust story”:
- Code: MCP threads now share the process’s
ApprovalStoreand aSessionApprovalsset (persisted and session approvals work over MCP);structuredContent.refusalsreports refused commands structurally;commandPolicyandsandboxdecode partial objects with defaults; transcripts are written 0600; unified-log diagnostics are private; a malformedconfig.jsonis exit 64 from every subcommand; the chat prompt goes to stderr;wisp approvals --helpspeaks of patterns. - Docs: new
docs/trust.mdand README front-door corrections (sandbox confines writes, network on by default, working quick-start examples, approvals and undo, Homebrew path, upgrade and uninstall);mcp.mdrefusal shape and orphan table;run_command.mdwritable-root wording;approval.mdtimeout scoping and stale decision list;policy-and-sandboxing.mdmarked historical;objective.mdamended for ADR 0013;design.mdSession versus server; index regrouped withreviews/;wisp.mdexit codes, subcommand count,chatpiping, config partials,/q, and “turn”.
Remaining Low items stand as listed in the Todo.