Commit Graph

425 Commits

Author SHA1 Message Date
Alan Wizemann d1af4318b3 docs(readme): replace "What's New in 2.7" with v2.8 highlights
The README's What's New section is the front-page first impression on
github.com/awizemann/scarf. v2.8 is the current release; the section
now leads with Hermes v0.13 catch-up highlights organized as Persistent
Goals + /queue, Kanban v0.13 diagnostics + recovery UX, Curator
archive + prune, Messaging Gateway expansion (Google Chat + cross-
platform allowlists), provider catalog refresh + Settings polish,
iOS read-only catch-up, and the two dogfood-discovered bug fixes
(dashboard flicker on v0.13 + sparse slash menu on resumed sessions).

Also bumps the Hermes Version table to mark v0.13.0 as current target
and rewrites the recommended-feature paragraph below it for the v2.8
surface. v2.7 highlights moved to a closing pointer in the v2.8
section so readers landing here aren't blindsided by the perf work.
2026-05-09 21:17:00 +02:00
Alan Wizemann ee86ef1a9a chore: Bump version to 2.8.0 v2.8.0 2026-05-09 20:56:32 +02:00
Alan Wizemann 9eecf8b443 chore(i18n): re-extract localizable strings for v0.13 surfaces
Xcode's `xcstringstool` auto-extracts these on local Debug build whenever
SwiftUI Text literals are added in Swift code. New entries land for the
v0.13 surfaces introduced across v2.8.0:

- "%lld diagnostic signal%@" — Kanban diagnostics tooltip (WS-3)
- "%lld queued" — chat queue chip count (WS-2)
- "%lld skill%@" — Curator archived list count (WS-4)
- "×%lld" — chat status bar compression-count chip (WS-8)

All entries are English-only ("state": "new"); Crowdin / manual
translation lands in a follow-up. Pre-release housekeeping so the
release script's clean-tree check passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:54:39 +02:00
Alan Wizemann f90ed384fd Merge pull request #81 from awizemann/coordination/v2.8.0-plans
docs(v2.8): coordinator review + 8 work-stream plans for Hermes v0.13.0 catch-up
2026-05-09 20:46:42 +02:00
Alan Wizemann 66d6a373c9 Merge pull request #91 from awizemann/fix/slash-menu-new-fallback
feat(chat): surface /new in slash menu pre-session as static fallback
2026-05-09 20:46:37 +02:00
Alan Wizemann ee8f946836 Merge pull request #90 from awizemann/fix/dashboard-debounce-v0.13
fix(dashboard): coalesce file-watcher fires + dedupe in-flight loads (v0.13)
2026-05-09 20:46:29 +02:00
Alan Wizemann e26acaff4e fix(chat): drop forward-looking version labels + add /exit to alwaysAvailable
Two follow-ups from code review on this branch:

1. Drop forward-looking Scarf version labels per the
   `feedback_no_version_bumps.md` rule (release notes own version
   labels, not in-code comments). "Added v2.8 alongside Hermes v0.13."
   becomes "Introduced alongside Hermes v0.13." on
   `HermesSlashCommand.Source.alwaysAvailable`. The `reset()`
   explanatory block in `RichChatViewModel` drops the two "v2.8.0"
   references — the rationale is unchanged, just stops marking the
   change with a Scarf-side version it might never ship under.

2. Add `/exit` to the active-session-only fallback set so the
   implementation matches the doc comment. The doc listed eight
   commands (`/clear`, `/compact`, `/cost`, `/model`, `/tools`,
   `/reload-skills`, `/help`, `/exit`) but only seven were appended.
   Adding `/exit` is the right call since it's a real Hermes ACP
   command; users typing `/exit` on a resumed session will now
   discover and dispatch it before the ACP-advertised version arrives.

Tests: M9SlashCommandTests 30/30 green, Mac scheme builds clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:43:34 +02:00
Alan Wizemann ce028b065f fix(dashboard): max-wait safeguard for scheduleCoalescedTick + drop forward-looking version label
Two follow-ups from code review on this branch:

1. Add `maxWait` (1.5 s) safeguard to `HermesFileWatcher.scheduleCoalescedTick`
   so the trailing-debounce can't be starved indefinitely under sustained
   activity. Each scheduled fire now picks the earlier of (a) the
   `coalesceWindow` quiet floor and (b) `maxWait` since the FIRST fire of
   the current burst. A 10 Hz `state.db-wal` write storm coincident with
   a `gateway_state.json` Start/Stop touch now publishes within
   `maxWait` instead of waiting for the WAL activity to subside. The
   single-fire / quiet-burst case is unchanged because both deadlines
   reduce to the same value.

2. Drop the forward-looking "v2.8 dogfood bug report" reference from a
   comment in `DashboardViewModel.load()` per the
   `feedback_no_version_bumps.md` rule (release notes own version
   labels, not in-code comments).

Tests: full ScarfCore suite green (450/450), Mac scheme builds clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:41:59 +02:00
Alan Wizemann 096dbe085e feat(chat): richer slash menu in resumed sessions; preserve agent commands across reset
Two fixes uncovered by v2.8.0 dogfooding when clicking a previous chat
in the sidebar (vs. starting a new one):

1. **Preserve `acpCommands` across `RichChatViewModel.reset()`**.
   Hermes ACP only emits `available_commands_update` after `session/new`,
   not after `session/load`. Wiping the cached set on every session
   switch meant resumed sessions landed at a 4-command fallback even
   though the agent identity (and therefore the command list) hadn't
   changed. The new comment in `reset()` documents the rationale; the
   host-switch case still tears down the whole `ContextBoundRoot`, so
   stale carry-over isn't reachable when the agent identity does change.

2. **Expand the static fallback when a session is active**. Adds the
   agent-level command set (`/clear`, `/compact`, `/cost`, `/model`,
   `/tools`, `/reload-skills`, `/help`) to `alwaysAvailableCommands`
   when `sessionId != nil`. `/new` continues to show in both states.
   Pre-session, only `/new` surfaces — the others all require a live
   session, and surfacing them would mislead. Deduped by name against
   the ACP-advertised set so the richer (server-authoritative)
   description / argument hint wins once Hermes does emit them.

The two fixes together cover all paths to the slash menu:
- Cold start, click resume → fix #2 paints the active-session set
- Hot path, switch sessions after a `session/new` → fix #1 keeps the
  ACP-advertised set in `acpCommands`
- Cold start, click "+ New" → ACP populates as before; unchanged

Discovered during v2.8.0 dogfooding against a live Hermes v0.13.0 host.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:29:56 +02:00
Alan Wizemann e7096bb44c fix(dashboard): coalesce file-watcher fires + dedupe in-flight loads (v0.13)
Hermes v0.13 writes to state.db-wal and rotating logs at ~10 Hz during
gateway activity (Checkpoints v2 single-store + session-durability writes
hit disk far more often than v0.12). Each FSEvents fire on a watched core
path was ticking HermesFileWatcher.lastChangeDate, which every observing
view (Dashboard, Projects, ProjectSessions, half a dozen widgets) re-fired
its `.onChange` / `.task(id:)` against. On Local hosts the dashboard
stacked 5+ concurrent `viewModel.load()` calls in 200 ms, contending on
the read-only state.db handle and surfacing as `BackendError error 3` (a
sqlite step error from a busy/closed handle) plus visible flickering as
isLoading thrashed.

Two-part fix:

1. **HermesFileWatcher** coalesces FSEvents fires into one
   `lastChangeDate` mutation per 500 ms quiet window. A 10 Hz burst of
   FSEvents collapses into 2 observable mutations per second instead of
   10. Both local FSEvents and remote-poll deltas funnel through the
   same `scheduleCoalescedTick` helper, so SSH contexts get the same
   protection. `stopWatching` cancels the pending timer alongside the
   sources so a tear-down doesn't fire one trailing mutation after.

2. **DashboardViewModel.load()** holds a single in-flight `Task` handle.
   When `.onChange` and `.task` race (or any future caller fires
   concurrently), the second caller awaits the first's completion
   instead of starting a parallel load. `isLoading` is no longer
   thrashed and the data-service refresh runs once per coalesced tick.

Pre-v0.13 hosts see no behavioural change — they already wrote to
state.db-wal at 1-2 Hz, well below the 500 ms coalesce window. v0.13
hosts now see a smooth dashboard that updates ~2 Hz during gateway
activity instead of flickering at 10 Hz.

Discovered during v2.8.0 dogfooding against a live v0.13.0 host.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:14:15 +02:00
Alan Wizemann a359177e76 Merge pull request #89 from awizemann/ws-9-ios-v0.13
feat(ios): v0.13 read-only catch-up (WS-9)
2026-05-09 19:44:57 +02:00
Alan Wizemann 394e223551 Merge integration/v2.8.0: land WS-6, WS-7, WS-8 2026-05-09 19:43:28 +02:00
Alan Wizemann c721e7a6ea Merge pull request #87 from awizemann/ws-5-gateway-v0.13
feat(gateway): Google Chat + allowlists + behavior toggles (WS-5)
2026-05-09 19:41:54 +02:00
Alan Wizemann 8c75efbdd8 Merge pull request #85 from awizemann/ws-4-curator-archive
feat(curator): archive + prune + list-archived (WS-4)
2026-05-09 19:41:38 +02:00
Alan Wizemann be0a6e27fc Merge pull request #88 from awizemann/ws-3-kanban-v0.13
feat(kanban): v0.13 diagnostics + recovery UX (WS-3)
2026-05-09 19:41:31 +02:00
Alan Wizemann 53d5fa7262 Merge pull request #82 from awizemann/ws-2-goals-and-queue
feat(chat): add /goal and /queue slash commands (WS-2)
2026-05-09 19:41:24 +02:00
Alan Wizemann a8cdb3e663 feat(ios): v0.13 read-only catch-up — goal pill, queue chip, Kanban diagnostics, Curator archived, Platforms (WS-9)
Mirrors the v0.13 surfaces from WS-2 (Persistent Goals + ACP /queue),
WS-3 (Kanban diagnostics + hallucination gate), WS-4 (Curator archive),
and WS-5 (Google Chat platform + cross-platform allowlists + behavior
toggles) onto ScarfGo. Per Phase H precedent, every iOS surface is
strictly read-only — write verbs (Verify / Reject, /goal --clear, queue
send, allowlist editing, archive Restore / Prune) live on Mac in v2.8.0
and are deferred to v2.8.x.

Five iOS additions, all capability-gated so pre-v0.13 hosts see the
v2.7.5 layout unchanged:

1. Chat — goal pill ("Goal: <text>") and queue chip ("N queued") render
   inside `projectContextBar` whenever a project, goal, or queue is
   present. The bar is no longer project-only; goal/queue chips render
   even outside a project chat. Goal text scales with Dynamic Type
   (semantic `.subheadline`); the full untruncated text rides VoiceOver
   via the chip's accessibility label.
2. Kanban — `ScarfGoKanbanDetailSheet` gains a `retries: N` chip in the
   header `FlowLayout`, a yellow "Worker-created — verify on Mac" badge
   for `pending` hallucination state, a red "Auto-blocked" banner with
   the server-supplied `auto_blocked_reason`, and tappable diagnostics
   chip-lists (task-level + per-run) that present a new
   `DiagnosticDetailSheet` with kind / severity / message / timestamp.
   No Verify or Reject buttons; the badge copy points users to the Mac
   app.
3. Curator — `CuratorView` appends a read-only "Archived" section that
   loads via `viewModel.loadArchive()` on appear and pull-to-refresh.
   Per-row name + category badge + reason + archived-at + size; footer
   signposts users to the Mac app for Restore / Prune.
4. Settings → Platforms — adds a Google Chat status row (configured /
   not configured), busy-ack and restart-notification rows summarized
   across `gatewayPlatforms` (yes / no / mixed (N platforms)), and
   collapsed DisclosureGroups for allowed channels / chats / rooms with
   monospaced "platform: id" entries when expanded. No editor.
5. Settings — green "v0.13 features active" `ScarfBadge` above the
   quick-edits section when `caps.isV013OrLater`. Tap presents a new
   `V013FeaturesSheet` listing the six v0.13 surfaces with one-sentence
   summaries; the section footer is explicit that editing lives on Mac.

Implements WS-9 of Scarf v2.8.0 (Hermes v0.13.0 catch-up).
Plan: scarf/docs/v2.8/WS-9-ios-v0.13-plan.md (on coordination/v2.8.0-plans).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 19:25:16 +02:00
Alan Wizemann 441d11404f Merge remote-tracking branch 'origin/ws-8-ux-v0.13' into integration/v2.8.0
# Conflicts:
#	scarf/scarf/Features/Chat/Views/ChatTranscriptPane.swift
#	scarf/scarf/Features/Chat/Views/SessionInfoBar.swift
2026-05-09 19:12:15 +02:00
Alan Wizemann 6e8480411a Merge remote-tracking branch 'origin/ws-7-settings-v0.13' into integration/v2.8.0
# Conflicts:
#	scarf/Packages/ScarfCore/Sources/ScarfCore/Models/HermesConfig.swift
#	scarf/Packages/ScarfCore/Sources/ScarfCore/Parsing/HermesConfig+YAML.swift
2026-05-09 19:11:29 +02:00
Alan Wizemann 3a764e81e0 Merge remote-tracking branch 'origin/ws-6-providers-v0.13' into integration/v2.8.0
# Conflicts:
#	scarf/Packages/ScarfCore/Sources/ScarfCore/Models/HermesConfig.swift
#	scarf/Packages/ScarfCore/Sources/ScarfCore/Parsing/HermesConfig+YAML.swift
2026-05-09 19:10:43 +02:00
Alan Wizemann 6e90741a17 Merge remote-tracking branch 'origin/ws-5-gateway-v0.13' into integration/v2.8.0 2026-05-09 19:09:38 +02:00
Alan Wizemann 93a3b40a67 Merge remote-tracking branch 'origin/ws-4-curator-archive' into integration/v2.8.0 2026-05-09 19:09:38 +02:00
Alan Wizemann 52f0ddb36c Merge remote-tracking branch 'origin/ws-3-kanban-v0.13' into integration/v2.8.0 2026-05-09 19:09:38 +02:00
Alan Wizemann cedee04f2a feat(kanban): v0.13 diagnostics + recovery UX (WS-3)
Layers Hermes v0.13's reliability + recovery affordances on top of the
v2.7.5 Kanban v3 board. New surface — gated end-to-end on
`HermesCapabilities.hasKanbanDiagnostics` (>= v0.13.0):

- **Hallucination gate.** Worker-created cards land in `pending` until
  the user verifies the underlying work exists. Inspector renders a
  yellow Verify / Reject banner above the body; cards dim to 0.6 with
  a question-mark glyph. Verify is optimistic — banner clears
  immediately, polling confirms. Reject routes through
  `comment` + `archive` so there's an audit trail.
- **Generic diagnostics engine.** `HermesKanbanDiagnostic` (new model +
  typed-mirror enum `KanbanDiagnosticKind`) renders cross-run signals
  on the inspector header and per-run signals under each Runs row.
  Card footer gains a stethoscope dot when any signal is attached.
- **`max_retries` create-time field + inspector chip.** Toggle-gated
  Stepper in the create sheet sends `--max-retries N`; chip on the
  inspector header reads it back read-only with a tooltip explaining
  there's no update verb.
- **Multi-line title input.** Create sheet's title becomes a
  `TextField(axis: .vertical, lineLimit: 1...4)`. Newlines are stripped
  client-side on pre-v0.13 hosts (which truncate at the first `\n`).
- **Auto-blocked reason banner.** When `task.auto_blocked_reason` is
  set, replaces the generic "Last run: blocked" with a red banner
  rendering the server reason verbatim. Card footer shows a 1-line
  truncated copy in red.
- **Tolerant decode contract.** Every new field is `Optional` with
  `decodeIfPresent`; diagnostics arrays use `try?` so a single
  malformed entry doesn't poison the row. v0.12 hosts decode unchanged.

Implements WS-3 of Scarf v2.8.0 (Hermes v0.13.0 catch-up).
Plan: scarf/docs/v2.8/WS-3-kanban-v0.13-plan.md (on
coordination/v2.8.0-plans).

TODOs marked inline pending integration against a live v0.13 binary:
WS-3-Q1 (verify verb name), WS-3-Q2 (diagnostics envelope vs task),
WS-3-Q4 (failure_count placement), WS-3-Q5 (darwin-zombie kind
string), WS-3-Q6 (max_retries default).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 19:06:38 +02:00
Alan Wizemann b4482e5ee7 feat(gateway): Google Chat platform + cross-platform allowlists + behavior toggles (WS-5)
Catches the Mac Messaging Gateway and Platforms surfaces up to Hermes
v0.13.0. Adds Google Chat as the 20th platform under Settings → Platforms,
gated on `hasGoogleChatPlatform`. Adds a per-platform "Gateway behavior"
subsection to the six platforms Hermes added v0.13 allowlist support to
(Slack, Mattermost, Google Chat, Telegram, WhatsApp, Matrix) — each
exposes the `allowed_channels` / `allowed_chats` / `allowed_rooms` editor
plus three new toggles (`busy_ack_enabled`, `gateway_restart_notification`,
`slash_command_notice_ttl_seconds`). The Messaging Gateway page header
gains a one-line cross-profile digest sourced from `hermes gateway list
--json`. SkillsView surfaces an informational row on skills whose body
contains the v0.13 `[[as_document]]` directive.

New ScarfCore types: `GatewayAllowlistKind` (channels/chats/rooms +
platform mapping), `GatewayPlatformSettings` (per-platform v0.13 bundle),
`GatewayConfigWriter` (pure YAML list-block editor — `hermes config set`
can't write lists; tested with 15 cases incl. round-trip + idempotence +
quoting + scalar-sibling preservation), `HermesGatewayListService`
(`hermes gateway list --json` parser tolerant of unknown keys + alt
field names; 13 tests), `HermesConfig.gatewayPlatforms` field. Mac VM
renamed to `MessagingGatewayViewModel` (single-feature local rename;
CLAUDE.md "the SidebarSection.gateway enum case stays" invariant
upheld). All 22 new tests pass; full ScarfCore suite green except 3
pre-existing `RemoteSQLiteBackendTests` failures unrelated to WS-5.

Capability-gated end-to-end. Pre-v0.13 hosts see no Google Chat row,
no cross-profile digest, no v0.13 toggles, and no `[[as_document]]`
info row — the v2.7.5 surface is byte-for-byte unchanged. Q1-Q3 wire-
shape unknowns (Google Chat identifier, YAML key path,
`gateway list --json` shape) are marked with `// TODO(WS-5-Q<N>)` and
defended by tolerant parsers + dual-spelling lookups.

Implements WS-5 of Scarf v2.8.0 (Hermes v0.13.0 catch-up).
Plan: scarf/docs/v2.8/WS-5-gateway-v0.13-plan.md (on coordination/v2.8.0-plans).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 19:05:55 +02:00
Alan Wizemann 4757b5ae49 feat(curator): archive + prune + list-archived (WS-4)
Catches the Curator surface up to Hermes v0.13's new write-side verbs
(`archive <skill>`, `prune`, `list-archived`, synchronous `run`). Adds
a new `CuratorService` actor in ScarfCore mirroring `KanbanService`'s
pattern (Sendable, pure I/O, `Task.detached(priority: .utility)` per
verb), tolerantly-decoded `HermesCuratorArchivedSkill` /
`CuratorPruneSummary` models, and `CuratorError` for inline-banner
surfacing.

Mac UX gains an "Archived" section between the leaderboards and the
last-report block (per-row Restore button), an "archivebox" button on
every active-skill leaderboard row to manually archive, a destructive
"Prune Archived…" confirm sheet enumerating each skill (template-
uninstall pattern — Cancel owns `.defaultAction`, Prune is on the red
`ScarfDestructiveButton`), and a synchronous-with-progress "Run Now"
on v0.13+ hosts (600s timeout, `ProgressView` while in-flight).
Failure path routes through a yellow inline error banner instead of a
modal alert. The legacy `CuratorRestoreSheet` stays accessible from
the overflow menu but only on pre-v0.13 hosts; on v0.13+ the per-row
Restore in the new Archived section replaces it.

All new surfaces gate on `HermesCapabilities.hasCuratorArchive` —
pre-v0.13 hosts see the v2.7.x layout unchanged. iOS picks up the new
`runNow(synchronous:)` signature with the v0.13 capability flag; the
read-only Archived section + WS-9 marker is left for the next stream.
14 new parser tests in `HermesCuratorParserTests` cover the JSON
happy path, the `{"archived": [...]}` envelope, the text fallback
(`--json` not supported), `"no archived skills"` sentinel folding,
prune-dry-run with both wrapper + bare-array shapes, and zero-skill
prune. All 369 ScarfCore tests pass; `xcodebuild` for the `scarf`
scheme succeeds.

Wire-shape unknowns (CLI flag presence on real v0.13) carry
`// TODO(WS-4-Q<N>)` markers in `CuratorService` and fall back
defensively when a flag isn't recognized. Implements WS-4 of Scarf
v2.8.0 (Hermes v0.13.0 catch-up). Plan:
scarf/docs/v2.8/WS-4-curator-archive-plan.md (on
coordination/v2.8.0-plans).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 19:03:13 +02:00
Alan Wizemann 0070441243 feat(profiles): add --no-skills toggle to create-profile sheet
Adds an "Empty profile (no skills)" toggle to the Mac create-profile
sheet, gated on `hasProfileNoSkills` (v0.13+). When ON, the create
flow appends `--no-skills` to `hermes profile create`. The toggle is
disabled (greyed out) when "Full copy of active profile" is on, per
WS-7 plan Decision H — a full clone copies skills wholesale, so
`--no-skills` would be a contradiction at the UX layer. The wire
itself stays permissive: a user can stack `--clone --no-skills` to
clone config but skip skills, which is a plausible workflow.

Defensive write-strip: even though the toggle is hidden on pre-v0.13
hosts, the call site reads `createNoSkills` through the capability
gate so a stale state value can't sneak `--no-skills` past argparse
on a CLI that doesn't know it.

iOS Profiles is read-only (per CLAUDE.md "v0.12 iOS catch-up
Phase H") so no toggle there.

TODO marker (WS-7-Q8) flags the assumed `--clone-all` interaction —
verify Hermes's behaviour with both flags during integration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 19:03:06 +02:00
Alan Wizemann 57a6340985 feat(providers): catalog refresh + image_gen.model + OpenRouter caching (WS-6)
Surfaces the v0.13 provider catalog work in Scarf v2.8.0. Five new model IDs
(deepseek/deepseek-v4-pro, x-ai/grok-4.3, openrouter/owl-alpha,
tencent/hy3-preview, arcee/trinity-large-thinking) flow through
models_dev_cache.json on next refresh — no manual catalog entries
needed; the picker reaches them automatically. The grok-4.20-beta →
grok-4.20 rename is handled via a new ModelCatalogService.modelAliases
map plus resolveModelAlias() helper, called from validateModel(),
model(_:_:), and provider(for:) at read time. Lossless: stored configs
are never rewritten.

Vercel AI Gateway is demoted to the bottom of the picker via a new
demotedProviders set + sort-comparator axis (between subscription-gated
and alphabetical). Always-on, no capability gate — sort-order
consistency across Hermes versions.

image_gen.model (top-level v0.13 YAML key) and
openrouter.response_cache.enabled (provisional key shape per
TODO(WS-6-Q1)) are surfaced as new SettingsSection rows in
AuxiliaryTab, capability-gated on hasImageGenModel +
hasOpenRouterResponseCache so pre-v0.13 hosts hide them. Image-gen
picker has a curated 7-entry allowlist (HermesImageGenModel) plus
free-form Custom model ID entry.

CLAUDE.md gains two schema-drift bullets next to the existing
overlayOnlyProviders requirement (modelAliases + demotedProviders
mirror with hermes_cli/providers.py).

Tests: 4 new M0cServicesTests (sort axis, alias resolution + cross-
provider isolation, image-gen allowlist, demoted-set sentinel) and 2
new M6ConfigCronTests (YAML round-trip + empty-default).

Implements WS-6 of Scarf v2.8.0 (Hermes v0.13.0 catch-up).
Plan: scarf/docs/v2.8/WS-6-providers-v0.13-plan.md
(on coordination/v2.8.0-plans).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 19:02:45 +02:00
Alan Wizemann 0f78856e6e feat(settings): v0.13 polish — redaction hint, display.language picker, xAI cloning badge (WS-8)
Three Settings-tab surfaces tracking v0.13 release notes:

- **Redaction default-flip awareness** (Advanced → Caching & Redaction):
  inline hint below the existing toggle whose copy depends on
  `HermesCapabilities.isV013OrLater`. v0.13 flipped the server-side
  default from OFF (v0.12) to ON, but Scarf's parser still treats
  "absent key" as `false`. Hint disambiguates so users on v0.13 hosts
  understand redaction is on server-side even when the toggle reads OFF.

- **`display.language` picker** (General → Locale): 8-option enum (`""`
  default + en/zh/ja/de/es/fr/uk/tr) capability-gated on
  `hasDisplayLanguage`. Persists via `hermes config set
  display.language <code>`. Empty string preserves "no key" semantics
  (Hermes-default English); explicit `en` pins it. Required a small
  `optionLabel:` overload on `PickerRow` so non-English labels
  (中文 / 日本語 / etc.) render alongside their codes.

- **xAI Custom Voices badge** (Voice → Text-to-Speech): adds `xai`
  to the TTS provider picker (un-gated — xAI TTS shipped earlier),
  exposes Voice ID + Model fields, and renders a "Cloning supported"
  ScarfBadge gated on `hasXAIVoiceCloning`. Hint copy points at
  `hermes voice` for cloned-voice management since Scarf has no
  in-app surface for that yet (out-of-scope for v2.8).

Capability gates: `isV013OrLater` (hint discriminator),
`hasDisplayLanguage` (picker), `hasXAIVoiceCloning` (badge). Pre-v0.13
hosts see the v2.7.5 layout unchanged.

`TODO(WS-8-Q2)` flags the assumed xAI YAML keys (`tts.xai.voice_id` /
`tts.xai.model` mirroring elevenlabs) for grep-verify against
`~/.hermes/hermes-agent/hermes_cli/voice/tts.py`.

iOS deferred to v2.9 (Q4): `Scarf iOS` Settings is read-mostly and
doesn't have a write surface for either the language picker or the
xAI fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:59:38 +02:00
Alan Wizemann 5877bf6519 feat(updater): forward-compat HermesUpdaterCommandBuilder for hermes update --yes (WS-8)
Pure-function helper that builds argv arrays for `hermes update`,
gated on `HermesCapabilities`. Pre-v0.12 → bare `update`; v0.12+
honors `--check`; v0.13+ honors `--yes` for unattended runs.

No in-app "Update Hermes" affordance ships in v2.7.5 — Sparkle handles
Scarf-self-update and `hermes update` is invoked by users in their
terminal. This is forward-compat plumbing so the eventual UI surface
shares flag selection across Mac / iOS / remote without re-deriving
from scratch.

Test matrix in `M0eUpdaterTests` covers all six combinations
(pre-v0.12, v0.12 ± unattended ± check, v0.13 ± unattended ± check)
plus an empty-capabilities fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:59:12 +02:00
Alan Wizemann f19f19cd56 feat(chat): surface v0.13 compression count + bracket-aware slash hint (WS-8)
Two small chat-surface additions tracking Hermes v0.13:

- Plumb a `compressionCount` field through `ACPPromptResult` and
  `RichChatViewModel.acpCompressionCount` so `SessionInfoBar` can render
  a `🗜 ×N` chip next to the token counter when the agent has performed
  context compactions. Capability-gated on
  `HermesCapabilities.hasContextCompressionCount` and `count > 0` so
  pre-v0.13 hosts (which always emit 0) and fresh sessions never see
  the chip. Wire decode tolerates camelCase + snake_case;
  `TODO(WS-8-Q1)` flags the assumption that the field rides on
  `usage` — if v0.13 emits via a separate `session/update` notification
  the bigger fix is described in the WS-8 plan.

- Slash-menu argument hint is now bracket-aware: hints starting with
  `<` or `[` pass through verbatim, others wrap as `<hint>`. v0.13's
  `/new [name]` ships through unchanged without rendering as
  `<[name]>`. No flag check at the renderer — agent payload is the
  source of truth.

Coordination with WS-2: both WSes touch `SessionInfoBar`. WS-2 owns
the queue chip on the left half; this WS owns the compression chip on
the right half. The added `capabilities` parameter is shared — kept
additive so WS-2's later merge produces no file-level conflict.

Tests: extends `M0dViewModelsTests` (compression count tracking +
reset semantics) and `ScarfCoreSmokeTests` (decode default + explicit
v0.13 init path).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:58:58 +02:00
Alan Wizemann 6c96fcfa43 feat(settings): add Web Tools tab with v0.13 search/extract split
Adds a new "Web Tools" Settings tab (between Browser and Voice) with
two distinct shapes that share the same chrome:

- Pre-v0.13: a single "Backend" picker writing the legacy
  `web_tools.backend` key (so v0.12 users still configure web tools).
- v0.13+: two pickers — Search backend writes
  `web_tools.search.backend` (SearXNG appears here only — Hermes
  registers it as a search-only dispatch), Extract backend writes
  `web_tools.extract.backend`.

Capability gate: `hasWebToolsBackendSplit` chooses which shape
renders. The tab itself is always visible — pre-v0.13 users would
otherwise lose access to the legacy combined-backend picker.

Model layer:
- `HermesConfig.webToolsBackend` / `webToolsSearchBackend` /
  `webToolsExtractBackend` — three fields, each round-tripping its
  own YAML key. Defaults: `duckduckgo` / `duckduckgo` / `reader`.
- YAML parser reads all three keys via the existing `str(...)`
  helper. Pre-v0.13 hosts populate only `webToolsBackend`; the
  split keys default to the same backend so the picker shows the
  same value the user already had.

TODO markers (WS-7-Q6/Q7) flag the inline backend lists + legacy
fallback semantics — verify against `~/.hermes/hermes-agent/
hermes_cli/web_tools.py` during integration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:56:08 +02:00
Alan Wizemann edac142d08 feat(chat): add /goal and /queue slash commands (WS-2)
Adds Hermes v0.13's Persistent Goals and ACP /queue surfaces to the
rich-chat composer. /goal <text> locks the agent on a target across
turns (rendered as an info-tinted "Goal locked" pill in the chat
header, with a context-menu Clear action that dispatches /goal --clear);
/queue <text> queues a prompt to run after the current turn (rendered
as a warning-tinted chip with a popover listing queued prompts +
relative timestamps). Both ride .acpNonInterruptive so the chat keeps
"Agent working…" off, and both surface a 4-second transient toast
mirroring /steer's existing UX.

Capability-gated end-to-end: the rich-chat slash menu reads through
RichChatViewModel.capabilitiesGate (a new @ObservationIgnored field
fed by ChatViewModel.attachCapabilitiesStore on Mac and a parallel
.task(id:) on iOS), so pre-v0.13 hosts never see /goal or /queue.
/steer is greyed-out on idle sessions when hasACPSteerOnIdle is off
(pre-v0.13 hosts only). The "Clear all" queue-popover button is
intentionally absent in v2.8.0 — Hermes' wire-shape for /queue --clear
isn't verified yet, so a button that lies about server-side state is
worse than no button (per WS-2 plan Q2 decision).

Optimistic-only: there is no authoritative read-back path for the
active goal in v2.8.0. The pill paints synchronously off the
optimistic write the moment the user sends /goal …; cross-session
resume won't re-paint it until the user types /goal again. A
TODO(WS-2-Q1) marker in RichChatViewModel.recordActiveGoal points at
the read-back hook for v2.8.1; TODO(WS-2-Q5) flags the verbatim
/queue argument shape for coordinator wire-verification; TODO(WS-2-Q7)
flags the /goal non-interruptive classification. TODO(v2.8.1) in
handlePromptComplete is the deferred "auto-resumed from checkpoint"
indicator (WS-2 plan Q3 decision).

iOS surfaces no UI yet (deferred to WS-9), but the iOS controller's
_sendImpl mirrors the dispatch so the shared RichChatViewModel state
stays aligned across platforms — otherwise an iOS user who ran /goal
then opened the same session on Mac would see an empty pill.

Tests: extends M9SlashCommandTests with 13 new cases covering the
non-interruptive list contents, capability-gated availableCommands
filtering on v0.12 vs v0.13, parseGoalArgument variants, optimistic
mutators (recordActiveGoal / recordQueuedPrompt / popQueuedPrompt),
isNonInterruptiveSlash recognition, and reset() drainage.

Implements WS-2 of Scarf v2.8.0 (Hermes v0.13.0 catch-up).
Plan: scarf/docs/v2.8/WS-2-goals-and-queue-plan.md (on coordination/v2.8.0-plans).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:55:54 +02:00
Alan Wizemann fd33b714e3 feat(cron): add --no-agent watchdog toggle gated on hasCronNoAgent
Adds a "Run script only (no agent call)" toggle to the cron job
editor. When ON, the prompt + skills sections dim + disable
visually but stay rendered (no layout shift mid-edit), the
script field stays fully active, and the form passes
`noAgent: true` to `createJob`/`updateJob`. The toggle is hidden
on pre-v0.13 hosts via `supportsNoAgent: hasCronNoAgent` and
defensively stripped at the call site (`hasCronNoAgent ?
form.noAgent : false` on create, `: nil` on edit) — same shape
as the v0.12 `workdir` strip.

Read-side: `HermesCronJob.noAgent: Bool?` is decoded via
`decodeIfPresent` so pre-v0.13 jobs.json files round-trip
unchanged. The display rule `job.noAgent == true` treats
`nil` and `false` identically — a script-only job must opt in.

Write-side:
- `createJob` appends `--no-agent` and passes an empty positional
  prompt (per WS-7-Q5) to keep argparse happy when the prompt is
  the trailing positional.
- `updateJob` sends `--no-agent` / `--agent` to flip the flag in
  edit mode (per WS-7-Q4 — verify the toggle-off spelling on
  integration; if Hermes is one-way, disable the toggle in edit
  mode with a tooltip).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:43:03 +02:00
Alan Wizemann c81a8a56e8 feat(mcp): add SSE transport support gated on hasMCPSSETransport
Extends MCPTransport with a third .sse case (alongside stdio + http),
plumbed through the YAML parser, add-server form, list view, detail
view, and editor. The add-server form filters .sse out of the segmented
picker on pre-v0.13 hosts (capability-gated on hasMCPSSETransport) so
Hermes never sees a transport flag it can't parse. The editor renders
a third numeric "SSE read timeout" field only for .sse servers.

YAML layer:
- HermesMCPServer.sseReadTimeout: Int? — defaulted in init, decoded
  from `sse_read_timeout` scalar.
- parseMCPServersBlock: 3-way transport discriminator — `transport: sse`
  scalar wins, then url-bearing entries default to .http (v0.12 shape),
  command-bearing to .stdio. Pre-v0.13 entries are byte-for-byte
  unaffected.
- HermesFileService.addMCPServerSSE writes via `hermes mcp add --url
  <u> --transport sse [--sse-read-timeout <t>]`.
- HermesFileService.setMCPServerSSETimeout patches the scalar via the
  same surgical patcher used by setMCPServerTimeouts.

TODO markers (WS-7-Q1/Q2/Q3) flag the wire-format unknowns the plan
called out — verify against a v0.13 Hermes install during integration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:34:27 +02:00
Alan Wizemann 3e470c7155 Merge pull request #80 from awizemann/ws-1-capabilities-v0.13
feat(capabilities): add Hermes v0.13 capability flags + version bump (WS-1)
2026-05-09 18:17:51 +02:00
Alan Wizemann 963d0e1a5c feat(capabilities): add isV013OrLater convenience predicate
Surfaces a v0.12 → v0.13 boundary check that doesn't proxy through any
specific feature flag. Used by WS-8 (redaction default-state hint copy,
"v0.13 features active" Settings badge in iOS WS-9) where the call site
isn't actually about a specific feature — it's about whether the host is
on the v0.13 line.

Equivalent to any individual v0.13 flag (e.g. `hasGoals`); both resolve
to the same `>= 0.13.0` threshold. Convenience exists to keep call sites
honest: `caps.isV013OrLater` reads better than `caps.hasGoals` when the
context isn't goal-related.

Tests: 4 new fixtures covering v0.13 host (true), v0.12 host (false),
empty/undetected (false), and v0.14 host (true). 19 total tests in the
suite, all passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:08:14 +02:00
Alan Wizemann cf8a737ace docs(v2.8): add 8 work-stream plans + coordinator review for Hermes v0.13.0 catch-up
Each plan is the product of one Opus 4.7 1M-context planning agent dispatched
in parallel to investigate a single v0.13 surface. Plans are read-only — they
identify file-by-file edits, type additions, capability gates, tests, and
open questions, but no code is written yet. Implementation agents will be
dispatched per stream once the cross-stream open-questions matrix is resolved.

- WS-2: Persistent Goals + ACP /queue slash commands
- WS-3: Kanban v0.13 diagnostics + recovery UX
- WS-4: Curator archive + prune + list-archived
- WS-5: Messaging Gateway v0.13 expansion (Google Chat, allowlists, behavior toggles)
- WS-6: Provider catalog refresh + image_gen.model + OpenRouter response caching
- WS-7: Settings tab additions (MCP SSE, Cron --no-agent, Web Tools split, Profiles --no-skills)
- WS-8: UX polish (compression count, /new <name>, redaction default, display.language)
- WS-9: ScarfGo iOS catch-up (read-only mirrors of WS-2/3/4/5)

COORDINATOR-REVIEW.md compiles the cross-stream collision matrix, the open-
questions matrix (54 questions clustered into wire-shape unknowns,
architectural decisions, and out-of-scope deferrals), and the recommended
sequencing for implementation + review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:03:41 +02:00
Alan Wizemann 52c802676f feat(capabilities): add Hermes v0.13 capability flags + version bump
Adds 22 new capability flags grouped under a v0.13 (v2026.5.7) MARK
section in HermesCapabilities, covering Persistent Goals, ACP /queue
+ /steer-on-idle, Kanban diagnostics + recovery UX, Curator archive
+ prune, Google Chat (20th platform), cross-platform allowlists,
MCP SSE transport, Cron --no-agent, Web Tools backend split, Profiles
--no-skills, context compression count, /new <name>, OpenRouter cache,
image_gen.model, display.language, xAI voice cloning, video_analyze,
and the transform_llm_output plugin hook.

Each flag gates on >= 0.13.0 so v0.13 patch releases (0.13.4 etc.)
still light up every flag. Existing v0.12 flags unchanged. Test suite
extends with v0.13.0/2026.5.7 fixtures, a v0.13.4 patch-release case,
explicit "v0.13 flags off on v0.12 host" coverage, and updates the
future-version test to v0.14.0.

CLAUDE.md target line bumps to v2026.5.7 (v0.13.0); a new v2026.5.7
section mirrors the v0.12 / v0.11 scaffolding describing the Scarf-
relevant subset. The v0.12 + v0.11 historical sections remain intact
since pre-v0.13 hosts still consume those flags.

Foundation for the v2.8.0 Scarf release — every subsequent work-stream
(WS-2 through WS-9) consumes flags added here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:31:51 +02:00
Alan Wizemann 5d8873d305 chore: Bump version to 2.7.5 v2.7.5 2026-05-08 13:59:21 +02:00
Alan Wizemann 49bc4efe83 fix(kanban): enrich LocalTransport subprocess env so kanban dispatcher can spawn workers
GUI-launched Scarf inherits macOS's launch-services PATH
(`/usr/bin:/bin:/usr/sbin:/sbin`). Scarf itself finds `hermes` via
absolute-path resolution in `HermesPathSet.hermesBinaryCandidates`,
but when the kanban dispatcher (a child of Scarf) tries to spawn a
worker, the worker inherits the same stripped PATH and Hermes's spawn
machinery prints `\`hermes\` executable not found on PATH. Install
Hermes Agent or activate its venv before running the kanban
dispatcher.` — recording `outcome=spawn_failed` on the run.

`LocalTransport` now mirrors `SSHTransport.environmentEnricher`:
adds an `environmentEnricher: (() -> [String: String])?` static, and
applies it to every subprocess. `scarfApp.swift` wires it at launch
to the same `HermesFileService.enrichedEnvironment()` login-shell
probe (`zsh -l -i` → `zsh -l` fallback) the SSH transport already
uses, so subprocesses see `~/.local/bin`, `/opt/homebrew/bin`, and
the user's credential env vars.

Defense-in-depth: `subprocessEnvironment(forExecutable:)` always
prepends the executable's own directory to PATH if missing — covers
early-startup paths and test harnesses where the enricher hasn't
been wired yet.

Two new tests in `KanbanModelsTests` lock in:
1. The fallback (no enricher → executable's dir lands on PATH)
2. The enricher win for PATH + the empty-string-aware copy semantics
   for credential env vars (process env happens to set
   `ANTHROPIC_API_KEY=""` as an empty string in some environments;
   the enricher's non-empty value must still take effect)

Release notes for v2.7.5 updated to document the fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 13:59:21 +02:00
Alan Wizemann adcc984091 feat(kanban): full read/write board with per-project tenants
Lifts Scarf's Kanban surface from the v2.6 read-only list to a
drag-and-drop board with the complete Hermes v0.12 mutation surface
wired up, plus per-project boards bound to a Scarf-minted tenant slug
and a read-only board on iOS.

Why now: the v2.6 list was a placeholder shipped while upstream Kanban
collab was still mid-rework. v0.12 stabilized the 27-verb CLI; this
release makes Scarf a real GUI client for it. Driving real tasks
end-to-end exposed and closed a connected bug pattern (claim vs
dispatch, silent skipped_unassigned, integer-vs-ISO timestamps,
parser-leaked "(no" sentinel) that would have shipped as latent UX
papercuts otherwise.

ScarfCore: KanbanService actor (Sendable, pure I/O) wrapping every
verb; KanbanTenantReader cross-platform manifest projection; eight
new model types (TaskDetail, Comment, Event, Run, Stats, Assignee,
CreateRequest, Filters); KanbanError; pure transition planner that
maps drag-drop column changes to verb sequences, tested against
canonical Hermes JSON fixtures.

Mac: KanbanBoardView orchestrator with five-column drag-drop layout,
optimistic-merge state, KanbanInspectorPane side-pane (Comments /
Events / Runs / Log tabs, Log streams worker stdout every 2s while
running), inline assignee picker, health banner for unassigned and
last-failed-run states. New Task sheet defaults to active profile
and auto-fires kanban dispatch on submit. Sidebar moved Kanban from
Manage to Monitor. Read-only KanbanListView preserved as Board|List
toggle for narrow windows / accessibility.

Per-project: DashboardTab.kanban tab on every project gated on
hasKanban; KanbanTenantResolver mints scarf:<slug> tenants on first
interaction and persists to .scarf/manifest.json (immutable across
rename); ProjectAgentContextService surfaces the tenant in the
AGENTS.md scarf-managed block so agents pass --tenant <slug> on
kanban create. New kanban_summary dashboard widget; vocabulary
mirrored in tools/widget-schema.json and site/widgets.js.

iOS: read-only board on the project tab via paged single-column
Picker, modal detail sheet with Comments / Events / Runs. Mutations
+ drag-drop deferred to v2.8.

Tests: 19 new pure-logic tests covering decoding, planner verb
mapping, argv assembly, glance string formatting, and parser
rejection of the kanban assignees empty-state sentinel. All 348
ScarfCore tests pass.

Constraints documented in CLAUDE.md: no within-column reorder
(Hermes has no update --priority verb); no live watch streaming
yet (5s polling for board, 2s for log); no bulk re-tag for legacy
NULL-tenant tasks. Pre-v0.12 Hermes hosts gracefully hide the
surface end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 13:59:21 +02:00
Alan Wizemann fd80f4f95a Create FUNDING.yml 2026-05-07 12:55:53 +02:00
Alan Wizemann 9f240ae291 chore: Bump version to 2.7.1 v2.7.1 2026-05-07 12:46:11 +02:00
Alan Wizemann 9c149b288b fix(docs): restore Sonoma compatibility messaging in BUILDING.md + CONTRIBUTING.md
Scarf's `MACOSX_DEPLOYMENT_TARGET` is `14.6` (Sonoma) on the main
`scarf` target, set in 86762ea. Sonoma support is intentional —
several users dogfood on macOS 14.x and we want to keep them on the
release channel. Yesterday's BUILDING.md and the long-stale
CONTRIBUTING.md statement both claimed macOS/Xcode 26.x as minimums,
which would have steered Sonoma contributors and users away from a
build that actually runs on their box.

Correct values:

- Runtime min: **macOS 14.6 (Sonoma)** — matches the deployment target.
- Build min: **Xcode 16.0** — needed for Swift 6 strict-concurrency
  features the codebase uses.

Add a load-bearing-callout to BUILDING.md so future doc edits don't
silently raise the floor again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:13:18 +02:00
Alan Wizemann 37afbdeffc feat(build): contributor-friendly local-build.sh + BUILDING.md
Adds `scripts/local-build.sh` for unsigned command-line Debug builds
so contributors without an Apple Developer account can clone, build,
and run without provisioning gymnastics. The script:

- Detects arm64 / x86_64
- Verifies xcode-select, xcrun, xcodebuild are present
- Probes the Metal toolchain and offers an interactive install (gated
  on `[[ -t 0 && -z "${CI:-}" ]]` — CI never gets prompted)
- Resolves Swift packages, builds Debug with signing disabled
- Optionally `ditto`s the result to /Applications/scarf.app on
  explicit y/N

`BUILDING.md` documents prerequisites alongside the script. Existing
canonical Release universal CLI in README stays — `local-build.sh`
is an alternative for contributors, not a replacement for the
shipping build.

Cherry-picked from #76 with thanks to @unixwzrd. BUILDING.md's
prerequisites are corrected to match the actual deployment target
(macOS 26.2, Xcode 26.2+).

Co-Authored-By: M S <unixwzrd.register@mac.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:08:33 +02:00
Alan Wizemann bfd9bab9a0 fix(health): stop external dashboards by port, not pkill -f
`stopDashboard()` used to fall back to `pkill -f "hermes dashboard"`
when the running dashboard wasn't a Scarf-spawned subprocess. That's
broad enough to match shell history, log tails, README readers, and
this very source file — anything with the substring "hermes
dashboard" in its argv was a kill target.

Replace with a port-anchored lookup: `lsof -tiTCP:<port> -sTCP:LISTEN`
returns the PID actually bound to the dashboard port, then we
`SIGTERM` only that one process. Trusting the port is correct here:
Scarf owns the configured port and the user-visible intent is "stop
the thing on this port."

We deliberately omit `lsof -c hermes`. Hermes installs as a Python
shebang script (verified locally — `file ~/.local/bin/hermes` →
"a python3 script text executable"), so the kernel COMM is `python` /
`python3`, never `hermes`. A `-c hermes` filter would silently miss
every standard install.

Cherry-picked from #76 with thanks to @unixwzrd for the direction;
this version drops the `-c hermes` filter to actually fire on real
Hermes installs.

Co-Authored-By: M S <unixwzrd.register@mac.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:08:23 +02:00
Alan Wizemann 2e0eb63ea4 fix(health): tighten Hermes gateway pgrep so unrelated commands don't match
`hermesPIDResult()` was running `pgrep -f hermes`, which matched any
process with "hermes" anywhere in its argv — `hermes acp` chat
sessions Scarf itself spawns, `hermes -z` one-shots, log tails, even
this very file in an editor. The Dashboard "Hermes is running" badge
read true even when the gateway daemon was down.

Narrow the match to the gateway shape specifically. Two alternations
cover both invocation forms used in the wild:

- `python -m hermes_cli.main gateway run …` (the launchctl form)
- `/path/to/hermes gateway run …` (the script-path form)

Verified locally against an actual gateway PID:

    cmd=/Users/.../python -m hermes_cli.main gateway run --replace

The first alternation matches via the `-m hermes_cli.main gateway run`
boundary. All callers — `stopHermes()`, `DashboardViewModel`,
`HealthViewModel`, `SettingsViewModel`, `scarfApp` — semantically
want the gateway PID specifically, so the narrower match is the
right shape, not a behavior change.

Cherry-picked from #76 with thanks to @unixwzrd for the diagnosis
and the regex.

Co-Authored-By: M S <unixwzrd.register@mac.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:08:11 +02:00
Alan Wizemann 3a3c87e033 fix(skills): scope What's New pill to Installed tab + reword updated→changed
Issue #78 — The "What's New" pill at the top of the Skills page
announced "18 new, 3 updated since you last looked" while the Updates
sub-tab simultaneously said "No Updates / All skills are up to date."
Two surfaces measuring two different things both used the word
"update": the pill counts local file deltas since the user last
clicked "Mark as seen", while the Updates body runs `hermes skills
check` to find skills with newer upstream versions available. From
the user's seat the screen contradicted itself.

Two changes:

1. Render the pill only on the Installed sub-tab (Mac + ScarfGo).
   Local file deltas are contextually meaningful only on the tab
   that surfaces installed skills; showing them above Browse Hub or
   Updates was misleading.

2. Reword the pill: "X updated since you last looked" → "X changed
   since you last looked". Keeps `SkillSnapshotDiff.updatedCount` as
   the field name (it's still about file changes, not version bumps);
   only the user-visible string changes. Removes the vocabulary
   collision with the Updates tab's separate upstream-update check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:51:05 +02:00
Alan Wizemann f9e3cd38f5 fix(skills): client-side filter for All-Sources hub search
Issue #79 — Browse Hub clearly listed "honcho" but searching for
"honcho" with the source picker on "All Sources" returned nothing.
Root cause is on the Hermes side: `hermes skills search <query>`
without a `--source` flag routes through the centralized
`hermes-index` source and skips the external API sources
(skills-sh, github, clawhub, lobehub, well-known, claude-marketplace).
Browse aggregates those sources too, so any skill that lives only in
the API tier shows up in browse but disappears in search. Same picker,
same query, contradictory results.

Rather than chase Hermes's index gaps, redefine "All Sources" search
in Scarf to mean filter-what-you-see — the canonical type-to-filter
UX users already expect on a list. Source-specific searches keep the
CLI shell-out for full upstream search semantics on that registry.

Implementation:
- New `lastBrowseResults` cache populated on every successful
  `browseHub()`. Setter is `internal` so the test suite can seed
  without invoking the live CLI; out-of-module callers can still
  only read.
- `searchHub()` now branches on `hubSource`. The "all" branch filters
  the cache via `localizedCaseInsensitiveContains` against name,
  description, and identifier, runs synchronously on the calling
  actor (UI invocations are already on MainActor) so the user sees
  the narrowed list without a render-tick gap.
- If the cache is empty (search-before-browse), `browseHubThenFilter`
  performs one CLI fetch, populates the cache, then applies the
  filter — failure surfaces a "Search failed" banner instead of a
  silent empty state.
- Source-specific search still shells out to
  `hermes skills search <query> --source <s> --limit 40`.

Adds five regression tests covering name match, description match,
case-insensitive folding, no-match message state, and the empty-query
fallthrough to browse.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:50:52 +02:00