Skip to content

Add WhatsApp identity guard fixtures - #2

Merged
XelHaku merged 1 commit into
mainfrom
autoloop/20260425T014006Z/w1/2-2.b.4-whatsapp-identity-resolution-self-chat-guard
Apr 25, 2026
Merged

Add WhatsApp identity guard fixtures#2
XelHaku merged 1 commit into
mainfrom
autoloop/20260425T014006Z/w1/2-2.b.4-whatsapp-identity-resolution-self-chat-guard

Conversation

@XelHaku

@XelHaku XelHaku commented Apr 25, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

XelHaku added a commit that referenced this pull request Apr 25, 2026
…tton pair

Five micro-fixes targeting the last seams between hero elements.

Qualifier→CTA breathing room (priority #1)
- .hero-note margin-bottom 24 → 32px desktop / 18 → 24px mobile.
  The early-stage stamp and the INSTALL button were visually
  merging into one unit; the wider gap separates them so the eye
  reads them as two distinct elements (qualifier, then action).

VIEW SOURCE attached to INSTALL (priority #2)
- .hero-ctas gap 10 → 6px. The secondary link now reads as the
  ghost-half of the primary button rather than a detached link
  trailing below.

INSTALL less marketing-y
- padding 14×28 → 12×24, font 13.5 → 13. Still clearly the
  dominant CTA but stops looking like it wants to stretch
  edge-to-edge. Reads as "click me" not "convert me".

Hero-note left rule dimmed
- border-left from solid var(--accent) (full saturation) to
  rgba(240, 200, 75, 0.55) (~55% intensity). The accent stripe
  still signals identity but no longer competes for attention
  with the $ prompt above and the INSTALL fill below.

Hero grid hidden on mobile
- @media (max-width: 640) { .hero::after { display: none } }.
  The grid is a desktop-only counterweight — at mobile widths
  there's no right-gutter empty space for it to occupy, so it
  just overlapped the CTA buttons and added noise. Grid stays
  on desktop where it has actual empty space to fill.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@XelHaku
XelHaku merged commit e38af76 into main Apr 25, 2026
@XelHaku
XelHaku deleted the autoloop/20260425T014006Z/w1/2-2.b.4-whatsapp-identity-resolution-self-chat-guard branch April 25, 2026 07:47
XelHaku added a commit that referenced this pull request Apr 25, 2026
13-task TDD plan for an optional architecture-planner-loop pathway
that periodically downloads NousResearch/hermes-agent issues, rule-
filters ~2400 → ~50-150 high-signal candidates, ranks them, writes
a digest the planner prompt consumes, and extracts a grounded
keyword vocabulary that reinforces the existing (currently dormant)
Keywords feature.

Embeds pain analysis of the existing keyword mechanism (disuse in
autonomous mode, substring-only matching, no grounded vocabulary)
and mitigates pains #1 and #3 via signal-derived suggested_keywords
surfaced to the LLM. Pain #2 is intentionally out of scope (YAGNI).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
XelHaku added a commit that referenced this pull request Apr 25, 2026
Item #2 (docs side) from docs/cmd-loops-improvements.md. The cmd/*
flag change already landed in e53fbde; this updates the wrapper
documentation so the example invocations (--codexu / --claudeu /
--opencode) match the new --backend <name> form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
XelHaku added a commit that referenced this pull request May 7, 2026
…progress

`gormes update` now exposes the operator-explicit pre-update backup choice
through the structured progress UX. The actual backup writer (zip
creation, size/duration reporting, retention) is the next slice; this row
ships the FLAG/DECISION surface so the wiring lands first.

What's added:

- `--backup` flag opts into a single-run pre-update backup of ~/.gormes.
- `--no-backup` flag force-skips the backup; beats both `--backup` and
  the future config-level opt-in (matches Hermes' precedence).
- `UpdateLifecycleOptions.Backup`, `NoBackup`, `BackupConfigEnabled`
  threaded through from cobra to RunUpdateLifecycle.
- New evidence kinds:
  - `update_pre_backup_skipped`   (--no-backup or default-skip-with-config)
  - `update_pre_backup_requested` (--backup or config-enabled)
- `emitPreUpdateBackupPolicy` runs BEFORE any git mutation and resolves
  via the existing ResolveBackupPolicy helper from
  internal/cli/backup_policy.go, so a future writer slice can wire
  behind the same trigger without re-deriving precedence.
- Glyph mapping in cmd/gormes/update.go extended:
  - `*_skipped` → ℹ (dim)
  - `*_requested` → ◆ (bright cyan, mirrors Hermes' "◆ Creating
    pre-update backup..." marker)

Silent-default contract: when neither --backup nor --no-backup is set
and BackupConfigEnabled is false, NO backup-related evidence is emitted.
Most operators don't need to hear about the skipped backup on every
update run (parity with Hermes' default).

Tests:

- internal/cli/update_lifecycle_backup_test.go — 5 lifecycle tests:
  - NoBackupFlag emits skipped evidence with backup_disabled_by_flag reason
  - BackupFlag emits requested evidence with backup_forced reason +
    explicit "writer not yet implemented" detail (so operators don't
    expect a backup file to exist)
  - NoBackupBeatsBackup precedence rule
  - DefaultEmitsNoBackupEvidence (silent-default contract)
  - PreBackupRunsBeforeGitMutation (ordering is the contract for the
    follow-up writer slice — a backup taken after pull is useless for
    rollback)
- cmd/gormes/update_progress_ui_test.go — 2 UI tests:
  - BackupFlagFlowsToLifecycle proves cobra→options plumbing
  - PreBackupGlyphs proves ℹ for skipped, ◆ for requested

Implements row #2 from the gormes-update parity batch (audit agent's
recommended order). Reuses ResolveBackupPolicy and the structured
progress UX from c83d10e. Unblocks a follow-up writer slice that can
wire the actual backup file creation behind the existing
update_pre_backup_requested trigger without re-deriving the precedence
rules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
XelHaku added a commit that referenced this pull request May 9, 2026
* feat: add OpenCode backend support to cron/loop scripts

* feat: add Telegram thread-not-found fallback + send retry safety

Add retry logic to SendThread/SendThreadReply/SendThreadChatAction:
- Thread-not-found errors retry once without message_thread_id
- Non-thread BadRequest fails immediately (no retry)
- TimedOut never retries (avoids duplicate messages)
- Transient NetworkErrors bounded to 3 attempts
- New helpers: isThreadNotFoundError, isTimedOutError, isTransientNetworkError

TDD: 15 new tests across thread_fallback_test.go + send_retry_test.go.
Matches Hermes b816fd4e2 _is_thread_not_found_error retry loop semantics.

* docs: update progress for Telegram forum thread fallback row completion

Row status: planned→complete. Planner pass: sharpened contract_status
draft→validated, updated ready_when/not_ready_when to reflect completed
thread-aware send seam dependency. Regenerated all progress-derived docs
via go run ./cmd/progress write. 720 complete, 49 planned remaining.

* fix: update Phase 2 test expectations from in_progress to complete

Phase 2 has all 21/21 subphases and 73 routing/context rows shipped.
Tests TestLoad_RealFile and TestLoad_RealFile_Phase2ExecutionQueue
were asserting in_progress for Phase 2 and 2.B.5 respectively; both
are now complete. This restores CI green.

* feat: add browser session inactivity cleanup with configurable timeout

Port Hermes BROWSER_SESSION_INACTIVITY_TIMEOUT (default 300s) to Gormes as a
BrowserSessionTracker with background reap goroutine. TDD delivers six fixtures:
idle reaping, active preservation, reap-interval ticking, env-var override,
nil-backend safety, and close-failure evidence recording.

* docs: add browser inactivity cleanup row to Phase 5.C progress

All-topic parity sweep (CLI/TUI, provider/auth, gateway/channels, tools,
sessions/memory/Goncho, install/runtime, browser automation, docs/public,
release/operator) identified browser inactivity cleanup as the highest-impact
source-backed gap. Added P2 row under 5.C with contract, write_scope,
test_commands, and Hermes upstream ref. Regenerated all progress surfaces.

* feat: add SkillValidator ForceLoad, async, and canary execution support

Completes Phase 6.L 'Skill validation on load with execution proof' (P2):
- ForceLoad field on SkillValidator persists validation errors while
  allowing operator override.
- ValidateAsync and ValidateCanaryAsync run in background goroutines
  with channel-based result delivery (<500ms guarantee).
- ValidateCanary uses language-specific minimal safe inputs (python,
  bash, javascript) to catch gross syntax/import errors quickly.
- ForceLoadMsg formats human-readable force-load explanations.
- 13 new tests covering: force-load on/off, async success/failure,
  canary success/failure, fallback for unknown languages, duration,
  and ForceLoadMsg content verification.

* docs: complete Phase 6.L skill validation row and close deprecated umbrellas

Parity sweep (2026-05-07) findings applied:
- Phase 6.L 'Skill validation on load with execution proof' (P2) marked complete.
- Phase 5.O deprecated CLI profile umbrella marked complete (replaced by siblings).
- Phase 5.D Multimodal in/out umbrella closed (all children complete).
- Regenerated progress-driven docs and landing site progress data.

* chore: vendor hermes-agent as a git submodule on origin/main

Convert the in-repo hermes-agent reference checkout from a gitignored side-
clone into a tracked submodule pinned to NousResearch/hermes-agent main.

Local mods on the previous side-clone (Karpathy guardrails template added to
AGENTS.md/CLAUDE.md by onboarding tooling) were not upstream work and were
backed up to /tmp before resetting. The submodule now matches upstream HEAD
exactly so the gormes-hermes-parity sweep classifies against current truth
instead of a 3-day-stale checkout.

* docs: refresh hermes-agent submodule at parity sweep start

Teach gormes-hermes-parity to update the in-repo hermes-agent submodule to
upstream main HEAD before reading parity references, and to pass that bumped
sha through to gormes-git when builder rows land. Falls back to fetch+ff-only
for legacy clones, no-op if absent. Prevents stale-baseline classification.

* chore: drop hermes-agent from .gitignore now that it is a submodule

Remove the gitignore entry that hid the previous side-clone, and replace it
with a marker comment pointing to .gitmodules. Without this, the submodule
worktree would still be ignored at the parent level and confuse contributor
tooling.

* docs: complete Phase 6.L code executor and dependency resolver rows

Mark 'Skill code execution runtime' (P2) and 'Skill dependency resolution
and composition' (P3) as complete with implementation evidence and passing
test verification. Both rows had full Go implementations on disk
(internal/skills/code_executor.go, dependency_resolver.go) with passing
tests but were incorrectly marked as planned in progress.json.

Evidence: 4 code executor tests (success, error exit, empty code, default
timeout) + 5 dependency resolver/composer tests (linear, circular, missing,
compose, validate chain) all pass. Regenerated progress surfaces.

* docs: add 6 builder-ready rows from Hermes v0.12.0 + v0.13.0 parity sweep

gormes-planner pass over Hermes releases v2026.4.8..v2026.5.7
(v0.8.0..v0.13.0). Adds six progress.json rows materializing the
highest-leverage gaps surfaced by the sweep, with full builder contracts
(contract, contract_status, slice_size, execution_owner, trust_class,
degraded_mode, fixture, source_refs, ready_when, not_ready_when, blocked_by,
unblocks, acceptance, write_scope, test_commands, done_signal, note,
provenance):

- 5.Q  P2/medium  Provider client lazy-init for TUI cold-start budget
                  (v0.12.0 #17046 lead of the ~57% cold-start cut family)
- 5.I  P2/small   Plugin lifecycle hook: transform_llm_output
                  (v0.13.0 #21235)
- 5.D  P2/medium  Native video_analyze tool contract (v0.13.0 #19301)
- 5.M  P1/medium  Kanban worker heartbeat, reclaim, and zombie detection
                  (v0.13.0 #21183, #21214, #20188, #20410)
- 7.E  P2/small   Google Chat shared-chassis platform adapter seam
                  (v0.13.0 #21306, #21331)
- 5.J  P0/small   Auth state TOCTOU close + redaction default-on parity
                  (v0.13.0 #21193, #21194, #21241; reverts v0.12.0 #16794)

Each row's `unblocks` field names sibling work explicitly deferred from this
pass (cold-start mtime cache / tool memoize / pattern precompile,
pre_gateway_dispatch / pre_approval / duration_ms hooks, Gemini video
transport, Google Chat transport binding, etc.) so future planner picks have
an obvious next step. Provenance is recorded as the schema struct
(origin_type=upstream); upstream_ref left empty pending symbol-level mapping
when each row is picked up by a builder.

Derived surfaces regenerated by `go run ./cmd/progress write`. Validated by
`go run ./cmd/progress validate` (7 phases ok) and
`go test ./internal/progress -count=1` (ok).

* docs: add 7 missing upstream Hermes doc mirrors for mirrored coverage parity

* docs: add Phase 8 (Reputation & Publication) + 12-month success plan

Strategic pivot for Gormes from "Hermes in Go" parity chase to
TrebuchetDynamics reputation play. The methodology is the product;
Hermes-parity is the receipt that proves the methodology works.

New strategy doc: docs/content/building-gormes/strategy/success-plan.md
- North Star: "TrebuchetDynamics is the team that figured out how to
  autonomously port large Python projects to Go in production. Gormes is
  the receipt."
- Quarterly roadmap (Q1 foundation → Q2 toolkit OSS → Q3 sharp v1.0 →
  Q4 compound).
- 30-day action sprint (TD blog, README rewrite, writeup #1 draft, sharp
  differentiator decision, $/iteration cost telemetry).
- Operating principles: publication cadence > commit cadence; solo devs
  win on opinion not scope; the loop is the product; cost discipline.
- Reputation metrics scoreboard replacing row-count vanity metrics.
- Risk register including publication paralysis, identity drift, loop
  spend without learning.

New progress.json Phase 8 with 7 subphases and 10 builder-tractable rows
(all gormes-owned, provenance.origin_type=gormes):

- 8.A Publication Infrastructure
    P1 TD engineering blog scaffolded and live
    P3 TD social presence connected to blog feed
- 8.B Repository Messaging
    P1 README rewrite to methodology-first positioning
    P2 gormes.ai landing page positioning audit
- 8.C Engineering Writeups
    P1 Engineering writeup #1: autonomous Hermes-porting loop
- 8.D Sharp v1.0
    P0 Sharp v1.0 differentiator decision
    P1 Single-binary cross-platform release pipeline
- 8.E Toolkit Extraction
    P2 Agentic-porting-kit repo scaffold
- 8.F Cost Discipline & Loop Economics
    P1 Loop $/iteration cost metric in status file
- 8.G Community & External Contributions
    P3 Built-with-Gormes page scaffold

Each row carries the full builder contract (contract, contract_status,
slice_size, execution_owner, trust_class, degraded_mode, fixture,
source_refs, ready_when, not_ready_when, blocked_by, unblocks,
acceptance, write_scope, test_commands or no_test_required, done_signal,
note, provenance). Strategy-only rows that need operator taste (writeup
voice, differentiator decision) explicitly carry no_test_required with a
human-checked acceptance contract.

Validated: go run ./cmd/progress write/validate (8 phases ok),
go test ./internal/progress (ok), git diff --check.

* docs: rewrite landing page hero + add methodology section (8.B materialization)

Materializes progress.json row 8.B "gormes.ai landing page positioning audit"
under the methodology-first North Star captured in success-plan.md.

Hero changes:
- New title: "Hermes-Compatible AI Agent Runtime in One Go Binary".
- New headline: "AI agent runtime in one Go binary." (outcome-first per
  landing-quality-rubric).
- New sub-lines lead with Hermes-ecosystem compatibility, then introduce
  the autonomous engineering loop as the trust play.
- New kicker: "OPEN SOURCE · MIT LICENSE · AUTONOMOUSLY PORTED FROM HERMES".
- Secondary CTA changes from "View on GitHub" to "See how it is built"
  pointing at the new methodology section.
- Proof strip adds "Autonomous porting loop ships daily".

New section: HOW IT IS BUILT (#methodology), placed between hero and
install. Contains:
- A measured-loop-output metrics row (rows shipped, code base, binary)
  bound to benchmarks.json so it stays current.
- Four pillar cards: validation-gated commits, progress.json as system
  of record, Hermes is the parity oracle, reusable porting toolkit.
- Link to /building-gormes/architecture_plan/.

Trust posture:
- Adds "Every autonomous-loop commit passes a validation gate (go test,
  progress validate, git diff --check) before landing" to the trust list.

Top nav restructured:
- "Docs" replaced with "How it is built" (jumps to methodology).
- "Install" added as a top-level anchor.

Playwright tests updated to match the new strings; mobile-overflow tests
updated for the new headline. All 5 tests pass at iPhone SE through
iPhone Plus widths.

Validated:
  npm run build              -> ok
  npm run test:e2e           -> 5 passed
  go run ./cmd/progress      -> validated 8 phases
  git diff --cached --check  -> ok

Other dirty paths in the worktree (kanban .go files, progress.json
status updates, derived builder-loop docs) belong to the autonomous
loop's in-flight work on row 5.M and will land via the loop's own
gormes-git step.

* Release v0.1.06 (v2026.5.7)

The Pivot Release. Adopts the Hermes-style dual versioning taxonomy
(canonical semver tag v0.1.06 paired with date alias v2026.5.7),
captures a 12-month methodology-first strategy, and stands up Phase 8
in progress.json.

Highlights
- Methodology-first repositioning: success-plan.md is the new North
  Star ("TrebuchetDynamics is the team that figured out how to
  autonomously port large Python projects to Go in production. Gormes
  is the receipt").
- Phase 8 (Reputation & Publication) added with seven subphases
  (8.A-8.G) and ten gormes-owned builder-ready rows.
- Six Hermes-parity rows materialized from upstream v2026.4.30 +
  v2026.5.7 sweep (cold-start lazy-init, transform_llm_output hook,
  video_analyze tool, Kanban worker heartbeat, Google Chat adapter,
  auth TOCTOU + redaction default-on P0).
- Landing page rewrite: outcome-first hero, new methodology section
  with live loop-output metrics, four pillar cards.
- hermes-agent now tracked as git submodule pinned to upstream
  NousResearch/hermes-agent main (sha 7e2af0c2e).
- opencode/deepseek-v4-pro builder backend added alongside codexu;
  GORMES_BUILDER_BACKEND switch on the cron loop.
- Phase 6.L code executor and dependency resolver rows complete.

Versioning Taxonomy
- Canonical git tag: v0.1.06 (semver; gates with cmd/gormes/version.go).
- Date alias: v2026.5.7 (Hermes-style vYYYY.M.D, surfaced in release
  notes filename header, GitHub release title, and CHANGELOG entry).
- A future row migrates the canonical git tag to the date form once
  the release workflow extracts version from cmd/gormes/version.go
  independently of the tag string.

Validation
  go test ./... -count=1            ok (full suite)
  go run ./cmd/progress validate    validated 8 phases
  git diff --check                  ok
  landing build                     ok
  landing e2e                       5 passed (homepage + 4 mobile vps)

Files
- cmd/gormes/version.go             0.1.05 -> 0.1.06
- CHANGELOG.md                      [0.1.06] - 2026-05-07 entry
- RELEASE_v0.1.06.md                Hermes-style release notes (new)
- webpages/landing/src/data/release.json
                                    version bump for landing surface

* docs: add gormes-install skill — sandbox-isolated install testing

New repo-local skill at docs/development-skills/gormes-install/.
Symlinked into .agents/skills/, .claude/skills/, .codex/skills/.

Purpose
- Test and validate Gormes install + setup paths against a real
  release. Surface install-time issues that pure CI cannot catch
  (PATH leaks, shell-rc edits, symlink hijacking, misleading status
  messages, hidden side effects on existing installs).

Authored from a real test pass against v0.1.06 (the release just
cut). Three open install issues filed in references/known-issues.md
from that pass:

- iso-bin-hijack — sandbox install hijacks production
  ~/.local/bin/gormes symlink despite GORMES_BIN_DIR being set to a
  sandbox path. Active-PATH-command update should respect sandbox
  prefix.
- iso-shellrc-leak — sandbox install permanently edits ~/.bashrc and
  ~/.profile with a /tmp/-rooted PATH entry. After /tmp is reaped,
  every login shell has a dangling PATH. No installer-emitted
  cleanup path.
- msg-systemd-fiction — install transcript unconditionally announces
  "systemd user service installed" even when no service file is
  written and `systemctl --user list-unit-files 'gormes*'` reports
  zero units.

All three need follow-up rows under Phase 5.P (installer) or 5.O
(CLI surfaces) and TDD coverage via gormes-tdd-slice. This skill
finds and documents; it does not patch.

Skill structure
- SKILL.md       — mission, when-to-use table, hard constraints,
                   nine-step workflow, validation, final-report.
- references/known-issues.md — living register with format spec and
                   coverage gaps worth closing (uninstall, --local,
                   Termux, WSL2, root install, install.ps1, fresh
                   machine without Go/git, curl|bash flow,
                   upgrade-in-place with non-default config).
- references/test-recipes.md — copy-paste recipes for sandbox setup,
                   pre/post-state capture, dry-run, real install,
                   diff, functional smoke, production restoration,
                   uninstall, branch/tag pass.

Validated
  python3 quick_validate.py docs/development-skills/gormes-install   ok
  go run ./cmd/progress validate                                     8 phases ok
  git diff --check                                                   ok

* chore: update hermes-agent submodule pointer

* docs: file 3 install issues from gormes-install pass as 5.P planner rows

Materializes the three open install issues found by the gormes-install
sandbox pass on 2026-05-07 against install.sh@08706c6d3 (the v0.1.06
release-cut commit). Each row carries the full builder contract and
references the skill's known-issues register as the source of truth.

Rows added under Phase 5.P (Docker / Packaging):

- P1/small  Install isolation: GORMES_BIN_DIR is an authoritative
            sandbox boundary
            (fixes iso-bin-hijack — sandbox install hijacks production
            ~/.local/bin/gormes symlink despite GORMES_BIN_DIR being
            set to a sandbox path)

- P2/small  Install isolation: skip shell-rc PATH write when bin dir
            is under /tmp
            (fixes iso-shellrc-leak — sandbox install permanently
            writes PATH lines to ~/.bashrc / ~/.profile from sandbox
            paths that dangle when /tmp is reaped)
            blocked_by: iso-bin-hijack row (shares sandbox-detection
                       helper)

- P2/small  Install transcript: only print systemd block when unit
            file actually written
            (fixes msg-systemd-fiction — install transcript
            unconditionally announces "systemd user service installed"
            even when no unit file exists)

All three rows are provenance.origin_type=gormes (install.sh is
gormes-owned, not a Hermes port). Each acceptance contract pins the
fix to a Go integration test under internal/installtest/ that execs
install.sh in a temp HOME with isolated PATH and asserts on
post-state diffs. Tests must shim systemctl so outcomes are
deterministic regardless of host systemd availability.

Validated:
  go run ./cmd/progress write     ok
  go run ./cmd/progress validate  validated 8 phases
  go test ./internal/progress     ok 0.736s
  git diff --check                ok

* docs: retract msg-systemd-fiction install issue (verification error)

When investigating row 5.P "Install transcript: only print systemd
block when unit file actually written" for a TDD fix, found the
underlying bug does not exist. install.sh's gating
  if has systemctl && systemctl --user >/dev/null 2>&1; then
    install_systemd_user_service
  fi
already correctly conditions the unit-file write and the transcript
block on systemd availability. The unit IS written
(~/.config/systemd/user/gormes-gateway.service exists and
`systemctl --user is-enabled gormes-gateway` reports `enabled`).

Root cause of the original false positive: the gormes-install pass
on 2026-05-07T17:27Z verified "no systemd unit installed" by running
`systemctl --user list-unit-files 'gormes*'` and reading
"0 unit files listed" as ground truth. That command can transiently
miss a unit immediately after `daemon-reload` until systemd re-scans.
A single negative result is not authoritative.

Changes
- Withdraw progress.json row 5.P "Install transcript: only print
  systemd block when unit file actually written" (P2). 5.P item count
  goes from 9 back to 8. The two remaining install-isolation rows
  (iso-bin-hijack P1, iso-shellrc-leak P2) are real and stay.
- Move msg-systemd-fiction in
  webpages/docs/development-skills/gormes-install/references/known-issues.md
  from "Open Issues" to a new "Withdrawn (False Positives)" section
  with the full retraction context, root cause, and the verification
  lesson learned.
- Tighten the gormes-install SKILL.md pre-state capture step to
  require THREE independent signals when checking systemd unit
  presence (file existence + is-enabled + list-unit-files without a
  glob pattern), so future passes do not repeat the misdiagnosis.
- Update test-recipes.md pre/post-state snapshot scripts to use the
  three-signal pattern instead of the misleading single
  `list-unit-files 'gormes*'` query.

Validated:
  python3 quick_validate.py gormes-install                  Skill is valid!
  go run ./cmd/progress validate                            validated 8 phases
  go test ./internal/progress -count=1                      ok 0.770s
  git diff --check                                          ok

Trust-but-verify lesson preserved in the skill's known-issues.md so
future install passes start from corrected ground truth, not from
the original mistaken evidence.

* fix(install): respect GORMES_BIN_DIR/GORMES_PREFIX as sandbox boundary

Closes iso-bin-hijack from gormes-install pass on 2026-05-07.

Before: install.sh's update_active_command() ran unconditionally after
publishing the binary. It walked `which -a gormes` plus ~/.local/bin/gormes
and ~/go/bin/gormes, replacing every other gormes symlink it found on
PATH with one pointing at the just-installed sandbox build. After /tmp
reaping, the production ~/.local/bin/gormes symlink dangled.

After: when GORMES_BIN_DIR or GORMES_PREFIX is set, the operator has
declared a sandbox boundary. update_active_command() now early-returns
with `skipping active PATH command update (sandbox bin dir set ...)`.
print_install_plan_body() and print_verbose_plan() surface the decision
as `update_active_path_command: skipped|yes` so dry-run plans expose the
choice.

Test: internal/installtest/iso_bin_dir_test.go covers four cases via
fast `--dry-run` plan inspection (no git clone, no go build):
GORMES_BIN_DIR skips, GORMES_PREFIX skips, default still updates,
verbose surfaces the reason. End-to-end manual verification on
2026-05-07 confirmed a real sandbox install with GORMES_BIN_DIR set
leaves the production ~/.local/bin/gormes symlink target unchanged.

Implements progress.json row 5.P "Install isolation: GORMES_BIN_DIR is
an authoritative sandbox boundary". known-issues.md entry updated to
fixed-in-2026-05-07.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ci): align deploy-gormes-www asserts with methodology-first landing

The Deploy gormes.ai workflow's post-build verify step was failing on
the v0.1.06 release commit (run 25511450496) because it asserted hero
strings from before the 8.B landing rewrite. The page builds fine; only
the asserts were stale.

Updates:
- Replace stale positive asserts with current strings:
  "AI agent runtime in one Go binary." (hero h1)
  "An autonomous engineering loop ports Hermes to Go, every day." (methodology h2)
- Add new positive asserts that lock in the 8.B pivot so a regression
  to the old pre-pivot hero gets caught:
  "HOW IT IS BUILT" (methodology kicker)
  "Validated rows shipped" (methodology metrics block)
- Move the now-stale hero string to the negative-assert block:
  "Run AI agents from one Go binary."
  "Gormes runs local agent sessions, provider turns, memory, dashboards, and chat gateways from one Go binary."

Also picks up the website install.sh mirror refresh (now includes the
iso-bin-hijack sandbox-bin-dir fix from a7116c11e) and the daily
benchmarks rebuild.

Local verification: every positive asserts grep-matches the current
dist/index.html; every negative asserts grep-misses it.

Closes the red Deploy gormes.ai run from the v0.1.06 merge commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(install): close iso-shellrc-leak and iso-systemd-hijack

Two more sandbox-isolation bugs discovered by the gormes-install pass on
2026-05-07. Both have the same shape as iso-bin-hijack and the same fix
shape: gate on the existing sandbox_bin_dir_set helper.

iso-shellrc-leak (P2):
Before: ensure_path_in_shell_config() unconditionally appended
"export PATH=/tmp/gormes-install-test/<ts>/bin:$PATH" lines to ~/.bashrc,
~/.profile, ~/.zshrc, or fish config. After /tmp reaping, every login
shell carried a dangling PATH entry the operator had to sed out by hand.

After: ensure_path_in_shell_config() early-returns with
PATH_CONFIG_RESULT=sandbox_skipped when sandbox_bin_dir_set is true,
after exporting the bin dir into the current install run's PATH (so
downstream verification still works). Logs the boundary decision.

iso-systemd-hijack (P0):
Before: print_service_instructions() unconditionally rewrote the
production user systemd unit ~/.config/systemd/user/gormes-gateway.service
with ExecStart pointing at /tmp/gormes-install-test/<ts>/home/bin/gormes
and Environment=GORMES_HOME=/tmp/gormes-install-test/<ts>/home. After
the next reboot or /tmp reap, the operator's gormes-gateway service
fails to start because its binary path no longer exists. Worse than
iso-bin-hijack: failure is invisible until the operator tries to use
the gateway. Same surface on macOS via the launchd plist.

After: print_service_instructions() early-returns when
sandbox_bin_dir_set is true, with a log line naming both
~/.config/systemd/user/ and ~/Library/LaunchAgents/. Plan emitters
surface install_system_service: skipped|yes.

Plan output (print_install_plan_body and print_verbose_plan) now
exposes all three boundary decisions:
- update_active_path_command: skipped|yes
- edit_shell_rc_files: skipped|yes
- install_system_service: skipped|yes

Tests: 6 new dry-run plan tests across iso_shellrc_test.go and
iso_systemd_dir_test.go (skipped GORMES_BIN_DIR, skipped GORMES_PREFIX,
default-yes regression fence for each surface).

End-to-end verification on 2026-05-07: real sandbox install with
GORMES_BIN_DIR set left ~/.bashrc and ~/.profile gormes-line counts
byte-identical and ~/.config/systemd/user/gormes-gateway.service sha256
byte-identical.

Implements progress.json rows:
- 5.P "Install isolation: skip shell-rc PATH write when bin dir is under /tmp" (note: shipped fix uses sandbox_bin_dir_set boundary, not the originally-planned bin-dir-under-/tmp heuristic)
- 5.P "Install isolation: skip system service install when sandbox bin dir is set" (new row, P0)

known-issues.md entries updated to fixed-in-2026-05-07.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: add loop $/iteration cost metric with --cost-report subcommand

* docs: unblock P0 5.J TOCTOU row, complete P1 8.F cost metric, regenerate progress surfaces

* chore: gitignore scheduled tasks lock file

* feat(security): close auth.json TOCTOU window, add redaction default-on contract

Use tools.AtomicReplace (tempfile -> fsync -> rename) in
writeCredentialPoolAuthStore instead of raw os.WriteFile to
prevent concurrent readers from observing partially-written
auth.json. New internal/redaction package provides
RedactionConfig with default-on contract (Enabled:true) and
explicit operator opt-out via Enabled:false.

* docs: mark P0 TOCTOU row complete, sharpen Docker umbrella, add gateway auto-resume row

Parity sweep + planner pass:
- P0 5.J TOCTOU row: contract_status validated, then marked complete
- 5.B Docker: split inventory umbrella into concrete P1 builder row
- 5.N: new P2 gateway auto-resume row from Hermes v0.13.0
- Regenerate all progress-derived docs and site data

* feat: gateway auto-resume on restart recovers interrupted sessions

TDD landed autoResumePendingSessions in Manager.Run: on gateway
startup, scans session metadata for ResumePending sessions, injects
synthetic empty-text submit events for sessions with registered
channels, and marks orphaned sessions as non-resumable with
adapter_not_ready evidence.

Five fixture-backed tests prove: interrupted session recovery,
orphaned session terminated, startup resilience, channel-neutral
behavior (telegram/slack/discord), and session-boundary hook
preservation. Added NonResumableAdapterNotReady constant and
ManagerConfig.SkipAutoResume for test isolation.

* docs: parity sweep cleanup + gateway auto-resume row complete

- Cleaned 3 stale needs_human planner verdicts on completed rows
- Added P2 Brave Search + DDGS web search provider parity row (5.C)
- Marked Gateway auto-resume on restart complete with evidence
- Regenerated all derived progress surfaces

* chore: update hermes-agent submodule pointer

* feat(kanban): worker heartbeat, zombie reclaim, unified failure counter, auto-block, darwin detection

Implement Kanban worker heartbeat extension (extends claim_expires by
heartbeatTTL), stale-heartbeat zombie detection and reclaim with retry
budget, unified failure counter across spawn/timeout/crash outcomes,
auto-block on incomplete worker exit, and darwin PID-state zombie
detection via syscall.Kill (cross-platform stub on other GOOS).

TDD coverage: 8 acceptance tests over in-memory SQLite with fake clock.
Mirrors Hermes v0.13.0 PRs #21183, #21214, #20188, #20410.

* docs: mark kanban heartbeat row complete, regenerate progress surfaces, bump hermes submodule

Mark P1 'Kanban worker heartbeat, reclaim, and zombie detection' row
complete with TDD evidence. Regenerate all derived progress docs via
'go run ./cmd/progress write'. Bump hermes-agent submodule to
cff821e2d (upstream main from parity sweep).

* feat(tools): Docker execution backend with mount policy, env filtering, and fake-client tests

* docs: regenerate progress surfaces from parity sweep and Docker row, bump hermes submodule

* feat(install): default to release binary fetch instead of source build

curl https://gormes.ai/install.sh | bash now completes in ~2 seconds via
signed binary fetch from GitHub Releases instead of ~2-5 minutes via Go
toolchain download + git clone + go build. The slow source-build path
was the only install method, even though v0.1.06 already publishes 18
release assets (linux/darwin/windows × amd64/arm64 .tar.gz + .sha256 +
SBOM + build provenance attestations).

What's added:

- release_platform_arch() — maps uname -s/-m to a release-asset slug
  (linux-amd64, linux-arm64, darwin-amd64, darwin-arm64). Returns
  empty for unsupported platforms.

- decide_install_method() — 5-rule precedence picks binary-fetch or
  source-build at parse time so plan emitters can declare it:
  1. --local → source-build (operator wants their working tree)
  2. --from-source / GORMES_INSTALL_FROM_SOURCE=1 → source-build
  3. --branch != main → source-build (release binaries are only
     published from main)
  4. unsupported platform → source-build (graceful)
  5. otherwise → binary-fetch (the new fast path)

- fetch_release_binary() — GitHub API resolves the latest tag, curl
  or wget downloads gormes-<v>-<arch>.tar.gz + .sha256, sha256sum or
  shasum -a 256 verifies, tar -xzf extracts, mv installs to
  managed_bin_dir. Any failure (network, missing asset, hash
  mismatch, missing tools) triggers a clear log line and falls back
  to source-build so installs still succeed in restricted environments.

- Plan emitters now declare:
  - install_method: binary-fetch (linux-amd64 from latest release...)
    OR source-build (--from-source flag set | unsupported platform | ...)
  - source: github releases | managed git checkout | <local path>
  Verbose plan also emits source_mode, release_arch, release_api so
  operators can audit the network call before running real install.

End-to-end verification on Linux/amd64: a fresh sandbox install with
GORMES_BIN_DIR set completed in 1.832s wall, with Go never invoked
(grep -c 'Checking Go' = 0), exit=0, and `gormes version` reporting
0.1.06 from the published release.

Tests: 5 new dry-run plan tests in internal/installtest/install_method_test.go
covering the default binary-fetch path, --from-source override, env-var
override, non-default-branch fallback, and verbose plan details.

Implements progress.json row 5.P "Install: prefer pre-built release
binary over source build by default". Closes the binary-fetch coverage
gap noted in webpages/docs/development-skills/gormes-install/references/test-recipes.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(setup): prettify wizard with framed headers, screen clearing, and color

The fresh-install setup wizard now matches Hermes' UX polish: each section
clears the prior screen, renders a Hermes-style framed header, and uses
color/bold to highlight the active selection.

Source: 2026-05-07 fresh-Fedora install transcript from operator. The
operator-visible flow was previously a Spartan stack of plain-text prompts
and a 47-line provider list with no visual hierarchy.

What's added:

- internal/cli/wizard_ui.go — TTY-gated styling helpers:
  - ClearScreen(io.Writer) emits ANSI clear+home only on a TTY and only
    when GORMES_NO_CLEAR_SCREEN is not opted out.
  - Bold/Dim/Cyan/BrightCyan/Yellow/Green wrap text in ANSI escapes only
    when the writer is a TTY and NO_COLOR is not set (no-color.org).
  - PrintHeader emits a Hermes-style ╭──╮│ title │╰──╯ box with width
    clamp 32-60 cols.
  - PrintSectionDivider emits a 60-char dim horizontal rule.

- internal/cli/wizard_ui_test.go — 7 unit tests proving:
  - ClearScreen is a no-op on non-TTY writers (critical for piped
    transcripts and CI logs).
  - Color helpers pass through plain text on non-TTY and under NO_COLOR.
  - PrintHeader renders the framed title even without ANSI on non-TTY.
  - GORMES_NO_CLEAR_SCREEN opt-out blocks the screen clear.

Wired into:

- cmd/gormes/setup.go — runSetupFirstTimeChoice ("How would you like to
  set up Gormes?"), runSetupQuick, runSetupProviderSection,
  runSetupModelSection, printSetupTopLevelMenu all now ClearScreen +
  PrintHeader at section entry. Active menu option (the default) is
  rendered in bright-cyan ` → (●)` + bold label; inactive options use
  dim `(○)` markers.

- cmd/gormes/model.go — promptProviderChoice ClearScreen + PrintHeader
  ("Choose a provider") + colored markers: yellow `*` for the default
  provider, bright-cyan number + bold label, dim numbers + auth-type
  tag for the rest. The 47-provider list now visually scans top-down
  with the default provider standing out.

Existing test contract preserved: every Setup/Model test still asserts
on `strings.Contains` of the literal section names, and PrintHeader
emits the title verbatim within the box so those checks still pass.

Visually verified under `script -qfc` (faked TTY): screen clears
between sections, framed bright-cyan headers render with bold titles,
selection markers are colored, NO_COLOR=1 strips all escapes cleanly,
and pipe redirection (e.g., for log capture) emits plain text.

Note: this fix lands on main, but fresh `curl ... | bash` operators
will only see the prettified wizard after v0.1.07 cuts (install.sh's
binary-fetch path downloads the latest release binary, not main HEAD).
Operators can opt into source-build for immediate access via
`curl ... | bash --from-source` or GORMES_INSTALL_FROM_SOURCE=1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: add MCP channels_list tool with ChannelDirectoryProvider seam

Implements Hermes MCP channels_list tool (#21474 fix) in Go-native form:
- ChannelDirectoryProvider interface for reading platform channel data
- channelsListHandler reads from Platforms map, returns id/name/chat_id/enabled/platform per channel
- Platform filter support via optional 'platform' argument
- Degraded-mode error when directory unavailable
- Registered in RegisterDefaultTools alongside existing MCP tools

* docs: complete MCP channels_list row, elevate to P1, sharpen 4 umbrella rows

Parity sweep follow-up:
- MCP channels_list tool: status=complete with evidence
- Elevated priority from P2 to P1 (unblocked, validated contract)
- Added no_test_required to 4 umbrella rows: 61-tool registry port,
  Atomic checkpoints, 49-file CLI tree port, Config/profile/auth/setup
- Regenerated all progress-driven docs and site data

* feat: complete Brave Search + DDGS web search provider parity (P1 row closure)

- Add TestBraveProvider_DegradedWhenNoKey acceptance test
- Mark Brave Search + DDGS row complete in progress.json
- All acceptance criteria covered: Brave API search, DuckDuckGo search,
  backend matrix resolution, degraded-mode when key missing
- Implementation in internal/tools/web_tools.go as part of rows 5.C[6]/5.C[7]
- Reprioritized P2→P1 to match upstream Hermes v0.13.0 parity demand

* feat: complete Sandbox isolation depth selection row (P3) with config loader and TDD fixtures

* docs: mark Sandbox isolation depth selection complete, regenerate progress surfaces

* chore: track upstream hermes-agent (author map + UV_NO_CONFIG fix)

* feat(update): structured progress UX with banner, glyphs, and summary

`gormes update` previously emitted Spartan output that operators struggled
to scan:

    update branch: main
    update_check	checked update readiness for main

It now matches the Hermes-style cadence:

    ⚕ Updating Gormes Agent...

    update branch: main
    ℹ update_check	checked update readiness for main

    ✓ Update complete!

What's added:

- Bold `⚕ Updating Gormes Agent...` banner at the top so the operator
  immediately sees what's happening.
- Per-evidence glyph + color via `updateGlyphAndColor`:
  - `*_failed`, `*_error`         → ✗ (bold)
  - `*_unavailable`, `*_timeout`  → ⚠ (yellow)
  - `update_check`, `*_log_mirrored`, `update_not_managed_checkout` → ℹ (dim)
  - default                       → ✓ (green)
- Final `✓ Update complete!` (green+bold) on success or `✗ Update failed`
  (bold) on failure. Operator recovery still routes to stderr.
- Dim `update branch:` / `previous branch:` labels so the values stand out.

Backward compatibility: every UpdateEvidenceKind string still appears
verbatim in stdout so downstream parsers and existing tests that grep for
`update_*` kinds keep working. Verified by
TestUpdateCommand_PreservesEvidenceKindsForMachineReadability.

Sandbox safety: the cli.* color/style helpers no-op on non-TTY writers
(bytes.Buffer in tests, pipes/redirects in real use) and under NO_COLOR=1
so transcript captures and CI logs stay clean. Verified by
TestUpdateCommand_NoColorStripsAnsi.

6 new tests in cmd/gormes/update_progress_ui_test.go cover banner ordering,
success summary, failure summary + recovery routing, evidence-kind
backward compat, glyph-by-class mapping, and NO_COLOR strip.

Implements the first row from the gormes-update parity batch (the audit
agent's recommended #1, "structured progress UX"). Reuses the wizard_ui.go
helpers shipped in e7f492211 — same vocabulary across `gormes setup` and
`gormes update`. Unblocks the 5 remaining gormes-update parity rows
(skill sync, profile fan-out, web build, pre-update backup, config
migration prompt) which all build on this transcript shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(update): wire pre-update backup policy decision into structured progress

`gormes update` now exposes the operator-explicit pre-update backup choice
through the structured progress UX. The actual backup writer (zip
creation, size/duration reporting, retention) is the next slice; this row
ships the FLAG/DECISION surface so the wiring lands first.

What's added:

- `--backup` flag opts into a single-run pre-update backup of ~/.gormes.
- `--no-backup` flag force-skips the backup; beats both `--backup` and
  the future config-level opt-in (matches Hermes' precedence).
- `UpdateLifecycleOptions.Backup`, `NoBackup`, `BackupConfigEnabled`
  threaded through from cobra to RunUpdateLifecycle.
- New evidence kinds:
  - `update_pre_backup_skipped`   (--no-backup or default-skip-with-config)
  - `update_pre_backup_requested` (--backup or config-enabled)
- `emitPreUpdateBackupPolicy` runs BEFORE any git mutation and resolves
  via the existing ResolveBackupPolicy helper from
  internal/cli/backup_policy.go, so a future writer slice can wire
  behind the same trigger without re-deriving precedence.
- Glyph mapping in cmd/gormes/update.go extended:
  - `*_skipped` → ℹ (dim)
  - `*_requested` → ◆ (bright cyan, mirrors Hermes' "◆ Creating
    pre-update backup..." marker)

Silent-default contract: when neither --backup nor --no-backup is set
and BackupConfigEnabled is false, NO backup-related evidence is emitted.
Most operators don't need to hear about the skipped backup on every
update run (parity with Hermes' default).

Tests:

- internal/cli/update_lifecycle_backup_test.go — 5 lifecycle tests:
  - NoBackupFlag emits skipped evidence with backup_disabled_by_flag reason
  - BackupFlag emits requested evidence with backup_forced reason +
    explicit "writer not yet implemented" detail (so operators don't
    expect a backup file to exist)
  - NoBackupBeatsBackup precedence rule
  - DefaultEmitsNoBackupEvidence (silent-default contract)
  - PreBackupRunsBeforeGitMutation (ordering is the contract for the
    follow-up writer slice — a backup taken after pull is useless for
    rollback)
- cmd/gormes/update_progress_ui_test.go — 2 UI tests:
  - BackupFlagFlowsToLifecycle proves cobra→options plumbing
  - PreBackupGlyphs proves ℹ for skipped, ◆ for requested

Implements row #2 from the gormes-update parity batch (audit agent's
recommended order). Reuses ResolveBackupPolicy and the structured
progress UX from c83d10e77. Unblocks a follow-up writer slice that can
wire the actual backup file creation behind the existing
update_pre_backup_requested trigger without re-deriving the precedence
rules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(navivox): render tool-call artifacts in chat tiles

Extend the chat _ToolCallBody to list each NavivoxToolArtifact below the
tool-call summary with an attachment icon, kind label, title, and optional
artifact summary line. The artifact data was already populated by the
SshNavivoxChannel; this just surfaces it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(progress): add gateway whitelist parity row

Track Hermes' allowed_chats/channels/rooms whitelist parity for Telegram,
Slack, Discord, Mattermost, Matrix, and DingTalk as a P1 planned slice.
Sources cite Hermes 69d025e4a (Telegram/Mattermost/Matrix/DingTalk),
cd3ef685c (Slack), and PR #7044 (Discord). Generated docs and site
mirrors regenerated to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: sync install.sh mirror to source

Regenerate webpages/landing/public/install.sh to match root install.sh
which was updated to default to release binary fetch instead of source
build (commit 044cd264d). The mirror had drifted one commit behind.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(update): wire bundled-skill profile sync into update lifecycle

`gormes update` now runs the existing internal/skills.SyncBundledSkillsToProfiles
helper after a successful pull and renders Hermes-parity counts in the
structured progress UX. Skill sync is best-effort: errors emit
update_skill_sync_failed evidence but never fail the overall update.

What's added:

- internal/cli SkillSyncRunner type — abstract seam decoupled from
  internal/skills (the lifecycle package stays import-free; the cmd-side
  adapter does the conversion).
- internal/cli SkillSyncResult + SkillSyncProfileResult types — mirror
  the skills helper's per-profile counts (Added, Unchanged, Conflicts,
  Failed) without coupling.
- internal/cli emitSkillSync helper runs after pull but BEFORE gateway
  restart so the gateway can pick up newly-bundled skills on its
  restart cycle.
- formatSkillSyncSummary renders Hermes-parity:
    `default: +5 new, 12 unchanged, 1 user-modified (kept)`
  with zero-count buckets omitted to keep the transcript short.
- Two new evidence kinds: update_skill_sync_completed (per profile),
  update_skill_sync_failed (when seam returns an error).
- updateCommandSeams.SkillSyncFor — injectable factory; default builds
  the production adapter that calls SyncBundledSkillsToProfiles against
  `<checkout>/skills` and the active profile root from config.GormesHome.

Silent-default contract preserved: when the seam is nil (the case for
non-managed checkouts where bundled skills aren't applicable), no
update_skill_sync_* evidence is emitted. Same shape as the pre-update
backup silent-default from 56efc4042.

Tests:

- internal/cli/update_lifecycle_skill_sync_test.go (4 lifecycle tests):
  - NilSkillSync emits no evidence (silent default contract)
  - SkillSyncSuccess emits one completed evidence per profile with
    correct count strings ("+5 new", "user-modified")
  - SkillSyncFailure emits failed evidence with error message AND the
    update still returns success (best-effort contract)
  - SkillSyncRunsAfterPull verifies the ordering invariant
- cmd/gormes/update_progress_ui_test.go (3 cmd tests):
  - SkillSyncSeamWiredByDefault proves the default adapter wires when
    checkout/skills exists and GORMES_HOME is set
  - SkillSyncSeamNilWhenSkillsAbsent proves silent-default for
    non-managed checkouts
  - SkillSyncGlyphs proves ✓ for completed, ✗ for failed in the
    structured progress UX

Implements row #3 from the gormes-update parity batch (audit agent's
recommended order). Reuses the structured progress UX from c83d10e77
and the silent-default pattern from 56efc4042. Real adapter calls the
existing SyncBundledSkillsToProfiles helper from internal/skills (which
was already row-complete from prior planner work).

Owned divergence vs Hermes:
- Multi-profile fan-out: only the `default` profile is synced today.
  Named-profile discovery is a follow-up slice (parity row exists).
- "↑ updated" counts: helper does not distinguish updated-from-source
  vs unchanged. Counts surface as Added/Unchanged only.
- "− removed from manifest" counts: helper does not track removals
  from the bundled tree. Same follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(update): wire web UI rebuild step into update lifecycle

`gormes update` now rebuilds the web UI bundle after pull when a
`<checkout>/web/package.json` exists. Best-effort: failures emit
update_web_build_failed evidence but never fail the overall update
(matches Hermes' soft-failure contract — only `hermes web` treats a
build error as fatal).

What's added:

- internal/cli WebBuildRunner + WebBuildResult types (parallel to
  SkillSyncRunner pattern, decoupled from os/exec).
- 4 new evidence kinds:
  - update_web_build_completed   → ✓ green (success)
  - update_web_build_skipped     → ℹ dim (--skip-web or no package.json)
  - update_web_build_unavailable → ⚠ yellow (npm not on PATH)
  - update_web_build_failed      → ✗ bold (npm install / npm run build
                                   non-zero exit)
- internal/cli emitWebBuild helper runs after skill sync and maps the
  WebBuildResult to the right evidence kind based on Skipped/Unavailable
  flags or error.
- cmd/gormes --skip-web flag for operator opt-out.
- updateCommandSeams.WebBuildFor — injectable factory; default builds
  the production adapter that runs `npm install --silent` then
  `npm run build` in `<checkoutDir>/web`.

Silent-default contract preserved: when the factory returns nil (no
web/package.json in checkout), the lifecycle emits no web_build_*
evidence at all. Most non-managed checkouts and runtimes without a
web/ tree never see the feature.

Tests:

- internal/cli/update_lifecycle_web_build_test.go (5 lifecycle tests):
  - NilWebBuild emits no evidence (silent-default contract)
  - WebBuildCompleted emits completed evidence with seam Detail
  - WebBuildSkipped emits skipped evidence with seam Reason
  - WebBuildUnavailable emits unavailable evidence with seam Reason
  - WebBuildFailure emits failed evidence + update still returns success
- cmd/gormes/update_progress_ui_test.go (4 cmd tests):
  - WebBuildFactoryNilWhenNoPackageJson silent-default
  - WebBuildFactoryWiredWhenPackageJsonPresent factory wires runner
  - WebBuildSkipFlagShortCircuitsRunner --skip-web returns Skipped
    without invoking npm
  - WebBuildGlyphs verifies all 4 evidence kinds map to the right
    structured-progress glyphs

Implements row #5 from the gormes-update parity batch (audit agent's
recommended order — #4 goncho sync was deferred per the audit's "if
internal/goncho profile-sync surface does not exist yet, P2 is fine"
caveat; gormes/internal/goncho has no profile-sync surface today).

Reuses the structured progress UX from c83d10e77 and the
silent-default + best-effort patterns from 56efc4042 + f30f72c25.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: gateway allowed_chats/channels/rooms whitelist parity

Add shared WhitelistConfig + IsAllowed + ParseWhitelistConfig in
internal/gateway/whitelist.go with 8 focused TDD fixtures. Extend
TelegramCfg and SlackCfg with AllowedChats/AllowedChannels list config
fields plus accessor methods and env-var parsing. Wire whitelist
filtering into gateway Manager.allowed() check.

Ports Hermes v0.13.0 whitelist feature (69d025e4a + cd3ef685c) across
Telegram, Slack, and Discord platforms. Per-platform adapter wiring is
follow-up child rows.

* docs(progress): mark gateway whitelist row complete, regenerate progress surfaces

* feat(navivox): show selected agent indicator chip in chat AppBar

* chore: bump hermes-agent submodule

Advance hermes-agent from 7f369bfe5 to 7d66d30d7. Upstream picks up
TUI /sessions slash command, personality switch session preservation,
sessions+skills menu merge, personality fix merge, and kanban dashboard
tooltips.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(update): wire config schema migration prompt into update lifecycle

`gormes update` now checks the on-disk config schema version after the
web build step. When the version is outdated:

- With --yes: auto-applies via internal/config.MigrateConfigFile and
  emits ✓ update_config_migrate_completed.
- Without --yes: emits ⚠ update_config_migrate_needed advisory pointing
  the operator at `gormes config migrate` (or rerun with --yes).

When the config is already current, no evidence is emitted (silent
default — no nag on every update). Best-effort: errors from either the
check or migrate seam emit ✗ update_config_migrate_failed but never
fail the overall update.

What's added:

- internal/cli ConfigCheckRunner + ConfigMigrateRunner seam types
  (parallel to SkillSyncRunner / WebBuildRunner pattern; decoupled from
  internal/config so the lifecycle stays import-free).
- internal/cli ConfigVersionResult struct mirroring CheckReport.
- 3 new evidence kinds:
  - update_config_migrate_completed → ✓ (auto-applied)
  - update_config_migrate_needed    → ⚠ (operator action required)
  - update_config_migrate_failed    → ✗ (check or migrate error)
- internal/cli emitConfigMigrate helper with 7-branch decision table
  documented inline. Runs after web build, before gateway restart.
- cmd/gormes default adapters wrap internal/config.Check and
  internal/config.MigrateConfigFile into the seam shape.
- updateGlyphAndColor extended: `*_needed` suffix → ⚠ (yellow,
  operator-action-required category).

Tests:

- internal/cli/update_lifecycle_config_migrate_test.go (6 lifecycle tests):
  - NilConfigCheck silent default
  - ConfigAlreadyCurrent silent (no nag)
  - ConfigOutdatedWithoutYes emits needed + does NOT call migrate
  - ConfigOutdatedWithYes auto-applies + emits completed
  - ConfigMigrateError emits failed + update still succeeds
  - ConfigCheckError emits failed + does NOT attempt migrate
- cmd/gormes/update_progress_ui_test.go ConfigMigrateGlyphs verifies
  ✓/⚠/✗ for the 3 new kinds.

Implements row #6 from the gormes-update parity batch (audit agent's
recommended order). Reuses the structured progress UX from c83d10e77
and the silent-default + best-effort patterns established by 56efc4042
(backup), f30f72c25 (skill sync), and fa4a10d00 (web build).

Owned divergence vs Hermes' richer prompt:

- Interactive y/n at the prompt is intentionally not in this slice.
  Hermes' interactive flow would require a stdin reader seam and more
  branching. The slice ships --yes auto-apply + advisory-when-not, and
  the operator interactive path remains owned by the existing
  `gormes config migrate` command.
- Per-bucket missing_env / missing_config counts (Hermes' "⚠️ N
  required setting(s) need configuration") are not in this slice.
  CheckReport's Issues field carries them but the slice surfaces only
  version-skew. Detailed breakdown is a follow-up if operators ask.

Audit batch progress: 5 of 6 update-parity rows shipped (structured
UX, pre-update backup, skill sync, web build, config migrate).
Remaining: goncho sync (#4) — deferred until internal/goncho gains a
profile-sync surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(navivox): risk-aware approval banner badge

Replace the plain "Risk: $risk" text with a labeled badge: "High risk"
gets a warning icon, "Medium risk" / "Low risk" show a label, and unknown
or null risk renders no badge. The risk-aware badge makes high-risk
approvals scannable at a glance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(strategy): ratify v1 differentiator and bump release-pipeline rows to P1

Add docs/content/building-gormes/strategy/v1-differentiator.md with the
ratified-on 2026-05-07 differentiator paragraph, 30-skill curated list,
and exclusion list. Promote release-pipeline / WhatsApp Baileys /
Signal-transport / Matrix-seam rows to P1/P2 priorities.

Repair a trailing-comma JSON syntax error introduced by the planning
note. Generated docs and site mirrors regenerated to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: bump version to 0.1.07

Sync cmd/gormes/version.go and webpages/landing/src/data/release.json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: sync CHANGELOG.md with v0.1.07 release entries

* docs: add Hermes-style v0.1.07 release notes

The Operator-First Release. ~735 commits since v0.1.06.

Highlights:
- install.sh defaults to release-binary fetch (~7x faster fresh installs)
- Setup wizard prettified (framed headers, screen clear, color)
- gormes update parity: 5 of 6 audit-batch rows shipped (structured
  progress UX, pre-update backup, skill sync, web build, config migrate)
- Install isolation: iso-shellrc-leak + iso-systemd-hijack closed
- Repaired red Deploy gormes.ai workflow on main
- Loop \$/iteration cost metric

Same operating day as v0.1.06; date alias v2026.5.7 is shared.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: vendored WhatsApp Baileys bridge and removed QR pairing blocker

Vendored the Hermes-compatible Baileys bridge into scripts/whatsapp-bridge/ with provenance notice. Removed BLOCKER from preflight output. All WhatsApp tests pass.

* docs: parity sweep, agent lifecycle hooks row, WhatsApp QR closure

Bounded all-topic parity sweep across CLI/TUI, provider/auth, gateway/channels, tools, sessions/memory/Goncho, install/runtime, browser, docs, and release surfaces. Added agent lifecycle hooks (agent:start/step/end) row to Phase 5.O. Marked WhatsApp live Baileys QR pairing wizard complete. Bumped hermes-agent submodule to current upstream main (307c85e5c).

* feat(update): implement pre-update backup writer (zip of ~/.gormes)

Closes the writer-not-yet-implemented deferral from 56efc4042. When
--backup is set AND the writer seam is wired (the production default),
gormes update now creates a real zip archive of the operator's
~/.gormes before any git mutation.

What's added:

- internal/cli WriteBackupZip(ctx, sourceDir, destPath) (BackupResult, error)
  - Walks sourceDir with filepath.WalkDir
  - Skips paths matching IsExcludedFromBackup (checkpoints/, backups/,
    *.db-wal, *.db-shm, *.db-journal)
  - Streams to destPath.tmp first, renames on success (atomic)
  - Returns Path, SizeBytes (final archive byte count), DurationMs (wall)
- internal/cli BackupResult struct + BackupWriter seam type
- 2 new evidence kinds:
  - update_pre_backup_completed → ✓ green
  - update_pre_backup_failed    → ✗ bold
- emitPreUpdateBackupPolicy now invokes the writer when policy resolves
  to Requested AND BackupWriter seam is non-nil; fallback (nil seam)
  preserves the prior policy-only deferral evidence so existing tests
  don't regress.
- formatBackupSize / formatBackupDuration render human-readable size
  (B/KB/MB/GB) and duration (Nms or N.NNs) inline in evidence detail.
- backup_policy.go IsExcludedFromBackup now skips `backups/` subtree
  (so subsequent backups don't include all prior backups, growing
  geometrically).
- cmd/gormes defaultBackupWriterFor wires WriteBackupZip against
  config.GormesHome with destPath = `<home>/backups/pre-update-<UTC>.zip`.

Tests:

- internal/cli/backup_writer_test.go (3 unit tests):
  - IncludesPlainFilesExcludesSidecars verifies the walk picks up
    config.toml + auth.json + nested/dir/skill.md but skips
    checkpoints/snap.bin + log.db-{wal,shm,journal}
  - AtomicRenameOnSuccessNoTmpLeak verifies no .tmp leftovers
  - RejectsEmptyArgs guards misconfigured callers
- internal/cli/update_lifecycle_backup_writer_test.go (5 lifecycle tests):
  - BackupRequestedNilWriter keeps the deferral evidence (backward
    compat with the policy-only slice)
  - BackupWriterSuccess emits completed evidence with path + size + duration
  - BackupWriterFailure emits failed + update still succeeds (best-effort)
  - BackupWriterNotInvokedWhenNoBackup proves --no-backup short-circuits
  - BackupSizeFormatting covers B/KB/MB/GB scaling

End-to-end visual proof on a sandbox HOME:

    ⚕ Updating Gormes Agent...

    update branch: main
    ✓ update_pre_backup_completed    /tmp/.gormes/backups/pre-update-20260508T012115Z.zip (329B, 10ms)

Real backup file written: 329 bytes in 10ms. Future evolution: retention
pruning (keep last N), ConfigEnabled wiring from real config (currently
flag-driven only), per-bucket size summary in detail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: agent lifecycle hooks (agent:start, step, end) with Kernel callback

Adds HookAgentStart/HookAgentStep/HookAgentEnd hook point constants and
AgentLifecycleHook callback to Kernel Config, firing at turn start, after
each tool iteration, and on all turn exit paths. Matches Hermes'
gateway/hooks.py lifecycle event dispatch pattern.

* docs: mark agent lifecycle hooks complete with evidence and sources

Updates progress.json row from planned to complete with validated contract,
Hermes source line references, implementation evidence, and test coverage.
Removes the row from agent-queue and next-slices. Regenerates mirror
progress.json copies for landing and go-renderer sites.

* chore: bump hermes-agent submodule for hooks.py lifecycle parity

Pulls upstream hermes-agent to faa13e49f for verified hook event dispatch
line references used in agent lifecycle hooks contract validation.

* flutter: Navivox key store providers and keys screen update

Adds Riverpod-based key store providers (identityStoreProvider,
serverStoreProvider) with stream-based watch pattern. Updates KeysScreen
to ConsumerWidget showing imported servers and identities, with empty
state for no keys.

* feat(update): pre-update backup retention pruning (keep newest 5)

After each successful pre-update backup write, the production adapter
now prunes older backup zips so the directory doesn't grow forever.
Default retention: 5 (matches Hermes' `updates.backup_keep` default).

What's added:

- internal/cli PruneBackups(backupDir, keep) (count, freed, err) helper:
  - Filters by `pre-update-*.zip` filename pattern (operator-owned files
    in the same directory survive untouched).
  - Sorts candidates by mtime, keeps newest `keep`, removes the rest.
  - keep <= 0 → no-op safety (operators who pass --backup-keep 0 by
    mistake should not lose data).
  - Missing directory → no-op (fresh installs with no prior backups).
  - Per-file remove errors are silently skipped — pruning is
    best-effort and must not block update completion.
- internal/cli BackupResult extended with PrunedCount + PrunedBytes.
- internal/cli emitPreUpdateBackupPolicy detail formatter appends
  `; pruned N older (M freed)` only when PrunedCount > 0; the no-prune
  common case (first --backup, recently-cleaned state) keeps the
  transcript short.
- cmd/gormes defaultBackupWriterFor calls PruneBackups after a
  successful WriteBackupZip with keep=defaultBackupKeep (5).

Tests:

- internal/cli/backup_prune_test.go (5 unit tests):
  - KeepsNewestN: 5 fixtures with monotonic mtimes, keep=2 → 3 removed,
    newest 2 survive.
  - NoOpWhenAtOrUnderKeep: 1 file, keep=5 → no-op.
  - IgnoresNonBackupFiles: operator's NOTES.md + manifest.json +
    other-archive.tar.gz survive a prune that reduces 3 backup files to 1.
  - KeepZeroIsNoOpSafety: keep=0 and keep=-1 both no-op.
  - MissingDirReturnsNoOp: non-existent dir returns (0, 0, nil).
- internal/cli/update_lifecycle_backup_writer_test.go (2 lifecycle tests):
  - BackupCompletedDetailIncludesPruneInfoWhenPositive: PrunedCount=3
    surfaces "pruned 3 older" + "freed" in evidence detail.
  - BackupCompletedDetailOmitsPruneSuffixWhenZero: PrunedCount=0 emits
    no prune phrasing.

End-to-end verification with a sandbox HOME seeded with 6 backup files:

    ⚕ Updating Gormes Agent...

    update branch: main
    ✓ update_pre_backup_completed    /tmp/.gormes/backups/pre-update-20260508T014058Z.zip (329B, 2ms); pruned 2 older (240B freed)

After run: 5 files in backups/ (newest 5 of the 6 seeds + 1 new write,
2 oldest removed) — matches the keep=5 contract exactly.

Future-natural follow-up: wire `updates.backup_keep` from real config
into a configurable retention budget (currently hardcoded to 5).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: plugin lifecycle hook transform_llm_output with TDD fixtures

* docs: mark transform_llm_output complete, unblock 7 parity rows, regenerate surfaces

* feat: gateway allowed_chats whitelist config and update test coverage

* fix(update): survive missing config.toml on fresh install

* feat(signal): add signal-cli HTTP JSON-RPC + SSE bridge bootstrap layer

Implement the fakeable Signal transport/bootstrap layer that bridges
the existing signal.Bot seam to the signal-cli HTTP daemon. Five TDD
acceptance tests cover config/health/lock lifecycle, SSE URL encoding,
attachment fetch params, reconnect/recovery, and outbound send with
typing-stop through a fake HTTP transport.

Progress: Phase 7.A Signal subphase is now fully complete (4/4 rows).

* fix(progress): update Phase 7.A test expectations for completed Signal bootstrap row

* docs: close 6 inert umbrellas (5.O + 5.V), mark Matrix seam complete, regenerate surfaces

* fix: add hermes gateway list to CLI parity manifest and tests

* feat(matrix): add shared-chassis bot seam over threadtext contract

* chore: pre-existing update lifecycle, backup writer, config writer changes

* feat: add restore command, backup restore lifecycle, and update tests

* chore: pre-existing status command changes

* feat(restore): dry-run validates zip before printing would-extract

Restore dry-run (without --yes) now validates the zip archive is openable and free of path-traversal entries BEFORE showing the "would extract" message. A corrupt or malicious zip will now surface the rejection immediately instead of misleading the operator.

* feat(auth): add --json flag to auth status for machine-readable output

gormes auth status <provider> --json now emits a parseable JSON document with the same redacted credential fields the human surface renders. Enables credential-health monitoring in CI/cron without scraping the human row.

* fix(restore): add zip pre-validation to RestoreFromZip extract path

Extends restore zip validation to the actual extract path (not just dry-run). Pre-validates the entire archive before extracting any files, so a malicious entry late in the zip cannot land earlier safe entries on disk.

* refactor(doctor): route output through cobra writers, use newExitCodeError instead of os.Exit

* feat(doctor): add JSON output mode and constructor command pattern

- internal/doctor: add StatusSkip, MarshalJSON, and JSON struct tags
- cmd/gormes/doctor: refactor doctorCmd to newDoctorCommand() constructor
  with doctorReporter for dual text/JSON output, eliminating shared var
  test contamination
- cmd/gormes: wire newDoctorCommand() in root, update tests to use
  newRootCommand() with subcommand args
- cmd/gormes/doctor_runE_test: add fleet-health JSON assertion test

* test(doctor): add failed-field assertion for JSON monitoring contract

* feat(doctor): track failed bool in JSON output for monitoring parity

* feat(doctor): add drSeverity and pending incremental JSON refinements

* feat(version): add git_commit field to --json output for fleet binary verification

* feat: add git_commit field to --json version output and refactor versionCmd to constructor pattern

- Refactor versionCmd from package-level var to newVersionCommand() constructor
  to prevent cross-test flag contamination (same anti-pattern that broke
  doctorCmd before its constructor refactor)
- Add TestVersionCommand_ConstructorReturnsIndependentInstances test
- Add git_commit field to versionReportJSON, populated via ldflag in CI/Makefile
- Update deploy-gormes-www.yml, release.yml, Makefile build commands with
  -X main.GitCommit=${GIT_COMM…
XelHaku added a commit that referenced this pull request May 10, 2026
… permanent delete

Live regression 2026-05-10: a sandbox uninstall test
(GORMES_INSTALL_HOME=/tmp/...) accidentally targeted the operator's REAL
~/.gormes and PERMANENTLY DELETED .env (provider keys), memory.db
(Goncho conversation history), config.toml, ~/.gormes/bin/gormes, and
~/.local/bin/gormes. Two independent root causes lined up to make this
maximally destructive:

1. install.sh's run_uninstall() did not export GORMES_HOME before
   invoking the gormes binary's `uninstall` subcommand. The gormes
   process inherited the operator's default $HOME-derived ~/.gormes
   path instead of the sandbox path the operator actually pointed at.

2. cmd/gormes/uninstall.go's executeUninstall used os.RemoveAll
   directly — permanent unrecoverable deletion. There was no fallback
   to a freedesktop trash, so even when the wrong scope was chosen,
   recovery was impossible.

Fix #1 — install.sh run_uninstall pins scope:
  GORMES_HOME="$(managed_home_dir)"
  export GORMES_HOME

managed_home_dir() returns $GORMES_INSTALL_HOME when set, else
$HOME/.gormes. So sandbox uninstalls now stay in the sandbox; default
uninstalls still target ~/.gormes as before.

Fix #2 — cmd/gormes/uninstall.go pickArtifactMover():
  - default: `gio trash <path>` when gio is on PATH (move to
    freedesktop trash, recoverable from any file manager's trash UI)
  - GORMES_UNINSTALL_FORCE_PURGE=1: opt-in permanent delete for CI
    cleanup, container teardown, secure wipe
  - fallback: permanent delete with an explicit label that names the
    missing dependency ("gio not available; install glib2-tools for
    recoverable trash") so operators understand why their uninstall
    isn't trash-aware

Removal mode is now logged to stdout and surfaced in --json output as
removal_mode: "...", so fleet automation can verify which mode fired.

Recovery from this incident only worked because an earlier May-2
uninstall on the same operator's host had used a trash-aware path,
leaving ~/.local/share/Trash/files/.gormes/ as a partial backup. The
new default would have made that explicit instead of accidental.

Tests:
- TestPickArtifactMover_PrefersGioTrashWhenAvailable
- TestPickArtifactMover_ForcePurgeOptsIntoPermanentDelete
- TestPickArtifactMover_ForcePurgeAcceptsTrueAlias
- TestPickArtifactMover_DefaultOnHostWithoutGio

Existing uninstall tests (12) still pass — they all run with t.TempDir()
paths that gio trash handles identically to os.RemoveAll for cleanup
purposes.
XelHaku added a commit that referenced this pull request May 11, 2026
…ests (#184)

* chore: rename builder loop scripts

* feat: add google chat channel seam

* fix: avoid dashboard flag shorthand collision

* fix: align installer public surface tests

* fix: include panic excerpt in crash stderr

* fix: route Goncho sqlite opens through busy-timeout helper

* feat: add Matrix sender filter parity

* fix: clarify update check copy

* feat: add Microsoft Graph helper parity

* fix: show empty kanban list placeholder

* fix: reject unknown active profiles

* fix: fall back to build vcs metadata

* feat: expand cron deliver all routing intent

* feat: surface profile distribution metadata

* fix: reject plugin subcommand typos

* fix: reject parent command typos

* feat: add config get command

* fix: show unset config values

* test: isolate tui save export fixtures

* fix: treat missing turns table as empty

* test: cover legacy xdg uninstall cleanup

* fix: remove legacy xdg state on uninstall

* fix: treat fresh install state as empty

* test: isolate discord channel home state

* feat: track msgraph webhook platform drift

* fix: suggest parent command typos

* fix: report empty goncho system status

* feat: emit onboard wizard json plan

* fix: avoid duplicate mcp login errors

* chore: ignore whatsapp bridge node modules

* fix: normalize cli json degraded states

* docs: document native Windows installer

* tools: refresh Hermes ea86714 parity manifest

* fix: normalize gateway discovery json beacons

* fix: normalize gateway status json empties

* test: cover fresh install json inventory surfaces

* feat: emit config get json

* test: assert json surfaces carry build provenance

* feat: track teams pipeline plugin metadata

* test: refresh docs home install assertion

* feat: add TUI websocket attach transport

* fix: emit kanban not-found JSON

* feat: add matrix bootstrap parity boundary

* docs: refresh Hermes Teams docs mirror

* feat: add mattermost bootstrap boundary

* ci: add termux release artifact coverage

* docs: lead README with methodology positioning

* web: align landing with methodology positioning

* feat: add project-mode execute code sandbox

* feat: normalize cron approval mode

* feat: bind native tui kanban slash command

* test: make logs fallback test deterministic

* docs: close Windows installer parity row

* feat: add Windows gateway scheduled task lifecycle

* fix: route gateway kanban through shared runner

* feat: wire execute code mode config

* test: stabilize kanban dispatcher shutdown checks

* feat: add kanban dashboard dispatch quick path

* fix: port shift-enter tui newline parity

* docs: reconcile shift-enter parity row

* fix: align TUI quit slash with Hermes

* test: refresh Hermes patch tool parity manifest

* feat: serialize Hermes fast-mode overrides

* feat: add Kanban triage specify parity

* test: stabilize Discord manager smoke

* ci: enforce release archive size cap

* docs: close release archive size gate row

* ci: add release notes artifact sizes

* feat: add slack manifest generator

* release: prepare v0.2.0 (v2026.5.8)

Minor bump 0.1 → 0.2 marks the move from case-by-case bug fixing to a
CI-guarded contract for the --json arc. The autonomous engineering loop
now ships its own conformance fence:

- Six-battery fresh-install E2E suite (cmd/gormes/fresh_install_e2e_test.go)
  asserts no-null-arrays, typo-suggestions across 9 parents, fresh-install
  read-only exit-zero, build-provenance prepended on every captured-state
  --json document, parseable JSON on stdout, and structured
  {action: "not_found"} on lookup failures.
- Native Windows installer (scripts/install.ps1) is first-class alongside
  install.sh.
- gormes config get --json: structured {build, key, value, secret_redacted,
  set} with secret-key redaction.
- Fresh-install UX uniform across legacy XDG migration, missing-DB read
  paths, and parent-command typo handling.

Landing.js + Playwright reverted to v0.1.07 baseline. Methodology landing
rewrite is a separate post-release slice; this release ships the fence,
not the pitch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: bound gateway startup disconnect cleanup

* fix: route kanban commands through current board

* feat: inject release build date provenance

* fix: target install's managed checkout in gormes update, not cwd

`gormes update` defaulted CheckoutDir to os.Getwd, walking up from
whatever directory the operator invoked the command from. Run from
inside the gormes-agent dev tree, it switched the tree's branch from
`development` to `main` and ran a web build there — mutating the
user's source instead of the install.

resolveManagedCheckoutDir() mirrors install.sh's managed_checkout_dir():
GORMES_INSTALL_DIR override → $GORMES_INSTALL_HOME/gormes-agent →
$HOME/.gormes/gormes-agent. Never falls back to cwd. The lifecycle's
existing update_not_managed_checkout guard correctly fails the run
when the resolved path isn't a git worktree (e.g., on a binary-fetch
install with no managed clone).

Two RED → GREEN tests pin the contract:
- env-var override path (GORMES_INSTALL_DIR wins outright)
- default fallback path (managed_home/gormes-agent, never cwd)

Found during v0.2.0 fresh-install probe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: remove dangling published-binary symlink during uninstall

`gormes uninstall --yes` removed the managed binary at
`<home>/bin/gormes` but left the install.sh-published PATH symlink at
`~/.local/bin/gormes` dangling. After uninstall, `which gormes` returned
empty, but the broken symlink persisted and confused reinstalls and
shell completions.

New `published-binary` artifact group enumerates the install.sh
PATH-linked locations:
  - $GORMES_BIN_DIR (operator override; install.sh exports this)
  - $GORMES_PREFIX/bin (compatibility prefix)
  - $HOME/.local/bin (non-root default)
  - /usr/local/bin (root linux default)

Surgical safety: only entries that are SYMLINKS whose target resolves
into the gormes home are returned. A real binary at the same path
(built from source, package-managed, manually placed) is never touched.

Two RED → GREEN tests pin the contract:
- TestUninstall_RemovesPublishedBinarySymlink — symlink → managed
  binary gets removed and listed in the JSON outcome.
- TestUninstall_LeavesUnrelatedGormesBinaryAlone — non-symlink at
  the same path is preserved.

Found during v0.2.0 fresh-install probe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: bind Matrix E2EE bootstrap device id

Complete the Matrix E2EE device-id crypto-store progress row with TDD coverage for resolved runtime device IDs, configured override behavior, missing-device degradation, and store-level put_device_id binding.

Validated with go test ./... -count=1, go run ./cmd/progress validate, git diff --check, Matrix row tests, and docs/landing public-surface gates.

* fix: publish install.sh and install.ps1 as release assets

A curl following the natural GitHub release URL pattern

  https://github.com/.../releases/download/v0.2.0/install.sh

returned 404 — install.sh and install.ps1 were not GitHub release
assets, only landing-served at https://gormes.ai/install.sh. Users
following the README's tagged-release URLs hit a wall and had to
already know about the landing path to bootstrap.

Publish step now stages install.sh + install.ps1 into dist/ before
the softprops upload, and the release-notes block surfaces the
canonical curl/irm one-liners so the install URLs are discoverable
from the GitHub release page itself.

RED → GREEN test in webpages/docs/install/release_workflow_test.go
asserts both scripts appear in the upload glob and the release notes.

Found during v0.2.0 fresh-install probe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: add singularity command preflight contract

* feat: add Camofox browser bridge

* fix: pause interrupted gateway goals

* fix(tui): trim soft-wrap markdown spaces

* feat: add kanban run history command

* feat: add kanban boards read model

* docs: add nix flake packaging contract

* feat: add kanban board override flag

* fix: preserve kanban named board roots

* fix: cap gateway shutdown disconnects

* ci: attest release sboms

* docs: close release sbom attestation row

* feat: add kanban dashboard run history

* feat: add kanban gc retention

* fix: release signal lock before close returns

* feat: prefer Windows release binary installs

* feat: honor cron approval mode for terminal tools

* ci: add OCI image smoke workflow

* feat: add QQ Bot transport bootstrap seam

* test: guard cron origin delivery identity

* fix: normalize partial cron job records

* web: add cron dashboard page

* fix: preserve Telegram DM topic routing

* ci: attest release build provenance

* feat: support delegate task batches

* fix: align kanban comment handoff policy

* docs: close delegate task batch parity row

* fix: preserve google chat relay sender type

* feat: add gateway approval queue resolver

* feat: add cron standalone sender fallback

* feat: complete behavioral pattern extraction

* feat: add profile clone-all create policy

* web: add built-with Gormes proof page

* fix: mirror telegram streaming edit safety

* web: sync release date alias

* feat: add Feishu update prompt cards

* docs: sync release prep target matrix

* feat: add curator archive prune commands

* fix: coerce stringified tool-call arguments

* feat: add kanban stats command

* docs: close Kanban operator surface row

* feat: add claw cleanup compatibility

* fix: default top-level logout provider

* docs: close logout fallback progress row

* feat: add V4A patch mode to native patch tool

* fix: deliver fast discord final frames

* feat: classify minimax oauth provider

* feat: restore fallback provider CLI

* fix: align Telegram table row-label rendering

* feat: add Google Chat standalone cron sender

* feat: support V4A patch moves

* chore: sync release benchmark metadata

* feat: clear slash confirmations on reset

* Emit JSON for invalid input errors

* Add Kanban notify subscriptions

* feat: add structured lint evidence to file tools

* fix: surface home-tree wildcard under honest `gormes-home` group

`gormes uninstall --json` preview classified the home-directory
wildcard `<home>/` under the "logs" group (because
config.CrashLogDir() returns GormesHome()). Fleet automation reading

  {"name": "logs", "paths": ["/home/xel/.gormes/"]}

reasonably interpreted that as "removes log files," but
`os.RemoveAll("<home>")` actually nukes the entire home tree:
config, sessions, skills, subagents, kanban DB, the managed binary
subdirectory — everything not enumerated by another group.

Renamed split:
- "logs" now lists only explicit log files (gormes.log)
- new "gormes-home" group lists the home-tree wildcard with a
  truthful name; ordered last so per-feature groups appear first
  and the wholesale-removal scope is unambiguous.

RED → GREEN test pins the contract:
- TestUninstall_HomeDirWildcardIsNotMisclassifiedAsLogs

Found during v0.2.0 fresh-install probe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: emit JSON on cobra typo-with-suggestion under any parent

`gormes config gat --json` and `gormes kanban shor --json` escaped
the v0.2.0 conformance fence: cobra's built-in `findSuggestions`
short-circuits in `Find()` and returns
`unknown command "gat" for "gormes config"; did you mean "get"?`
directly to Execute() — bypassing the parent's RunE guard installed
by `installParentUnknownSubcommandGuards` (which only fires on the
no-suggestion case).

`executeRootCommand` now post-processes the Execute() error: when
--json is in args AND the error matches cobra's
`unknown command "X" for "Y"` pattern, it emits the same
`{build, action: "unknown_subcommand", error}` document the parent
guard would have produced. errors.As(exitCodeError) skip prevents
double-emit when an inner RunE already handled it (mcp parent).

Two RED → GREEN subtests in the existing conformance fence:
- config_typo_with_suggestion (config gat → get)
- kanban_typo_with_suggestion (kanban shor → show)

Found during v0.2.0 fresh-install e2e probe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: cover gateway --json edge + install.sh apply-by-default

#12: `gormes gateway xyz --json` rejected `--json` as "unknown flag"
because gateway parent has its own RunE (`runGateway`) so cobra
parses flags BEFORE subcommand routing. The user's intent — "I
asked for JSON output of an invocation containing --json" — must
still produce JSON. Wrapper at executeRootCommand now also
recognizes the cobra `unknown flag: --json` pattern and emits the
same `{build, action: "unknown_subcommand", error}` document.

#13: `sh install.sh --uninstall` (no flags) ran `gormes uninstall`
which defaults to dry-run, leaving the operator looking at a
preview and assuming cleanup happened. Asymmetric vs. install
which actually installs by default. run_uninstall now scans the
passthrough args for an explicit `--dry-run` opt-in and prepends
`--yes --dry-run=false` when absent, preserving caller intent
(`install.sh --uninstall --dry-run` still previews).

RED → GREEN coverage:
- TestFreshInstallE2E_InvalidInputJSONEmitsStructuredError gains
  a `gateway_unknown_subcommand_json` subtest.
- TestInstallSh_UninstallDefaultsToApply runs install.sh against a
  recording shim and asserts the three contracts: bare apply,
  --dry-run preserved, passthrough flags survive.

Found during v0.2.0 fresh-install e2e probe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: sync Go toolchain floor with go.mod

* chore: prepare v0.2.1 release metadata

* chore: sync benchmark Go floor metadata

* fix: expand mixed platform toolset composites

* fix: align cron auth-header scanner parity

* docs: rebalance README runtime-first per feedback

External feedback flagged the methodology-first hero as creating
two competing identities (usable agent runtime vs. methodology
demonstration), with the methodology framing burying what the
product actually does. The 2026-05-07 strategy pivot stays intact
for site/landing/blog copy; README walks back to runtime-first
because that's where operators land.

New hero leads with the runtime story; methodology lives in a
dedicated "How It's Built" section near the bottom as supporting
evidence. Section reorder follows the recommended flow: Hero →
Quick Install → First Proof → What Works Today → Why People Switch
→ Daily Use → Migration → Build From Source → Docs → Security →
How It's Built → Status.

Other changes:
- "Capability Map" → "What Works Today" with Supported/
  Experimental/Roadmap labels (drops internal `runtime-ready`/
  `fixture-backed`/`row-backed` jargon).
- Strip the "Current development head after vX..." dev-branch
  changelog leak (belongs in CHANGELOG, not the README).
- Consolidate Security & Trust from 5 bullets to 3.
- Drop Operator Cheatsheet (redundant with Daily Use).
- 271 → 194 lines.
- Update install-script command to canonical landing URL
  (gormes.ai/install.sh).
- Rename "First Run" → "First Proof" (operator-facing).
- Bump latest release pointer from v0.2.0 to v0.2.1.

Tests updated to encode the new contract:
- TestREADMEStartsWithMethodology → TestREADMELeadsWithRuntime
  (asserts runtime anchor in the lead, methodology mentioned
  somewhere — just not first).
- TestREADMEMentionsDifferentiator: relax window to 80 words and
  drop "single 30 MB Go binary" from the lead-block requirement
  (binary size now lives in Status).
- TestREADMEPreservesOperatorSections: track new section names
  and the canonical install URL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: refresh benchmark metadata

* fix: emit subcommand_required JSON for parent --json (no subcommand)

`gormes <parent> --json` (with no subcommand) printed Help text on
stdout for 13 parents — silently violating the conformance contract
that --json paths produce parseable JSON. Affects config, kanban,
session, auth, mcp, memory, goncho, profile, curator, system,
security, channels, agent.

The recursive installParentUnknownSubcommandGuards helper now
registers a hidden --json flag on every guarded parent and emits a
structured `subcommand_required` document on stdout when the flag
fires:

  {build, action: "subcommand_required", parent, available, error}

`available` enumerates the parent's named subcommands so fleet
automation can discover the surface programmatically without
scraping Help text.

Auth and mcp parents have their own RunE so the guard skips them;
both now check --json explicitly and route through the same
emitJSONSubcommandRequired helper for parity.

Three new subtests in the existing conformance fence battery pin
the contract for representative cases (config, kanban, agent), plus
auth and mcp for the explicit-RunE path.

Found during a deeper v0.2.1 e2e probe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: support Telegram guest mention mode

* release: prepare v0.2.2 (v2026.5.9)

Same-day patch over v0.2.1 carrying #14 — 13 parent commands
that printed Help text on stdout for `gormes <parent> --json`
(no subcommand) now emit a structured `subcommand_required`
document with the available subcommand list.

Date alias shared with v0.2.1 follows the v0.1.06 / v0.1.07
precedent for back-to-back same-day releases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: add python lint evidence to file tools

* fix: notify context engine on session reset

* docs: add provider default model progress row

* fix: refresh Hermes memory guidance

* fix: resolve provider default models

* docs: complete provider default model row

* feat: add OpenRouter Grok affinity header

* feat: add Telegram STT fallback resolver

* fix: guard Kanban schema migration races

* docs: update progress for STT and Kanban rows

* fix: include Teams in channel capabilities

* fix: preserve detached Windows gateway interrupts

* fix: propagate ACP session cwd

* fix: refresh Google Chat install hint

* feat: add OpenRouter Pareto request plugin

* feat: prefer Groq STT transcriber selection

* docs: file pure-Go STT exploration row

Track the architectural question of how Gormes should ship local STT given
the Go ecosystem currently has no production-quality pure-Go (CGO_ENABLED=0)
STT library.

Research pass 2026-05-09 confirmed every actively-maintained Go STT library
requires cgo: ggerganov/whisper.cpp/bindings/go (2026-05-02), kardianos
fork, mutablelogic/go-whisper (2026-01-27), xPrimeTime/go-whisper-ct2
(2026-01-10, 0 stars, requires building CTranslate2 + libsndfile +
libsamplerate + openblas from source), and k2-fsa/sherpa-onnx-go-linux
(v1.13.1, the most production-grade cgo option). The only pure-Go path
(agnivade/whisper-wasi via wazero+WASI) is a 2-star prototype with 5
commits, ~5x slower than native (90s vs 17s), no thread support in wazero,
and explicitly disclaimed by its author.

The new row in phase 5 / subphase 5.E captures the four mutually-exclusive
architectural choices (status quo / cgo build-tag / WASI productionization /
wait+monitor) with per-path tradeoffs and per-path acceptance bullets, so
future planners do not re-research the same dead ends.

Companion row "Telegram voice STT HTTP-provider fallback" already shipped
today gives operators a no-install option (Groq free tier wired via
GROQ_API_KEY env var) in the meantime — independent of which long-term
architectural path is eventually chosen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: refresh benchmark data

* feat: add kanban worker log command

* feat: silence Telegram placeholders by default

* feat: add shell completion command

* docs: plan WASI STT path

* chore: refresh benchmark metadata

* feat: add wazero WASI smoke harness

* fix: preserve proxy replay metadata

* ci: name sbom attestations in release notes

* fix: add shell lint evidence to file tools

* feat: bind FAL image generation queue API

* feat: pass gateway photos as image parts

* fix: preserve config comments on set

* docs: record parity sweep progress

* fix(provider): classify generic timeout messages

* fix: preserve gateway image attachments

* feat: add whisper wasi discovery fixture

* docs: sync Hermes optional skill mirror

* feat: surface curator rename summaries

* chore: log gateway image attachment conversion

* fix: admit image-only channel turns

* feat: pin kanban board for chat tools

* feat: add kanban notify delivery engine

* web: refresh landing methodology copy

* feat: add native image path hints

* docs: correct image hint row scope

* ci: align gormes.ai deploy guards with methodology hero

The deploy-gormes-www.yml content guards still required the pre-rewrite
hero copy ("AI agent runtime in one Go binary.", "An autonomous engineering
loop ports Hermes to Go, every day.", "HOW IT IS BUILT") that 95864c2
removed. With those guards stale, the next push to main would fail at the
"Verify homepage content" step before reaching the Cloudflare Pages deploy.

Repoint positive grep assertions at the new methodology-first hero strings
and add the old phrases to the negative-grep list so future drift back to
parity-first copy fails the deploy instead of silently shipping.

* chore: refresh benchmark snapshots

* feat: retry text-only on vision rejection

* fix: preserve fallback credential aliases

* test: allow landing e2e port override

* release: prepare v0.2.3 (v2026.5.9)

Third same-day patch over v0.2.2; shared date alias follows the v0.1.06 /
v0.1.07 and same-day v0.2.1 / v0.2.2 precedent for back-to-back releases.

Cut to deploy the methodology-first landing rewrite to www.gormes.ai
(deploy-gormes-www.yml only fires on push to main). Riding along: the
multimodal photo passthrough + admission gate, native image input mode
wiring, vision-unsupported retry, Telegram voice STT HTTP fallback, the
first WASI STT exploration rows, provider/auth/runtime polish (config
comments preserved on set, generic timeout classification, file-tool
shell+python+structured lint evidence, fallback credential aliases,
proxy replay metadata, FAL image gen binding, OpenRouter Pareto plugin,
shell completion command, Telegram placeholder default, curator rename
summaries), and the kanban suite (board pinning, notify delivery engine,
worker log command).

See CHANGELOG.md for the full annotated list.

* Implement image input mode routing

* feat: add native vision tool results

* feat: add gateway restart notification opt-out

* fix: add telegram audio transcription diagnostics

* docs: mirror upstream Hermes user stories

* fix: add stream drop retry diagnostics

* fix: polish kanban slash help UX

* fix: harden background review skill prompts

* web: use local Astro binary for landing e2e

* fix: mirror Codex Spark model metadata

* Add Whisper tiny.en model cache helper

* fix(stt): read Groq response as text, matches response_format=text request

The Groq Whisper provider was sending response_format=text in the
multipart form but then attempting json.NewDecoder(...).Decode(...) on
the response body. Groq honored the requested format and returned the
raw transcript starting with whatever character the user actually said.
The JSON decoder choked on the first non-{ character with "invalid
character 'T' looking for beginning of value" (or the same error with
whatever the leading byte happened to be), the whole STT path failed
silently behind the existing sanitizer, and Telegram voice messages
came back as "audio transcription provider failed".

Replace the JSON decode with io.ReadAll(resp.Body) so the read shape
matches the response_format we asked for. Updated the existing Groq
provider test to serve plain text (matches real Groq behavior under
response_format=text) and to assert response_format=text is in the
outgoing request, which doubles as a regression for the parse failure.

Live evidence: gateway log line at 2026-05-10T01:24:22 captured
diagnostic=stt_groq_parse_failure
detail="Groq STT parse response: invalid character 'T' looking for
beginning of value" — the diagnostic plumbing landed in 9c902de
plus the iteration-2 expansion below pinpointed the exact failure
site in one round trip.

* feat(channels/telegram): expand STT failure diagnostics

Builds on the diagnostic plumbing added in 9c902de:

- telegramAudioErrorDiagnostic now distinguishes the seven Groq STT
  failure sites (network, file_open, request_build, parse, copy,
  writer_close, form). The previous version collapsed all of them to
  stt_groq_local_failure, which is what made the live "audio
  transcription provider failed" earlier today ambiguous between a
  network failure and a parse failure. The newly granular
  stt_groq_parse_failure is what pinpointed the
  response_format/JSON-decode mismatch (fixed in the parent commit).

- New telegramAudioErrorRedactedDetail returns a 256-char-bounded,
  redacted substring of err.Error() suitable for the WARN log. Strips
  Telegram bot-token-shaped substrings and Telegram getFile direct
  URLs so log forwarders cannot leak credentials. Provider-side errors
  (Groq HTTP body, dial errors, TLS messages) pass through.

- Both bot.go WARN call sites (voice + audio) now log
  diagnostic=<token> detail=<redacted-truncated-err> alongside the
  existing sanitized err= field. Operators can group by token or read
  the underlying error without rebuild cycles.

* release: prepare v0.2.4 (v2026.5.10)

Cuts to ship the Telegram voice STT fix to all install.sh users.
Without v0.2.4, every operator with v0.2.3 installed gets "audio
transcription provider failed" the moment they send a voice note —
the Groq Whisper provider was sending response_format=text but
JSON-decoding the response, and the JSON decoder choked on the first
non-{ character of the real transcript.

Also rolls forward today's diagnostic plumbing (codexu's 9c902de
+ this branch's iteration-2 expansion) so future STT regressions
surface a granular diagnostic= token and a redacted detail= field
in the WARN log instead of collapsing to "audio unavailable".

See CHANGELOG.md for the full annotated list.

* feat: add WASI Whisper transcriber

* fix: parse OpenAI STT text responses

* feat: wire WASI Whisper into Telegram STT

* feat: add WASI Whisper audio preprocessing package

* feat: bind WASI Whisper audio preprocessing

* feat: record WASI Whisper benchmark evidence

* fix: add patch no-match recovery hints

* feat: add kanban tail event follower

* feat: add fuzzy patch replace strategies

* fix: normalize unicode fuzzy patch matches

* feat(provider): add stream drop upstream diagnostics

* test(landing): avoid e2e port collisions

* fix: guard curator prompt against transient failures

* feat: add block-anchor patch matching

* fix: fuzzy match V4A patch hunks

* fix: rollback failed v4a patch applies

* ci: smoke release binary metadata

* fix: bind release date alias to GitHub releases

* feat: add context-aware patch replacement

* feat: add Kanban orchestrator board tools

* fix: verify patch replace persistence

* fix: stabilize kanban tool list order

* fix: cover Hermes bundled platform plugins

* fix: harden kanban worker spawn resolution

Mirror the refreshed Hermes dispatcher executable fallback in Gormes by resolving default Kanban worker spawns through PATH-first gormes lookup and current-executable fallback. Update progress evidence and mirror the new upstream plugin LLM docs page required by the refreshed Hermes submodule.

* fix: harden kanban corrupt timestamp reads

* fix: shape browser console CDP results

* feat: expand Hermes i18n locale parity

* fix(uninstall): scope to managed home + prefer recoverable trash over permanent delete

Live regression 2026-05-10: a sandbox uninstall test
(GORMES_INSTALL_HOME=/tmp/...) accidentally targeted the operator's REAL
~/.gormes and PERMANENTLY DELETED .env (provider keys), memory.db
(Goncho conversation history), config.toml, ~/.gormes/bin/gormes, and
~/.local/bin/gormes. Two independent root causes lined up to make this
maximally destructive:

1. install.sh's run_uninstall() did not export GORMES_HOME before
   invoking the gormes binary's `uninstall` subcommand. The gormes
   process inherited the operator's default $HOME-derived ~/.gormes
   path instead of the sandbox path the operator actually pointed at.

2. cmd/gormes/uninstall.go's executeUninstall used os.RemoveAll
   directly — permanent unrecoverable deletion. There was no fallback
   to a freedesktop trash, so even when the wrong scope was chosen,
   recovery was impossible.

Fix #1 — install.sh run_uninstall pins scope:
  GORMES_HOME="$(managed_home_dir)"
  export GORMES_HOME

managed_home_dir() returns $GORMES_INSTALL_HOME when set, else
$HOME/.gormes. So sandbox uninstalls now stay in the sandbox; default
uninstalls still target ~/.gormes as before.

Fix #2 — cmd/gormes/uninstall.go pickArtifactMover():
  - default: `gio trash <path>` when gio is on PATH (move to
    freedesktop trash, recoverable from any file manager's trash UI)
  - GORMES_UNINSTALL_FORCE_PURGE=1: opt-in permanent delete for CI
    cleanup, container teardown, secure wipe
  - fallback: permanent delete with an explicit label that names the
    missing dependency ("gio not available; install glib2-tools for
    recoverable trash") so operators understand why their uninstall
    isn't trash-aware

Removal mode is now logged to stdout and surfaced in --json output as
removal_mode: "...", so fleet automation can verify which mode fired.

Recovery from this incident only worked because an earlier May-2
uninstall on the same operator's host had used a trash-aware path,
leaving ~/.local/share/Trash/files/.gormes/ as a partial backup. The
new default would have made that explicit instead of accidental.

Tests:
- TestPickArtifactMover_PrefersGioTrashWhenAvailable
- TestPickArtifactMover_ForcePurgeOptsIntoPermanentDelete
- TestPickArtifactMover_ForcePurgeAcceptsTrueAlias
- TestPickArtifactMover_DefaultOnHostWithoutGio

Existing uninstall tests (12) still pass — they all run with t.TempDir()
paths that gio trash handles identically to os.RemoveAll for cleanup
purposes.

* fix(hermes): collapse double /v1 prefix in OpenAI-compatible URL builder

Live regression 2026-05-10: an operator wired up OpenRouter using the
documented base URL `https://openrouter.ai/api/v1` and got the cryptic
"Not Found: provider returned HTML error body" error with no
indication that openAICompatibleURL had joined the basePath /api/v1
with the chat-completions path /v1/chat/completions to produce
`https://openrouter.ai/api/v1/v1/chat/completions` (double /v1, 404).

Operators copy-pasting the documented base URL is the natural
intuition across every OpenAI-compatible provider whose docs include
/v1 in the advertised base URL — OpenAI itself, OpenRouter, Together,
Groq chat, DeepInfra, etc. The defect class is silent: the request
shape is structurally valid, the error body is HTML (the upstream
service returned a generic 404 page), and the resulting Go error
("Not Found: provider returned HTML error body") doesn't surface the
URL it tried.

Fix: when basePath ends with "/v1" AND endpointPath starts with "/v1/",
strip basePath's trailing "/v1" so both shapes
(endpoint = "https://openrouter.ai/api" and
 endpoint = "https://openrouter.ai/api/v1") resolve to the same URL.

Other prefix shapes are explicitly preserved:
- baseURL ending in "/v1/proxy" (middle /v1, not trailing) — no collapse
- endpointPath like "/responses" or "/messages" (non-/v1) — no collapse
- baseURL without scheme (e.g. test stubs) — falls through to raw concat

Eight-case TestOpenAICompatibleURL_CollapsesDoubleV1Prefix pins the
contract: openrouter+/v1, openrouter+/v1/, openrouter without /v1,
OpenAI with /v1 (same defect class), Anthropic /v1/messages (must
still work), Azure /responses (basePath /v1 must be preserved),
middle /v1 (no collapse), no-scheme fallthrough.

Verified end-to-end: rebuilt + hot-swapped the gateway binary, set
hermes.endpoint = "https://openrouter.ai/api/v1" (the previously-broken
shape), ran `gormes --oneshot "Reply with exactly: openrouter url fix
lives."` and got the exact reply through OpenRouter→DeepSeek v4 flash.

* release: prepare v0.2.5 (v2026.5.10)

Cuts to ship today's three follow-on fixes to install.sh users:

1. CRITICAL: install.sh --uninstall scope leak that wiped operators'
   real ~/.gormes when run from a sandbox (.env, memory.db,
   config.toml, binaries — unrecoverable on hosts without gio trash
   in the path the operator's environment used).

2. install.sh uninstall now defaults to recoverable trash via gio
   instead of permanent delete, with GORMES_UNINSTALL_FORCE_PURGE=1
   for callers who legitimately need scrubbed disk.

3. OpenRouter (and other OpenAI-compatible) base URL with the
   documented /v1 suffix no longer 404s — the URL builder now
   collapses double-/v1 prefixes so endpoint = "https://openrouter.ai/api/v1"
   and endpoint = "https://openrouter.ai/api" both work.

4. OpenAI STT response_format=text + JSON-decode mismatch fixed
   (companion to the v0.2.4 Groq fix; same defect class, copy-paste
   bug in the OpenAI provider).

Riding along: codexu's autonomous-loop work since v0.2.4 — pure-Go
WASI Whisper transcriber wired into Telegram STT (no CGO, no Python,
local fully-offline transcription path), V4A patch tool resilience
hardening (block-anchor matching, fuzzy strategies, unicode
normalization, rollback on failure), kanban orchestrator board tools
+ tail event follower, provider stream-drop upstream diagnostics,
Hermes i18n locale parity expansion, and CI release-binary metadata
smoke gate.

See CHANGELOG.md for the full annotated list.

* fix(tooltrace): show URL/query head, not tail, in tool-call previews

`web_search`, `web_extract`, `web_crawl`, and `browser_navigate` were on
the right-edge-truncate list, so Telegram showed previews like
`web_search: "...et docs websocket WSS Quickstart CLOB"` and
`web_extract: "...et.com/market-data/websocket/overview"` — hiding the
search query's first words and the URL's domain behind a leading "...".

Move them off the right-edge list. File-path tools (`read_file`,
`write_file`, `patch`) keep tail-preview because the filename usually
matters more than the directory chain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(cron): add opencode-tuned prompt for the local-model builder lane

The codex prompt assumes MCP-style skill calls (`gormes-skill-manager`,
`gormes-git`) and a large-frontier model can hold the full parity-then-
plan-then-build pipeline in head. opencode discovers SKILL.md files via
its `skill` tool, has no MCP endpoint for these skills, and runs against
a local ollama model (qwen3-coder:30b MoE on this rig) with much less
cognitive headroom — the existing prompt sent the model into infinite
`skill_mcp` retry loops.

Add `opencode_prompt()`: tells the agent to use the `skill` tool (not
`skill_mcp`), bias toward one tiny safe step per cycle, exit cleanly
when confused, and never touch unfamiliar untracked files. Wire the
backend dispatch to pick the right prompt for the active backend.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(cron): teach opencode prompt about strict tool-call schemas

qwen3-coder:30b on opencode follows the structured-exit pattern but
doesn't know the bash tool requires both `command` and `description`
args, so it failed its first call with a SchemaError and bailed
cleanly via "I am stopping because: ...". Add explicit reminders for
the bash/read/edit/write tool shapes opencode enforces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(progress): plan gateway /model interactive provider/model picker

Author Phase 2.B.5 row "Gateway /model interactive provider/model picker"
covering the Hermes _handle_model_command no-arg path: 2-step provider→model
inline-keyboard picker on Telegram (mp:/mm:/mg:/mb:/mx: callback family),
"Select a provider:" header parity, per-session model/provider override seam,
and static-text fallback for non-inline channels. Distinct from the complete
Cobra-CLI row at Phase 5.O — this slice writes a per-session override, not
hermes.model/hermes.provider in TOML.

Source-refs pinned at hermes-agent@d4b26df8 (current submodule sha) covering
gateway/run.py:_handle_model_command, platforms/telegram.py:send_model_picker
+ _build_model_keyboard + _handle_model_picker_callback, and
hermes_cli/model_switch.py:list_picker_providers.

Relax internal/progress/progress_test.go:Phase2ExecutionQueue to allow 2.B.5
derived status of complete OR in_progress now that a newly discovered
gateway-handler gap reopens the subphase. Per-row complete/validated asserts
below remain locked.

Regenerated docs/site progress mirrors via `go run ./cmd/progress write`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(gateway): align tool-progress preview tests with head-edge truncation

Commit b60a0e3 (fix(tooltrace): show URL/query head, not tail, in
tool-call previews) moved web_search/web_extract/web_crawl/browser_navigate
off the right-edge-truncate list in internal/tooltrace, but missed the
gateway-level render tests still asserting the old tail-preview shape:

- TestFormatToolProgressPlain_TruncatedWebPreviewsKeepRightEdge
- TestFormatToolProgressPlain_MineruGatewayTranscriptShape

Update those tests to match the shipped head-preview behavior (URL/query
head visible, trailing "..." after truncation) and rename the first test
to KeepLeftEdge so the name matches reality. Production code is unchanged.

Unblocks PR-to-main CI gate; the failing tests passed on main only because
main does not yet carry b60a0e3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant