feat(grammar): allow bare !post — narrative from device metadata (#124)#159
Conversation
#124) Bare !post (no caption) is now parseable; previously it returned undefined and replied "Unknown command. Try !help", wasting a satellite send. The LLM constructs the narrative purely from enrichment context (place name, weather, time) under a no-note system prompt that explicitly forbids inventing activities or feelings — observational voice, anchored only to metadata. Surfaced 2026-04-27 during the #111 production traffic turn-on when the operator sent a bare !post expecting "TrailScribe figures it out". Closes Phase 2.5's last open issue (milestone now 0/3). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Staging cost probe — doneFlipped staging Ledger delta (2 calls combined: 1 bare + 1 with-note)
Per-transaction mean: ~$0.003. That's 6.5% of the PRD §6 $0.05 envelope — well inside target. Both calls returned 200; bare took ~26s, with-note took ~12s (the bare path's longer latency is the model elaborating from sparse metadata vs. anchoring on a caption). The probe doesn't separate per-call tokens (the ledger rollup is an aggregate), but the combined-mean already answers the open question: bare-mode cost is comfortably inside envelope. If the with-note path's typical token count (~240 prompt / ~180 completion in unit tests) is the baseline, the bare path's share of the +932/+250 delta is at most ~750/+250 = roughly 1.5× the with-note prompt count — which is the expected direction (model has more enrichment to lean on, not less) and still nowhere near the budget ceiling. P2-16 real-device verification will confirm field UX (does the LLM produce a readable observational narrative with no caption?), but cost is no longer a merge blocker. Ready for your merge approval. |
Summary
!post(no caption) now parses and produces a journal post — previously returnedundefinedand replied "Unknown command. Try !help", wasting a satellite send.noteis nowstring | undefinedon thepostParsedCommand variant. Whitespace-only notes are treated as bare (cleaner than the empty-string ambiguity).Surfaced 2026-04-27 during #111 production traffic turn-on (operator typed bare
!postexpecting "TrailScribe figures it out").Acceptance criteria (from issue body)
src/core/grammar.ts— bare!postparses as{ type: "post" }(note omitted at the type level — distinguishes "bare" from "empty after trim")src/core/types.ts—note?: stringon thepostvariantsrc/core/commands/post.ts— branches oncmd.note === undefined; defensive empty-narrative fallback (Posted from {placeName}orPosted from the field)src/core/narrative.ts—SYSTEM_PROMPTsplit intoWITH_NOTE/NO_NOTE;buildUserPromptomits theNote:line when absent!postwith GPS, bare + no-GPS edge case)docs/field-commands.mddocuments the bare mode!helpreply text updates:!post [note] — blogTests: 332/332 (was 327; +5 new). Typecheck + lint clean.
Cost envelope (open question from issue body)
The risk note flagged whether bare-mode input tokens stay under PRD §6's <$0.05/transaction. Plan: probe staging with one bare
!post+ one with-note!post, compare ledger deltas. Will post the numbers as a PR comment before requesting merge.Test plan
pnpm deploy:staging(pre-approved, dry-run first)!postagainst staging Worker; capture token usage from ledger!postagainst staging; compare deltas🤖 Generated with Claude Code