Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "hermes-helmet",
"displayName": "Hermes Helmet",
"version": "0.10.0",
"version": "0.11.0",
"description": "First-officer skills for Hermes Helmet: setup, single-issue delivery, and dependent-issue coordination, plus the Captain's Bridge mod, which opens a pane bound to the current conversation with /captains-bridge.",
"author": {
"name": "Machine Wisdom",
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hermes-helmet",
"version": "0.10.0",
"version": "0.11.0",
"description": "First-officer skills for Hermes Helmet: setup, single-issue delivery, and dependent-issue coordination.",
"author": {
"name": "Machine Wisdom",
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@ All notable changes to Hermes Helmet are recorded here. The project follows

## Unreleased

### 0.11.0

- Show the Captain’s Bridge Changes First view in the Claude mod
([#69](https://github.com/MachineWisdomAI/hermes-helmet/issues/69), part of
[#55](https://github.com/MachineWisdomAI/hermes-helmet/issues/55)). The pane
runs `helmet bridge read` for the bound session (30 s limit, exact session ID,
known transcript path when present), validates the schema major and session
before adopting it, and draws one element tree for terminal and desktop:
objective and outcome, then What changed, What remains unresolved and Other
recorded activity, with the completion report and the disposition as separate
labeled boxes. Item detail adds actor-labeled steps, View before and after
only where supported, evidence-bound links, elapsed time from cited records,
collapsed supporting records and Back with its place restored. Refresh records
rereads saved records only, keeps the view stack and selection, never restamps
the walkthrough’s `readAt`, and shows “Explanation read records at {readAt};
{n} newer records since.” Reader failures name the cause and fix and keep the
last useful view. The reader answer must carry the expected schema, valid
records, coverage counts and warnings before it replaces anything. Long
explanations and supporting records are drawn as word-wrapped pieces, so
scrolling never skips a whole long block, and Back stays in the fixed header.
Each overview item button now carries its title and status as one string
label, which the advertised minimum Claude Code 2.1.293 requires to draw the
pane. The call-allowlist parser accepts Claude Code’s `(via helper)` tag. No model
is called and nothing is sent.

### 0.10.0

- Captain’s Bridge Show Me and Retro now state a portable skill-discovery path
Expand Down
36 changes: 32 additions & 4 deletions docs/first-officer-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,42 @@ instructions (and its own MCP server) only. The skills above are unchanged and
keep working if your organization policy disables mods; only the Bridge is
skipped.

This release is the mod skeleton. Run `/captains-bridge` in any session to open
This release adds the reader and the Changes First view. Run `/captains-bridge` in any session to open
the Captain's Bridge pane beside the conversation. It is bound to that exact
session ID, starts no turn and messages no one, so it can be opened while the
first officer is working and at narrow terminal widths. It never opens by
itself and runs no timers. In VS Code and headless or SDK sessions, where no
pane draws, the command returns a short text status instead. The walkthrough,
refresh and the background explanation arrive in later releases
under the same state contract.
pane draws, the command returns a short text status instead. The background
explanation arrives in a later release under the same state
contract.

The Bridge now reads the session's saved records and draws the Changes First
view. Opening the pane (and **Refresh records**) runs
`helmet bridge read --session <id>` for this exact conversation, adding
`--transcript <path>` when the plugin saw the path at session start; otherwise
the reader looks the session up by its exact ID. The reader is read-only, the
call stops after 30 seconds, and no model is called and no message is sent.
Until an explanation exists the pane shows record counts, coverage and
**Refresh records**, never the raw transcript. **Update explanation** arrives
in a later release; a walkthrough shown here is drawn from the plugin state
contract, with its objective and outcome first, then *What changed*, *What
remains unresolved* and *Other recorded activity*. Opening an item shows its
explanation, actor-labeled handoff, review and repair steps, the completion
report and the disposition as separate labeled boxes, **View before and
after** only where the records support one, links whose address appears in a
cited record, elapsed time from the cited record timestamps, and the
supporting records collapsed. **Back** restores the previous item and scroll
position. A completed run or a quiet interval is never presented as accepted
delivery or a stall.

**Refresh records** rereads only the saved records. It keeps your place and
never changes the time the explanation read the records. When the records have
moved on, the header says "Explanation read records at {readAt}; {n} newer
records since." and lists read time and coverage warnings. If `helmet` cannot
be started or is too old, exits without a reader error, prints something that
is not the expected JSON, or speaks an incompatible schema, the pane names the
cause and the fix and keeps the last useful view; set `helmetCommand` to an
absolute path when Claude Desktop does not inherit your shell `PATH`.

Show Me and Retro run your own installed commands in this same session. The
Bridge checks `$.command.list()` when it opens and on every refresh, and accepts
Expand Down
43 changes: 40 additions & 3 deletions hooks/actions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -291,12 +291,49 @@ for (const surface of SURFACES) {
test('the explanation stays intact around a press', async ($, on) => {
const h = host(on, { listed: names('show-me') })
await open($)
fixture.selected = 'widget'
// Overview: the walkthrough is drawn with its item, then an item press
// from the actual pane opens the detail and the action keeps it intact.
const pane = await $.ui.mount({ ...PANE, surface })
expect(await pane.find({ text: 'Ship the synthetic widget' })).not.toBe(undefined)
expect(await pane.find({ key: 'item:widget' })).not.toBe(undefined)
await pane.press({ key: 'item:widget' })
await pane.press({ key: 'show-me' })
expect(h.runs.length).toBe(1)
expect(await pane.find({ text: 'Objective: Ship the synthetic widget' })).not.toBe(undefined)
expect(await pane.find({ text: '- Add the synthetic widget (Merged)' })).not.toBe(undefined)
expect(h.runs[0].args).toContain('Add the synthetic widget')
expect(await pane.find({ text: 'Explanation' })).not.toBe(undefined)
expect(await pane.find({ text: 'Add the synthetic widget' })).not.toBe(undefined)
await pane.unmount()
})

test('every Refresh records press discovers commands again', async ($, on) => {
const h = host(on, { listed: names() })
await open($)
const pane = await $.ui.mount({ ...PANE, surface })
expect(await pane.find({ key: 'show-me' })).toBe(undefined)
const before = h.listCalls
h.listed.current = names('show-me')
await pane.press({ key: 'refresh' })
expect(h.listCalls).toBeGreaterThan(before)
await pane.press({ key: 'refresh' })
expect(h.listCalls).toBeGreaterThan(before + 1)
await pane.press({ key: 'show-me' })
expect(h.runs.length).toBe(1)
await pane.unmount()
})

test('the item opened in the real pane sets the scope, and back returns to the session', async ($, on) => {
const h = host(on, { listed: names('show-me', 'retro') })
await open($)
const pane = await $.ui.mount({ ...PANE, surface })
await pane.press({ key: 'item:widget' })
await pane.press({ key: 'retro' })
expect(h.runs.length).toBe(1)
expect(h.runs[0].args).toContain('Add the synthetic widget')
expect(h.runs[0].args).toContain(`${REF(1)} (2026-10-09T16:00:00Z)`)
expect(h.runs[0].args).toContain(`${REF(2)} (2026-10-09T16:02:30Z)`)
await pane.press({ key: 'back' })
await pane.press({ key: 'show-me' })
expect(h.runs[1]).toEqual({ command: 'show-me', args: 'this session' })
await pane.unmount()
})
})
Expand Down
Loading
Loading