Reorganize CLI around task-oriented journeys - #857
Conversation
Add canonical setup, client, privacy, cache, MCP, enrichment, graph, vector, and dev routes with hidden compatibility aliases and semantic boot profiles. Reconcile Claude OTEL status and session health, update teaching, and add repository-wide CLI evidence.
…no config The new "journey sections" help test asserted graph in Additional commands, but graph project/compact are contributed only by @hypaware/context-graph, which (like every plugin command) needs to be config-active to appear in help. The test calls dispatch(['--help']) with no config, so no plugin commands are selected; every other test in this file that expects graph explicitly enables the plugin first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four defects found reviewing b8a1085. - `hyp session` on an install without @hypaware/ai-gateway rendered an empty subcommand table (exit 0), and `hyp session zzz` an `expected one of:` with nothing after it. The `session` group is now core-owned, so it matches before the dispatch-miss repair path can run. An empty core group is probed on its own tokens and reports the plugin that fills it plus the repair, the way master did and the way top-level help's own epilogue promises. - `hyp client status` compared the client's telemetry port against a listener port read straight off the persisted source snapshot, so a stopped daemon printed `[endpoint drift]` while `hyp status` correctly stayed silent (its `client_telemetry_stale` diagnostic is liveness gated on purpose). The projection now carries the same gate. - `hyp session status` printed per-recorder lines with `recorders.slice(1)`, but the headline speaks for the first `not_ignored` outcome, which is not always `recorders[0]`. With the gateway ignored and a second recorder recording, that reprinted the second recorder and dropped the gateway's answer entirely. It now drops the outcome the headline already covered. Status also reports an unaddressed gateway on stderr, as the mutations already do. - `hyp query vector`'s subcommand blurb advertised `status`, which resolved back to `query vector` and reprinted the same blurb with exit 0. `vector status` stays the canonical direct operation per LLP 0248 and now answers to `query vector status` as well; the group summary in the manifest and the registration agree again. Also bucket an unrecognized help category into "Additional commands" instead of dropping the row from `hyp --help` with no error. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review of
|
| argv | master | b8a1085 |
|---|---|---|
hyp session |
hyp: 'session' is provided by @hypaware/ai-gateway, which is not in the active config + repair, exit 2 |
group help with an empty Subcommands: list, exit 0 |
hyp session zzz |
same repair, exit 2 | hyp session: unknown subcommand 'zzz' / expected one of: (literally empty), exit 2 |
The new inactive-plugin check at src/core/cli/dispatch.js:401 is guarded by
matched.rest.length > 0, so bare hyp session skips it, and
longestCommandPrefix returns undefined for ['session','zzz'] so that case
falls through too. This also contradicts the epilogue hyp --help prints in
this same PR: "run it anyway: hyp names the plugin that provides it and prints
how to enable it."
test/core/cli-consistency-gate.test.js cannot catch it: the group test does
if (children.length === 0) continue, and the unknown-subcommand test iterates
an empty child list.
Fix: an empty core-owned group is probed on its own canonical tokens, so
hyp session, hyp session ignore, hyp session zzz, and hyp session --help
all report the owning plugin and the repair. Regression test added in
test/core/dispatch-inactive-plugin.test.js; confirmed it fails on b8a1085
and passes after.
2. hyp client status claims endpoint drift from a dead daemon's snapshot (medium, fixed)
src/core/commands/status.js:260 (listenerEndpointFromReport, used at :181)
reads details.listen_port straight off report.sources with no liveness gate.
The equivalent hyp status diagnostic is deliberately gated
(src/core/daemon/status.js:1230), with a comment explaining that "the listener
was last bound to X" is not a claim a dead daemon's snapshot can support.
hyp client status boots with bootProfile: 'none', so report.sources falls
back to the persisted status.json. Daemon stopped, last snapshot holding an
ephemeral listener port, client marker holding the default: hyp status
correctly stays silent while hyp client status prints [endpoint drift] and
endpoint_drift: true. The two surfaces this projection exists to reconcile
disagree.
Fix: the projection now carries the same daemon.running gate, so a stopped
daemon reports listener not running and endpoint_drift: null. Fixture in
test/core/client-status-otel.test.js gained the daemon field it was missing,
plus a dead-daemon case.
3. hyp session status drops one recorder's answer and prints another twice (low, fixed)
hypaware-core/plugins-workspace/ai-gateway/src/session_command.js:302-320
picks primary = outcomes.find(o => o.status === 'not_ignored'), which is not
necessarily outcomes[0], but writeStatus renders the remaining rows from
report.recorders.slice(1). With outcomes = [gateway: ignored, openclaw: not_ignored] the headline and endpoint/endpoint_source describe openclaw,
slice(1) reprints openclaw, and the gateway's ignored answer never appears.
The ignored branch was safe only because that status implies every outcome is
ignored.
Same file, runSessionStatus: resolvedTargets.gatewayError was surfaced only
when targets.length === 0, so an unresolvable gateway plus a resolvable extra
recorder yielded a plain ignored / exit 0. runMutation writes
hyp session: gateway not addressed: ... to stderr for the identical condition.
Fix: a secondaryRecorders(report) helper drops the outcome the headline
already spoke for (matched by endpoint, which is unique because
resolveAdvertisedRecordersForCli dedupes against the gateway), and status now
reports an unaddressed gateway on stderr like the mutations do. The JSON
recorders array keeps its gateway-first inventory order, so no consumer shape
changes. Covered in test/plugins/ai-gateway-session-both-recorders.test.js.
4. hyp query vector's own blurb points at a route that loops (low, fixed)
vector search moved to query vector search but vector status did not,
while runVector (hypaware-core/plugins-workspace/vector-search/src/commands.js:26)
prints a subcommand table listing status. query vector status was not
registered, so longest-prefix matching resolved hyp query vector status back
to query vector, which ignores argv and reprints the same blurb with exit 0: a
user following the printed guidance loops. The manifest summary ("see
subcommand: search") and the registration summary ("see subcommands: search,
status") also disagreed.
Fix: vector status stays the canonical direct operation, because
LLP 0248 lists it among the
direct operations and vector keeps its top-level help row because of it. It
now answers to query vector status as an alias, so the group's advertised
table is navigable, and both summaries were reconciled. The user-facing usage
and error strings for search were canonicalized to hyp query vector search.
5. An unrecognized help category silently vanishes from hyp --help (low/latent, guarded)
src/core/registry/commands.js:59 invents
command.category ??= command.plugin ? 'additional' : command.name.split(' ')[0],
while orderedHelpNames (src/core/cli/dispatch.js:858) only ever emits four
categories. Any command whose category is none of them is dropped from
hyp --help with no error and no test coverage. Harmless today (every visible
top-level core command carries an explicit category, verified by enumerating the
registry), but dev smoke already demonstrates the mechanism with
category: 'dev' and only escapes notice because it is hidden.
Fix: helpSectionFor() buckets an unrecognized category into "Additional
commands" rather than matching nothing. No output changes today; it removes the
silent-drop failure mode. The registry's fallback and audience derivation were
left alone deliberately, since changing them would move existing subcommands
between audiences for no benefit.
Checked and clean
- Alias plumbing (
registry.matchlongest-prefix +aliasIndex):hyp policy set,
hyp detach,hyp backfill --help,hyp mcp --remote,hyp smoke,hyp init
all still resolve; collision detection covers both directions. - Nested-group navigation in
makeGroupCommand:hyp client history,
hyp client history bogus,hyp client bogus,hyp cacherender/error correctly. - Inactive-plugin reporting through the new canonical paths:
hyp client claude-desktop [install],hyp client claude-account status,
hyp query vector,hyp graphall name the owning plugin and the repair. ctx.commands.runseam afteractivateSeamCommandPluginsswitched from
head-token to full-name matching: every caller matches a manifestname/alias
exactly.decideBootProfile(argv, registry): the explicitbootProfilefields plus the
name fallbacks reproduce the old argv-prefix behavior forsetup/init,
status,version,smoke, and alldaemon *.resetCentralLayerToSeedsymlink unlink fix (src/core/config/apply.js:231) is
correct and ENOENT-tolerant.- The claude picker/preset change (dropping
gateway_proxy_mode, the anthropic
upstream, andrequired_upstreams) is consistent with claude's OTEL-only attach
mode already on master, and is what makes theclaude_attach_detachsmoke's new
empty-stderr assertion correct.
Nothing left open
No finding was deliberately left unfixed. Note that the release still owes the
real-Claude claude_otel_shape_check acceptance procedure, as the PR body
already says; the hermetic smokes do not substitute for it.
Verification after the fixes
npm test: 4508 passed, 1 skipped, 0 failed (baseline onb8a1085: 4493 / 0).npm run typecheck: clean.- Smokes green:
cli_bundled_plugins_activated,package_bin_boot,
status_diagnostics,client_attach_idempotent,claude_telemetry_capture,
hypignore_capture_drop,vector_search_local_fixture,
walkthrough_picker_to_first_query,claude_attach_detach. - All new
@refanchors resolve to existing LLP sections.
…r renderer - dispatch: a non-empty task group now only reports an inactive plugin when the probe matches something *deeper* than the tokens the group consumed. `longestCommandPrefix`'s flag-stripped fallback collapsed `hyp query --json` to the bare group token, so any inactive plugin contributing under `query` was blamed for what is really an unknown subcommand. - dispatch: the top-level miss path calls `renderInactivePluginError` instead of hand-rolling the same message, so one condition cannot print two different repair lines (fleet/fleet admin vs organization/administrator). - context-graph: register the namespace description under both `graph` and `query graph`. LLP 0248 keeps `graph project|compact` as direct operations while the journey moved to `query graph neighbors`, so registering only `query graph` left `hyp graph --help` rendering a bare table. - clients: the installed-but-unreachable give-up message named `hyp start`, which is not a command; it now names `hyp daemon start`. - comments: drop references to the `admin` group, which does not exist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # hypaware-core/plugins-workspace/claude/skills/hypaware-query/SKILL.md # hypaware-core/plugins-workspace/codex/skills/hypaware-query/SKILL.md # src/core/cli/core_commands.js
Conflict resolutions, all preserving both sides: - docs/PRIVACY.md: keep master's new "What else macOS attach leaves behind" paragraph and its expanded CA-lifetime paragraph (every permitted host, the launchd variable), respelled to this branch's canonical `hyp client detach claude` route. - ai-gateway/src/source.js: keep master's corrected stale-CA remedy (a plain re-attach cannot clear the CA, so name `--purge` or turning proxy_mode back on) with the `hyp client detach` spelling. test/plugins/ai-gateway-proxy-mode.test.js pins the new spelling. - test/core/attach-proxy-migration.test.js: honor this branch's deletion. The branch implements accepted LLP 0262 by dropping `compose.gateway_proxy_mode` from the claude picker row, so no client declares it and `maybeOfferProxyModeMigration` never fires; master's added --json pins cover a path that is unreachable here. Also fixes a merge artifact: both branches minted LLP 0249 independently. Master's 0249 (proxy-mode-default-attach.plan) is the published record, so this branch's plan is renumbered to LLP 0266, along with its two @refs in ai-gateway/src/session_command.js and the research handoff that names it. npm test: 4551 passed, 1 skipped, 0 failed. npm run typecheck: clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
neutral resolved the conflict with Three conflicts, all resolved on the merits:
Two calls worth a human's eye:
No other change was made; no merge, no ready-flip. |
… new spellings Two review findings on the task-oriented CLI rollover. 1. test/core/attach-proxy-migration.test.js was deleted outright. It is green on master, and the branch's own change (the claude picker row no longer composing gateway_proxy_mode / gateway_upstream, per LLP 0262) is what made 8 of its 11 cases fail: `maybeOfferProxyModeMigration` is now unreachable for every bundled client. Deleting the file left that with no pin at all. Replaced with a file that pins the outcome from both ends: structurally, that no bundled picker row declares the flag the offer gates on, and behaviorally, that a real attach of claude against a base-URL-era config asks nothing, points at nothing, and writes nothing in each shape that used to get a question or a pointer (interactive, --json, non-TTY, all). Verified it has teeth: re-adding gateway_proxy_mode to the claude row fails 5 of the 6. 2. README.md was not part of the rollover (LLP 0266 M4 covers docs), so the front door still taught only the pre-rollover spellings, and its diagnostics table described `gateway_missing_anthropic_upstream` as the @hypaware/claude condition - which this branch removes by dropping required_upstreams from the claude manifest and the validate.js fallback. The kind survives via @hypaware/openclaw, so the row is corrected rather than deleted. Command spellings rolled over to the canonical journeys, with the still-accepted `hyp attach` / `hyp detach` / `hyp unattach` forms kept visible in the attach section. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review round:
|
|
Triage at head Deferred findings are tracked in follow-up issue #946: #946 This PR can merge safely. |
Summary
Deliberately deferred
Verification