Skip to content

fix(cli): asking a command what it does must not run it - #42

Merged
colosair merged 1 commit into
mainfrom
fix/help-and-completeness
Sep 10, 2026
Merged

colosair merged 1 commit into
mainfrom
fix/help-and-completeness

Conversation

@colosair

Copy link
Copy Markdown
Owner

요약

실기계에서 jam setup --help 가 도움말을 찍지 않고 setup 마법사를 돌렸다. 두 호스트에 JAM 을 재등록했고 기록된 등록을 한 판번호에서 다른 판번호로 옮겼다 — 사람이 문서라고 생각한 것을 읽는 동안에. 그 구멍과, 같은 성격의 완전성 표시 오류를 함께 닫는다.

baseline: JAM 1.7.0.

변경 사항

A. 명령에게 무엇을 하는지 묻는 것이 그것을 돌리는 것과 같아서는 안 된다 (P0)

  • packages/server/src/cli-entry.ts — --help/-h 를 dispatcher 앞에서 가로챈다. 원인은 좁고 여파는 좁지 않았다: --help 를 명령 그 자체로만 알아봤기 때문에, 그것을 달고 온 모든 하위 명령이 자기 handler 로 그대로 떨어졌다. dispatcher 가 닿을 수 있는 모든 변경 경로가 거기 포함된다.
  • packages/launcher/src/cli.ts — launcher 는 runtime 을 dispatcher 로 넘기기 전에 자기가 답하므로 같은 문을 따로 세웠다.

명령별 목록을 만들지 않았다. 그런 목록은 누군가 명령을 하나 추가하는 첫 순간에 낡는다.

B. 완전성은 회수에 대한 것이지 출력 크기에 대한 것이 아니다

세 가지 다른 상황이 두 개의 틀린 코드로 접혀 있었다.

한 페이지에서 멈춘 preview       → PARTIAL_API_RESPONSE 로 보고
page cap 에 닿은 완주 walk       → OUTPUT_BUDGET 으로 보고
token 예산을 넘은 결과           → 이미 옳았다 (notes 로)

앞의 둘은 아닌 실패로 읽힌다. "partial API response" 는 Jira 가 요청보다 적게 답했다는 뜻이고, preview 는 호출자가 요청한 그대로를 받은 것이다. "output budget" 은 크기에 맞추려고 무언가를 버렸다는 뜻인데, page cap 은 읽기가 얼마나 멀리 걸었는가의 문제이고 아무것도 잘리지 않았다.

  • domain/completeness.ts — IncompleteReason 에 PREVIEW_LIMIT·PAGINATION_LIMIT 추가. CompletenessMeta 에 moreAvailable·returnedCount 추가.
  • application/search-issues.ts — 둘을 가려 세우고, 두 새 필드를 항상 채운다.

세 번째는 이미 옳았고 그대로 고정했다 — 결과는 완전하면서 동시에 원하지 않을 만큼 클 수 있고, 그렇게 말하는 것이 complete 를 false 로 만들어서는 안 된다.

pagination 은 JAM 안에 남는다. continuation token 은 호출자에게 나가지 않는다 — 그것을 내보내면 pagination 이 agent 의 일이 되고, 이 경계는 정확히 그것을 막으려고 있다.

테스트 / 검증

  • npm test — 582 pass · 0 fail
  • npm run release:check — clean
  • tests/unit/help-no-side-effect.test.ts (31, 신규) — 변경을 일으키는 모든 dispatcher 경로 × --help/-h. 설정·등록·자격증명·Jira 를 바꾸는 모듈 전부를 mock 으로 세우고 호출되지 않았음을 단언한다. 도움말 없이 부르면 그대로 도는 것도 함께 고정
  • tests/unit/completeness-semantics.test.ts (7, 신규) — 네 상황을 각각, 그리고 두 가지가 동시에 참일 때 서로 섞이지 않는 것. continuation token 이 호출자에게 나가지 않는다·JQL 은 그대로 Jira 로 간다 포함

리뷰 포인트

  1. dispatcher 에서 막았지 명령마다 막지 않았다. 보고된 것은 setup --help 하나지만 구멍은 dispatcher 에 있었다. 명령별 방어는 다음 명령이 추가될 때 조용히 빠진다.
  2. PREVIEW_LIMIT 과 PAGINATION_LIMIT 을 가른 이유는 호출자의 다음 행동이 다르기 때문이다. 전자는 scope="complete" 로 다시 부르면 되고, 후자는 안전 상한에 닿은 것이라 질의를 좁혀야 한다. 한 코드로 두 상황을 말하면 어느 쪽 조언도 정확하지 않다.
  3. complete=true + notes 조합을 그대로 뒀다. 크기 때문에 complete 를 뒤집으면 "읽기가 끝났다" 는 사실이 사라진다.

미결 / 후속

  • MCP/CLI parity 전수 대조는 이 PR 범위 밖이다. --help 경로에 한해 CLI 쪽만 닫았다.
  • UNCERTAIN 판정의 의미는 이번에 바꾸지 않았다(변경 없음이 의도된 결과다).

🤖 Generated with Claude Code

…h limit was hit

Two defects found in real use after 1.7.0. Neither is in the write path.

`jam setup --help` did not print help. It ran the setup wizard, re-registered
JAM with two hosts, and moved the recorded registration from one version to
another while the person was reading what they thought was documentation.

The cause was narrow: `--help` was recognised only as the command itself, so
`help`, `--help` and `-h` worked at the top level and nowhere else. Every
subcommand carrying the flag fell through to its handler — setup, update,
refresh, uninstall, runtime use, auth login, auth logout, and both write
commands. The report named setup because that is the one somebody typed.

One guard in the dispatcher rather than a flag per command: the property belongs
to the dispatcher, and a per-command list is a list that will be incomplete the
first time somebody adds a command. The launcher needs its own, because it
answers the runtime group itself and never reaches the dispatcher — `runtime
use --help` was writing the runtime selection.

The second is about what "incomplete" means. Three different situations were
collapsing into two codes that named the wrong cause:

```text
preview stops at page one        was PARTIAL_API_RESPONSE — reads as "Jira gave
                                 us less than we asked for". It gave us exactly
                                 what we asked for
complete walk hits the page cap  was OUTPUT_BUDGET — reads as "we dropped
                                 something to fit a size". Nothing was dropped;
                                 the walk stopped
result over the token budget     already correct: complete stays true and the
                                 size goes in notes
```

PREVIEW_LIMIT and PAGINATION_LIMIT now name those two, leaving OUTPUT_BUDGET for
payload actually dropped and PARTIAL_API_RESPONSE for Jira answering with less
than it was asked. `moreAvailable` and `returnedCount` join the metadata, which
is what a caller needs to decide whether to widen — and no more than that. The
continuation token still never leaves JAM; handing it out would move pagination
to the caller, which is the boundary this owns. JQL is passed through unchanged;
JAM does not narrow a query on the caller's behalf.

Nothing about the write path moves. plan → apply → read-back verify is
untouched, UNCERTAIN still refuses re-entry on the same plan, and no command
gained a step. A caller that never used a help flag and never read `reason` sees
no difference at all.

Regression: 582 pass / 0 fail (was 575). The help contract is asserted across
every mutating dispatcher path with both flag spellings, and asserts that the
mutating modules were not called — not merely that something was printed.

Co-Authored-By: Claude Opus 5 <[email protected]>
@colosair
colosair merged commit 4507c5e into main Sep 10, 2026
5 checks passed
@colosair
colosair deleted the fix/help-and-completeness branch September 10, 2026 08:13
colosair added a commit that referenced this pull request Sep 10, 2026
… which limit was hit (#43)

Version, lockfile and release note for 1.8.0. No behaviour is added here — the
change itself merged as #42.

The three packages move to 1.8.0 in lockstep and depend on each other at that
exact version.

Contents of the release:

- `--help` on any subcommand prints usage instead of running the command. The
  guard is in the dispatcher, ahead of the switch, so it covers every mutating
  path rather than a list that would go stale.
- `PREVIEW_LIMIT` and `PAGINATION_LIMIT` replace two misleading reasons, and
  `moreAvailable` / `returnedCount` say what a caller needs to decide what to do
  next.

Gate:

    npm run release:verify   build · test · release:check · smoke — all green

Recorded as not verified in the note: the reported `jam setup --help` incident
was not reproduced (doing so would change this machine's registration again),
the two new meta fields are optional in the type rather than enforced, and usage
still goes to stdout from the server and stderr from the launcher.

Published acceptance runs after release.yml, before the tag.

Co-authored-by: Claude Opus 5 <[email protected]>
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