Skip to content

feat: add lineage-aware session search hits - #12

Merged
XelHaku merged 1 commit into
mainfrom
autoloop/20260425T024216Z/w5/3-3.e.8-lineage-aware-source-filtered-search-hits
Apr 25, 2026
Merged

feat: add lineage-aware session search hits#12
XelHaku merged 1 commit into
mainfrom
autoloop/20260425T024216Z/w5/3-3.e.8-lineage-aware-source-filtered-search-hits

Conversation

@XelHaku

@XelHaku XelHaku commented Apr 25, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

@XelHaku
XelHaku merged commit 2657e19 into main Apr 25, 2026
@XelHaku
XelHaku deleted the autoloop/20260425T024216Z/w5/3-3.e.8-lineage-aware-source-filtered-search-hits branch April 25, 2026 07:48
XelHaku added a commit that referenced this pull request Apr 25, 2026
Implements items #4, #7, #14, #15 from docs/cmd-loops-improvements.md
plus a partial #12 (parser errors map to exit code 2; richer codes for
backend timeout / verify failure are deferred until internal packages
emit semantic errors).

#4 — Graceful shutdown:
  * main() wraps context.Background() with signal.NotifyContext for
    SIGINT/SIGTERM; the context is plumbed through run, runAutoloop,
    builderloop.RunOnce, plannerloop.RunOnce, and the service install
    paths. systemd's TimeoutStopSec now gives the backend a clean
    cancellation chance instead of jumping straight to SIGKILL.

#7 — Keywords in planner summary:
  * printRunSummary echoes "keywords: <values>" when any are set so
    operators can confirm topical-focus mode actually engaged.

#14 — --repo-root / REPO_ROOT:
  * Both binaries accept --repo-root <path> anywhere in argv (consumed
    before subcommand dispatch) and fall back to REPO_ROOT then
    os.Getwd. systemd units no longer have to rely on
    WorkingDirectory= alignment to operate.

#15 — digest --output clobber guard:
  * Replaced os.WriteFile (silent overwrite) with O_CREATE|O_EXCL.
    --force opts back into truncate-and-overwrite. Default protects
    against fat-fingered paths like '--output README.md'.

#12 (partial) — Parser errors → exit 2:
  * Introduced errParse sentinel in each binary. main() maps
    errors.Is(err, errParse) to os.Exit(2); everything else still
    exits 1. Future work: tag config errors / no-candidates / backend
    timeout / verify-failed once the internal packages surface them
    distinctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
XelHaku added a commit that referenced this pull request Apr 25, 2026
#12 — Structured exit codes (now full delivery):
  * New ErrPostPromotionVerifyFailed sentinel in internal/builderloop;
    runPostPromotionVerifyAndRepair joins it with the underlying error
    on terminal verify failure so cmd/ can errors.Is detect it.
  * classifyExit helper in both binaries maps:
      errParse                              → 2 (parse / config)
      builderloop.ErrPostPromotionVerifyFailed → 30 (builder-loop only)
      context.DeadlineExceeded / Canceled   → 20 (backend timeout)
      anything else                         → 1 (internal)
  * "no-work=10" intentionally NOT emitted: a 0-candidates idle tick is
    normal, and systemd Restart=on-failure would flap. Documented in
    the improvements doc.

#13 — --format json (now full delivery):
  * digest: builderloop.DigestLedgerCounts(path) added as the
    structured companion to DigestLedger; cmd emits the full event ->
    count map as JSON.
  * audit: cmd emits {"summary":"<text>"}; structured audit ledger
    already exists at <auditDir>/report.ndjson + report.csv, so the
    wrapper just gives scripts a stable JSON object.
  * status: cmd emits {"status":"<text>"}; structured planner state
    lives in planner_state.json.
  * digest gets a --format flag in addition to the existing
    --output/--force, with a JSON-also-respects-clobber-guard path.

Tests cover classifyExit for all four cases per binary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
XelHaku added a commit that referenced this pull request May 9, 2026
#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>
XelHaku added a commit that referenced this pull request May 9, 2026
…sues (#178)

* 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_COMMIT}
- Update release_workflow_test.go contract to expect GIT_COMMIT ldflag

* refactor: convert memoryCmd to newMemoryCommand constructor pattern

- Refactor memoryCmd from package-level var to newMemoryCommand() constructor
  to prevent cross-test flag contamination (same anti-pattern as versionCmd)
- Convert memoryStatusCmd to newMemoryStatusCommand() constructor
- Add TestMemoryCommand_ConstructorReturnsIndependentInstances test
- Wire newMemoryCommand() into root command registration

* refactor: remove resetGonchoDoctorFlags workaround, wire newGonchoCommand constructor

- Remove resetGonchoDoctorFlags() call that the constructor pattern made obsolete
- Wire newGonchoCommand() in place of gonchoCmd in root command registration
- Add TestGonchoCommand_ConstructorReturnsIndependentInstances test

* refactor: convert sessionCmd and remove old gonchoCmd package-level vars

- Convert sessionCmd + subcommands to newSessionCommand() constructor tree
  (list, export, delete, prune, browse all get constructor functions)
- Remove resetSessionsCommandFlagState() workaround now made obsolete
- Add TestSessionCommand_ConstructorReturnsIndependentInstances test
- Remove old gonchoCmd/gonchoDoctorCmd package-level vars now superseded
  by newGonchoCommand()/newGonchoDoctorCommand()
- Wire newSessionCommand() into root command registration

* refactor: convert gatewayCmd and telegramCmd to constructor pattern

Replace gatewayCmd and telegramCmd package-level vars with
newGatewayCommand() and newTelegramCommand() constructors.
Move init()-based subcommand wiring and flag registration
into the constructors so each newRootCommand() instance
gets a fresh independent tree. Add a constructor-isolation
test proving the gateway tree owns 10 distinct children
per call (6 real + 4 mutating-unavailable placeholders).

* feat: add Mattermost shared-chassis bot seam with dedup and mention gating

New internal/channels/mattermost package adds a Seam over
threadtext for Mattermost posted-event parsing before REST/WS
transport. Handles double-encoded posted event JSON, drops
self/system/duplicate posts, preserves root_id as canonical
thread_id, models reply_mode=thread vs off, and uses pure-input
mention gating (DM/free-channel/require-mention decisions).
15 fixture tests exercise the full event lifecycle including
cancel hook suppression, allowed-channel whitelist enforcement,
and @mention stripping.

* feat: add git_dirty field to version --json, backup writer, and CLI test coverage

- version --json: add git_dirty, go_version, os, arch fields
- doctor: improve runE flow with additional diagnostics
- restore: expand restore command with backup writer integration
- status/update/auth/secrets: improve CLI command coverage
- internal/cli: backup_writer, backup_restore, active_profile,
  busy_command, update_lifecycle enhancements with test coverage
- CI: update release and deploy workflows
- Makefile: build target updates

* docs: sharpen 3 Phase 5 umbrella rows with 2026-05-07 parity source_refs, complete Mattermost seam

Planner pass: add upstream source_refs to Skill registries (skills_tool.py,
skill_commands.py, etc.), Dangerous action gating (approval.py, tirith/), and
Atomic checkpoints (checkpoints.py, checkpoint_manager.py) umbrella rows.

Builder pass: complete Mattermost shared-chassis bot seam row (7.7.C).
Fifteen fixture tests prove posted-event parsing, dedup, self/system drops,
mention gating, reply-mode thread/off, and hook cancellation.

Regenerate all derived progress surfaces (agent queue, next slices,
blocked slices, umbrella cleanup, landing data, contract readiness).

* fix: harden builder-loop test runner cleanup with unconditional SIGKILL

* fix: distinguish missing/corrupt zip in restore error messages

ValidateRestoreZip, RestoreFromZip, and SummarizeRestoreZipImpact
now surface 'zip not found: <path>' for missing files versus
'zip unreadable: ...' for corrupt archives. Previously both cases
chained the os.Open wording behind a duplicate 'open' prefix,
forcing operators to decode nested error chains to triage.

* chore: add missing encoding/json import to auth command test

* feat: add --json flag to auth list and profile show/list

auth list --json emits {build, provider, credentials:[...]} with
pre-redacted credential status. profile show --json emits {build,
active, root} with redacted root path. profile list --json emits
{build, active, profiles:[{name, active},...]} for fleet automation.

* fix: fast-fail backup writer with operator-friendly missing source error

* fix: bump discord e2e poll timeouts to eliminate flakes under parallel load

* docs: add Checkpoints CLI parity row (P2, builder-ready) to progress.json

* fix: add HTTP client timeout to logs command, refactor uninstall to use io.Writer

* test: add goncho doctor test coverage

* fix: goncho doctor production wiring

* feat: add gormes checkpoints CLI (status/list/prune/clear/clear-legacy)

* docs: mark Checkpoints CLI row complete with evidence

* fix: parallel agent improvements (channels, gateway, usage, uninstall fixes)

* feat: add provider client lazy-init pool for cold-start budget

internal/provider/lazy_client.go: ClientPool with Register/Get/Reset
that defers provider HTTP client construction until first access.
Only the selected provider is constructed; unselected providers
never invoke their factory.

internal/provider/lazy_client_test.go: 6 tests covering
does-not-construct-unselected, constructed-once, reset-for-testing,
unknown-provider, factory-error, and concurrent-access.

internal/runtime/coldstart_bench_test.go: BenchmarkGormesColdStart
enforces a 5ms budget on 10-registered-provider cold start.

cmd/gormes/provider_client.go: wires getOrCreateProviderClient into
the oneshot path via newOneshotHTTPClient.

hermes/client.go: adds ErrProviderUnavailable sentinel.

Source: Hermes v0.12.0 PR #17046 (lazy OpenAI/Anthropic/Firecrawl)
inside the ~57% TUI cold-start cut.

* docs: mark Provider client lazy-init row complete, regenerate progress surfaces

5.Q row: contract validated, write_scope corrected, row implemented
and marked complete with full evidence. Progress surfaces regenerated
via go run ./cmd/progress write.

* feat: setup reset breadcrumb, memory/restore/session enhancements

setup.go: reset config preserves prior config as breadcrumb file,
ResetConfig returns (string, error) to surface the backup path.
setup_reset_breadcrumb_test.go: tests for breadcrumb output.
memory/restore/session: operator-facing command enhancements.

* feat: checkpoint manager enhancements with pruned-orphan threshold

* feat: add --json flag to checkpoints prune, clear, and clear-legacy commands

Emit machine-readable JSON for operator scripts performing GC, CI/CD
jobs running destructive prunes, and dashboards auditing checkpoint
state without scraping human-readable prose output.

* feat: add video_analyze tool with vision provider routing seam

Implements  tool (P2, Phase 5.D) that accepts a video
path or HTTPS URL plus prompt, checks provider vision capability, and
returns analysis text or typed evidence. Fixture-proven against
fakeVideoAnalyzeProvider with vision routing, unsupported provider
rejection, workspace path sanitization, and URL scheme allowlist.
Registered alongside image_generate in the default tool registry.
No real Gemini transport — provider seam ready for downstream adapters.

* feat: add --json flag to agent, config, curator, gateway, and uninstall commands

Extends the machine-readable JSON output contract to agent reset,
config show, curator status, gateway reload, and uninstall commands.
Each emits a structured build-provenance-led envelope for operator
scripts, fleet automation, and dashboards.

* docs: mark video_analyze row complete, regenerate progress surfaces

Updates progress.json: video_analyze tool contract_status→validated,
status→complete. Regenerates all derived progress docs and site data
via go run ./cmd/progress write. Parity sweep timestamp updated.

* feat: complete curator --json output parity

* feat: add --json flag to plugins list for fleet automation auditing

* test: add JSON-structured-report test for skills sync --json

* feat: add --json flags for skills sync, session delete, session prune

* feat: add --json flags to auth logout/remove/reset, plus curator and gateway-stop improvements

* feat: add --json machine output to curator and profile commands for fleet automation

* chore: bump hermes-agent submodule reference

* feat: add --json flags to auth, kanban, logs, mcp-login, navivox, plugins, setup, usage, and whatsapp commands

* feat: add --json flag to onboard command for fleet automation

* feat: add /kanban gateway slash command with store-backed subcommands

* feat: add --json flags to acp, gateway-discover, and logout commands

* docs: mark kanban slash row in_progress, regenerate progress surfaces

* feat: add build provenance to migrate JSON output for fleet automation

* feat: extend build provenance to migrate cleanup, security, and system JSON output

* docs: parity sweep: sharpen 5.M kanban refs, add specify row, bump hermes-agent submodule

* feat: add build provenance to session export --json output

* feat: add build provenance to config show/edit --json output

* feat: add --json flag to skills list command for fleet automation

* feat: add build provenance to kanban CLI JSON output

* feat: add kanban dashboard API routes with lane summary and task list

* docs: parity sweep: clear stale Kanban blockers, add dashboard route evidence, regenerate progress surfaces

* feat: add --json flag to navivox setup-host for fleet automation provisioning

* feat: add build provenance to dashboard kanban and status API responses

* chore: bump hermes-agent submodule (auth fix + cron routing intent)

* feat: add LoadNousOAuthCredentials to round-trip persisted Nous OAuth device-code state

* feat: extend build provenance to cron admin, plugin inventory, and model dashboard endpoints

* docs: parity sweep sharpen Kanban evidence, add Nous OAuth row, regenerate progress surfaces

* feat: expose BuildInfo on /health and /v1/capabilities responses

* feat: add BuildInfo to /health/detailed response model and tests

* feat: implement Nous OAuth device code login, refresh, and agent key minting

* docs: parity sweep cleanup + Nous OAuth row complete + hermes-agent submodule refresh

* feat: apiserver build provenance + CLI skills URL install + progressctl enhancements

* feat: port Hermes i18n static-message system to Go

- internal/i18n/: core package with T(), language resolution
  (explicit lang arg > GORMES_LANGUAGE env > SetConfigLanguage > en),
  YAML catalog loading with dotted-key flattening, 8-language
  support (en/zh/ja/de/es/fr/tr/uk) with full alias table
- locales/: 8 YAML catalogs ported from hermes-agent/locales/
  covering approval prompts and gateway slash replies
- internal/config/config.go: add display.language to DisplayCfg
- 15 tests: fallback chain, catalog parity, format substitution,
  env/config resolution, missing file, broken YAML

* docs: parity sweep, i18n row, Kanban fixture fix, dashboard, bump hermes-agent

- All-topic Hermes/Gormes parity sweep: i18n identified as
  source-backed missing feature (Hermes has agent/i18n.py with
  8 languages; Gormes had zero i18n)
- progress.json: added Phase 5.W i18n row (P2, builder-ready),
  fixed Kanban in_progress row phantom fixture paths (removed
  non-existent internal/gateway/kanban_command_test.go,
  internal/tui/kanban_slash_test.go, internal/dashboard/;
  pointed at real files)
- 23 vague planned rows identified (no write_scope) for
  future sharpening
- Kanban dashboard: capabilities endpoint tests, dashboard.go,
  dashboard_contract_test.go, runs.go from ongoing Kanban work
- hermes-agent: submodule bumped to a3131862b (upstream parity
  sweep baseline)
- Regenerated all progress-driven docs and site data
  (go run ./cmd/progress write)

* fix: publish run.stopped SSE event before closing subscribers

runRegistry.stop() now records a typed run.stopped lifecycle event
into the SSE backlog before closing subscriber channels so SSE
consumers see a terminal event symmetrical with run.completed and
run.failed. Fleet automation surfacing run lifecycles depends on
this typed terminus.

* feat: include build provenance in legacy /api/jobs responses

Legacy /api/jobs list and get endpoints now include build info
at the top of their JSON envelopes so fleet automation can
attribute responses to binary version.

* feat: add run lifecycle counters to health endpoint (completed, failed, stopped)

* feat: add run event lifecycle counters to detailed health API

* feat: auto-populate run lifecycle counters from runRegistry in /health/detailed

* feat: add strict-mode CWD and interpreter parity contract with TDD fixtures

* feat: add request_total and enhanced run events tracking to health API

* docs: regenerate progress surfaces after strict-mode parity sweep

* feat: advertise runs_list filters in /v1/capabilities for SDK discovery

* feat: add terminated_at to run status for dashboard duration computation

* feat: fail closed with terminal_cwd_deleted when configured cwd is deleted

* docs: parity sweep, terminal cwd guard complete, goal-loop row added

* chore: bump hermes-agent submodule for parity sweep baseline

* feat: support multi-value status filters in runs list endpoint

* feat: expose session_id in run registry snapshots and API responses

* chore: bump hermes-agent submodule for parity sweep baseline

* feat: add session_id filter to runs list, advertise run lifecycle events in capabilities

* feat: add Kanban multi-board registry with slug validation, board isolation, and CLI surface

* feat: add oldest_active_age_seconds to health endpoint, refine session_id runs filter and lifecycle events

* docs: parity sweep — mark skill registries row complete, convert to builder-ready evidence

* feat: expose tool_calls_count in run status endpoint

* feat: add SSE prelude snapshot to run event stream

* feat: enrich run event streams

* 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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
XelHaku added a commit that referenced this pull request May 9, 2026
* 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_COMMIT}
- Update release_workflow_test.go contract to expect GIT_COMMIT ldflag

* refactor: convert memoryCmd to newMemoryCommand constructor pattern

- Refactor memoryCmd from package-level var to newMemoryCommand() constructor
  to prevent cross-test flag contamination (same anti-pattern as versionCmd)
- Convert memoryStatusCmd to newMemoryStatusCommand() constructor
- Add TestMemoryCommand_ConstructorReturnsIndependentInstances test
- Wire newMemoryCommand() into root command registration

* refactor: remove resetGonchoDoctorFlags workaround, wire newGonchoCommand constructor

- Remove resetGonchoDoctorFlags() call that the constructor pattern made obsolete
- Wire newGonchoCommand() in place of gonchoCmd in root command registration
- Add TestGonchoCommand_ConstructorReturnsIndependentInstances test

* refactor: convert sessionCmd and remove old gonchoCmd package-level vars

- Convert sessionCmd + subcommands to newSessionCommand() constructor tree
  (list, export, delete, prune, browse all get constructor functions)
- Remove resetSessionsCommandFlagState() workaround now made obsolete
- Add TestSessionCommand_ConstructorReturnsIndependentInstances test
- Remove old gonchoCmd/gonchoDoctorCmd package-level vars now superseded
  by newGonchoCommand()/newGonchoDoctorCommand()
- Wire newSessionCommand() into root command registration

* refactor: convert gatewayCmd and telegramCmd to constructor pattern

Replace gatewayCmd and telegramCmd package-level vars with
newGatewayCommand() and newTelegramCommand() constructors.
Move init()-based subcommand wiring and flag registration
into the constructors so each newRootCommand() instance
gets a fresh independent tree. Add a constructor-isolation
test proving the gateway tree owns 10 distinct children
per call (6 real + 4 mutating-unavailable placeholders).

* feat: add Mattermost shared-chassis bot seam with dedup and mention gating

New internal/channels/mattermost package adds a Seam over
threadtext for Mattermost posted-event parsing before REST/WS
transport. Handles double-encoded posted event JSON, drops
self/system/duplicate posts, preserves root_id as canonical
thread_id, models reply_mode=thread vs off, and uses pure-input
mention gating (DM/free-channel/require-mention decisions).
15 fixture tests exercise the full event lifecycle including
cancel hook suppression, allowed-channel whitelist enforcement,
and @mention stripping.

* feat: add git_dirty field to version --json, backup writer, and CLI test coverage

- version --json: add git_dirty, go_version, os, arch fields
- doctor: improve runE flow with additional diagnostics
- restore: expand restore command with backup writer integration
- status/update/auth/secrets: improve CLI command coverage
- internal/cli: backup_writer, backup_restore, active_profile,
  busy_command, update_lifecycle enhancements with test coverage
- CI: update release and deploy workflows
- Makefile: build target updates

* docs: sharpen 3 Phase 5 umbrella rows with 2026-05-07 parity source_refs, complete Mattermost seam

Planner pass: add upstream source_refs to Skill registries (skills_tool.py,
skill_commands.py, etc.), Dangerous action gating (approval.py, tirith/), and
Atomic checkpoints (checkpoints.py, checkpoint_manager.py) umbrella rows.

Builder pass: complete Mattermost shared-chassis bot seam row (7.7.C).
Fifteen fixture tests prove posted-event parsing, dedup, self/system drops,
mention gating, reply-mode thread/off, and hook cancellation.

Regenerate all derived progress surfaces (agent queue, next slices,
blocked slices, umbrella cleanup, landing data, contract readiness).

* fix: harden builder-loop test runner cleanup with unconditional SIGKILL

* fix: distinguish missing/corrupt zip in restore error messages

ValidateRestoreZip, RestoreFromZip, and SummarizeRestoreZipImpact
now surface 'zip not found: <path>' for missing files versus
'zip unreadable: ...' for corrupt archives. Previously both cases
chained the os.Open wording behind a duplicate 'open' prefix,
forcing operators to decode nested error chains to triage.

* chore: add missing encoding/json import to auth command test

* feat: add --json flag to auth list and profile show/list

auth list --json emits {build, provider, credentials:[...]} with
pre-redacted credential status. profile show --json emits {build,
active, root} with redacted root path. profile list --json emits
{build, active, profiles:[{name, active},...]} for fleet automation.

* fix: fast-fail backup writer with operator-friendly missing source error

* fix: bump discord e2e poll timeouts to eliminate flakes under parallel load

* docs: add Checkpoints CLI parity row (P2, builder-ready) to progress.json

* fix: add HTTP client timeout to logs command, refactor uninstall to use io.Writer

* test: add goncho doctor test coverage

* fix: goncho doctor production wiring

* feat: add gormes checkpoints CLI (status/list/prune/clear/clear-legacy)

* docs: mark Checkpoints CLI row complete with evidence

* fix: parallel agent improvements (channels, gateway, usage, uninstall fixes)

* feat: add provider client lazy-init pool for cold-start budget

internal/provider/lazy_client.go: ClientPool with Register/Get/Reset
that defers provider HTTP client construction until first access.
Only the selected provider is constructed; unselected providers
never invoke their factory.

internal/provider/lazy_client_test.go: 6 tests covering
does-not-construct-unselected, constructed-once, reset-for-testing,
unknown-provider, factory-error, and concurrent-access.

internal/runtime/coldstart_bench_test.go: BenchmarkGormesColdStart
enforces a 5ms budget on 10-registered-provider cold start.

cmd/gormes/provider_client.go: wires getOrCreateProviderClient into
the oneshot path via newOneshotHTTPClient.

hermes/client.go: adds ErrProviderUnavailable sentinel.

Source: Hermes v0.12.0 PR #17046 (lazy OpenAI/Anthropic/Firecrawl)
inside the ~57% TUI cold-start cut.

* docs: mark Provider client lazy-init row complete, regenerate progress surfaces

5.Q row: contract validated, write_scope corrected, row implemented
and marked complete with full evidence. Progress surfaces regenerated
via go run ./cmd/progress write.

* feat: setup reset breadcrumb, memory/restore/session enhancements

setup.go: reset config preserves prior config as breadcrumb file,
ResetConfig returns (string, error) to surface the backup path.
setup_reset_breadcrumb_test.go: tests for breadcrumb output.
memory/restore/session: operator-facing command enhancements.

* feat: checkpoint manager enhancements with pruned-orphan threshold

* feat: add --json flag to checkpoints prune, clear, and clear-legacy commands

Emit machine-readable JSON for operator scripts performing GC, CI/CD
jobs running destructive prunes, and dashboards auditing checkpoint
state without scraping human-readable prose output.

* feat: add video_analyze tool with vision provider routing seam

Implements  tool (P2, Phase 5.D) that accepts a video
path or HTTPS URL plus prompt, checks provider vision capability, and
returns analysis text or typed evidence. Fixture-proven against
fakeVideoAnalyzeProvider with vision routing, unsupported provider
rejection, workspace path sanitization, and URL scheme allowlist.
Registered alongside image_generate in the default tool registry.
No real Gemini transport — provider seam ready for downstream adapters.

* feat: add --json flag to agent, config, curator, gateway, and uninstall commands

Extends the machine-readable JSON output contract to agent reset,
config show, curator status, gateway reload, and uninstall commands.
Each emits a structured build-provenance-led envelope for operator
scripts, fleet automation, and dashboards.

* docs: mark video_analyze row complete, regenerate progress surfaces

Updates progress.json: video_analyze tool contract_status→validated,
status→complete. Regenerates all derived progress docs and site data
via go run ./cmd/progress write. Parity sweep timestamp updated.

* feat: complete curator --json output parity

* feat: add --json flag to plugins list for fleet automation auditing

* test: add JSON-structured-report test for skills sync --json

* feat: add --json flags for skills sync, session delete, session prune

* feat: add --json flags to auth logout/remove/reset, plus curator and gateway-stop improvements

* feat: add --json machine output to curator and profile commands for fleet automation

* chore: bump hermes-agent submodule reference

* feat: add --json flags to auth, kanban, logs, mcp-login, navivox, plugins, setup, usage, and whatsapp commands

* feat: add --json flag to onboard command for fleet automation

* feat: add /kanban gateway slash command with store-backed subcommands

* feat: add --json flags to acp, gateway-discover, and logout commands

* docs: mark kanban slash row in_progress, regenerate progress surfaces

* feat: add build provenance to migrate JSON output for fleet automation

* feat: extend build provenance to migrate cleanup, security, and system JSON output

* docs: parity sweep: sharpen 5.M kanban refs, add specify row, bump hermes-agent submodule

* feat: add build provenance to session export --json output

* feat: add build provenance to config show/edit --json output

* feat: add --json flag to skills list command for fleet automation

* feat: add build provenance to kanban CLI JSON output

* feat: add kanban dashboard API routes with lane summary and task list

* docs: parity sweep: clear stale Kanban blockers, add dashboard route evidence, regenerate progress surfaces

* feat: add --json flag to navivox setup-host for fleet automation provisioning

* feat: add build provenance to dashboard kanban and status API responses

* chore: bump hermes-agent submodule (auth fix + cron routing intent)

* feat: add LoadNousOAuthCredentials to round-trip persisted Nous OAuth device-code state

* feat: extend build provenance to cron admin, plugin inventory, and model dashboard endpoints

* docs: parity sweep sharpen Kanban evidence, add Nous OAuth row, regenerate progress surfaces

* feat: expose BuildInfo on /health and /v1/capabilities responses

* feat: add BuildInfo to /health/detailed response model and tests

* feat: implement Nous OAuth device code login, refresh, and agent key minting

* docs: parity sweep cleanup + Nous OAuth row complete + hermes-agent submodule refresh

* feat: apiserver build provenance + CLI skills URL install + progressctl enhancements

* feat: port Hermes i18n static-message system to Go

- internal/i18n/: core package with T(), language resolution
  (explicit lang arg > GORMES_LANGUAGE env > SetConfigLanguage > en),
  YAML catalog loading with dotted-key flattening, 8-language
  support (en/zh/ja/de/es/fr/tr/uk) with full alias table
- locales/: 8 YAML catalogs ported from hermes-agent/locales/
  covering approval prompts and gateway slash replies
- internal/config/config.go: add display.language to DisplayCfg
- 15 tests: fallback chain, catalog parity, format substitution,
  env/config resolution, missing file, broken YAML

* docs: parity sweep, i18n row, Kanban fixture fix, dashboard, bump hermes-agent

- All-topic Hermes/Gormes parity sweep: i18n identified as
  source-backed missing feature (Hermes has agent/i18n.py with
  8 languages; Gormes had zero i18n)
- progress.json: added Phase 5.W i18n row (P2, builder-ready),
  fixed Kanban in_progress row phantom fixture paths (removed
  non-existent internal/gateway/kanban_command_test.go,
  internal/tui/kanban_slash_test.go, internal/dashboard/;
  pointed at real files)
- 23 vague planned rows identified (no write_scope) for
  future sharpening
- Kanban dashboard: capabilities endpoint tests, dashboard.go,
  dashboard_contract_test.go, runs.go from ongoing Kanban work
- hermes-agent: submodule bumped to a3131862b (upstream parity
  sweep baseline)
- Regenerated all progress-driven docs and site data
  (go run ./cmd/progress write)

* fix: publish run.stopped SSE event before closing subscribers

runRegistry.stop() now records a typed run.stopped lifecycle event
into the SSE backlog before closing subscriber channels so SSE
consumers see a terminal event symmetrical with run.completed and
run.failed. Fleet automation surfacing run lifecycles depends on
this typed terminus.

* feat: include build provenance in legacy /api/jobs responses

Legacy /api/jobs list and get endpoints now include build info
at the top of their JSON envelopes so fleet automation can
attribute responses to binary version.

* feat: add run lifecycle counters to health endpoint (completed, failed, stopped)

* feat: add run event lifecycle counters to detailed health API

* feat: auto-populate run lifecycle counters from runRegistry in /health/detailed

* feat: add strict-mode CWD and interpreter parity contract with TDD fixtures

* feat: add request_total and enhanced run events tracking to health API

* docs: regenerate progress surfaces after strict-mode parity sweep

* feat: advertise runs_list filters in /v1/capabilities for SDK discovery

* feat: add terminated_at to run status for dashboard duration computation

* feat: fail closed with terminal_cwd_deleted when configured cwd is deleted

* docs: parity sweep, terminal cwd guard complete, goal-loop row added

* chore: bump hermes-agent submodule for parity sweep baseline

* feat: support multi-value status filters in runs list endpoint

* feat: expose session_id in run registry snapshots and API responses

* chore: bump hermes-agent submodule for parity sweep baseline

* feat: add session_id filter to runs list, advertise run lifecycle events in capabilities

* feat: add Kanban multi-board registry with slug validation, board isolation, and CLI surface

* feat: add oldest_active_age_seconds to health endpoint, refine session_id runs filter and lifecycle events

* docs: parity sweep — mark skill registries row complete, convert to builder-ready evidence

* feat: expose tool_calls_count in run status endpoint

* feat: add SSE prelude snapshot to run event stream

* feat: enrich run event streams

* 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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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